mammoth-cli 1.0.0__py3-none-any.whl

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 (150) hide show
  1. mammoth_cli/__init__.py +14 -0
  2. mammoth_cli/__main__.py +27 -0
  3. mammoth_cli/app.py +565 -0
  4. mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +109 -0
  5. mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +27 -0
  6. mammoth_cli/bundled_skill/mammoth-cli/references/input.md +24 -0
  7. mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +35 -0
  8. mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +30 -0
  9. mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +28 -0
  10. mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +26 -0
  11. mammoth_cli/commands/__init__.py +29 -0
  12. mammoth_cli/commands/activity.py +123 -0
  13. mammoth_cli/commands/addon.py +173 -0
  14. mammoth_cli/commands/agent.py +199 -0
  15. mammoth_cli/commands/ai.py +274 -0
  16. mammoth_cli/commands/annotation.py +166 -0
  17. mammoth_cli/commands/auth.py +567 -0
  18. mammoth_cli/commands/automation.py +245 -0
  19. mammoth_cli/commands/batch.py +204 -0
  20. mammoth_cli/commands/billing.py +462 -0
  21. mammoth_cli/commands/browse.py +151 -0
  22. mammoth_cli/commands/capability.py +43 -0
  23. mammoth_cli/commands/client_app.py +233 -0
  24. mammoth_cli/commands/completion.py +82 -0
  25. mammoth_cli/commands/config.py +309 -0
  26. mammoth_cli/commands/connector.py +464 -0
  27. mammoth_cli/commands/context.py +205 -0
  28. mammoth_cli/commands/dashboard.py +419 -0
  29. mammoth_cli/commands/data_app.py +239 -0
  30. mammoth_cli/commands/dataset.py +332 -0
  31. mammoth_cli/commands/doctor.py +90 -0
  32. mammoth_cli/commands/external_key.py +163 -0
  33. mammoth_cli/commands/file.py +243 -0
  34. mammoth_cli/commands/folder.py +224 -0
  35. mammoth_cli/commands/job.py +147 -0
  36. mammoth_cli/commands/notification.py +201 -0
  37. mammoth_cli/commands/parameter.py +284 -0
  38. mammoth_cli/commands/project.py +327 -0
  39. mammoth_cli/commands/registry.py +587 -0
  40. mammoth_cli/commands/report.py +61 -0
  41. mammoth_cli/commands/schedule.py +214 -0
  42. mammoth_cli/commands/schema.py +271 -0
  43. mammoth_cli/commands/skill.py +59 -0
  44. mammoth_cli/commands/snippet.py +200 -0
  45. mammoth_cli/commands/support.py +800 -0
  46. mammoth_cli/commands/template.py +134 -0
  47. mammoth_cli/commands/trash.py +115 -0
  48. mammoth_cli/commands/user.py +190 -0
  49. mammoth_cli/commands/view.py +1143 -0
  50. mammoth_cli/commands/view_ops.py +596 -0
  51. mammoth_cli/commands/webhook.py +235 -0
  52. mammoth_cli/commands/workflow.py +340 -0
  53. mammoth_cli/commands/workspace.py +359 -0
  54. mammoth_cli/context/__init__.py +1 -0
  55. mammoth_cli/context/credentials.py +223 -0
  56. mammoth_cli/context/endpoint.py +57 -0
  57. mammoth_cli/context/profiles.py +370 -0
  58. mammoth_cli/context/resolver.py +240 -0
  59. mammoth_cli/contracts/__init__.py +1 -0
  60. mammoth_cli/contracts/auth.py +32 -0
  61. mammoth_cli/errors/__init__.py +1 -0
  62. mammoth_cli/errors/envelope.py +144 -0
  63. mammoth_cli/manifest/__init__.py +1 -0
  64. mammoth_cli/manifest/loader.py +96 -0
  65. mammoth_cli/messages/__init__.py +1 -0
  66. mammoth_cli/output/__init__.py +1 -0
  67. mammoth_cli/output/envelope.py +42 -0
  68. mammoth_cli/output/normalize.py +84 -0
  69. mammoth_cli/output/policy.py +71 -0
  70. mammoth_cli/output/render.py +83 -0
  71. mammoth_cli/py.typed +0 -0
  72. mammoth_cli/runtime/__init__.py +0 -0
  73. mammoth_cli/runtime/confirm.py +124 -0
  74. mammoth_cli/runtime/executor.py +126 -0
  75. mammoth_cli/runtime/input_loader.py +140 -0
  76. mammoth_cli/runtime/invocation.py +87 -0
  77. mammoth_cli/runtime/options.py +172 -0
  78. mammoth_cli/runtime/session.py +82 -0
  79. mammoth_cli/runtime/strict.py +240 -0
  80. mammoth_cli/runtime/validate.py +120 -0
  81. mammoth_cli/services/__init__.py +1 -0
  82. mammoth_cli/services/argspec.py +301 -0
  83. mammoth_cli/services/coerce.py +172 -0
  84. mammoth_cli/services/conditions.py +85 -0
  85. mammoth_cli/services/dispatch.py +63 -0
  86. mammoth_cli/services/factory.py +45 -0
  87. mammoth_cli/services/input_fields.py +44 -0
  88. mammoth_cli/services/mapping.py +74 -0
  89. mammoth_cli/services/openapi_types.py +166 -0
  90. mammoth_cli/services/positionals.py +408 -0
  91. mammoth_cli/services/protocol.py +123 -0
  92. mammoth_cli/services/sdk_service.py +274 -0
  93. mammoth_cli/services/testing.py +153 -0
  94. mammoth_cli/services/type_system.py +427 -0
  95. mammoth_cli/skills/__init__.py +1 -0
  96. mammoth_cli/skills/installer.py +321 -0
  97. mammoth_cli/testing.py +55 -0
  98. mammoth_cli-1.0.0.dist-info/METADATA +92 -0
  99. mammoth_cli-1.0.0.dist-info/RECORD +150 -0
  100. mammoth_cli-1.0.0.dist-info/WHEEL +4 -0
  101. mammoth_cli-1.0.0.dist-info/entry_points.txt +3 -0
  102. mammoth_cli-1.0.0.dist-info/licenses/LICENSE +23 -0
  103. spec/manifests/_sdk_introspection.json +1459 -0
  104. spec/manifests/commands/activity.yaml +72 -0
  105. spec/manifests/commands/addon.yaml +239 -0
  106. spec/manifests/commands/agent.yaml +192 -0
  107. spec/manifests/commands/ai.yaml +152 -0
  108. spec/manifests/commands/annotation.yaml +191 -0
  109. spec/manifests/commands/auth.yaml +101 -0
  110. spec/manifests/commands/automation.yaml +273 -0
  111. spec/manifests/commands/batch.yaml +262 -0
  112. spec/manifests/commands/billing.yaml +813 -0
  113. spec/manifests/commands/browse.yaml +145 -0
  114. spec/manifests/commands/capability.yaml +74 -0
  115. spec/manifests/commands/client-app.yaml +193 -0
  116. spec/manifests/commands/completion.yaml +68 -0
  117. spec/manifests/commands/config.yaml +134 -0
  118. spec/manifests/commands/connector.yaml +944 -0
  119. spec/manifests/commands/context.yaml +101 -0
  120. spec/manifests/commands/dashboard.yaml +3595 -0
  121. spec/manifests/commands/data-app.yaml +485 -0
  122. spec/manifests/commands/dataset.yaml +580 -0
  123. spec/manifests/commands/doctor.yaml +35 -0
  124. spec/manifests/commands/external-key.yaml +151 -0
  125. spec/manifests/commands/file.yaml +341 -0
  126. spec/manifests/commands/folder.yaml +334 -0
  127. spec/manifests/commands/job.yaml +151 -0
  128. spec/manifests/commands/notification.yaml +188 -0
  129. spec/manifests/commands/parameter.yaml +527 -0
  130. spec/manifests/commands/project.yaml +669 -0
  131. spec/manifests/commands/report.yaml +36 -0
  132. spec/manifests/commands/schedule.yaml +194 -0
  133. spec/manifests/commands/schema.yaml +74 -0
  134. spec/manifests/commands/skill.yaml +167 -0
  135. spec/manifests/commands/snippet.yaml +311 -0
  136. spec/manifests/commands/support.yaml +1805 -0
  137. spec/manifests/commands/template.yaml +191 -0
  138. spec/manifests/commands/trash.yaml +105 -0
  139. spec/manifests/commands/user.yaml +280 -0
  140. spec/manifests/commands/version.yaml +35 -0
  141. spec/manifests/commands/view.yaml +4232 -0
  142. spec/manifests/commands/webhook.yaml +261 -0
  143. spec/manifests/commands/workflow.yaml +622 -0
  144. spec/manifests/commands/workspace.yaml +705 -0
  145. spec/manifests/openapi-operations.yaml +8853 -0
  146. spec/manifests/schema-v1.json +212 -0
  147. spec/manifests/sdk-catalog.source.yaml +4069 -0
  148. spec/manifests/sdk-methods.yaml +6971 -0
  149. spec/openapi/metadata.json +10 -0
  150. spec/openapi/openapi.json +1 -0
