deepctl-core 0.2.3__tar.gz → 0.2.4__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.
Files changed (22) hide show
  1. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/PKG-INFO +1 -1
  2. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/pyproject.toml +1 -1
  3. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/base_command.py +13 -22
  4. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/output.py +43 -17
  5. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/plugin_manager.py +63 -1
  6. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core.egg-info/PKG-INFO +1 -1
  7. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/README.md +0 -0
  8. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/setup.cfg +0 -0
  9. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/__init__.py +0 -0
  10. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/auth.py +0 -0
  11. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/base_group_command.py +0 -0
  12. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/client.py +0 -0
  13. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/config.py +0 -0
  14. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/models.py +0 -0
  15. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/plugin_env.py +0 -0
  16. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/py.typed +0 -0
  17. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/skill_generator.py +0 -0
  18. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core/timing.py +0 -0
  19. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core.egg-info/SOURCES.txt +0 -0
  20. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core.egg-info/dependency_links.txt +0 -0
  21. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core.egg-info/requires.txt +0 -0
  22. {deepctl_core-0.2.3 → deepctl_core-0.2.4}/src/deepctl_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-core
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Core components for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepctl-core"
7
- version = "0.2.3" # x-release-please-version
7
+ version = "0.2.4" # x-release-please-version
8
8
  description = "Core components for deepctl"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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
- console.print(
84
- f"[dim]Using credentials from {source}[/dim]"
85
- )
85
+ print_info(f"Using credentials from {source}")
86
86
  if project_id:
87
- console.print(
88
- f"[dim]Affecting project: {project_id}[/dim]"
89
- )
87
+ print_info(f"Affecting project: {project_id}")
90
88
  else:
91
- console.print(
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
- console.print(
106
- "[red]Error:[/red] Project ID is required for this command"
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
- console.print("\n[yellow]Command cancelled by user[/yellow]")
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
- console.print(f"[red]Command failed:[/red] {e}")
122
+ print_error(f"Command failed: {e}")
130
123
  if config.get("output.verbose", False):
131
- console.print_exception()
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 pipe.
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 stdout is not a terminal OR a known agentic
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
- if not sys.stdout.isatty():
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
- return any(os.environ.get(v) for v in _AGENTIC_ENV_VARS)
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
- console.print(f"[dim]DEBUG:[/dim] {message}")
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-core
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Core components for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
File without changes
File without changes