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,464 @@
1
+ """Handlers for the ``connector`` command family.
2
+
3
+ ``connector get``, ``connector list``, and ``connector active`` describe
4
+ connector types and active connections at the workspace level and take no
5
+ project scope. Every other command operates inside a resolved project: the
6
+ project id comes from ``--project`` or the active project (mirroring
7
+ ``folder.py``), and the connector/connection/data-source-config keys that
8
+ address a specific resource come from positional arguments, in the order they
9
+ appear in the command path (``connector_key``, then ``connection_key``, then
10
+ ``ds_config_key``).
11
+
12
+ Connection secrets always come from the strict ``--input`` document, never a
13
+ positional argument: ``config`` on ``connection create``, ``credentials`` on
14
+ ``connection update``, and the AI-chat ``body`` payloads. ``connection
15
+ create``/``update`` and ``ds-config create``/``update`` cause an external
16
+ side effect against a third-party service and always require ``--yes``;
17
+ ``connection delete`` and the ``ds-config`` deletes are destructive and
18
+ accept a prompt or ``--yes``.
19
+
20
+ Handlers dispatch through the generic
21
+ :meth:`~mammoth_cli.services.protocol.MammothService.call` seam to the public
22
+ SDK method named by the command's reviewed manifest ``sdk_symbol``.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from typing import Any
28
+
29
+ from mammoth_cli.errors.envelope import (
30
+ CODE_INVALID_ARGUMENT,
31
+ CODE_MISSING_ARGUMENT,
32
+ CODE_MISSING_FIELD,
33
+ CODE_SDK_SYMBOL_UNRESOLVED,
34
+ EXIT_USAGE,
35
+ CliError,
36
+ )
37
+ from mammoth_cli.manifest.loader import command_by_id
38
+ from mammoth_cli.runtime.confirm import (
39
+ POLICY_PROMPT_OR_YES,
40
+ POLICY_YES_ALWAYS,
41
+ enforce_confirmation,
42
+ )
43
+ from mammoth_cli.runtime.invocation import Invocation
44
+ from mammoth_cli.runtime.session import open_service, require_project, resolved_project
45
+
46
+ HandlerResult = tuple[Any, dict[str, Any]]
47
+
48
+ _DS_CONFIG_CREATE_OPTIONAL = ("query", "file_source", "table", "profile", "validate", "data_sample")
49
+
50
+
51
+ def _symbol(invocation: Invocation) -> str:
52
+ """Return the reviewed backing SDK symbol for this command."""
53
+ record = command_by_id(invocation.command_id)
54
+ if record is None or not record.get("sdk_symbol"):
55
+ raise CliError(
56
+ code=CODE_SDK_SYMBOL_UNRESOLVED,
57
+ message=f"No SDK symbol is recorded for '{invocation.command_id}'.",
58
+ exit_status=EXIT_USAGE,
59
+ )
60
+ return str(record["sdk_symbol"])
61
+
62
+
63
+ def _string_positional_at(invocation: Invocation, index: int, name: str) -> str | None:
64
+ """Return the positional argument at ``index``, or None if absent."""
65
+ if len(invocation.extra_args) <= index:
66
+ return None
67
+ return invocation.extra_args[index]
68
+
69
+
70
+ def _require_string_positional_at(invocation: Invocation, index: int, name: str) -> str:
71
+ """Return the required positional argument at ``index``, or raise usage."""
72
+ value = _string_positional_at(invocation, index, name)
73
+ if not value:
74
+ raise CliError(
75
+ code=CODE_MISSING_ARGUMENT,
76
+ message=f"This command requires a {name} argument.",
77
+ exit_status=EXIT_USAGE,
78
+ hint=f"Pass the {name} as a positional argument.",
79
+ )
80
+ return value
81
+
82
+
83
+ def _require_int_positional_at(invocation: Invocation, index: int, name: str) -> int:
84
+ """Return the required positional argument at ``index`` parsed as an int."""
85
+ raw = _string_positional_at(invocation, index, name)
86
+ if raw is None:
87
+ raise CliError(
88
+ code=CODE_MISSING_ARGUMENT,
89
+ message=f"This command requires a {name} argument.",
90
+ exit_status=EXIT_USAGE,
91
+ hint=f"Pass the {name} as a positional argument.",
92
+ )
93
+ try:
94
+ return int(raw)
95
+ except ValueError as exc:
96
+ raise CliError(
97
+ code=CODE_INVALID_ARGUMENT,
98
+ message=f"The {name} argument '{raw}' is not an integer.",
99
+ exit_status=EXIT_USAGE,
100
+ ) from exc
101
+
102
+
103
+ def _require_field(document: dict[str, Any] | None, field: str) -> Any:
104
+ """Return a required field from the ``--input`` document, or raise usage."""
105
+ if document is None or field not in document:
106
+ raise CliError(
107
+ code=CODE_MISSING_FIELD,
108
+ message=f"This command requires the '{field}' input field.",
109
+ exit_status=EXIT_USAGE,
110
+ hint=f"Pass it via --input, for example: --input '{{\"{field}\": ...}}'.",
111
+ )
112
+ return document[field]
113
+
114
+
115
+ def _forward_optional(
116
+ document: dict[str, Any], kwargs: dict[str, Any], fields: tuple[str, ...]
117
+ ) -> None:
118
+ """Copy each present field from ``document`` into ``kwargs`` unchanged."""
119
+ for field in fields:
120
+ if field in document:
121
+ kwargs[field] = document[field]
122
+
123
+
124
+ def _meta(invocation: Invocation, workspace_id: int, project_id: int | None) -> dict[str, Any]:
125
+ """Build the common envelope metadata for a connector command."""
126
+ return {
127
+ "profile": invocation.profile,
128
+ "workspace_id": workspace_id,
129
+ "project_id": project_id,
130
+ }
131
+
132
+
133
+ def connector_active(invocation: Invocation) -> HandlerResult:
134
+ """List active connectors with established connections (workspace-scoped)."""
135
+ with open_service(invocation) as (service, auth):
136
+ data = service.call(_symbol(invocation))
137
+ return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
138
+
139
+
140
+ def connector_get(invocation: Invocation) -> HandlerResult:
141
+ """Get one connector's details by key (workspace-scoped, no project)."""
142
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
143
+ with open_service(invocation) as (service, auth):
144
+ data = service.call(_symbol(invocation), connector_key=connector_key)
145
+ return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
146
+
147
+
148
+ def connector_list(invocation: Invocation) -> HandlerResult:
149
+ """List all available connectors (workspace-scoped, no project)."""
150
+ with open_service(invocation) as (service, auth):
151
+ data = service.call(_symbol(invocation))
152
+ return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
153
+
154
+
155
+ def connector_ai_chat(invocation: Invocation) -> HandlerResult:
156
+ """Send a message to the connector AI chat assistant. ``body`` is required input."""
157
+ project_id = require_project(invocation)
158
+ document = invocation.load_input()
159
+ body = _require_field(document, "body")
160
+ with open_service(invocation) as (service, auth):
161
+ data = service.call(_symbol(invocation), body=body, project_id=project_id)
162
+ return data, _meta(invocation, auth.workspace_id, project_id)
163
+
164
+
165
+ def connector_ai_history(invocation: Invocation) -> HandlerResult:
166
+ """Get chat history for a connection. The connection key is positional."""
167
+ project_id = require_project(invocation)
168
+ connection_key = _require_string_positional_at(invocation, 0, "connection key")
169
+ with open_service(invocation) as (service, auth):
170
+ data = service.call(
171
+ _symbol(invocation), connection_key=connection_key, project_id=project_id
172
+ )
173
+ return data, _meta(invocation, auth.workspace_id, project_id)
174
+
175
+
176
+ def connector_ai_session_list(invocation: Invocation) -> HandlerResult:
177
+ """List connector chat sessions in the active project."""
178
+ project_id = require_project(invocation)
179
+ with open_service(invocation) as (service, auth):
180
+ data = service.call(_symbol(invocation), project_id=project_id)
181
+ return data, _meta(invocation, auth.workspace_id, project_id)
182
+
183
+
184
+ def connector_ai_session_messages(invocation: Invocation) -> HandlerResult:
185
+ """Get messages for a connector chat session. The session id is positional."""
186
+ project_id = require_project(invocation)
187
+ session_id = _require_int_positional_at(invocation, 0, "session id")
188
+ with open_service(invocation) as (service, auth):
189
+ data = service.call(_symbol(invocation), session_id=session_id, project_id=project_id)
190
+ return data, _meta(invocation, auth.workspace_id, project_id)
191
+
192
+
193
+ def connector_ai_submit_column_selection(invocation: Invocation) -> HandlerResult:
194
+ """Submit a column selection to the connector chat flow. ``body`` is required input."""
195
+ project_id = require_project(invocation)
196
+ document = invocation.load_input()
197
+ body = _require_field(document, "body")
198
+ with open_service(invocation) as (service, auth):
199
+ data = service.call(_symbol(invocation), body=body, project_id=project_id)
200
+ return data, _meta(invocation, auth.workspace_id, project_id)
201
+
202
+
203
+ def connector_ai_submit_credentials(invocation: Invocation) -> HandlerResult:
204
+ """Submit connector credentials to the connector chat flow.
205
+
206
+ ``body`` (the credentials payload) is a required ``--input`` field; it is
207
+ never accepted as a positional argument.
208
+ """
209
+ project_id = require_project(invocation)
210
+ document = invocation.load_input()
211
+ body = _require_field(document, "body")
212
+ with open_service(invocation) as (service, auth):
213
+ data = service.call(_symbol(invocation), body=body, project_id=project_id)
214
+ return data, _meta(invocation, auth.workspace_id, project_id)
215
+
216
+
217
+ def connector_connection_create(invocation: Invocation) -> HandlerResult:
218
+ """Create a connection for a connector. ``--yes`` is always required.
219
+
220
+ ``config`` (the connection credentials) is a required ``--input`` field;
221
+ it is never accepted as a positional argument.
222
+ """
223
+ project_id = require_project(invocation)
224
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
225
+ document = invocation.load_input()
226
+ config = _require_field(document, "config")
227
+ enforce_confirmation(
228
+ invocation,
229
+ policy=POLICY_YES_ALWAYS,
230
+ action=f"create a {connector_key} connection",
231
+ )
232
+ with open_service(invocation) as (service, auth):
233
+ data = service.call(
234
+ _symbol(invocation), connector_key=connector_key, config=config, project_id=project_id
235
+ )
236
+ return data, _meta(invocation, auth.workspace_id, project_id)
237
+
238
+
239
+ def connector_connection_delete(invocation: Invocation) -> HandlerResult:
240
+ """Delete a connection. Prompt or ``--yes`` required."""
241
+ project_id = require_project(invocation)
242
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
243
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
244
+ enforce_confirmation(
245
+ invocation,
246
+ policy=POLICY_PROMPT_OR_YES,
247
+ action=f"delete connection {connection_key} of connector {connector_key}",
248
+ )
249
+ with open_service(invocation) as (service, auth):
250
+ data = service.call(
251
+ _symbol(invocation),
252
+ connector_key=connector_key,
253
+ connection_key=connection_key,
254
+ project_id=project_id,
255
+ )
256
+ return data, _meta(invocation, auth.workspace_id, project_id)
257
+
258
+
259
+ def connector_connection_get(invocation: Invocation) -> HandlerResult:
260
+ """Get one connection's details. Connector and connection keys are positional."""
261
+ project_id = require_project(invocation)
262
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
263
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
264
+ with open_service(invocation) as (service, auth):
265
+ data = service.call(
266
+ _symbol(invocation),
267
+ connector_key=connector_key,
268
+ connection_key=connection_key,
269
+ project_id=project_id,
270
+ )
271
+ return data, _meta(invocation, auth.workspace_id, project_id)
272
+
273
+
274
+ def connector_connection_list(invocation: Invocation) -> HandlerResult:
275
+ """List connections for a connector type. The connector key is positional."""
276
+ project_id = require_project(invocation)
277
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
278
+ with open_service(invocation) as (service, auth):
279
+ data = service.call(_symbol(invocation), connector_key=connector_key, project_id=project_id)
280
+ return data, _meta(invocation, auth.workspace_id, project_id)
281
+
282
+
283
+ def connector_connection_update(invocation: Invocation) -> HandlerResult:
284
+ """Update a connection's credentials. ``--yes`` is always required.
285
+
286
+ ``credentials`` is a required ``--input`` field; it is never accepted as
287
+ a positional argument.
288
+ """
289
+ project_id = require_project(invocation)
290
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
291
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
292
+ document = invocation.load_input()
293
+ credentials = _require_field(document, "credentials")
294
+ enforce_confirmation(
295
+ invocation,
296
+ policy=POLICY_YES_ALWAYS,
297
+ action=f"update credentials for connection {connection_key} of connector {connector_key}",
298
+ )
299
+ with open_service(invocation) as (service, auth):
300
+ data = service.call(
301
+ _symbol(invocation),
302
+ connector_key=connector_key,
303
+ connection_key=connection_key,
304
+ credentials=credentials,
305
+ project_id=project_id,
306
+ )
307
+ return data, _meta(invocation, auth.workspace_id, project_id)
308
+
309
+
310
+ def connector_ds_config_create(invocation: Invocation) -> HandlerResult:
311
+ """Create a data source configuration. ``--yes`` is always required."""
312
+ project_id = require_project(invocation)
313
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
314
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
315
+ document = invocation.load_input() or {}
316
+ kwargs: dict[str, Any] = {
317
+ "connector_key": connector_key,
318
+ "connection_key": connection_key,
319
+ "project_id": project_id,
320
+ }
321
+ _forward_optional(document, kwargs, _DS_CONFIG_CREATE_OPTIONAL)
322
+ enforce_confirmation(
323
+ invocation,
324
+ policy=POLICY_YES_ALWAYS,
325
+ action=f"create a data source config for connection {connection_key}",
326
+ )
327
+ with open_service(invocation) as (service, auth):
328
+ data = service.call(_symbol(invocation), **kwargs)
329
+ return data, _meta(invocation, auth.workspace_id, project_id)
330
+
331
+
332
+ def connector_ds_config_delete(invocation: Invocation) -> HandlerResult:
333
+ """Delete a data source configuration. Prompt or ``--yes`` required."""
334
+ project_id = require_project(invocation)
335
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
336
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
337
+ ds_config_key = _require_string_positional_at(invocation, 2, "ds config key")
338
+ enforce_confirmation(
339
+ invocation,
340
+ policy=POLICY_PROMPT_OR_YES,
341
+ action=f"delete data source config {ds_config_key}",
342
+ )
343
+ with open_service(invocation) as (service, auth):
344
+ data = service.call(
345
+ _symbol(invocation),
346
+ connector_key=connector_key,
347
+ connection_key=connection_key,
348
+ ds_config_key=ds_config_key,
349
+ project_id=project_id,
350
+ )
351
+ return data, _meta(invocation, auth.workspace_id, project_id)
352
+
353
+
354
+ def connector_ds_config_delete_all(invocation: Invocation) -> HandlerResult:
355
+ """Bulk-delete data source configurations for a connection. Prompt or ``--yes`` required."""
356
+ project_id = require_project(invocation)
357
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
358
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
359
+ document = invocation.load_input()
360
+ config_ids = _require_field(document, "config_ids")
361
+ enforce_confirmation(
362
+ invocation,
363
+ policy=POLICY_PROMPT_OR_YES,
364
+ action=f"delete data source configs for connection {connection_key}",
365
+ )
366
+ with open_service(invocation) as (service, auth):
367
+ data = service.call(
368
+ _symbol(invocation),
369
+ connector_key=connector_key,
370
+ connection_key=connection_key,
371
+ config_ids=config_ids,
372
+ project_id=project_id,
373
+ )
374
+ return data, _meta(invocation, auth.workspace_id, project_id)
375
+
376
+
377
+ def connector_ds_config_get(invocation: Invocation) -> HandlerResult:
378
+ """Get one data source configuration by key."""
379
+ project_id = require_project(invocation)
380
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
381
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
382
+ ds_config_key = _require_string_positional_at(invocation, 2, "ds config key")
383
+ with open_service(invocation) as (service, auth):
384
+ data = service.call(
385
+ _symbol(invocation),
386
+ connector_key=connector_key,
387
+ connection_key=connection_key,
388
+ ds_config_key=ds_config_key,
389
+ project_id=project_id,
390
+ )
391
+ return data, _meta(invocation, auth.workspace_id, project_id)
392
+
393
+
394
+ def connector_ds_config_list(invocation: Invocation) -> HandlerResult:
395
+ """List data source configurations for a connection."""
396
+ project_id = require_project(invocation)
397
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
398
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
399
+ with open_service(invocation) as (service, auth):
400
+ data = service.call(
401
+ _symbol(invocation),
402
+ connector_key=connector_key,
403
+ connection_key=connection_key,
404
+ project_id=project_id,
405
+ )
406
+ return data, _meta(invocation, auth.workspace_id, project_id)
407
+
408
+
409
+ def connector_ds_config_update(invocation: Invocation) -> HandlerResult:
410
+ """Update a data source configuration via JSON-patch ops. ``--yes`` is always required."""
411
+ project_id = require_project(invocation)
412
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
413
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
414
+ ds_config_key = _require_string_positional_at(invocation, 2, "ds config key")
415
+ document = invocation.load_input()
416
+ patch = _require_field(document, "patch")
417
+ enforce_confirmation(
418
+ invocation,
419
+ policy=POLICY_YES_ALWAYS,
420
+ action=f"update data source config {ds_config_key}",
421
+ )
422
+ with open_service(invocation) as (service, auth):
423
+ data = service.call(
424
+ _symbol(invocation),
425
+ connector_key=connector_key,
426
+ connection_key=connection_key,
427
+ ds_config_key=ds_config_key,
428
+ patch=patch,
429
+ project_id=project_id,
430
+ )
431
+ return data, _meta(invocation, auth.workspace_id, project_id)
432
+
433
+
434
+ def connector_query_generate(invocation: Invocation) -> HandlerResult:
435
+ """Generate a query for a connector using AI. ``prompt`` is required input."""
436
+ project_id = require_project(invocation)
437
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
438
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
439
+ document = invocation.load_input()
440
+ prompt = _require_field(document, "prompt")
441
+ with open_service(invocation) as (service, auth):
442
+ data = service.call(
443
+ _symbol(invocation),
444
+ connector_key=connector_key,
445
+ connection_key=connection_key,
446
+ prompt=prompt,
447
+ project_id=project_id,
448
+ )
449
+ return data, _meta(invocation, auth.workspace_id, project_id)
450
+
451
+
452
+ def connector_query_status(invocation: Invocation) -> HandlerResult:
453
+ """Get the status of an AI chat session for a connector connection."""
454
+ project_id = require_project(invocation)
455
+ connector_key = _require_string_positional_at(invocation, 0, "connector key")
456
+ connection_key = _require_string_positional_at(invocation, 1, "connection key")
457
+ with open_service(invocation) as (service, auth):
458
+ data = service.call(
459
+ _symbol(invocation),
460
+ connector_key=connector_key,
461
+ connection_key=connection_key,
462
+ project_id=project_id,
463
+ )
464
+ return data, _meta(invocation, auth.workspace_id, project_id)
@@ -0,0 +1,205 @@
1
+ """Bespoke `context project` command family: status, use, clear.
2
+
3
+ A project id is operational context, not authentication. It resolves from
4
+ ``--project``, then the selected profile's saved active project, then no
5
+ project at all. All three commands are local; none makes a network call.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import typer
13
+
14
+ from mammoth_cli.context import profiles
15
+ from mammoth_cli.errors.envelope import CODE_PROFILE_NOT_FOUND, EXIT_USAGE, CliError
16
+ from mammoth_cli.runtime import executor
17
+ from mammoth_cli.runtime import options as go
18
+ from mammoth_cli.runtime.invocation import Invocation
19
+
20
+
21
+ def _profile_name(invocation: Invocation) -> str:
22
+ return invocation.profile or profiles.get_selected()
23
+
24
+
25
+ def _project_source(invocation: Invocation, record: profiles.ProfileRecord | None) -> str:
26
+ if invocation.project is not None:
27
+ return "flag"
28
+ if record is not None and record.project_id is not None:
29
+ return "profile"
30
+ return "none"
31
+
32
+
33
+ def _profile_not_found_error(profile_name: str) -> CliError:
34
+ return CliError(
35
+ code=CODE_PROFILE_NOT_FOUND,
36
+ message=f"No profile named '{profile_name}' exists yet.",
37
+ exit_status=EXIT_USAGE,
38
+ hint="Run 'mammoth auth login' first.",
39
+ recovery_commands=["mammoth auth login"],
40
+ )
41
+
42
+
43
+ def _run_status(invocation: Invocation) -> tuple[dict[str, Any], dict[str, Any]]:
44
+ profile_name = _profile_name(invocation)
45
+ record = profiles.get_profile(profile_name)
46
+ project_id = invocation.project
47
+ if project_id is None and record is not None:
48
+ project_id = record.project_id
49
+ data = {
50
+ "profile": profile_name,
51
+ "project_id": project_id,
52
+ "source": _project_source(invocation, record),
53
+ }
54
+ return data, {"profile": profile_name, "project_id": project_id}
55
+
56
+
57
+ def context_project_status(
58
+ output: str = go.output_option(),
59
+ profile: str | None = go.profile_option(),
60
+ project: int | None = go.project_option(),
61
+ timeout: float | None = go.timeout_option(),
62
+ job_timeout: float | None = go.job_timeout_option(),
63
+ pipeline_timeout: float | None = go.pipeline_timeout_option(),
64
+ color: str = go.color_option(),
65
+ no_input: bool = go.no_input_option(),
66
+ no_progress: bool = go.no_progress_option(),
67
+ debug: bool = go.debug_option(),
68
+ input_file: str | None = go.input_file_option(),
69
+ input_format: str | None = go.input_format_option(),
70
+ ) -> None:
71
+ """Report the resolved active project id for one profile."""
72
+ invocation = go.make_invocation(
73
+ "context.project.status",
74
+ output=output,
75
+ profile=profile,
76
+ project=project,
77
+ timeout=timeout,
78
+ job_timeout=job_timeout,
79
+ pipeline_timeout=pipeline_timeout,
80
+ color=color,
81
+ no_input=no_input,
82
+ no_progress=no_progress,
83
+ debug=debug,
84
+ input_file=input_file,
85
+ input_format=input_format,
86
+ )
87
+
88
+ def producer() -> tuple[Any, dict[str, Any]]:
89
+ return _run_status(invocation)
90
+
91
+ executor.run(invocation.command_id, invocation.output, producer)
92
+
93
+
94
+ def _run_use(invocation: Invocation, *, project_id: int) -> tuple[dict[str, Any], dict[str, Any]]:
95
+ if project_id <= 0:
96
+ raise CliError(
97
+ code="invalid_project_id",
98
+ message="project_id must be a positive integer.",
99
+ exit_status=EXIT_USAGE,
100
+ )
101
+ profile_name = _profile_name(invocation)
102
+ existing = profiles.get_profile(profile_name)
103
+ if existing is None:
104
+ raise _profile_not_found_error(profile_name)
105
+ profiles.save_profile(
106
+ profiles.ProfileRecord(
107
+ name=existing.name,
108
+ workspace_id=existing.workspace_id,
109
+ server_prefix=existing.server_prefix,
110
+ project_id=project_id,
111
+ )
112
+ )
113
+ data = {"profile": profile_name, "project_id": project_id}
114
+ return data, {"profile": profile_name, "project_id": project_id}
115
+
116
+
117
+ def context_project_use(
118
+ output: str = go.output_option(),
119
+ profile: str | None = go.profile_option(),
120
+ project: int | None = go.project_option(),
121
+ timeout: float | None = go.timeout_option(),
122
+ job_timeout: float | None = go.job_timeout_option(),
123
+ pipeline_timeout: float | None = go.pipeline_timeout_option(),
124
+ color: str = go.color_option(),
125
+ no_input: bool = go.no_input_option(),
126
+ no_progress: bool = go.no_progress_option(),
127
+ debug: bool = go.debug_option(),
128
+ input_file: str | None = go.input_file_option(),
129
+ input_format: str | None = go.input_format_option(),
130
+ project_id: int = typer.Argument(..., help="Positive project id to make active."),
131
+ ) -> None:
132
+ """Save a positive project id as the active project for one profile."""
133
+ invocation = go.make_invocation(
134
+ "context.project.use",
135
+ output=output,
136
+ profile=profile,
137
+ project=project,
138
+ timeout=timeout,
139
+ job_timeout=job_timeout,
140
+ pipeline_timeout=pipeline_timeout,
141
+ color=color,
142
+ no_input=no_input,
143
+ no_progress=no_progress,
144
+ debug=debug,
145
+ input_file=input_file,
146
+ input_format=input_format,
147
+ )
148
+
149
+ def producer() -> tuple[Any, dict[str, Any]]:
150
+ return _run_use(invocation, project_id=project_id)
151
+
152
+ executor.run(invocation.command_id, invocation.output, producer)
153
+
154
+
155
+ def _run_clear(invocation: Invocation) -> tuple[dict[str, Any], dict[str, Any]]:
156
+ profile_name = _profile_name(invocation)
157
+ existing = profiles.get_profile(profile_name)
158
+ if existing is not None and existing.project_id is not None:
159
+ profiles.save_profile(
160
+ profiles.ProfileRecord(
161
+ name=existing.name,
162
+ workspace_id=existing.workspace_id,
163
+ server_prefix=existing.server_prefix,
164
+ project_id=None,
165
+ )
166
+ )
167
+ data = {"profile": profile_name, "project_id": None}
168
+ return data, {"profile": profile_name}
169
+
170
+
171
+ def context_project_clear(
172
+ output: str = go.output_option(),
173
+ profile: str | None = go.profile_option(),
174
+ project: int | None = go.project_option(),
175
+ timeout: float | None = go.timeout_option(),
176
+ job_timeout: float | None = go.job_timeout_option(),
177
+ pipeline_timeout: float | None = go.pipeline_timeout_option(),
178
+ color: str = go.color_option(),
179
+ no_input: bool = go.no_input_option(),
180
+ no_progress: bool = go.no_progress_option(),
181
+ debug: bool = go.debug_option(),
182
+ input_file: str | None = go.input_file_option(),
183
+ input_format: str | None = go.input_format_option(),
184
+ ) -> None:
185
+ """Clear the active project id for one profile. Idempotent."""
186
+ invocation = go.make_invocation(
187
+ "context.project.clear",
188
+ output=output,
189
+ profile=profile,
190
+ project=project,
191
+ timeout=timeout,
192
+ job_timeout=job_timeout,
193
+ pipeline_timeout=pipeline_timeout,
194
+ color=color,
195
+ no_input=no_input,
196
+ no_progress=no_progress,
197
+ debug=debug,
198
+ input_file=input_file,
199
+ input_format=input_format,
200
+ )
201
+
202
+ def producer() -> tuple[Any, dict[str, Any]]:
203
+ return _run_clear(invocation)
204
+
205
+ executor.run(invocation.command_id, invocation.output, producer)