deepctl-core 0.2.11__tar.gz → 0.2.12__tar.gz
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.
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/PKG-INFO +1 -1
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/pyproject.toml +1 -1
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/base_command.py +82 -2
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core.egg-info/PKG-INFO +1 -1
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/README.md +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/setup.cfg +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/__init__.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/auth.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/base_group_command.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/client.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/config.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/models.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/output.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/plugin_env.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/plugin_manager.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/py.typed +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/skill_generator.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core/timing.py +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core.egg-info/SOURCES.txt +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core.egg-info/requires.txt +0 -0
- {deepctl_core-0.2.11 → deepctl_core-0.2.12}/src/deepctl_core.egg-info/top_level.txt +0 -0
|
@@ -39,6 +39,7 @@ class BaseCommand(ABC):
|
|
|
39
39
|
raise ValueError("Command must have a name")
|
|
40
40
|
if not self.help:
|
|
41
41
|
raise ValueError("Command must have help text")
|
|
42
|
+
self._guided: bool = True
|
|
42
43
|
|
|
43
44
|
def execute(self, ctx: click.Context, **kwargs: Any) -> None:
|
|
44
45
|
"""Execute the command with Click context.
|
|
@@ -104,26 +105,103 @@ class BaseCommand(ABC):
|
|
|
104
105
|
)
|
|
105
106
|
raise click.ClickException("Project ID required")
|
|
106
107
|
|
|
108
|
+
self._guided = self.is_guided(ctx)
|
|
109
|
+
self._tag_telemetry_start(ctx)
|
|
110
|
+
|
|
107
111
|
# Execute the command
|
|
108
112
|
try:
|
|
109
113
|
with TimingContext(f"command_{self.name}_handler"):
|
|
110
114
|
result = self.handle(config, auth_manager, client, **kwargs)
|
|
111
115
|
|
|
116
|
+
status = "ok"
|
|
117
|
+
if result is not None and hasattr(result, "status"):
|
|
118
|
+
status = str(result.status)
|
|
119
|
+
self._tag_telemetry_status(status)
|
|
120
|
+
|
|
112
121
|
# Handle command result
|
|
113
122
|
if result is not None:
|
|
114
123
|
with TimingContext("output_processing"):
|
|
115
124
|
self.output_result(result, config)
|
|
116
125
|
|
|
117
126
|
except KeyboardInterrupt:
|
|
127
|
+
self._tag_telemetry_status("cancelled")
|
|
118
128
|
stderr_console.print("\n[yellow]Command cancelled by user[/yellow]")
|
|
119
129
|
raise click.Abort()
|
|
120
130
|
|
|
121
131
|
except Exception as e:
|
|
132
|
+
self._tag_telemetry_status("error")
|
|
122
133
|
print_error(f"Command failed: {e}")
|
|
123
134
|
if config.get("output.verbose", False):
|
|
124
135
|
stderr_console.print_exception()
|
|
125
136
|
raise click.ClickException(str(e))
|
|
126
137
|
|
|
138
|
+
def is_guided(self, ctx: click.Context) -> bool:
|
|
139
|
+
"""True only when the user invoked this command with no input at all.
|
|
140
|
+
|
|
141
|
+
'No input' = no positional arg, no flag, no option value, no env-var
|
|
142
|
+
override. The single rule for every command: any user-provided signal
|
|
143
|
+
means scripting intent — skip prompts. Only the bare invocation gets
|
|
144
|
+
the guided/interactive flow.
|
|
145
|
+
|
|
146
|
+
Returns False whenever telemetry's `_agentic` heuristic fires
|
|
147
|
+
(CI=1, --agent-friendly, --non-interactive, AI tool env vars, etc.).
|
|
148
|
+
"""
|
|
149
|
+
if _agentic:
|
|
150
|
+
return False
|
|
151
|
+
for param in ctx.command.params:
|
|
152
|
+
if not param.name:
|
|
153
|
+
continue
|
|
154
|
+
src = ctx.get_parameter_source(param.name)
|
|
155
|
+
if src is not None and src.name in ("COMMANDLINE", "ENVIRONMENT"):
|
|
156
|
+
return False
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
def _tag_telemetry_start(self, ctx: click.Context) -> None:
|
|
160
|
+
"""Annotate the active Sentry transaction with command-level usage signal.
|
|
161
|
+
|
|
162
|
+
Sets the transaction name to the full Click command path
|
|
163
|
+
(e.g. 'deepctl debug audio' instead of just 'debug') and tags it with
|
|
164
|
+
the user-provided flag NAMES (never values), the requested output
|
|
165
|
+
format, and the auth method. All values are bounded enums or
|
|
166
|
+
already-public flag identifiers — no PII risk.
|
|
167
|
+
|
|
168
|
+
Wrapped in a bare except so a Sentry hiccup, missing scope, or unknown
|
|
169
|
+
Click parameter-source enum value can never crash the user's command.
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
import sentry_sdk
|
|
173
|
+
|
|
174
|
+
scope = sentry_sdk.get_current_scope()
|
|
175
|
+
transaction = getattr(scope, "transaction", None)
|
|
176
|
+
if transaction is not None and ctx.command_path:
|
|
177
|
+
transaction.name = ctx.command_path
|
|
178
|
+
|
|
179
|
+
used: list[str] = []
|
|
180
|
+
for param in ctx.command.params:
|
|
181
|
+
if not param.name:
|
|
182
|
+
continue
|
|
183
|
+
src = ctx.get_parameter_source(param.name)
|
|
184
|
+
if src is not None and src.name == "COMMANDLINE":
|
|
185
|
+
used.append(param.name)
|
|
186
|
+
scope.set_tag("cmd.flags", ",".join(sorted(used)) or "(none)")
|
|
187
|
+
scope.set_tag("cmd.output_format", ctx.params.get("output") or "default")
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
def _tag_telemetry_status(self, status: str) -> None:
|
|
192
|
+
"""Tag the active Sentry transaction with the command outcome.
|
|
193
|
+
|
|
194
|
+
Status is a bounded enum: 'ok', 'cancelled', 'error', or whatever the
|
|
195
|
+
command's BaseResult.status returned (also bounded by the result
|
|
196
|
+
model). Bare except so telemetry can't crash the command teardown.
|
|
197
|
+
"""
|
|
198
|
+
try:
|
|
199
|
+
import sentry_sdk
|
|
200
|
+
|
|
201
|
+
sentry_sdk.get_current_scope().set_tag("cmd.status", status)
|
|
202
|
+
except Exception:
|
|
203
|
+
pass
|
|
204
|
+
|
|
127
205
|
@abstractmethod
|
|
128
206
|
def handle(
|
|
129
207
|
self,
|
|
@@ -294,7 +372,7 @@ class BaseCommand(ABC):
|
|
|
294
372
|
Returns:
|
|
295
373
|
True if confirmed, False otherwise
|
|
296
374
|
"""
|
|
297
|
-
if _agentic or not self.ci_friendly:
|
|
375
|
+
if _agentic or not self.ci_friendly or not getattr(self, "_guided", True):
|
|
298
376
|
return default
|
|
299
377
|
|
|
300
378
|
try:
|
|
@@ -318,7 +396,9 @@ class BaseCommand(ABC):
|
|
|
318
396
|
Returns:
|
|
319
397
|
User input
|
|
320
398
|
"""
|
|
321
|
-
if (
|
|
399
|
+
if (
|
|
400
|
+
_agentic or not self.ci_friendly or not getattr(self, "_guided", True)
|
|
401
|
+
) and default is not None:
|
|
322
402
|
return default
|
|
323
403
|
|
|
324
404
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|