gabriel / muse public
collaborators.py python
299 lines 10.7 KB
Raw
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