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.
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/.gitignore +5 -2
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/CHANGELOG.md +15 -0
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/PKG-INFO +2 -2
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/pyproject.toml +1 -1
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/src/deepctl_cmd_plugin/command.py +11 -2
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/tests/unit/test_plugin_command.py +117 -75
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/README.md +0 -0
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/src/deepctl_cmd_plugin/__init__.py +0 -0
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/src/deepctl_cmd_plugin/models.py +0 -0
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/src/deepctl_cmd_plugin/strategies.py +0 -0
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/tests/__init__.py +0 -0
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/tests/unit/__init__.py +0 -0
- {deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/tests/unit/test_strategies.py +0 -0
|
@@ -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
|
|
|
@@ -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.
|
|
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
|
-
|
|
478
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
self.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deepctl_cmd_plugin-0.1.11 → deepctl_cmd_plugin-0.1.13}/src/deepctl_cmd_plugin/strategies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|