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.
- mammoth_cli/__init__.py +14 -0
- mammoth_cli/__main__.py +27 -0
- mammoth_cli/app.py +565 -0
- mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +109 -0
- mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +27 -0
- mammoth_cli/bundled_skill/mammoth-cli/references/input.md +24 -0
- mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +35 -0
- mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +30 -0
- mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +28 -0
- mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +26 -0
- mammoth_cli/commands/__init__.py +29 -0
- mammoth_cli/commands/activity.py +123 -0
- mammoth_cli/commands/addon.py +173 -0
- mammoth_cli/commands/agent.py +199 -0
- mammoth_cli/commands/ai.py +274 -0
- mammoth_cli/commands/annotation.py +166 -0
- mammoth_cli/commands/auth.py +567 -0
- mammoth_cli/commands/automation.py +245 -0
- mammoth_cli/commands/batch.py +204 -0
- mammoth_cli/commands/billing.py +462 -0
- mammoth_cli/commands/browse.py +151 -0
- mammoth_cli/commands/capability.py +43 -0
- mammoth_cli/commands/client_app.py +233 -0
- mammoth_cli/commands/completion.py +82 -0
- mammoth_cli/commands/config.py +309 -0
- mammoth_cli/commands/connector.py +464 -0
- mammoth_cli/commands/context.py +205 -0
- mammoth_cli/commands/dashboard.py +419 -0
- mammoth_cli/commands/data_app.py +239 -0
- mammoth_cli/commands/dataset.py +332 -0
- mammoth_cli/commands/doctor.py +90 -0
- mammoth_cli/commands/external_key.py +163 -0
- mammoth_cli/commands/file.py +243 -0
- mammoth_cli/commands/folder.py +224 -0
- mammoth_cli/commands/job.py +147 -0
- mammoth_cli/commands/notification.py +201 -0
- mammoth_cli/commands/parameter.py +284 -0
- mammoth_cli/commands/project.py +327 -0
- mammoth_cli/commands/registry.py +587 -0
- mammoth_cli/commands/report.py +61 -0
- mammoth_cli/commands/schedule.py +214 -0
- mammoth_cli/commands/schema.py +271 -0
- mammoth_cli/commands/skill.py +59 -0
- mammoth_cli/commands/snippet.py +200 -0
- mammoth_cli/commands/support.py +800 -0
- mammoth_cli/commands/template.py +134 -0
- mammoth_cli/commands/trash.py +115 -0
- mammoth_cli/commands/user.py +190 -0
- mammoth_cli/commands/view.py +1143 -0
- mammoth_cli/commands/view_ops.py +596 -0
- mammoth_cli/commands/webhook.py +235 -0
- mammoth_cli/commands/workflow.py +340 -0
- mammoth_cli/commands/workspace.py +359 -0
- mammoth_cli/context/__init__.py +1 -0
- mammoth_cli/context/credentials.py +223 -0
- mammoth_cli/context/endpoint.py +57 -0
- mammoth_cli/context/profiles.py +370 -0
- mammoth_cli/context/resolver.py +240 -0
- mammoth_cli/contracts/__init__.py +1 -0
- mammoth_cli/contracts/auth.py +32 -0
- mammoth_cli/errors/__init__.py +1 -0
- mammoth_cli/errors/envelope.py +144 -0
- mammoth_cli/manifest/__init__.py +1 -0
- mammoth_cli/manifest/loader.py +96 -0
- mammoth_cli/messages/__init__.py +1 -0
- mammoth_cli/output/__init__.py +1 -0
- mammoth_cli/output/envelope.py +42 -0
- mammoth_cli/output/normalize.py +84 -0
- mammoth_cli/output/policy.py +71 -0
- mammoth_cli/output/render.py +83 -0
- mammoth_cli/py.typed +0 -0
- mammoth_cli/runtime/__init__.py +0 -0
- mammoth_cli/runtime/confirm.py +124 -0
- mammoth_cli/runtime/executor.py +126 -0
- mammoth_cli/runtime/input_loader.py +140 -0
- mammoth_cli/runtime/invocation.py +87 -0
- mammoth_cli/runtime/options.py +172 -0
- mammoth_cli/runtime/session.py +82 -0
- mammoth_cli/runtime/strict.py +240 -0
- mammoth_cli/runtime/validate.py +120 -0
- mammoth_cli/services/__init__.py +1 -0
- mammoth_cli/services/argspec.py +301 -0
- mammoth_cli/services/coerce.py +172 -0
- mammoth_cli/services/conditions.py +85 -0
- mammoth_cli/services/dispatch.py +63 -0
- mammoth_cli/services/factory.py +45 -0
- mammoth_cli/services/input_fields.py +44 -0
- mammoth_cli/services/mapping.py +74 -0
- mammoth_cli/services/openapi_types.py +166 -0
- mammoth_cli/services/positionals.py +408 -0
- mammoth_cli/services/protocol.py +123 -0
- mammoth_cli/services/sdk_service.py +274 -0
- mammoth_cli/services/testing.py +153 -0
- mammoth_cli/services/type_system.py +427 -0
- mammoth_cli/skills/__init__.py +1 -0
- mammoth_cli/skills/installer.py +321 -0
- mammoth_cli/testing.py +55 -0
- mammoth_cli-1.0.0.dist-info/METADATA +92 -0
- mammoth_cli-1.0.0.dist-info/RECORD +150 -0
- mammoth_cli-1.0.0.dist-info/WHEEL +4 -0
- mammoth_cli-1.0.0.dist-info/entry_points.txt +3 -0
- mammoth_cli-1.0.0.dist-info/licenses/LICENSE +23 -0
- spec/manifests/_sdk_introspection.json +1459 -0
- spec/manifests/commands/activity.yaml +72 -0
- spec/manifests/commands/addon.yaml +239 -0
- spec/manifests/commands/agent.yaml +192 -0
- spec/manifests/commands/ai.yaml +152 -0
- spec/manifests/commands/annotation.yaml +191 -0
- spec/manifests/commands/auth.yaml +101 -0
- spec/manifests/commands/automation.yaml +273 -0
- spec/manifests/commands/batch.yaml +262 -0
- spec/manifests/commands/billing.yaml +813 -0
- spec/manifests/commands/browse.yaml +145 -0
- spec/manifests/commands/capability.yaml +74 -0
- spec/manifests/commands/client-app.yaml +193 -0
- spec/manifests/commands/completion.yaml +68 -0
- spec/manifests/commands/config.yaml +134 -0
- spec/manifests/commands/connector.yaml +944 -0
- spec/manifests/commands/context.yaml +101 -0
- spec/manifests/commands/dashboard.yaml +3595 -0
- spec/manifests/commands/data-app.yaml +485 -0
- spec/manifests/commands/dataset.yaml +580 -0
- spec/manifests/commands/doctor.yaml +35 -0
- spec/manifests/commands/external-key.yaml +151 -0
- spec/manifests/commands/file.yaml +341 -0
- spec/manifests/commands/folder.yaml +334 -0
- spec/manifests/commands/job.yaml +151 -0
- spec/manifests/commands/notification.yaml +188 -0
- spec/manifests/commands/parameter.yaml +527 -0
- spec/manifests/commands/project.yaml +669 -0
- spec/manifests/commands/report.yaml +36 -0
- spec/manifests/commands/schedule.yaml +194 -0
- spec/manifests/commands/schema.yaml +74 -0
- spec/manifests/commands/skill.yaml +167 -0
- spec/manifests/commands/snippet.yaml +311 -0
- spec/manifests/commands/support.yaml +1805 -0
- spec/manifests/commands/template.yaml +191 -0
- spec/manifests/commands/trash.yaml +105 -0
- spec/manifests/commands/user.yaml +280 -0
- spec/manifests/commands/version.yaml +35 -0
- spec/manifests/commands/view.yaml +4232 -0
- spec/manifests/commands/webhook.yaml +261 -0
- spec/manifests/commands/workflow.yaml +622 -0
- spec/manifests/commands/workspace.yaml +705 -0
- spec/manifests/openapi-operations.yaml +8853 -0
- spec/manifests/schema-v1.json +212 -0
- spec/manifests/sdk-catalog.source.yaml +4069 -0
- spec/manifests/sdk-methods.yaml +6971 -0
- spec/openapi/metadata.json +10 -0
- spec/openapi/openapi.json +1 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Handlers for the ``agent`` command family (workspace-scoped).
|
|
2
|
+
|
|
3
|
+
The backing SDK methods never accept a ``project_id`` or ``workspace_id``
|
|
4
|
+
keyword argument — a chat scope names its own target (project, dataset, and so
|
|
5
|
+
on) and sessions are identified by an opaque ``session_id`` — so handlers here
|
|
6
|
+
never forward either explicitly, even though the active project id is still
|
|
7
|
+
reported in the envelope metadata. Handlers dispatch through the generic
|
|
8
|
+
:meth:`~mammoth_cli.services.protocol.MammothService.call` seam to the public
|
|
9
|
+
SDK method named by the command's reviewed manifest ``sdk_symbol``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from mammoth_cli.errors.envelope import (
|
|
17
|
+
CODE_MISSING_ARGUMENT,
|
|
18
|
+
CODE_MISSING_FIELD,
|
|
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.confirm import POLICY_PROMPT_OR_YES, enforce_confirmation
|
|
25
|
+
from mammoth_cli.runtime.invocation import Invocation
|
|
26
|
+
from mammoth_cli.runtime.session import open_service, resolved_project
|
|
27
|
+
|
|
28
|
+
HandlerResult = tuple[Any, dict[str, Any]]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _symbol(invocation: Invocation) -> str:
|
|
32
|
+
"""Return the reviewed backing SDK symbol for this command.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
invocation: The current command's resolved global options.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The dotted SDK symbol recorded in the command's manifest entry.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
CliError: ``sdk_symbol_unresolved`` when no symbol is recorded.
|
|
42
|
+
"""
|
|
43
|
+
record = command_by_id(invocation.command_id)
|
|
44
|
+
if record is None or not record.get("sdk_symbol"):
|
|
45
|
+
raise CliError(
|
|
46
|
+
code=CODE_SDK_SYMBOL_UNRESOLVED,
|
|
47
|
+
message=f"No SDK symbol is recorded for '{invocation.command_id}'.",
|
|
48
|
+
exit_status=EXIT_USAGE,
|
|
49
|
+
)
|
|
50
|
+
return str(record["sdk_symbol"])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _string_positional(invocation: Invocation) -> str | None:
|
|
54
|
+
"""Return the first positional argument, or None if absent.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
invocation: The current command's resolved global options.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The first extra argument, or None when none was given.
|
|
61
|
+
"""
|
|
62
|
+
return invocation.extra_args[0] if invocation.extra_args else None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _require_string_positional(invocation: Invocation, name: str) -> str:
|
|
66
|
+
"""Return the first positional argument, raising when absent.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
invocation: The current command's resolved global options.
|
|
70
|
+
name: A human-readable name for the argument, used in error messages.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The first extra argument.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
CliError: ``missing_argument`` when no positional was given.
|
|
77
|
+
"""
|
|
78
|
+
value = _string_positional(invocation)
|
|
79
|
+
if value is None:
|
|
80
|
+
raise CliError(
|
|
81
|
+
code=CODE_MISSING_ARGUMENT,
|
|
82
|
+
message=f"This command requires a {name} argument.",
|
|
83
|
+
exit_status=EXIT_USAGE,
|
|
84
|
+
hint=f"Pass the {name} as a positional argument.",
|
|
85
|
+
)
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _require_field(document: dict[str, Any] | None, field: str) -> Any:
|
|
90
|
+
"""Return a required field from the ``--input`` document, or raise usage.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
document: The parsed ``--input`` document, or None when absent.
|
|
94
|
+
field: The required field name.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The field's value.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
CliError: ``missing_field`` when the document is absent or lacks
|
|
101
|
+
``field``.
|
|
102
|
+
"""
|
|
103
|
+
if document is None or field not in document:
|
|
104
|
+
raise CliError(
|
|
105
|
+
code=CODE_MISSING_FIELD,
|
|
106
|
+
message=f"This command requires the '{field}' input field.",
|
|
107
|
+
exit_status=EXIT_USAGE,
|
|
108
|
+
hint=f"Pass it via --input, for example: --input '{{\"{field}\": ...}}'.",
|
|
109
|
+
)
|
|
110
|
+
return document[field]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _forward_optional(
|
|
114
|
+
document: dict[str, Any], kwargs: dict[str, Any], fields: tuple[str, ...]
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Copy each present field from ``document`` into ``kwargs``, unchanged.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
document: The parsed ``--input`` document.
|
|
120
|
+
kwargs: The keyword-argument mapping being built for the SDK call.
|
|
121
|
+
fields: The optional field names to forward when present.
|
|
122
|
+
"""
|
|
123
|
+
for field in fields:
|
|
124
|
+
if field in document:
|
|
125
|
+
kwargs[field] = document[field]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _meta(invocation: Invocation, workspace_id: int, project_id: int | None) -> dict[str, Any]:
|
|
129
|
+
"""Build the common envelope metadata for an agent command.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
invocation: The current command's resolved global options.
|
|
133
|
+
workspace_id: The authenticated workspace id.
|
|
134
|
+
project_id: The active project id, or None when not resolved.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The envelope metadata mapping.
|
|
138
|
+
"""
|
|
139
|
+
return {
|
|
140
|
+
"profile": invocation.profile,
|
|
141
|
+
"workspace_id": workspace_id,
|
|
142
|
+
"project_id": project_id,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def agent_chat(invocation: Invocation) -> HandlerResult:
|
|
147
|
+
"""Send a chat message to an agent. ``message``/``scope`` are required."""
|
|
148
|
+
document = invocation.load_input()
|
|
149
|
+
message = _require_field(document, "message")
|
|
150
|
+
scope = _require_field(document, "scope")
|
|
151
|
+
assert document is not None
|
|
152
|
+
kwargs: dict[str, Any] = {"message": message, "scope": scope}
|
|
153
|
+
_forward_optional(document, kwargs, ("agent_key", "session_id", "client_context", "selection"))
|
|
154
|
+
with open_service(invocation) as (service, auth):
|
|
155
|
+
data = service.call(_symbol(invocation), **kwargs)
|
|
156
|
+
return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def agent_session_delete(invocation: Invocation) -> HandlerResult:
|
|
160
|
+
"""Delete one agent session by id. Prompt or ``--yes`` required."""
|
|
161
|
+
session_id = _require_string_positional(invocation, "session id")
|
|
162
|
+
enforce_confirmation(
|
|
163
|
+
invocation, policy=POLICY_PROMPT_OR_YES, action=f"delete agent session {session_id}"
|
|
164
|
+
)
|
|
165
|
+
with open_service(invocation) as (service, auth):
|
|
166
|
+
data = service.call(_symbol(invocation), session_id=session_id)
|
|
167
|
+
return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def agent_session_list(invocation: Invocation) -> HandlerResult:
|
|
171
|
+
"""List agent sessions. All fields are optional and come from ``--input``.
|
|
172
|
+
|
|
173
|
+
``workspace_id`` is never forwarded even if present in the input document:
|
|
174
|
+
the authenticated client already scopes every call to its own workspace.
|
|
175
|
+
"""
|
|
176
|
+
document = invocation.load_input() or {}
|
|
177
|
+
kwargs: dict[str, Any] = {}
|
|
178
|
+
_forward_optional(document, kwargs, ("agent_key", "limit", "offset", "include_shared"))
|
|
179
|
+
with open_service(invocation) as (service, auth):
|
|
180
|
+
data = service.call(_symbol(invocation), **kwargs)
|
|
181
|
+
return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def agent_session_messages(invocation: Invocation) -> HandlerResult:
|
|
185
|
+
"""Get the messages of one agent session by id."""
|
|
186
|
+
session_id = _require_string_positional(invocation, "session id")
|
|
187
|
+
with open_service(invocation) as (service, auth):
|
|
188
|
+
data = service.call(_symbol(invocation), session_id=session_id)
|
|
189
|
+
return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def agent_session_set_visibility(invocation: Invocation) -> HandlerResult:
|
|
193
|
+
"""Set an agent session's visibility. ``visibility`` comes from ``--input``."""
|
|
194
|
+
session_id = _require_string_positional(invocation, "session id")
|
|
195
|
+
document = invocation.load_input()
|
|
196
|
+
visibility = _require_field(document, "visibility")
|
|
197
|
+
with open_service(invocation) as (service, auth):
|
|
198
|
+
data = service.call(_symbol(invocation), session_id=session_id, visibility=visibility)
|
|
199
|
+
return data, _meta(invocation, auth.workspace_id, resolved_project(invocation))
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Handlers for the ``ai`` command family (AI-assisted generation, project-scoped).
|
|
2
|
+
|
|
3
|
+
Every command in this family operates against the active project. For
|
|
4
|
+
``ai.condition.generate`` and ``ai.expression.generate`` the dataset id comes
|
|
5
|
+
from a positional argument, the natural-language ``intent`` (and, for
|
|
6
|
+
expressions, ``mode``) come from the strict ``--input`` document, and the
|
|
7
|
+
project id is forwarded explicitly because the backing SDK method accepts it.
|
|
8
|
+
``ai.sql.generate`` and ``ai.suggestion.list`` call SDK methods with no
|
|
9
|
+
``project_id`` parameter at all: the project is instead bound on the SDK
|
|
10
|
+
client when the service is opened, so these handlers still call
|
|
11
|
+
:func:`~mammoth_cli.runtime.session.require_project` to raise a friendly error
|
|
12
|
+
when no project is active, but do not forward ``project_id`` as a keyword
|
|
13
|
+
argument. Handlers dispatch through the generic
|
|
14
|
+
:meth:`~mammoth_cli.services.protocol.MammothService.call` seam to the public
|
|
15
|
+
SDK method named by the command's reviewed manifest ``sdk_symbol``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from mammoth_cli.errors.envelope import (
|
|
23
|
+
CODE_INVALID_ARGUMENT,
|
|
24
|
+
CODE_MISSING_ARGUMENT,
|
|
25
|
+
CODE_MISSING_FIELD,
|
|
26
|
+
CODE_SDK_SYMBOL_UNRESOLVED,
|
|
27
|
+
EXIT_USAGE,
|
|
28
|
+
CliError,
|
|
29
|
+
)
|
|
30
|
+
from mammoth_cli.manifest.loader import command_by_id
|
|
31
|
+
from mammoth_cli.runtime.invocation import Invocation
|
|
32
|
+
from mammoth_cli.runtime.session import open_service, require_project
|
|
33
|
+
|
|
34
|
+
HandlerResult = tuple[Any, dict[str, Any]]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _symbol(invocation: Invocation) -> str:
|
|
38
|
+
"""Return the reviewed backing SDK symbol for this command.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
invocation: The current command's resolved global options.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The dotted SDK symbol recorded in the manifest for this command.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
CliError: ``sdk_symbol_unresolved`` when the manifest has no symbol.
|
|
48
|
+
"""
|
|
49
|
+
record = command_by_id(invocation.command_id)
|
|
50
|
+
if record is None or not record.get("sdk_symbol"):
|
|
51
|
+
raise CliError(
|
|
52
|
+
code=CODE_SDK_SYMBOL_UNRESOLVED,
|
|
53
|
+
message=f"No SDK symbol is recorded for '{invocation.command_id}'.",
|
|
54
|
+
exit_status=EXIT_USAGE,
|
|
55
|
+
)
|
|
56
|
+
return str(record["sdk_symbol"])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _int_positional(invocation: Invocation, name: str) -> int | None:
|
|
60
|
+
"""Parse the first positional argument as an int, or return None if absent.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
invocation: The current command's resolved global options.
|
|
64
|
+
name: A human-readable name for the argument, used in error messages.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The parsed integer, or None when no positional argument was given.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
CliError: ``invalid_argument`` when the positional is not an integer.
|
|
71
|
+
"""
|
|
72
|
+
if not invocation.extra_args:
|
|
73
|
+
return None
|
|
74
|
+
raw = invocation.extra_args[0]
|
|
75
|
+
try:
|
|
76
|
+
return int(raw)
|
|
77
|
+
except ValueError as exc:
|
|
78
|
+
raise CliError(
|
|
79
|
+
code=CODE_INVALID_ARGUMENT,
|
|
80
|
+
message=f"The {name} argument '{raw}' is not an integer.",
|
|
81
|
+
exit_status=EXIT_USAGE,
|
|
82
|
+
) from exc
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _require_int_positional(invocation: Invocation, name: str) -> int:
|
|
86
|
+
"""Parse the first positional argument as an int, or raise usage.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
invocation: The current command's resolved global options.
|
|
90
|
+
name: A human-readable name for the argument, used in error messages.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
The parsed positive integer.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
CliError: ``missing_argument`` when no positional was given;
|
|
97
|
+
``invalid_argument`` when it is not an integer.
|
|
98
|
+
"""
|
|
99
|
+
value = _int_positional(invocation, name)
|
|
100
|
+
if value is None:
|
|
101
|
+
raise CliError(
|
|
102
|
+
code=CODE_MISSING_ARGUMENT,
|
|
103
|
+
message=f"This command requires a {name} argument.",
|
|
104
|
+
exit_status=EXIT_USAGE,
|
|
105
|
+
hint=f"Pass the {name} as a positional argument.",
|
|
106
|
+
)
|
|
107
|
+
return value
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _require_field(document: dict[str, Any] | None, field: str) -> Any:
|
|
111
|
+
"""Return a required field from the ``--input`` document, or raise usage.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
document: The parsed ``--input`` request document, or None.
|
|
115
|
+
field: The required field name.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
The field's value.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
CliError: ``missing_field`` when ``document`` is None or lacks ``field``.
|
|
122
|
+
"""
|
|
123
|
+
if document is None or field not in document:
|
|
124
|
+
raise CliError(
|
|
125
|
+
code=CODE_MISSING_FIELD,
|
|
126
|
+
message=f"This command requires the '{field}' input field.",
|
|
127
|
+
exit_status=EXIT_USAGE,
|
|
128
|
+
hint=f"Pass it via --input, for example: --input '{{\"{field}\": ...}}'.",
|
|
129
|
+
)
|
|
130
|
+
return document[field]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _forward_optional(
|
|
134
|
+
document: dict[str, Any], kwargs: dict[str, Any], fields: tuple[str, ...]
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Copy any present ``fields`` from ``document`` into ``kwargs``.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
document: The parsed ``--input`` request document.
|
|
140
|
+
kwargs: The keyword argument mapping being built for ``service.call``.
|
|
141
|
+
fields: The optional field names to forward when present.
|
|
142
|
+
"""
|
|
143
|
+
for field in fields:
|
|
144
|
+
if field in document:
|
|
145
|
+
kwargs[field] = document[field]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _meta(invocation: Invocation, workspace_id: int, project_id: int) -> dict[str, Any]:
|
|
149
|
+
"""Build the common envelope metadata for an ``ai`` command.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
invocation: The current command's resolved global options.
|
|
153
|
+
workspace_id: The resolved auth's workspace id.
|
|
154
|
+
project_id: The resolved active project id.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
The envelope metadata mapping.
|
|
158
|
+
"""
|
|
159
|
+
return {
|
|
160
|
+
"profile": invocation.profile,
|
|
161
|
+
"workspace_id": workspace_id,
|
|
162
|
+
"project_id": project_id,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def ai_condition_generate(invocation: Invocation) -> HandlerResult:
|
|
167
|
+
"""Generate a filter condition from a natural language intent.
|
|
168
|
+
|
|
169
|
+
The dataset id is a positional argument; ``intent`` is a required
|
|
170
|
+
``--input`` field; ``dataview_id`` and ``sequence_number`` are forwarded
|
|
171
|
+
when present.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
invocation: The current command's resolved global options.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
The raw generated-condition response and envelope metadata.
|
|
178
|
+
"""
|
|
179
|
+
project_id = require_project(invocation)
|
|
180
|
+
dataset_id = _require_int_positional(invocation, "dataset id")
|
|
181
|
+
document = invocation.load_input()
|
|
182
|
+
intent = _require_field(document, "intent")
|
|
183
|
+
kwargs: dict[str, Any] = {
|
|
184
|
+
"intent": intent,
|
|
185
|
+
"dataset_id": dataset_id,
|
|
186
|
+
"project_id": project_id,
|
|
187
|
+
}
|
|
188
|
+
assert document is not None
|
|
189
|
+
_forward_optional(document, kwargs, ("dataview_id", "sequence_number"))
|
|
190
|
+
with open_service(invocation) as (service, auth):
|
|
191
|
+
data = service.call(_symbol(invocation), **kwargs)
|
|
192
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def ai_expression_generate(invocation: Invocation) -> HandlerResult:
|
|
196
|
+
"""Generate a math/metric expression from a natural language intent.
|
|
197
|
+
|
|
198
|
+
The dataset id is a positional argument; ``intent`` and ``mode`` are
|
|
199
|
+
required ``--input`` fields; ``dataview_id`` and ``sequence_number`` are
|
|
200
|
+
forwarded when present.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
invocation: The current command's resolved global options.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
The raw generated-expression response and envelope metadata.
|
|
207
|
+
"""
|
|
208
|
+
project_id = require_project(invocation)
|
|
209
|
+
dataset_id = _require_int_positional(invocation, "dataset id")
|
|
210
|
+
document = invocation.load_input()
|
|
211
|
+
intent = _require_field(document, "intent")
|
|
212
|
+
mode = _require_field(document, "mode")
|
|
213
|
+
kwargs: dict[str, Any] = {
|
|
214
|
+
"intent": intent,
|
|
215
|
+
"mode": mode,
|
|
216
|
+
"dataset_id": dataset_id,
|
|
217
|
+
"project_id": project_id,
|
|
218
|
+
}
|
|
219
|
+
assert document is not None
|
|
220
|
+
_forward_optional(document, kwargs, ("dataview_id", "sequence_number"))
|
|
221
|
+
with open_service(invocation) as (service, auth):
|
|
222
|
+
data = service.call(_symbol(invocation), **kwargs)
|
|
223
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def ai_sql_generate(invocation: Invocation) -> HandlerResult:
|
|
227
|
+
"""Generate SQL from a natural language intent.
|
|
228
|
+
|
|
229
|
+
``intent`` comes from the first positional argument or an ``intent``
|
|
230
|
+
``--input`` field; ``sequence_number`` is forwarded when present. The
|
|
231
|
+
backing SDK method has no ``project_id`` parameter — it resolves the
|
|
232
|
+
project bound on the client — so an active project is required but not
|
|
233
|
+
forwarded as a keyword argument.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
invocation: The current command's resolved global options.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
The raw generated-SQL response and envelope metadata.
|
|
240
|
+
"""
|
|
241
|
+
project_id = require_project(invocation)
|
|
242
|
+
document = invocation.load_input() or {}
|
|
243
|
+
intent = (invocation.extra_args[0] if invocation.extra_args else None) or document.get("intent")
|
|
244
|
+
if not intent:
|
|
245
|
+
raise CliError(
|
|
246
|
+
code=CODE_MISSING_ARGUMENT,
|
|
247
|
+
message="An intent is required.",
|
|
248
|
+
exit_status=EXIT_USAGE,
|
|
249
|
+
hint="Pass the intent as a positional argument or an 'intent' input field.",
|
|
250
|
+
)
|
|
251
|
+
kwargs: dict[str, Any] = {"intent": intent}
|
|
252
|
+
_forward_optional(document, kwargs, ("sequence_number",))
|
|
253
|
+
with open_service(invocation) as (service, auth):
|
|
254
|
+
data = service.call(_symbol(invocation), **kwargs)
|
|
255
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def ai_suggestion_list(invocation: Invocation) -> HandlerResult:
|
|
259
|
+
"""List AI-powered transformation suggestions for the active project.
|
|
260
|
+
|
|
261
|
+
The backing SDK method takes no arguments — it resolves the project bound
|
|
262
|
+
on the client — so an active project is required but not forwarded as a
|
|
263
|
+
keyword argument.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
invocation: The current command's resolved global options.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
The raw suggestions response and envelope metadata.
|
|
270
|
+
"""
|
|
271
|
+
project_id = require_project(invocation)
|
|
272
|
+
with open_service(invocation) as (service, auth):
|
|
273
|
+
data = service.call(_symbol(invocation))
|
|
274
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Handlers for the ``annotation`` command family (project-scoped).
|
|
2
|
+
|
|
3
|
+
Every annotation operation runs inside a resolved project: the project id
|
|
4
|
+
comes from ``--project`` or the active project. An existing annotation is
|
|
5
|
+
addressed by a positional ``annotation_id``; creating one addresses its
|
|
6
|
+
target via the strict ``--input`` document (``target_type``/``target_id``).
|
|
7
|
+
Handlers dispatch through the generic
|
|
8
|
+
:meth:`~mammoth_cli.services.protocol.MammothService.call` seam to the public
|
|
9
|
+
SDK method named by the command's reviewed manifest ``sdk_symbol``.
|
|
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_MISSING_FIELD,
|
|
20
|
+
CODE_SDK_SYMBOL_UNRESOLVED,
|
|
21
|
+
EXIT_USAGE,
|
|
22
|
+
CliError,
|
|
23
|
+
)
|
|
24
|
+
from mammoth_cli.manifest.loader import command_by_id
|
|
25
|
+
from mammoth_cli.runtime.confirm import POLICY_PROMPT_OR_YES, enforce_confirmation
|
|
26
|
+
from mammoth_cli.runtime.invocation import Invocation
|
|
27
|
+
from mammoth_cli.runtime.session import open_service, require_project
|
|
28
|
+
|
|
29
|
+
HandlerResult = tuple[Any, dict[str, Any]]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _symbol(invocation: Invocation) -> str:
|
|
33
|
+
"""Return the reviewed backing SDK symbol for this command."""
|
|
34
|
+
record = command_by_id(invocation.command_id)
|
|
35
|
+
if record is None or not record.get("sdk_symbol"):
|
|
36
|
+
raise CliError(
|
|
37
|
+
code=CODE_SDK_SYMBOL_UNRESOLVED,
|
|
38
|
+
message=f"No SDK symbol is recorded for '{invocation.command_id}'.",
|
|
39
|
+
exit_status=EXIT_USAGE,
|
|
40
|
+
)
|
|
41
|
+
return str(record["sdk_symbol"])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _int_positional(invocation: Invocation, name: str) -> int | None:
|
|
45
|
+
"""Parse the first positional argument as an int, or return None if absent."""
|
|
46
|
+
if not invocation.extra_args:
|
|
47
|
+
return None
|
|
48
|
+
raw = invocation.extra_args[0]
|
|
49
|
+
try:
|
|
50
|
+
return int(raw)
|
|
51
|
+
except ValueError as exc:
|
|
52
|
+
raise CliError(
|
|
53
|
+
code=CODE_INVALID_ARGUMENT,
|
|
54
|
+
message=f"The {name} argument '{raw}' is not an integer.",
|
|
55
|
+
exit_status=EXIT_USAGE,
|
|
56
|
+
) from exc
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _require_int_positional(invocation: Invocation, name: str) -> int:
|
|
60
|
+
"""Parse the first positional argument as an int, or raise usage."""
|
|
61
|
+
value = _int_positional(invocation, name)
|
|
62
|
+
if value is None:
|
|
63
|
+
raise CliError(
|
|
64
|
+
code=CODE_MISSING_ARGUMENT,
|
|
65
|
+
message=f"This command requires a {name} argument.",
|
|
66
|
+
exit_status=EXIT_USAGE,
|
|
67
|
+
hint=f"Pass the {name} as a positional argument.",
|
|
68
|
+
)
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _require_field(document: dict[str, Any] | None, field: str) -> Any:
|
|
73
|
+
"""Return a required field from the ``--input`` document, or raise usage."""
|
|
74
|
+
if document is None or field not in document:
|
|
75
|
+
raise CliError(
|
|
76
|
+
code=CODE_MISSING_FIELD,
|
|
77
|
+
message=f"This command requires the '{field}' input field.",
|
|
78
|
+
exit_status=EXIT_USAGE,
|
|
79
|
+
hint=f"Pass it via --input, for example: --input '{{\"{field}\": ...}}'.",
|
|
80
|
+
)
|
|
81
|
+
return document[field]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _meta(invocation: Invocation, workspace_id: int, project_id: int) -> dict[str, Any]:
|
|
85
|
+
"""Build the common envelope metadata for an annotation command."""
|
|
86
|
+
return {
|
|
87
|
+
"profile": invocation.profile,
|
|
88
|
+
"workspace_id": workspace_id,
|
|
89
|
+
"project_id": project_id,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _forward_optional(
|
|
94
|
+
document: dict[str, Any], kwargs: dict[str, Any], fields: tuple[str, ...]
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Copy each present field from ``document`` into ``kwargs`` unchanged."""
|
|
97
|
+
for field in fields:
|
|
98
|
+
if field in document:
|
|
99
|
+
kwargs[field] = document[field]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def annotation_comment_add(invocation: Invocation) -> HandlerResult:
|
|
103
|
+
"""Add a comment to an annotation. Annotation id is positional; body is required input."""
|
|
104
|
+
project_id = require_project(invocation)
|
|
105
|
+
annotation_id = _require_int_positional(invocation, "annotation id")
|
|
106
|
+
document = invocation.load_input()
|
|
107
|
+
body = _require_field(document, "body")
|
|
108
|
+
with open_service(invocation) as (service, auth):
|
|
109
|
+
data = service.call(
|
|
110
|
+
_symbol(invocation), annotation_id=annotation_id, body=body, project_id=project_id
|
|
111
|
+
)
|
|
112
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def annotation_create(invocation: Invocation) -> HandlerResult:
|
|
116
|
+
"""Create an annotation on a target. Target and body come from ``--input``."""
|
|
117
|
+
project_id = require_project(invocation)
|
|
118
|
+
document = invocation.load_input()
|
|
119
|
+
target_type = _require_field(document, "target_type")
|
|
120
|
+
target_id = _require_field(document, "target_id")
|
|
121
|
+
body = _require_field(document, "body")
|
|
122
|
+
with open_service(invocation) as (service, auth):
|
|
123
|
+
data = service.call(
|
|
124
|
+
_symbol(invocation),
|
|
125
|
+
target_type=target_type,
|
|
126
|
+
target_id=target_id,
|
|
127
|
+
body=body,
|
|
128
|
+
project_id=project_id,
|
|
129
|
+
)
|
|
130
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def annotation_delete(invocation: Invocation) -> HandlerResult:
|
|
134
|
+
"""Delete one annotation by id. Prompt or ``--yes`` required."""
|
|
135
|
+
project_id = require_project(invocation)
|
|
136
|
+
annotation_id = _require_int_positional(invocation, "annotation id")
|
|
137
|
+
enforce_confirmation(
|
|
138
|
+
invocation, policy=POLICY_PROMPT_OR_YES, action=f"delete annotation {annotation_id}"
|
|
139
|
+
)
|
|
140
|
+
with open_service(invocation) as (service, auth):
|
|
141
|
+
data = service.call(_symbol(invocation), annotation_id=annotation_id, project_id=project_id)
|
|
142
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def annotation_list(invocation: Invocation) -> HandlerResult:
|
|
146
|
+
"""List annotations in the active project, optionally filtered by target."""
|
|
147
|
+
project_id = require_project(invocation)
|
|
148
|
+
document = invocation.load_input() or {}
|
|
149
|
+
kwargs: dict[str, Any] = {"project_id": project_id}
|
|
150
|
+
_forward_optional(document, kwargs, ("target_type", "target_id"))
|
|
151
|
+
with open_service(invocation) as (service, auth):
|
|
152
|
+
data = service.call(_symbol(invocation), **kwargs)
|
|
153
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def annotation_update(invocation: Invocation) -> HandlerResult:
|
|
157
|
+
"""Update an annotation's status. Annotation id is positional; status is required input."""
|
|
158
|
+
project_id = require_project(invocation)
|
|
159
|
+
annotation_id = _require_int_positional(invocation, "annotation id")
|
|
160
|
+
document = invocation.load_input()
|
|
161
|
+
status = _require_field(document, "status")
|
|
162
|
+
with open_service(invocation) as (service, auth):
|
|
163
|
+
data = service.call(
|
|
164
|
+
_symbol(invocation), annotation_id=annotation_id, status=status, project_id=project_id
|
|
165
|
+
)
|
|
166
|
+
return data, _meta(invocation, auth.workspace_id, project_id)
|