collaborators.py
python
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d
feat(pack): delta-encode snapshots in MPackBundle wire format
Sonnet 4.6
minor
⚠ breaking
121 days ago
| 1 | import argparse |
| 2 | from ._core import * |
| 3 | |
| 4 | def run_collaborator_list(args: argparse.Namespace) -> None: |
| 5 | """List all collaborators and their permission levels for a repository. |
| 6 | |
| 7 | Returns the list ordered by permission level. |
| 8 | |
| 9 | JSON output is the raw API response merged with the standard envelope. |
| 10 | Human-readable text goes to stdout for easy piping. |
| 11 | |
| 12 | Agent quickstart |
| 13 | ---------------- |
| 14 | :: |
| 15 | |
| 16 | muse hub collaborator list --json |
| 17 | muse hub collaborator list --json | jq '.collaborators[].handle' |
| 18 | |
| 19 | Exit codes |
| 20 | ---------- |
| 21 | 0 Success. |
| 22 | 1 Auth error. |
| 23 | 2 Not inside a Muse repository. |
| 24 | 3 API error. |
| 25 | """ |
| 26 | json_output: bool = args.json_output |
| 27 | elapsed = start_timer() |
| 28 | hub_url, identity = _get_hub_and_identity(hub_url_override=_resolve_hub_override(args)) |
| 29 | repo_id = _resolve_repo_id(hub_url, identity) |
| 30 | |
| 31 | data = _hub_api(hub_url, identity, "GET", f"/api/repos/{repo_id}/collaborators") |
| 32 | |
| 33 | if json_output: |
| 34 | print(json.dumps({**make_envelope(elapsed), **data})) |
| 35 | return |
| 36 | |
| 37 | collaborators = data.get("collaborators", []) |
| 38 | if not collaborators: |
| 39 | print("No collaborators.", file=sys.stderr) |
| 40 | return |
| 41 | for c in collaborators: |
| 42 | handle = c.get("handle", "") |
| 43 | perm = c.get("permission", "") |
| 44 | print(f" {handle:<32} {perm}") |
| 45 | |
| 46 | def run_collaborator_invite(args: argparse.Namespace) -> None: |
| 47 | """Invite a user as a collaborator on a repository. |
| 48 | |
| 49 | Requires admin or owner access. |
| 50 | |
| 51 | JSON output is the raw API response merged with the standard envelope. |
| 52 | Human-readable text goes to stderr. |
| 53 | |
| 54 | Agent quickstart |
| 55 | ---------------- |
| 56 | :: |
| 57 | |
| 58 | muse hub collaborator invite carol --permission write --json |
| 59 | muse hub collaborator invite carol --permission admin --json |
| 60 | # → {"muse_version": "...", ..., "handle": "carol", "permission": "write"} |
| 61 | |
| 62 | Exit codes |
| 63 | ---------- |
| 64 | 0 Collaborator invited. |
| 65 | 1 Auth or conflict error. |
| 66 | 2 Not inside a Muse repository. |
| 67 | 3 API error. |
| 68 | """ |
| 69 | handle: str = args.handle |
| 70 | permission: str = args.permission |
| 71 | json_output: bool = args.json_output |
| 72 | |
| 73 | if not handle.strip(): |
| 74 | print("❌ Handle must not be empty.", file=sys.stderr) |
| 75 | raise SystemExit(ExitCode.USER_ERROR) |
| 76 | |
| 77 | elapsed = start_timer() |
| 78 | hub_url, identity = _get_hub_and_identity(hub_url_override=_resolve_hub_override(args)) |
| 79 | repo_id = _resolve_repo_id(hub_url, identity) |
| 80 | |
| 81 | data = _hub_api( |
| 82 | hub_url, identity, "POST", |
| 83 | f"/api/repos/{repo_id}/collaborators", |
| 84 | body={"handle": handle, "permission": permission}, |
| 85 | ) |
| 86 | |
| 87 | if json_output: |
| 88 | print(json.dumps({**make_envelope(elapsed), **data})) |
| 89 | return |
| 90 | |
| 91 | print(f"✅ Invited '{sanitize_display(handle)}' as {permission} collaborator.", file=sys.stderr) |
| 92 | |
| 93 | def run_collaborator_update_permission(args: argparse.Namespace) -> None: |
| 94 | """Update a collaborator's permission level. |
| 95 | |
| 96 | Requires admin or owner access. |
| 97 | |
| 98 | JSON output is the raw API response merged with the standard envelope. |
| 99 | Human-readable text goes to stderr. |
| 100 | |
| 101 | Agent quickstart |
| 102 | ---------------- |
| 103 | :: |
| 104 | |
| 105 | muse hub collaborator update-permission carol --permission admin --json |
| 106 | muse hub collaborator update-permission carol --permission read --json |
| 107 | # → {"muse_version": "...", ..., "handle": "carol", "permission": "admin"} |
| 108 | |
| 109 | Exit codes |
| 110 | ---------- |
| 111 | 0 Permission updated. |
| 112 | 1 Auth or not-found error. |
| 113 | 2 Not inside a Muse repository. |
| 114 | 3 API error. |
| 115 | """ |
| 116 | handle: str = args.handle |
| 117 | permission: str = args.permission |
| 118 | json_output: bool = args.json_output |
| 119 | |
| 120 | elapsed = start_timer() |
| 121 | hub_url, identity = _get_hub_and_identity(hub_url_override=_resolve_hub_override(args)) |
| 122 | repo_id = _resolve_repo_id(hub_url, identity) |
| 123 | |
| 124 | data = _hub_api( |
| 125 | hub_url, identity, "PUT", |
| 126 | f"/api/repos/{repo_id}/collaborators/{handle}/permission", |
| 127 | body={"permission": permission}, |
| 128 | ) |
| 129 | |
| 130 | if json_output: |
| 131 | print(json.dumps({**make_envelope(elapsed), **data})) |
| 132 | return |
| 133 | |
| 134 | print(f"✅ Updated '{sanitize_display(handle)}' permission to {permission}.", file=sys.stderr) |
| 135 | |
| 136 | def run_collaborator_remove(args: argparse.Namespace) -> None: |
| 137 | """Remove a collaborator from a repository. |
| 138 | |
| 139 | Requires admin or owner access. The owner cannot be removed. |
| 140 | |
| 141 | JSON output includes envelope fields plus ``removed`` (bool) and |
| 142 | ``handle`` (str). |
| 143 | |
| 144 | Agent quickstart |
| 145 | ---------------- |
| 146 | :: |
| 147 | |
| 148 | muse hub collaborator remove carol --json |
| 149 | # → {"muse_version": "...", ..., "removed": true, "handle": "carol"} |
| 150 | |
| 151 | Exit codes |
| 152 | ---------- |
| 153 | 0 Collaborator removed. |
| 154 | 1 Auth or not-found error. |
| 155 | 2 Not inside a Muse repository. |
| 156 | 3 API error. |
| 157 | """ |
| 158 | handle: str = args.handle |
| 159 | json_output: bool = args.json_output |
| 160 | |
| 161 | elapsed = start_timer() |
| 162 | hub_url, identity = _get_hub_and_identity(hub_url_override=_resolve_hub_override(args)) |
| 163 | repo_id = _resolve_repo_id(hub_url, identity) |
| 164 | |
| 165 | _hub_api(hub_url, identity, "DELETE", f"/api/repos/{repo_id}/collaborators/{handle}") |
| 166 | |
| 167 | if json_output: |
| 168 | print(json.dumps({**make_envelope(elapsed), **{"removed": True, "handle": handle}})) |
| 169 | return |
| 170 | |
| 171 | print(f"✅ Removed '{sanitize_display(handle)}' from collaborators.", file=sys.stderr) |
| 172 | |
| 173 | def register(subs: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None: |
| 174 | """Register collaborators subcommands.""" |
| 175 | # ── collaborator ────────────────────────────────────────────────────────── |
| 176 | collab_p = subs.add_parser( |
| 177 | "collaborator", |
| 178 | help="Manage repository collaborators on MuseHub.", |
| 179 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 180 | ) |
| 181 | collab_subs = collab_p.add_subparsers(dest="collaborator_subcommand", metavar="COLLAB_COMMAND") |
| 182 | collab_subs.required = True |
| 183 | |
| 184 | collab_list_p = collab_subs.add_parser( |
| 185 | "list", |
| 186 | help="List collaborators on a repo.", |
| 187 | description=( |
| 188 | "List all collaborators and their permission levels for a repository.\n\n" |
| 189 | "Agent quickstart:\n" |
| 190 | " muse hub collaborator list --json\n\n" |
| 191 | "Exit codes: 0 success, 1 auth error, 2 not in repo, 3 API error." |
| 192 | ), |
| 193 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 194 | ) |
| 195 | collab_list_p.add_argument( |
| 196 | "--hub", dest="hub", default=None, metavar="URL", |
| 197 | help="Override the hub URL from config.", |
| 198 | ) |
| 199 | collab_list_p.add_argument( |
| 200 | "--repo", dest="repo", default=None, metavar="OWNER/REPO", |
| 201 | help="Specify repo as owner/repo.", |
| 202 | ) |
| 203 | collab_list_p.add_argument( |
| 204 | "--json", "-j", action="store_true", dest="json_output", |
| 205 | help="Emit JSON list of collaborators.", |
| 206 | ) |
| 207 | collab_list_p.set_defaults(func=run_collaborator_list) |
| 208 | |
| 209 | collab_invite_p = collab_subs.add_parser( |
| 210 | "invite", |
| 211 | help="Invite a user as a collaborator.", |
| 212 | description=( |
| 213 | "Invite a user to collaborate on a repository.\n" |
| 214 | "Requires admin or owner access.\n\n" |
| 215 | "Agent quickstart:\n" |
| 216 | " muse hub collaborator invite carol --permission write --json\n\n" |
| 217 | "Exit codes: 0 success, 1 auth/conflict error, 2 not in repo, 3 API error." |
| 218 | ), |
| 219 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 220 | ) |
| 221 | collab_invite_p.add_argument("handle", help="MSign handle of the user to invite.") |
| 222 | collab_invite_p.add_argument( |
| 223 | "--permission", "-p", default="write", choices=["read", "write", "admin"], |
| 224 | help="Permission level: read, write (default), or admin.", |
| 225 | ) |
| 226 | collab_invite_p.add_argument( |
| 227 | "--hub", dest="hub", default=None, metavar="URL", |
| 228 | help="Override the hub URL from config.", |
| 229 | ) |
| 230 | collab_invite_p.add_argument( |
| 231 | "--repo", dest="repo", default=None, metavar="OWNER/REPO", |
| 232 | help="Specify repo as owner/repo.", |
| 233 | ) |
| 234 | collab_invite_p.add_argument( |
| 235 | "--json", "-j", action="store_true", dest="json_output", |
| 236 | help="Emit JSON collaborator record on success.", |
| 237 | ) |
| 238 | collab_invite_p.set_defaults(func=run_collaborator_invite) |
| 239 | |
| 240 | collab_update_permission_p = collab_subs.add_parser( |
| 241 | "update-permission", |
| 242 | help="Update a collaborator's permission level.", |
| 243 | description=( |
| 244 | "Update an existing collaborator's permission level.\n" |
| 245 | "Requires admin or owner access.\n\n" |
| 246 | "Agent quickstart:\n" |
| 247 | " muse hub collaborator update-permission carol --permission admin --json\n\n" |
| 248 | "Exit codes: 0 success, 1 auth/not-found error, 2 not in repo, 3 API error." |
| 249 | ), |
| 250 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 251 | ) |
| 252 | collab_update_permission_p.add_argument("handle", help="MSign handle of the collaborator to update.") |
| 253 | collab_update_permission_p.add_argument( |
| 254 | "--permission", "-p", required=True, choices=["read", "write", "admin"], |
| 255 | help="New permission level.", |
| 256 | ) |
| 257 | collab_update_permission_p.add_argument( |
| 258 | "--hub", dest="hub", default=None, metavar="URL", |
| 259 | help="Override the hub URL from config.", |
| 260 | ) |
| 261 | collab_update_permission_p.add_argument( |
| 262 | "--repo", dest="repo", default=None, metavar="OWNER/REPO", |
| 263 | help="Specify repo as owner/repo.", |
| 264 | ) |
| 265 | collab_update_permission_p.add_argument( |
| 266 | "--json", "-j", action="store_true", dest="json_output", |
| 267 | help="Emit JSON with the updated collaborator record.", |
| 268 | ) |
| 269 | collab_update_permission_p.set_defaults(func=run_collaborator_update_permission) |
| 270 | |
| 271 | collab_remove_p = collab_subs.add_parser( |
| 272 | "remove", |
| 273 | help="Remove a collaborator from a repo.", |
| 274 | description=( |
| 275 | "Remove a collaborator from a repository.\n" |
| 276 | "Requires admin or owner access. The owner cannot be removed.\n\n" |
| 277 | "Agent quickstart:\n" |
| 278 | " muse hub collaborator remove carol --json\n\n" |
| 279 | "Exit codes: 0 success, 1 auth/not-found error, 2 not in repo, 3 API error." |
| 280 | ), |
| 281 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 282 | ) |
| 283 | collab_remove_p.add_argument("handle", help="MSign handle of the collaborator to remove.") |
| 284 | collab_remove_p.add_argument( |
| 285 | "--hub", dest="hub", default=None, metavar="URL", |
| 286 | help="Override the hub URL from config.", |
| 287 | ) |
| 288 | collab_remove_p.add_argument( |
| 289 | "--repo", dest="repo", default=None, metavar="OWNER/REPO", |
| 290 | help="Specify repo as owner/repo.", |
| 291 | ) |
| 292 | collab_remove_p.add_argument( |
| 293 | "--json", "-j", action="store_true", dest="json_output", |
| 294 | help="Emit JSON confirmation on success.", |
| 295 | ) |
| 296 | collab_remove_p.set_defaults(func=run_collaborator_remove) |
| 297 | |
| 298 | collab_p.set_defaults(func=lambda a: collab_p.print_help()) |
| 299 |
File History
1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d
feat(pack): delta-encode snapshots in MPackBundle wire format
Sonnet 4.6
minor
⚠
121 days ago