deepctl-cmd-plugin 0.1.11__tar.gz → 0.1.13__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.
@@ -129,7 +129,8 @@ dmypy.json
129
129
 
130
130
  # uv
131
131
  .uv_cache/
132
- uv.lock
132
+ # uv.lock IS committed: it pins the dev/CI environment so every run tests
133
+ # the same versions and dependency drift arrives as a reviewable PR.
133
134
 
134
135
  # IDEs
135
136
  .vscode/
@@ -157,4 +158,6 @@ Thumbs.db
157
158
  # tmps
158
159
  tmp_chatface/
159
160
  tmp_community-turbo/
160
- tmp_deepgram-cli/
161
+ tmp_deepgram-cli/
162
+ # Local Netlify folder
163
+ .netlify
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.13](https://github.com/deepgram/cli/compare/deepctl-cmd-plugin-v0.1.12...deepctl-cmd-plugin-v0.1.13) (2026-09-23)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **plugin:** return exit code 2, rather than success, when plugin removal is cancelled ([0dcfd2c](https://github.com/deepgram/cli/commit/0dcfd2c0d68a6095f4f6651e51ba6995f2b430d3))
9
+ * **plugin:** write the plugin-removal confirmation prompt to stderr so JSON output stays parseable ([0497ac1](https://github.com/deepgram/cli/commit/0497ac1892333c09d2a482f0b10ccd59d1f1dba2))
10
+
11
+ ## [0.1.12](https://github.com/deepgram/cli/compare/deepctl-cmd-plugin-v0.1.11...deepctl-cmd-plugin-v0.1.12) (2026-05-09)
12
+
13
+
14
+ ### Features
15
+
16
+ * **telemetry:** full Sentry observability + per-command usage tags ([#75](https://github.com/deepgram/cli/issues/75)) ([0fe43d2](https://github.com/deepgram/cli/commit/0fe43d2e00c58d8101ef2bd4b5aaf4437db9f0cf))
17
+
3
18
  ## [0.1.11](https://github.com/deepgram/cli/compare/deepctl-cmd-plugin-v0.1.10...deepctl-cmd-plugin-v0.1.11) (2026-03-09)
4
19
 
5
20
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: deepctl-cmd-plugin
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: Plugin management command for deepctl
5
5
  Author-email: Deepgram DevRel <devrel@deepgram.com>
6
6
  Requires-Python: >=3.10
@@ -2,7 +2,7 @@
2
2
  name = "deepctl-cmd-plugin"
3
3
  description = "Plugin management command for deepctl"
4
4
  readme = "README.md"
5
- version = "0.1.11" # x-release-please-version
5
+ version = "0.1.13" # x-release-please-version
6
6
  requires-python = ">=3.10"
7
7
  authors = [{ name = "Deepgram DevRel", email = "devrel@deepgram.com" }]
8
8
  dependencies = [
@@ -474,8 +474,17 @@ class PluginCommand(BaseGroupCommand):
474
474
  package = kwargs["package"]
475
475
  yes = kwargs.get("yes", False)
476
476
 
477
- if not yes and not click.confirm(f"Are you sure you want to remove {package}?"):
478
- return
477
+ # err=True: the prompt is interaction, not output. Keeping it off
478
+ # stdout is what lets `dg plugin remove ... -o json` stay parseable,
479
+ # and matches the sibling prompt in deepctl-cmd-keys.
480
+ if not yes and not click.confirm(
481
+ f"Are you sure you want to remove {package}?", err=True
482
+ ):
483
+ # Abort rather than return: this is a group subcommand, so there is
484
+ # no result for BaseCommand.EXIT_CODES to map, and a bare return
485
+ # exits 0 -- indistinguishable from a successful removal. main.py
486
+ # turns Abort into the documented exit 2 for user cancellation.
487
+ raise click.Abort()
479
488
 
480
489
  result = self.remove_plugin(config, auth_manager, client, package)
481
490
 
@@ -4,6 +4,7 @@ import json
4
4
  from pathlib import Path
5
5
  from unittest.mock import MagicMock, patch
6
6
 
7
+ import click
7
8
  import pytest
8
9
  from click.testing import CliRunner
9
10
  from deepctl_cmd_plugin.command import PluginCommand
@@ -79,12 +80,11 @@ class TestPluginCommand:
79
80
  """Test getting plugin state from existing file."""
80
81
  test_state = {"plugins": {"test-plugin": {"version": "1.0.0"}}}
81
82
 
82
- with patch.object(Path, "exists", return_value=True):
83
- with patch.object(
84
- Path, "read_text", return_value=json.dumps(test_state)
85
- ):
86
- state = self.command._get_plugin_state()
87
- assert state == test_state
83
+ with patch.object(Path, "exists", return_value=True), patch.object(
84
+ Path, "read_text", return_value=json.dumps(test_state)
85
+ ):
86
+ state = self.command._get_plugin_state()
87
+ assert state == test_state
88
88
 
89
89
  def test_save_plugin_state(self) -> None:
90
90
  """Test saving plugin state."""
@@ -140,34 +140,32 @@ class TestPluginCommand:
140
140
  # Mock state and version operations
141
141
  with patch.object(
142
142
  self.command, "_get_plugin_state"
143
- ) as mock_get_state:
144
- with patch.object(
145
- self.command, "_save_plugin_state"
146
- ) as mock_save_state:
147
- with patch.object(
148
- self.command,
149
- "_get_package_version",
150
- return_value="1.0.0",
151
- ):
152
- mock_get_state.return_value = {"plugins": {}}
153
-
154
- options = PluginInstallOptions(
155
- package="test-plugin"
156
- )
157
- result = self.command.install_plugin(
158
- self.config,
159
- self.auth_manager,
160
- self.client,
161
- options,
162
- )
163
-
164
- assert result.success is True
165
- assert "Successfully installed" in result.message
166
- # Strategy should use the plugin env python
167
- cmd = mock_strategy_run.call_args[0][0]
168
- assert cmd[0] == "/path/to/plugin/python"
169
- # Should save plugin state
170
- mock_save_state.assert_called_once()
143
+ ) as mock_get_state, patch.object(
144
+ self.command, "_save_plugin_state"
145
+ ) as mock_save_state, patch.object(
146
+ self.command,
147
+ "_get_package_version",
148
+ return_value="1.0.0",
149
+ ):
150
+ mock_get_state.return_value = {"plugins": {}}
151
+
152
+ options = PluginInstallOptions(
153
+ package="test-plugin"
154
+ )
155
+ result = self.command.install_plugin(
156
+ self.config,
157
+ self.auth_manager,
158
+ self.client,
159
+ options,
160
+ )
161
+
162
+ assert result.success is True
163
+ assert "Successfully installed" in result.message
164
+ # Strategy should use the plugin env python
165
+ cmd = mock_strategy_run.call_args[0][0]
166
+ assert cmd[0] == "/path/to/plugin/python"
167
+ # Should save plugin state
168
+ mock_save_state.assert_called_once()
171
169
 
172
170
  def test_install_plugin_git_url(self) -> None:
173
171
  """Test handling git URL in install options."""
@@ -237,6 +235,54 @@ class TestPluginCommand:
237
235
  assert result.success is False
238
236
  assert "not installed" in result.message
239
237
 
238
+ def test_remove_declined_aborts_instead_of_exiting_zero(self) -> None:
239
+ """Declining the prompt must exit 2, not 0.
240
+
241
+ `_handle_remove` is a group subcommand returning None, so there is no
242
+ result for BaseCommand.EXIT_CODES to map to an exit code. A bare
243
+ return made a declined removal indistinguishable from a successful
244
+ one for any script branching on the exit code, contradicting the
245
+ contract published in the README. Abort is what main.py turns into 2.
246
+ """
247
+ with (
248
+ patch("click.confirm", return_value=False),
249
+ patch.object(self.command, "remove_plugin") as mock_remove,
250
+ ):
251
+ with pytest.raises(click.Abort):
252
+ self.command._handle_remove(
253
+ self.config,
254
+ self.auth_manager,
255
+ self.client,
256
+ package="test-plugin",
257
+ )
258
+
259
+ mock_remove.assert_not_called()
260
+
261
+ def test_remove_with_yes_skips_the_prompt(self) -> None:
262
+ """Positive control: --yes must not prompt and must not abort."""
263
+ with (
264
+ patch("click.confirm") as mock_confirm,
265
+ patch.object(self.command, "remove_plugin") as mock_remove,
266
+ patch.object(self.command, "_maybe_update_skills"),
267
+ ):
268
+ mock_remove.return_value = PluginOperationResult(
269
+ success=True,
270
+ action="remove",
271
+ package="test-plugin",
272
+ message="Successfully removed test-plugin",
273
+ )
274
+
275
+ self.command._handle_remove(
276
+ self.config,
277
+ self.auth_manager,
278
+ self.client,
279
+ package="test-plugin",
280
+ yes=True,
281
+ )
282
+
283
+ mock_confirm.assert_not_called()
284
+ mock_remove.assert_called_once()
285
+
240
286
  @patch("deepctl_cmd_plugin.command.subprocess.run")
241
287
  def test_discover_from_environment(self, mock_run: MagicMock) -> None:
242
288
  """Test discovering plugins from a specific environment."""
@@ -317,32 +363,29 @@ class TestPluginCommand:
317
363
 
318
364
  with patch.object(
319
365
  self.command, "_discover_plugins", return_value=test_plugins
320
- ):
321
- with patch(
322
- "deepctl_cmd_plugin.command.console.print"
323
- ) as mock_print:
324
- with patch(
325
- "deepctl_cmd_plugin.command.print_info"
326
- ) as mock_print_info:
327
- with patch.object(
328
- self.command.detector, "detect"
329
- ) as mock_detect:
330
- mock_detect.return_value.method = InstallMethod.SYSTEM
331
-
332
- self.command.list_plugins(
333
- self.config,
334
- self.auth_manager,
335
- self.client,
336
- verbose=True,
337
- )
338
-
339
- # Should print a table
340
- mock_print.assert_called_once()
341
- # Should show system installation info via print_info
342
- assert any(
343
- "system" in str(call).lower()
344
- for call in mock_print_info.call_args_list
345
- )
366
+ ), patch(
367
+ "deepctl_cmd_plugin.command.console.print"
368
+ ) as mock_print, patch(
369
+ "deepctl_cmd_plugin.command.print_info"
370
+ ) as mock_print_info, patch.object(
371
+ self.command.detector, "detect"
372
+ ) as mock_detect:
373
+ mock_detect.return_value.method = InstallMethod.SYSTEM
374
+
375
+ self.command.list_plugins(
376
+ self.config,
377
+ self.auth_manager,
378
+ self.client,
379
+ verbose=True,
380
+ )
381
+
382
+ # Should print a table
383
+ mock_print.assert_called_once()
384
+ # Should show system installation info via print_info
385
+ assert any(
386
+ "system" in str(call).lower()
387
+ for call in mock_print_info.call_args_list
388
+ )
346
389
 
347
390
  def test_setup_commands(self) -> None:
348
391
  """Test that all subcommands are properly set up."""
@@ -396,21 +439,20 @@ class TestPluginCommand:
396
439
  # Test search all
397
440
  with patch(
398
441
  "deepctl_cmd_plugin.command.console.print"
399
- ) as mock_print:
400
- with patch(
401
- "deepctl_cmd_plugin.command.print_info"
402
- ) as mock_info:
403
- self.command._handle_search(
404
- self.config, self.auth_manager, self.client
405
- )
406
-
407
- # Should print a table
408
- mock_print.assert_called_once()
409
- # Should show install hint
410
- assert any(
411
- "install" in str(call)
412
- for call in mock_info.call_args_list
413
- )
442
+ ) as mock_print, patch(
443
+ "deepctl_cmd_plugin.command.print_info"
444
+ ) as mock_info:
445
+ self.command._handle_search(
446
+ self.config, self.auth_manager, self.client
447
+ )
448
+
449
+ # Should print a table
450
+ mock_print.assert_called_once()
451
+ # Should show install hint
452
+ assert any(
453
+ "install" in str(call)
454
+ for call in mock_info.call_args_list
455
+ )
414
456
 
415
457
  # Test search with query
416
458
  with patch(