deepctl-core 0.2.3__tar.gz → 0.2.5__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.3 → deepctl_core-0.2.5}/PKG-INFO +1 -1
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/pyproject.toml +1 -1
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/base_command.py +13 -22
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/output.py +43 -17
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/plugin_manager.py +63 -1
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/skill_generator.py +2 -2
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core.egg-info/PKG-INFO +1 -1
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/README.md +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/setup.cfg +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/__init__.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/auth.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/base_group_command.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/client.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/config.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/models.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/plugin_env.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/py.typed +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core/timing.py +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core.egg-info/SOURCES.txt +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core.egg-info/requires.txt +0 -0
- {deepctl_core-0.2.3 → deepctl_core-0.2.5}/src/deepctl_core.egg-info/top_level.txt +0 -0
|
@@ -9,6 +9,7 @@ from rich.console import Console
|
|
|
9
9
|
from .auth import AuthManager
|
|
10
10
|
from .client import DeepgramClient
|
|
11
11
|
from .config import Config
|
|
12
|
+
from .output import _agentic, print_error, print_info, print_warning, stderr_console
|
|
12
13
|
from .timing import TimingContext
|
|
13
14
|
|
|
14
15
|
console = Console()
|
|
@@ -21,6 +22,7 @@ class BaseCommand(ABC):
|
|
|
21
22
|
name: str = ""
|
|
22
23
|
help: str = ""
|
|
23
24
|
short_help: str | None = None
|
|
25
|
+
hidden: bool = False # set True to hide from dg --help (e.g. deprecated aliases)
|
|
24
26
|
|
|
25
27
|
# Command requirements
|
|
26
28
|
requires_auth: bool = False
|
|
@@ -80,17 +82,11 @@ class BaseCommand(ABC):
|
|
|
80
82
|
"explicit flags",
|
|
81
83
|
"environment variables",
|
|
82
84
|
]:
|
|
83
|
-
|
|
84
|
-
f"[dim]Using credentials from {source}[/dim]"
|
|
85
|
-
)
|
|
85
|
+
print_info(f"Using credentials from {source}")
|
|
86
86
|
if project_id:
|
|
87
|
-
|
|
88
|
-
f"[dim]Affecting project: {project_id}[/dim]"
|
|
89
|
-
)
|
|
87
|
+
print_info(f"Affecting project: {project_id}")
|
|
90
88
|
else:
|
|
91
|
-
|
|
92
|
-
"[yellow]Warning: No project ID specified[/yellow]"
|
|
93
|
-
)
|
|
89
|
+
print_warning("No project ID specified")
|
|
94
90
|
|
|
95
91
|
except Exception:
|
|
96
92
|
# guard() already printed helpful error messages;
|
|
@@ -102,12 +98,9 @@ class BaseCommand(ABC):
|
|
|
102
98
|
with TimingContext("project_validation"):
|
|
103
99
|
project_id = auth_manager.get_project_id()
|
|
104
100
|
if not project_id:
|
|
105
|
-
|
|
106
|
-
"
|
|
107
|
-
|
|
108
|
-
console.print(
|
|
109
|
-
"Set DEEPGRAM_PROJECT_ID environment variable or "
|
|
110
|
-
"configure via profile"
|
|
101
|
+
print_error(
|
|
102
|
+
"Project ID is required for this command. "
|
|
103
|
+
"Set DEEPGRAM_PROJECT_ID or configure via profile."
|
|
111
104
|
)
|
|
112
105
|
raise click.ClickException("Project ID required")
|
|
113
106
|
|
|
@@ -122,13 +115,13 @@ class BaseCommand(ABC):
|
|
|
122
115
|
self.output_result(result, config)
|
|
123
116
|
|
|
124
117
|
except KeyboardInterrupt:
|
|
125
|
-
|
|
118
|
+
stderr_console.print("\n[yellow]Command cancelled by user[/yellow]")
|
|
126
119
|
raise click.Abort()
|
|
127
120
|
|
|
128
121
|
except Exception as e:
|
|
129
|
-
|
|
122
|
+
print_error(f"Command failed: {e}")
|
|
130
123
|
if config.get("output.verbose", False):
|
|
131
|
-
|
|
124
|
+
stderr_console.print_exception()
|
|
132
125
|
raise click.ClickException(str(e))
|
|
133
126
|
|
|
134
127
|
@abstractmethod
|
|
@@ -301,8 +294,7 @@ class BaseCommand(ABC):
|
|
|
301
294
|
Returns:
|
|
302
295
|
True if confirmed, False otherwise
|
|
303
296
|
"""
|
|
304
|
-
if not self.ci_friendly:
|
|
305
|
-
# In CI environments, always return the default
|
|
297
|
+
if _agentic or not self.ci_friendly:
|
|
306
298
|
return default
|
|
307
299
|
|
|
308
300
|
try:
|
|
@@ -326,8 +318,7 @@ class BaseCommand(ABC):
|
|
|
326
318
|
Returns:
|
|
327
319
|
User input
|
|
328
320
|
"""
|
|
329
|
-
if not self.ci_friendly and default is not None:
|
|
330
|
-
# In CI environments, return the default
|
|
321
|
+
if (_agentic or not self.ci_friendly) and default is not None:
|
|
331
322
|
return default
|
|
332
323
|
|
|
333
324
|
try:
|
|
@@ -22,25 +22,48 @@ from rich.table import Table
|
|
|
22
22
|
|
|
23
23
|
# ── Agentic / non-TTY detection ──────────────────────────────────
|
|
24
24
|
|
|
25
|
-
_AGENTIC_ENV_VARS = (
|
|
26
|
-
"CLAUDE_CODE",
|
|
27
|
-
"CODEX_CLI",
|
|
28
|
-
"GEMINI_CLI",
|
|
29
|
-
"AIDER",
|
|
30
|
-
"CONTINUE_GLOBAL_DIR",
|
|
31
|
-
)
|
|
32
|
-
|
|
33
25
|
|
|
34
26
|
def is_agentic() -> bool:
|
|
35
|
-
"""Detect if running inside an AI coding tool or non-interactive
|
|
27
|
+
"""Detect if running inside an AI coding tool or non-interactive context.
|
|
28
|
+
|
|
29
|
+
Uses a layered signal approach:
|
|
30
|
+
- Hard signals: known AI-tool environment variables (high confidence)
|
|
31
|
+
- Soft signals: score-based TTY/CI heuristics (3+ = probable agent)
|
|
36
32
|
|
|
37
|
-
Returns True when
|
|
38
|
-
environment variable is set. This lets commands produce
|
|
39
|
-
machine-friendly output automatically.
|
|
33
|
+
Returns True when the context is likely non-interactive or AI-driven.
|
|
40
34
|
"""
|
|
41
|
-
|
|
35
|
+
env = os.environ
|
|
36
|
+
|
|
37
|
+
# Hard signals: explicit agent mode flag or known AI tool env vars
|
|
38
|
+
if "--agent-friendly" in sys.argv:
|
|
39
|
+
return True
|
|
40
|
+
if env.get("CLAUDECODE"):
|
|
41
|
+
return True
|
|
42
|
+
if env.get("CLAUDE_CODE_ENTRYPOINT"):
|
|
43
|
+
return True
|
|
44
|
+
if env.get("CODEX_SANDBOX"):
|
|
45
|
+
return True
|
|
46
|
+
if env.get("CODEX_SANDBOX_NETWORK_DISABLED"):
|
|
42
47
|
return True
|
|
43
|
-
|
|
48
|
+
if env.get("OR_APP_NAME") == "Aider":
|
|
49
|
+
return True
|
|
50
|
+
if "aider" in env.get("OR_SITE_URL", ""):
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
# Soft signals — score-based; none is conclusive alone
|
|
54
|
+
score = 0
|
|
55
|
+
if not sys.stdin.isatty():
|
|
56
|
+
score += 1
|
|
57
|
+
if not sys.stdout.isatty():
|
|
58
|
+
score += 1
|
|
59
|
+
if env.get("CI") in ("true", "1"):
|
|
60
|
+
score += 1
|
|
61
|
+
if not env.get("TERM") or env.get("TERM") == "dumb":
|
|
62
|
+
score += 1
|
|
63
|
+
if "NO_COLOR" in env:
|
|
64
|
+
score += 1
|
|
65
|
+
|
|
66
|
+
return score >= 3
|
|
44
67
|
|
|
45
68
|
|
|
46
69
|
_agentic = is_agentic()
|
|
@@ -345,7 +368,10 @@ def print_debug(message: str) -> None:
|
|
|
345
368
|
message: Debug message
|
|
346
369
|
"""
|
|
347
370
|
if _output_config["verbose"]:
|
|
348
|
-
|
|
371
|
+
if _output_config["agentic"]:
|
|
372
|
+
stderr_console.print(f"DEBUG: {message}")
|
|
373
|
+
else:
|
|
374
|
+
console.print(f"[dim]DEBUG:[/dim] {message}")
|
|
349
375
|
|
|
350
376
|
|
|
351
377
|
def create_progress_bar(description: str = "Processing...") -> Progress:
|
|
@@ -418,7 +444,7 @@ def confirm_action(message: str, default: bool = False) -> bool:
|
|
|
418
444
|
Returns:
|
|
419
445
|
True if confirmed, False otherwise
|
|
420
446
|
"""
|
|
421
|
-
if _output_config["quiet"]:
|
|
447
|
+
if _output_config["quiet"] or _output_config["agentic"]:
|
|
422
448
|
return default
|
|
423
449
|
|
|
424
450
|
try:
|
|
@@ -443,7 +469,7 @@ def prompt_input(message: str, default: str | None = None) -> str:
|
|
|
443
469
|
Returns:
|
|
444
470
|
User input
|
|
445
471
|
"""
|
|
446
|
-
if _output_config["quiet"] and default is not None:
|
|
472
|
+
if (_output_config["quiet"] or _output_config["agentic"]) and default is not None:
|
|
447
473
|
return default
|
|
448
474
|
|
|
449
475
|
try:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Plugin manager for deepctl command discovery and loading."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import sys
|
|
4
5
|
from importlib import metadata
|
|
5
6
|
from pathlib import Path
|
|
@@ -211,7 +212,7 @@ class PluginManager:
|
|
|
211
212
|
command_func.__name__ = command_instance.name.replace("-", "_")
|
|
212
213
|
command_func.__doc__ = command_instance.help
|
|
213
214
|
|
|
214
|
-
# Build help text with examples
|
|
215
|
+
# Build help text with examples (+ AI hint when in agentic context)
|
|
215
216
|
help_text = self._build_help_text(command_instance)
|
|
216
217
|
|
|
217
218
|
# Create base command
|
|
@@ -220,11 +221,33 @@ class PluginManager:
|
|
|
220
221
|
callback=command_func,
|
|
221
222
|
help=help_text,
|
|
222
223
|
short_help=command_instance.short_help or command_instance.help,
|
|
224
|
+
hidden=command_instance.hidden,
|
|
223
225
|
)
|
|
224
226
|
|
|
225
227
|
# Add arguments and options
|
|
226
228
|
cmd = self._add_command_arguments(cmd, command_instance)
|
|
227
229
|
|
|
230
|
+
# Add --agent-friendly as an eager option so it fires before argument
|
|
231
|
+
# validation (like --help does), allowing AI agents to query metadata
|
|
232
|
+
# without supplying required positional arguments.
|
|
233
|
+
manager_ref = self
|
|
234
|
+
|
|
235
|
+
def _agent_friendly_callback(
|
|
236
|
+
ctx: click.Context, _param: click.Parameter, value: bool
|
|
237
|
+
) -> None:
|
|
238
|
+
if value:
|
|
239
|
+
manager_ref._output_command_ai_metadata(command_instance)
|
|
240
|
+
ctx.exit()
|
|
241
|
+
|
|
242
|
+
cmd = click.option(
|
|
243
|
+
"--agent-friendly",
|
|
244
|
+
is_flag=True,
|
|
245
|
+
is_eager=True,
|
|
246
|
+
expose_value=False,
|
|
247
|
+
help="Output machine-readable JSON metadata for this command and exit.",
|
|
248
|
+
callback=_agent_friendly_callback,
|
|
249
|
+
)(cmd)
|
|
250
|
+
|
|
228
251
|
return cmd
|
|
229
252
|
|
|
230
253
|
def _build_help_text(self, instance: Any) -> str:
|
|
@@ -242,8 +265,47 @@ class PluginManager:
|
|
|
242
265
|
help_text += "\n\nExamples:\n"
|
|
243
266
|
for ex in examples:
|
|
244
267
|
help_text += f" {ex}\n"
|
|
268
|
+
help_text += "\n\nFor agents, use --agent-friendly."
|
|
245
269
|
return help_text
|
|
246
270
|
|
|
271
|
+
def _output_command_ai_metadata(self, instance: Any) -> None:
|
|
272
|
+
"""Output AI-friendly JSON metadata for a command to stdout."""
|
|
273
|
+
|
|
274
|
+
def _type_name(t: Any) -> str:
|
|
275
|
+
return t.__name__ if hasattr(t, "__name__") else str(t)
|
|
276
|
+
|
|
277
|
+
arguments = (
|
|
278
|
+
instance.get_arguments() if hasattr(instance, "get_arguments") else []
|
|
279
|
+
)
|
|
280
|
+
metadata_doc = {
|
|
281
|
+
"command": instance.name,
|
|
282
|
+
"description": instance.help,
|
|
283
|
+
"agent_hints": getattr(instance, "agent_help", "") or instance.help,
|
|
284
|
+
"examples": list(getattr(instance, "examples", [])),
|
|
285
|
+
"requires_auth": bool(getattr(instance, "requires_auth", False)),
|
|
286
|
+
"requires_project": bool(getattr(instance, "requires_project", False)),
|
|
287
|
+
"non_interactive": True,
|
|
288
|
+
"output_formats": ["json", "yaml", "table", "csv"],
|
|
289
|
+
"parameters": [
|
|
290
|
+
{
|
|
291
|
+
"name": arg.get("name")
|
|
292
|
+
or (arg.get("names", [""])[0] if arg.get("names") else ""),
|
|
293
|
+
"help": arg.get("help", ""),
|
|
294
|
+
"required": bool(
|
|
295
|
+
arg.get("required", not arg.get("is_option", False))
|
|
296
|
+
),
|
|
297
|
+
"type": "bool"
|
|
298
|
+
if arg.get("is_flag")
|
|
299
|
+
else _type_name(arg.get("type", str)),
|
|
300
|
+
"is_option": bool(arg.get("is_option", False)),
|
|
301
|
+
"is_flag": bool(arg.get("is_flag", False)),
|
|
302
|
+
"default": arg.get("default"),
|
|
303
|
+
}
|
|
304
|
+
for arg in arguments
|
|
305
|
+
],
|
|
306
|
+
}
|
|
307
|
+
click.echo(json.dumps(metadata_doc, indent=2, default=str))
|
|
308
|
+
|
|
247
309
|
def _create_click_group(self, group_instance: BaseGroupCommand) -> click.Group:
|
|
248
310
|
"""Create a Click group from a BaseGroupCommand instance.
|
|
249
311
|
|
|
@@ -551,8 +551,8 @@ def render_developer_guide(
|
|
|
551
551
|
lines.append("")
|
|
552
552
|
lines.append("```bash")
|
|
553
553
|
lines.append("dg login # Authenticate")
|
|
554
|
-
lines.append("dg
|
|
555
|
-
lines.append("dg
|
|
554
|
+
lines.append("dg listen audio.wav # Transcribe a file")
|
|
555
|
+
lines.append("dg listen --mic # Live transcription from mic")
|
|
556
556
|
lines.append('dg speak "Hello world" # Text-to-speech')
|
|
557
557
|
lines.append("dg projects list # List projects")
|
|
558
558
|
lines.append("dg usage # View API usage")
|
|
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
|