@@ -0,0 +1,462 @@
1
+ """Handlers for the ``billing`` command family (workspace-scoped).
2
+
3
+ Billing operations act on the current workspace's Chargebee/Stripe billing
4
+ state and financial records, so every command in this family carries the
5
+ manifest's ``confirm_target`` policy: the caller must pass ``--yes`` and an
6
+ exact ``--confirm TARGET`` before the underlying SDK call is made, even for
7
+ read-only lookups. The target is the specific resource id when one exists
8
+ (an invoice id or a payment method id); otherwise it is the resolved
9
+ workspace id, since the action affects the workspace's overall billing
10
+ state. Handlers dispatch through the generic
11
+ :meth:`~mammoth_cli.services.protocol.MammothService.call` seam to the public
12
+ SDK method named by the command's reviewed manifest ``sdk_symbol``.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ from mammoth_cli.errors.envelope import (
20
+ CODE_INVALID_ARGUMENT,
21
+ CODE_MISSING_ARGUMENT,
22
+ CODE_MISSING_FIELD,
23
+ CODE_SDK_SYMBOL_UNRESOLVED,
24
+ EXIT_USAGE,
25
+ CliError,
26
+ )
27
+ from mammoth_cli.manifest.loader import command_by_id
28
+ from mammoth_cli.runtime.confirm import POLICY_CONFIRM_TARGET, enforce_confirmation
29
+ from mammoth_cli.runtime.invocation import Invocation
30
+ from mammoth_cli.runtime.session import open_service
31
+
32
+ HandlerResult = tuple[Any, dict[str, Any]]
33
+
34
+
35
+ def _symbol(invocation: Invocation) -> str:
36
+ """Return the reviewed backing SDK symbol for this command."""
37
+ record = command_by_id(invocation.command_id)
38
+ if record is None or not record.get("sdk_symbol"):
39
+ raise CliError(
40
+ code=CODE_SDK_SYMBOL_UNRESOLVED,
41
+ message=f"No SDK symbol is recorded for '{invocation.command_id}'.",
42
+ exit_status=EXIT_USAGE,
43
+ )
44
+ return str(record["sdk_symbol"])
45
+
46
+
47
+ def _string_positional(invocation: Invocation) -> str | None:
48
+ """Return the first positional argument, or None if absent."""
49
+ return invocation.extra_args[0] if invocation.extra_args else None
50
+
51
+
52
+ def _require_string_positional(invocation: Invocation, name: str) -> str:
53
+ """Return the first positional argument, or raise ``missing_argument``."""
54
+ value = _string_positional(invocation)
55
+ if not value:
56
+ raise CliError(
57
+ code=CODE_MISSING_ARGUMENT,
58
+ message=f"This command requires a {name} argument.",
59
+ exit_status=EXIT_USAGE,
60
+ hint=f"Pass the {name} as a positional argument.",
61
+ )
62
+ return value
63
+
64
+
65
+ def _int_positional(invocation: Invocation, name: str) -> int | None:
66
+ """Parse the first positional argument as an int, or return None if absent."""
67
+ if not invocation.extra_args:
68
+ return None
69
+ raw = invocation.extra_args[0]
70
+ try:
71
+ return int(raw)
72
+ except ValueError as exc:
73
+ raise CliError(
74
+ code=CODE_INVALID_ARGUMENT,
75
+ message=f"The {name} argument '{raw}' is not an integer.",
76
+ exit_status=EXIT_USAGE,
77
+ ) from exc
78
+
79
+
80
+ def _require_int_positional(invocation: Invocation, name: str) -> int:
81
+ """Return the first positional argument as an int, or raise ``missing_argument``."""
82
+ value = _int_positional(invocation, name)
83
+ if value is None:
84
+ raise CliError(
85
+ code=CODE_MISSING_ARGUMENT,
86
+ message=f"This command requires a {name} argument.",
87
+ exit_status=EXIT_USAGE,
88
+ hint=f"Pass the {name} as a positional argument.",
89
+ )
90
+ return value
91
+
92
+
93
+ def _require_field(document: dict[str, Any] | None, field: str) -> Any:
94
+ """Return a required field from the ``--input`` document, or raise usage."""
95
+ if document is None or field not in document:
96
+ raise CliError(
97
+ code=CODE_MISSING_FIELD,
98
+ message=f"This command requires the '{field}' input field.",
99
+ exit_status=EXIT_USAGE,
100
+ hint=f"Pass it via --input, for example: --input '{{\"{field}\": ...}}'.",
101
+ )
102
+ return document[field]
103
+
104
+
105
+ def _forward_optional(
106
+ document: dict[str, Any], kwargs: dict[str, Any], fields: tuple[str, ...]
107
+ ) -> None:
108
+ """Copy any of ``fields`` present in ``document`` into ``kwargs``."""
109
+ for field in fields:
110
+ if field in document:
111
+ kwargs[field] = document[field]
112
+
113
+
114
+ def _meta(invocation: Invocation, workspace_id: int, project_id: int | None) -> dict[str, Any]:
115
+ """Build the common envelope metadata for a billing command."""
116
+ return {
117
+ "profile": invocation.profile,
118
+ "workspace_id": workspace_id,
119
+ "project_id": project_id,
120
+ }
121
+
122
+
123
+ def billing_chargebee_plan(invocation: Invocation) -> HandlerResult:
124
+ """Get the workspace's Chargebee plan details. ``--yes --confirm WS`` required."""
125
+ with open_service(invocation) as (service, auth):
126
+ enforce_confirmation(
127
+ invocation,
128
+ policy=POLICY_CONFIRM_TARGET,
129
+ action=f"view the Chargebee plan for workspace {auth.workspace_id}",
130
+ target=str(auth.workspace_id),
131
+ )
132
+ data = service.call(_symbol(invocation))
133
+ return data, _meta(invocation, auth.workspace_id, None)
134
+
135
+
136
+ def billing_hosted_page(invocation: Invocation) -> HandlerResult:
137
+ """Get a Chargebee hosted-page URL. Object type is positional or an input field."""
138
+ document = invocation.load_input() or {}
139
+ object_type = _string_positional(invocation) or document.get("object_type")
140
+ if not object_type:
141
+ raise CliError(
142
+ code=CODE_MISSING_ARGUMENT,
143
+ message="A hosted-page object type is required.",
144
+ exit_status=EXIT_USAGE,
145
+ hint="Pass the object type as a positional argument or an 'object_type' input field.",
146
+ )
147
+ kwargs: dict[str, Any] = {"object_type": object_type}
148
+ _forward_optional(document, kwargs, ("plan_id",))
149
+ with open_service(invocation) as (service, auth):
150
+ enforce_confirmation(
151
+ invocation,
152
+ policy=POLICY_CONFIRM_TARGET,
153
+ action=f"request a hosted page for workspace {auth.workspace_id}",
154
+ target=str(auth.workspace_id),
155
+ )
156
+ data = service.call(_symbol(invocation), **kwargs)
157
+ return data, _meta(invocation, auth.workspace_id, None)
158
+
159
+
160
+ def billing_invoice_charge(invocation: Invocation) -> HandlerResult:
161
+ """Charge the workspace's outstanding invoice. ``--yes --confirm WS`` required."""
162
+ with open_service(invocation) as (service, auth):
163
+ enforce_confirmation(
164
+ invocation,
165
+ policy=POLICY_CONFIRM_TARGET,
166
+ action=f"charge the invoice for workspace {auth.workspace_id}",
167
+ target=str(auth.workspace_id),
168
+ )
169
+ data = service.call(_symbol(invocation))
170
+ return data, _meta(invocation, auth.workspace_id, None)
171
+
172
+
173
+ def billing_invoice_get(invocation: Invocation) -> HandlerResult:
174
+ """Get one invoice by id. ``--yes --confirm INVOICE_ID`` required."""
175
+ invoice_id = _require_int_positional(invocation, "invoice id")
176
+ enforce_confirmation(
177
+ invocation,
178
+ policy=POLICY_CONFIRM_TARGET,
179
+ action=f"view invoice {invoice_id}",
180
+ target=str(invoice_id),
181
+ )
182
+ with open_service(invocation) as (service, auth):
183
+ data = service.call(_symbol(invocation), invoice_id=invoice_id)
184
+ return data, _meta(invocation, auth.workspace_id, None)
185
+
186
+
187
+ def billing_invoice_list(invocation: Invocation) -> HandlerResult:
188
+ """List invoices for the workspace. ``--yes --confirm WS`` required."""
189
+ document = invocation.load_input() or {}
190
+ kwargs: dict[str, Any] = {}
191
+ _forward_optional(document, kwargs, ("limit", "sort"))
192
+ with open_service(invocation) as (service, auth):
193
+ enforce_confirmation(
194
+ invocation,
195
+ policy=POLICY_CONFIRM_TARGET,
196
+ action=f"list invoices for workspace {auth.workspace_id}",
197
+ target=str(auth.workspace_id),
198
+ )
199
+ data = service.call(_symbol(invocation), **kwargs)
200
+ return data, _meta(invocation, auth.workspace_id, None)
201
+
202
+
203
+ def billing_stripe_cancel(invocation: Invocation) -> HandlerResult:
204
+ """Cancel the workspace's Stripe subscription. ``--yes --confirm WS`` required."""
205
+ document = invocation.load_input() or {}
206
+ kwargs: dict[str, Any] = {}
207
+ _forward_optional(document, kwargs, ("body",))
208
+ with open_service(invocation) as (service, auth):
209
+ enforce_confirmation(
210
+ invocation,
211
+ policy=POLICY_CONFIRM_TARGET,
212
+ action=f"cancel the Stripe subscription for workspace {auth.workspace_id}",
213
+ target=str(auth.workspace_id),
214
+ )
215
+ data = service.call(_symbol(invocation), **kwargs)
216
+ return data, _meta(invocation, auth.workspace_id, None)
217
+
218
+
219
+ def billing_stripe_checkout_url(invocation: Invocation) -> HandlerResult:
220
+ """Get a Stripe Checkout URL. ``success_url``/``cancel_url`` come from ``--input``."""
221
+ document = invocation.load_input()
222
+ success_url = _require_field(document, "success_url")
223
+ cancel_url = _require_field(document, "cancel_url")
224
+ kwargs: dict[str, Any] = {"success_url": success_url, "cancel_url": cancel_url}
225
+ assert document is not None
226
+ _forward_optional(document, kwargs, ("add_payment_method_only",))
227
+ with open_service(invocation) as (service, auth):
228
+ enforce_confirmation(
229
+ invocation,
230
+ policy=POLICY_CONFIRM_TARGET,
231
+ action=f"create a Stripe checkout session for workspace {auth.workspace_id}",
232
+ target=str(auth.workspace_id),
233
+ )
234
+ data = service.call(_symbol(invocation), **kwargs)
235
+ return data, _meta(invocation, auth.workspace_id, None)
236
+
237
+
238
+ def billing_stripe_create(invocation: Invocation) -> HandlerResult:
239
+ """Create a Stripe subscription. Plan id is positional; interval is optional."""
240
+ plan_id = _require_int_positional(invocation, "plan id")
241
+ document = invocation.load_input() or {}
242
+ kwargs: dict[str, Any] = {"plan_id": plan_id}
243
+ _forward_optional(document, kwargs, ("billing_interval",))
244
+ with open_service(invocation) as (service, auth):
245
+ enforce_confirmation(
246
+ invocation,
247
+ policy=POLICY_CONFIRM_TARGET,
248
+ action=f"create a Stripe subscription for workspace {auth.workspace_id}",
249
+ target=str(auth.workspace_id),
250
+ )
251
+ data = service.call(_symbol(invocation), **kwargs)
252
+ return data, _meta(invocation, auth.workspace_id, None)
253
+
254
+
255
+ def billing_stripe_end_trial(invocation: Invocation) -> HandlerResult:
256
+ """End the workspace's Stripe trial. ``--yes --confirm WS`` required."""
257
+ with open_service(invocation) as (service, auth):
258
+ enforce_confirmation(
259
+ invocation,
260
+ policy=POLICY_CONFIRM_TARGET,
261
+ action=f"end the Stripe trial for workspace {auth.workspace_id}",
262
+ target=str(auth.workspace_id),
263
+ )
264
+ data = service.call(_symbol(invocation))
265
+ return data, _meta(invocation, auth.workspace_id, None)
266
+
267
+
268
+ def billing_stripe_get(invocation: Invocation) -> HandlerResult:
269
+ """Get the workspace's Stripe billing state. ``--yes --confirm WS`` required."""
270
+ with open_service(invocation) as (service, auth):
271
+ enforce_confirmation(
272
+ invocation,
273
+ policy=POLICY_CONFIRM_TARGET,
274
+ action=f"view Stripe billing state for workspace {auth.workspace_id}",
275
+ target=str(auth.workspace_id),
276
+ )
277
+ data = service.call(_symbol(invocation))
278
+ return data, _meta(invocation, auth.workspace_id, None)
279
+
280
+
281
+ def billing_stripe_history(invocation: Invocation) -> HandlerResult:
282
+ """Get the workspace's Stripe billing history. ``--yes --confirm WS`` required."""
283
+ with open_service(invocation) as (service, auth):
284
+ enforce_confirmation(
285
+ invocation,
286
+ policy=POLICY_CONFIRM_TARGET,
287
+ action=f"view Stripe billing history for workspace {auth.workspace_id}",
288
+ target=str(auth.workspace_id),
289
+ )
290
+ data = service.call(_symbol(invocation))
291
+ return data, _meta(invocation, auth.workspace_id, None)
292
+
293
+
294
+ def billing_stripe_payment_method_delete(invocation: Invocation) -> HandlerResult:
295
+ """Delete a Stripe payment method. ``--yes --confirm PAYMENT_METHOD_ID`` required."""
296
+ payment_method_id = _require_string_positional(invocation, "payment method id")
297
+ enforce_confirmation(
298
+ invocation,
299
+ policy=POLICY_CONFIRM_TARGET,
300
+ action=f"delete payment method {payment_method_id}",
301
+ target=payment_method_id,
302
+ )
303
+ with open_service(invocation) as (service, auth):
304
+ data = service.call(_symbol(invocation), payment_method_id=payment_method_id)
305
+ return data, _meta(invocation, auth.workspace_id, None)
306
+
307
+
308
+ def billing_stripe_payment_method_list(invocation: Invocation) -> HandlerResult:
309
+ """List the workspace's Stripe payment methods. ``--yes --confirm WS`` required."""
310
+ with open_service(invocation) as (service, auth):
311
+ enforce_confirmation(
312
+ invocation,
313
+ policy=POLICY_CONFIRM_TARGET,
314
+ action=f"list Stripe payment methods for workspace {auth.workspace_id}",
315
+ target=str(auth.workspace_id),
316
+ )
317
+ data = service.call(_symbol(invocation))
318
+ return data, _meta(invocation, auth.workspace_id, None)
319
+
320
+
321
+ def billing_stripe_payment_method_set_default(invocation: Invocation) -> HandlerResult:
322
+ """Set a Stripe payment method as default. ``--yes --confirm PAYMENT_METHOD_ID`` required."""
323
+ payment_method_id = _require_string_positional(invocation, "payment method id")
324
+ enforce_confirmation(
325
+ invocation,
326
+ policy=POLICY_CONFIRM_TARGET,
327
+ action=f"set payment method {payment_method_id} as default",
328
+ target=payment_method_id,
329
+ )
330
+ with open_service(invocation) as (service, auth):
331
+ data = service.call(_symbol(invocation), payment_method_id=payment_method_id)
332
+ return data, _meta(invocation, auth.workspace_id, None)
333
+
334
+
335
+ def billing_stripe_portal_url(invocation: Invocation) -> HandlerResult:
336
+ """Get a Stripe billing portal URL. ``--yes --confirm WS`` required."""
337
+ document = invocation.load_input() or {}
338
+ kwargs: dict[str, Any] = {}
339
+ _forward_optional(document, kwargs, ("return_url",))
340
+ with open_service(invocation) as (service, auth):
341
+ enforce_confirmation(
342
+ invocation,
343
+ policy=POLICY_CONFIRM_TARGET,
344
+ action=f"request a Stripe billing portal URL for workspace {auth.workspace_id}",
345
+ target=str(auth.workspace_id),
346
+ )
347
+ data = service.call(_symbol(invocation), **kwargs)
348
+ return data, _meta(invocation, auth.workspace_id, None)
349
+
350
+
351
+ def billing_stripe_preview_invoice(invocation: Invocation) -> HandlerResult:
352
+ """Preview the workspace's next Stripe invoice. ``--yes --confirm WS`` required."""
353
+ document = invocation.load_input() or {}
354
+ kwargs: dict[str, Any] = {}
355
+ _forward_optional(
356
+ document, kwargs, ("connector_ids", "additional_storage_gb", "additional_user_seats")
357
+ )
358
+ with open_service(invocation) as (service, auth):
359
+ enforce_confirmation(
360
+ invocation,
361
+ policy=POLICY_CONFIRM_TARGET,
362
+ action=f"preview the Stripe invoice for workspace {auth.workspace_id}",
363
+ target=str(auth.workspace_id),
364
+ )
365
+ data = service.call(_symbol(invocation), **kwargs)
366
+ return data, _meta(invocation, auth.workspace_id, None)
367
+
368
+
369
+ def billing_stripe_retry_payment(invocation: Invocation) -> HandlerResult:
370
+ """Retry the workspace's failed Stripe payment. ``--yes --confirm WS`` required."""
371
+ with open_service(invocation) as (service, auth):
372
+ enforce_confirmation(
373
+ invocation,
374
+ policy=POLICY_CONFIRM_TARGET,
375
+ action=f"retry the Stripe payment for workspace {auth.workspace_id}",
376
+ target=str(auth.workspace_id),
377
+ )
378
+ data = service.call(_symbol(invocation))
379
+ return data, _meta(invocation, auth.workspace_id, None)
380
+
381
+
382
+ def billing_stripe_status(invocation: Invocation) -> HandlerResult:
383
+ """Get the workspace's Stripe subscription status. ``--yes --confirm WS`` required."""
384
+ with open_service(invocation) as (service, auth):
385
+ enforce_confirmation(
386
+ invocation,
387
+ policy=POLICY_CONFIRM_TARGET,
388
+ action=f"view Stripe status for workspace {auth.workspace_id}",
389
+ target=str(auth.workspace_id),
390
+ )
391
+ data = service.call(_symbol(invocation))
392
+ return data, _meta(invocation, auth.workspace_id, None)
393
+
394
+
395
+ def billing_stripe_sync(invocation: Invocation) -> HandlerResult:
396
+ """Sync the workspace's Stripe billing state. ``--yes --confirm WS`` required."""
397
+ with open_service(invocation) as (service, auth):
398
+ enforce_confirmation(
399
+ invocation,
400
+ policy=POLICY_CONFIRM_TARGET,
401
+ action=f"sync Stripe billing state for workspace {auth.workspace_id}",
402
+ target=str(auth.workspace_id),
403
+ )
404
+ data = service.call(_symbol(invocation))
405
+ return data, _meta(invocation, auth.workspace_id, None)
406
+
407
+
408
+ def billing_stripe_upcoming_invoice(invocation: Invocation) -> HandlerResult:
409
+ """Get the workspace's upcoming Stripe invoice. ``--yes --confirm WS`` required."""
410
+ with open_service(invocation) as (service, auth):
411
+ enforce_confirmation(
412
+ invocation,
413
+ policy=POLICY_CONFIRM_TARGET,
414
+ action=f"view the upcoming Stripe invoice for workspace {auth.workspace_id}",
415
+ target=str(auth.workspace_id),
416
+ )
417
+ data = service.call(_symbol(invocation))
418
+ return data, _meta(invocation, auth.workspace_id, None)
419
+
420
+
421
+ def billing_stripe_usage(invocation: Invocation) -> HandlerResult:
422
+ """Get the workspace's Stripe usage figures. ``--yes --confirm WS`` required."""
423
+ with open_service(invocation) as (service, auth):
424
+ enforce_confirmation(
425
+ invocation,
426
+ policy=POLICY_CONFIRM_TARGET,
427
+ action=f"view Stripe usage for workspace {auth.workspace_id}",
428
+ target=str(auth.workspace_id),
429
+ )
430
+ data = service.call(_symbol(invocation))
431
+ return data, _meta(invocation, auth.workspace_id, None)
432
+
433
+
434
+ def billing_subscription_get(invocation: Invocation) -> HandlerResult:
435
+ """Get the workspace's subscription details. ``--yes --confirm WS`` required."""
436
+ document = invocation.load_input() or {}
437
+ kwargs: dict[str, Any] = {}
438
+ _forward_optional(document, kwargs, ("fields",))
439
+ with open_service(invocation) as (service, auth):
440
+ enforce_confirmation(
441
+ invocation,
442
+ policy=POLICY_CONFIRM_TARGET,
443
+ action=f"view the subscription for workspace {auth.workspace_id}",
444
+ target=str(auth.workspace_id),
445
+ )
446
+ data = service.call(_symbol(invocation), **kwargs)
447
+ return data, _meta(invocation, auth.workspace_id, None)
448
+
449
+
450
+ def billing_subscription_update(invocation: Invocation) -> HandlerResult:
451
+ """Apply a patch to the workspace's subscription. ``patch`` comes from ``--input``."""
452
+ document = invocation.load_input()
453
+ patch = _require_field(document, "patch")
454
+ with open_service(invocation) as (service, auth):
455
+ enforce_confirmation(
456
+ invocation,
457
+ policy=POLICY_CONFIRM_TARGET,
458
+ action=f"update the subscription for workspace {auth.workspace_id}",
459
+ target=str(auth.workspace_id),
460
+ )
461
+ data = service.call(_symbol(invocation), patch=patch)
462
+ return data, _meta(invocation, auth.workspace_id, None)
@@ -0,0 +1,151 @@
1
+ """Handlers for the ``browse`` command family (resource discovery, read-only).
2
+
3
+ Every browse operation is a read: it dispatches through the generic
4
+ :meth:`~mammoth_cli.services.protocol.MammothService.call` seam to the public
5
+ SDK method named by the command's reviewed manifest ``sdk_symbol``. Commands
6
+ that take a ``project_id`` parameter resolve it from ``--project`` or the
7
+ active project (never from a positional); a command's own resource id (for
8
+ example ``folder_id``) comes from the first CLI positional. The workspace id
9
+ is never forwarded explicitly -- the SDK client already carries it.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+ from mammoth_cli.errors.envelope import (
17
+ CODE_INVALID_ARGUMENT,
18
+ CODE_MISSING_ARGUMENT,
19
+ CODE_SDK_SYMBOL_UNRESOLVED,
20
+ EXIT_USAGE,
21
+ CliError,
22
+ )
23
+ from mammoth_cli.manifest.loader import command_by_id
24
+ from mammoth_cli.runtime.invocation import Invocation
25
+ from mammoth_cli.runtime.session import open_service, require_project, resolved_project
26
+
27
+ HandlerResult = tuple[Any, dict[str, Any]]
28
+
29
+
30
+ def _symbol(invocation: Invocation) -> str:
31
+ """Return the reviewed backing SDK symbol for this command."""
32
+ record = command_by_id(invocation.command_id)
33
+ if record is None or not record.get("sdk_symbol"):
34
+ raise CliError(
35
+ code=CODE_SDK_SYMBOL_UNRESOLVED,
36
+ message=f"No SDK symbol is recorded for '{invocation.command_id}'.",
37
+ exit_status=EXIT_USAGE,
38
+ )
39
+ return str(record["sdk_symbol"])
40
+
41
+
42
+ def _int_positional(invocation: Invocation, name: str) -> int | None:
43
+ """Parse the first positional argument as an int, or return None if absent."""
44
+ if not invocation.extra_args:
45
+ return None
46
+ raw = invocation.extra_args[0]
47
+ try:
48
+ return int(raw)
49
+ except ValueError as exc:
50
+ raise CliError(
51
+ code=CODE_INVALID_ARGUMENT,
52
+ message=f"The {name} argument '{raw}' is not an integer.",
53
+ exit_status=EXIT_USAGE,
54
+ ) from exc
55
+
56
+
57
+ def _require_int_positional(invocation: Invocation, name: str) -> int:
58
+ """Parse the first positional argument as an int, or raise ``missing_argument``."""
59
+ value = _int_positional(invocation, name)
60
+ if value is None:
61
+ raise CliError(
62
+ code=CODE_MISSING_ARGUMENT,
63
+ message=f"This command requires a {name} argument.",
64
+ exit_status=EXIT_USAGE,
65
+ hint=f"Pass the {name} as a positional argument.",
66
+ )
67
+ return value
68
+
69
+
70
+ def _meta(invocation: Invocation, workspace_id: int, project_id: int | None) -> dict[str, Any]:
71
+ """Build the common envelope metadata for a browse command."""
72
+ return {
73
+ "profile": invocation.profile,
74
+ "workspace_id": workspace_id,
75
+ "project_id": project_id,
76
+ }
77
+
78
+
79
+ def _forward_optional(
80
+ document: dict[str, Any], kwargs: dict[str, Any], fields: tuple[str, ...]
81
+ ) -> None:
82
+ """Copy each field present in ``document`` into ``kwargs`` unchanged."""
83
+ for field in fields:
84
+ if field in document:
85
+ kwargs[field] = document[field]
86
+
87
+
88
+ def browse_folder(invocation: Invocation) -> HandlerResult:
89
+ """Browse resources inside one folder of the active project.
90
+
91
+ The folder id is the first positional argument; the project id comes
92
+ from ``--project`` or the active project.
93
+ """
94
+ project_id = require_project(invocation)
95
+ folder_id = _require_int_positional(invocation, "folder id")
96
+ document = invocation.load_input() or {}
97
+ kwargs: dict[str, Any] = {"folder_id": folder_id, "project_id": project_id}
98
+ _forward_optional(document, kwargs, ("level", "fields"))
99
+ with open_service(invocation) as (service, auth):
100
+ data = service.call(_symbol(invocation), **kwargs)
101
+ return data, _meta(invocation, auth.workspace_id, project_id)
102
+
103
+
104
+ def browse_project(invocation: Invocation) -> HandlerResult:
105
+ """Browse the contents of the active project (datasets, folders)."""
106
+ project_id = require_project(invocation)
107
+ document = invocation.load_input() or {}
108
+ kwargs: dict[str, Any] = {"project_id": project_id}
109
+ _forward_optional(
110
+ document, kwargs, ("fields", "name", "browse_type", "sort", "offset", "limit")
111
+ )
112
+ with open_service(invocation) as (service, auth):
113
+ data = service.call(_symbol(invocation), **kwargs)
114
+ return data, _meta(invocation, auth.workspace_id, project_id)
115
+
116
+
117
+ def browse_root(invocation: Invocation) -> HandlerResult:
118
+ """Browse resources across all workspaces the caller has access to."""
119
+ document = invocation.load_input() or {}
120
+ kwargs: dict[str, Any] = {}
121
+ _forward_optional(
122
+ document,
123
+ kwargs,
124
+ (
125
+ "fields",
126
+ "name",
127
+ "browse_type",
128
+ "created_at",
129
+ "updated_at",
130
+ "sort",
131
+ "offset",
132
+ "limit",
133
+ "ids",
134
+ "include_hidden",
135
+ "level",
136
+ "permissions",
137
+ ),
138
+ )
139
+ with open_service(invocation) as (service, auth):
140
+ data = service.call(_symbol(invocation), **kwargs)
141
+ return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
142
+
143
+
144
+ def browse_workspace(invocation: Invocation) -> HandlerResult:
145
+ """Browse all resources in the active workspace (projects, datasets, folders)."""
146
+ document = invocation.load_input() or {}
147
+ kwargs: dict[str, Any] = {}
148
+ _forward_optional(document, kwargs, ("level", "fields", "limit"))
149
+ with open_service(invocation) as (service, auth):
150
+ data = service.call(_symbol(invocation), **kwargs)
151
+ return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
@@ -0,0 +1,43 @@
1
+ """Capability discovery generated from the reviewed manifests.
2
+
3
+ An agent uses these to discover which operations exist, their support state,
4
+ canonical command, SDK symbol, mutation class, and confirmation/wait/pagination
5
+ policies — without reading source.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from mammoth_cli.manifest.loader import command_by_id, load_operations
13
+
14
+
15
+ def capability_entries() -> list[dict[str, Any]]:
16
+ """One capability record per OpenAPI operation."""
17
+ entries: list[dict[str, Any]] = []
18
+ for op in load_operations():
19
+ command_id = op.get("canonical_command")
20
+ command = command_by_id(command_id) if command_id else None
21
+ entries.append(
22
+ {
23
+ "operation_id": op["operation_id"],
24
+ "identity": op["identity"],
25
+ "disposition": op["disposition"],
26
+ "canonical_command": command_id,
27
+ "command_path": command["command_path"] if command else None,
28
+ "sdk_symbol": op.get("sdk_symbol"),
29
+ "mutation_class": command["mutation_class"] if command else None,
30
+ "confirmation": command["confirmation"] if command else None,
31
+ "wait_policy": command["wait_policy"] if command else None,
32
+ "pagination_policy": command["pagination_policy"] if command else None,
33
+ "acceptance_evidence": command["acceptance_evidence"] if command else None,
34
+ }
35
+ )
36
+ return sorted(entries, key=lambda entry: entry["operation_id"])
37
+
38
+
39
+ def get_capability(operation_id: str) -> dict[str, Any] | None:
40
+ for entry in capability_entries():
41
+ if entry["operation_id"] == operation_id:
42
+ return entry
43
+ return None