deepctl-cmd-update 0.2.6__tar.gz → 0.2.8__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 (17) hide show
  1. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/PKG-INFO +2 -2
  2. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/pyproject.toml +2 -2
  3. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update/command.py +27 -8
  4. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update/installation.py +31 -5
  5. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update/plugin_update_check.py +2 -2
  6. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update/startup_check.py +15 -1
  7. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update/version_check.py +4 -2
  8. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update.egg-info/PKG-INFO +2 -2
  9. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update.egg-info/requires.txt +1 -1
  10. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/README.md +0 -0
  11. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/setup.cfg +0 -0
  12. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update/__init__.py +0 -0
  13. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update/models.py +0 -0
  14. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update.egg-info/SOURCES.txt +0 -0
  15. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update.egg-info/dependency_links.txt +0 -0
  16. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update.egg-info/entry_points.txt +0 -0
  17. {deepctl_cmd_update-0.2.6 → deepctl_cmd_update-0.2.8}/src/deepctl_cmd_update.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-cmd-update
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Update command for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: deepctl-core>=0.1.10
18
+ Requires-Dist: deepctl-core>=0.3.1
19
19
  Requires-Dist: click>=8.0.0
20
20
  Requires-Dist: pydantic>=2.0.0
21
21
  Requires-Dist: rich>=13.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepctl-cmd-update"
7
- version = "0.2.6" # x-release-please-version
7
+ version = "0.2.8" # x-release-please-version
8
8
  description = "Update command for deepctl"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -22,7 +22,7 @@ classifiers = [
22
22
  keywords = ["deepgram", "cli", "update", "self-update"]
23
23
  requires-python = ">=3.10"
24
24
  dependencies = [
25
- "deepctl-core>=0.1.10",
25
+ "deepctl-core>=0.3.1",
26
26
  "click>=8.0.0",
27
27
  "pydantic>=2.0.0",
28
28
  "rich>=13.0.0",
@@ -69,8 +69,16 @@ class UpdateCommand(BaseCommand):
69
69
  auth_manager: Any, # Not used for update command
70
70
  client: Any, # Not used for update command
71
71
  **kwargs: Any,
72
- ) -> dict[str, Any]:
73
- """Handle the update command execution."""
72
+ ) -> UpdateResult | dict[str, Any]:
73
+ """Handle the update command execution.
74
+
75
+ Returns the model rather than a dict wherever the exit code has to
76
+ be non-zero: BaseCommand.exit_code_for reads `.status` off the
77
+ result, and a dict has none, so a dict always exits 0. The three
78
+ failure paths carry status="error" (exit 1) and the decline path
79
+ status="cancelled" (exit 2). output_result unwraps either, so the
80
+ JSON payload is the same shape from both.
81
+ """
74
82
  console = get_console()
75
83
 
76
84
  # Extract arguments from kwargs
@@ -93,10 +101,13 @@ class UpdateCommand(BaseCommand):
93
101
  version_info = asyncio.run(version_checker.check_version(force=True))
94
102
  except Exception as e:
95
103
  print_error(f"Failed to check for updates: {e}")
104
+ # status="error" on the model, not a dict: see the docstring.
105
+ # A command that failed has to exit 1, as the README says.
96
106
  return UpdateResult(
107
+ status="error",
97
108
  success=False,
98
109
  message=f"Failed to check for updates: {e}",
99
- ).model_dump()
110
+ )
100
111
 
101
112
  # Display version info
102
113
  message = format_version_message(version_info)
@@ -136,7 +147,7 @@ class UpdateCommand(BaseCommand):
136
147
  "update.installation_method", install_info.method.value
137
148
  )
138
149
  config._set_config_value("update.installation_path", install_info.path)
139
- config.save()
150
+ config.save(implicit=True)
140
151
 
141
152
  # Display installation info
142
153
  console.print(f"Installation method: [cyan]{install_info.method}[/cyan]")
@@ -167,14 +178,20 @@ class UpdateCommand(BaseCommand):
167
178
  # Confirm update
168
179
  if not yes and not Confirm.ask("\nDo you want to proceed with the update?"):
169
180
  print_info("Update cancelled")
181
+ # Return the model, not .model_dump(): BaseCommand.exit_code_for
182
+ # reads `.status` off the result, and a plain dict has none -- a
183
+ # declined update used to exit 0 while its own payload said
184
+ # "Update cancelled by user". status="cancelled" is the documented
185
+ # exit 2.
170
186
  return UpdateResult(
187
+ status="cancelled",
171
188
  success=False,
172
189
  message="Update cancelled by user",
173
190
  current_version=version_info.current_version,
174
191
  latest_version=version_info.latest_version,
175
192
  update_available=version_info.update_available,
176
193
  installation_method=install_info.method.value,
177
- ).model_dump()
194
+ )
178
195
 
179
196
  # Execute update
180
197
  print_info("Updating deepctl...")
@@ -193,7 +210,7 @@ class UpdateCommand(BaseCommand):
193
210
 
194
211
  # Clear version cache
195
212
  config._set_config_value("update.cached_version_info", None)
196
- config.save()
213
+ config.save(implicit=True)
197
214
 
198
215
  return UpdateResult(
199
216
  success=True,
@@ -212,13 +229,14 @@ class UpdateCommand(BaseCommand):
212
229
  console.print(f"[yellow]{shlex.join(update_command)}[/yellow]")
213
230
 
214
231
  return UpdateResult(
232
+ status="error",
215
233
  success=False,
216
234
  message=f"Update failed: {error_msg}",
217
235
  current_version=version_info.current_version,
218
236
  latest_version=version_info.latest_version,
219
237
  update_available=version_info.update_available,
220
238
  installation_method=install_info.method.value,
221
- ).model_dump()
239
+ )
222
240
 
223
241
  except Exception as e:
224
242
  print_error(f"Failed to execute update: {e}")
@@ -228,10 +246,11 @@ class UpdateCommand(BaseCommand):
228
246
  console.print(f"[yellow]{update_command}[/yellow]")
229
247
 
230
248
  return UpdateResult(
249
+ status="error",
231
250
  success=False,
232
251
  message=f"Failed to execute update: {e}",
233
252
  current_version=version_info.current_version,
234
253
  latest_version=version_info.latest_version,
235
254
  update_available=version_info.update_available,
236
255
  installation_method=install_info.method.value,
237
- ).model_dump()
256
+ )
@@ -7,6 +7,7 @@ import sys
7
7
  from enum import Enum
8
8
  from pathlib import Path
9
9
 
10
+ from deepctl_core._launcher import get_launcher, launched_via_npm
10
11
  from pydantic import BaseModel
11
12
 
12
13
 
@@ -20,6 +21,7 @@ class InstallMethod(str, Enum):
20
21
  UV_TOOL = "uv_tool"
21
22
  UVX = "uvx"
22
23
  PIPX_RUN = "pipx_run"
24
+ NPM = "npm"
23
25
  SYSTEM = "system"
24
26
  DEVELOPMENT = "development"
25
27
  UNKNOWN = "unknown"
@@ -96,7 +98,7 @@ class InstallationDetector:
96
98
  "--upgrade",
97
99
  "deepctl",
98
100
  ],
99
- InstallMethod.HOMEBREW: ["brew", "upgrade", "deepctl"],
101
+ InstallMethod.HOMEBREW: ["brew", "upgrade", "deepgram"],
100
102
  InstallMethod.UV_TOOL: [
101
103
  "uv",
102
104
  "tool",
@@ -105,6 +107,7 @@ class InstallationDetector:
105
107
  ],
106
108
  InstallMethod.UVX: None, # Ephemeral, no update needed
107
109
  InstallMethod.PIPX_RUN: None, # Ephemeral, no update needed
110
+ InstallMethod.NPM: None, # Updated through npm, see instructions
108
111
  InstallMethod.SYSTEM: None, # Handled separately
109
112
  InstallMethod.DEVELOPMENT: None, # Can't auto-update
110
113
  InstallMethod.UNKNOWN: None,
@@ -121,7 +124,23 @@ class InstallationDetector:
121
124
  Human-readable update instructions
122
125
  """
123
126
  if info.method == InstallMethod.HOMEBREW:
124
- return "Please update using Homebrew: brew upgrade deepctl"
127
+ return "Please update using Homebrew: brew update && brew upgrade deepgram"
128
+
129
+ # The launcher's venv or uv cache is private to it, so pip/uv run
130
+ # from inside would update the wrong thing; only npm can update it.
131
+ if info.method == InstallMethod.NPM:
132
+ if get_launcher() == "npx":
133
+ return (
134
+ "Running via npx. Use the latest version with: "
135
+ "npx @deepgram/cli@latest"
136
+ )
137
+ return (
138
+ "Installed via npm. Update with: "
139
+ "npm install -g @deepgram/cli@latest "
140
+ "(or run npx @deepgram/cli@latest). "
141
+ "If @deepgram/cli is a dependency of your project, update it "
142
+ "there instead: npm install @deepgram/cli@latest"
143
+ )
125
144
 
126
145
  if info.method == InstallMethod.UVX:
127
146
  return (
@@ -138,7 +157,9 @@ class InstallationDetector:
138
157
  if info.method == InstallMethod.SYSTEM:
139
158
  # Try to detect the system package manager
140
159
  if sys.platform.startswith("darwin"):
141
- return "Please update using Homebrew: brew upgrade deepctl"
160
+ return (
161
+ "Please update using Homebrew: brew update && brew upgrade deepgram"
162
+ )
142
163
  elif sys.platform.startswith("linux"):
143
164
  # Check for common package managers
144
165
  if Path("/etc/debian_version").exists():
@@ -261,8 +282,8 @@ class InstallationDetector:
261
282
  ) -> InstallMethod:
262
283
  """Detect the installation method.
263
284
 
264
- Priority: development > oneshot > homebrew > uv_tool > pipx >
265
- uv > system > pip > unknown
285
+ Priority: npm > development > oneshot > homebrew > uv_tool >
286
+ pipx > uv > system > pip > unknown
266
287
 
267
288
  Args:
268
289
  package_location: Path to package
@@ -272,6 +293,11 @@ class InstallationDetector:
272
293
  Returns:
273
294
  Detected installation method
274
295
  """
296
+ # The npm launcher runs deepctl through uv or its own venv, which
297
+ # would otherwise be detected as uvx or pip.
298
+ if launched_via_npm():
299
+ return InstallMethod.NPM
300
+
275
301
  # Development installation
276
302
  if is_editable:
277
303
  return InstallMethod.DEVELOPMENT
@@ -261,7 +261,7 @@ def print_pending_plugin_notifications() -> None:
261
261
  u = updates[0]
262
262
  sys.stderr.write(
263
263
  f"\033[33mPlugin update available: {u['name']} {u['current']} → {u['latest']}"
264
- f" — run 'deepctl plugin update {u['name']}' to upgrade\033[0m\n"
264
+ f" — run 'dg plugin update {u['name']}' to upgrade\033[0m\n"
265
265
  )
266
266
  else:
267
267
  sys.stderr.write("\033[33mPlugin updates available:\033[0m\n")
@@ -270,7 +270,7 @@ def print_pending_plugin_notifications() -> None:
270
270
  f"\033[33m {u['name']} {u['current']} → {u['latest']}\033[0m\n"
271
271
  )
272
272
  sys.stderr.write(
273
- "\033[33mRun 'deepctl plugin update <name>' to upgrade\033[0m\n"
273
+ "\033[33mRun 'dg plugin update <name>' to upgrade\033[0m\n"
274
274
  )
275
275
  except (BrokenPipeError, OSError, ValueError):
276
276
  # The output stream closed (e.g. an MCP host disconnected stdio
@@ -15,6 +15,8 @@ import time
15
15
  from pathlib import Path
16
16
  from typing import Any
17
17
 
18
+ from deepctl_core._launcher import launched_via_npm, npm_upgrade_command
19
+
18
20
  # Sentinel used when the background check is suppressed or has no result
19
21
  _NO_RESULT: dict[str, Any] = {}
20
22
 
@@ -42,8 +44,20 @@ def _is_ci() -> bool:
42
44
  return any(os.environ.get(v) for v in _CI_ENV_VARS)
43
45
 
44
46
 
47
+ def upgrade_hint() -> str:
48
+ """Return the command a user should run to upgrade deepctl."""
49
+ if launched_via_npm():
50
+ return npm_upgrade_command()
51
+ return "dg update"
52
+
53
+
45
54
  def _is_oneshot() -> bool:
46
55
  """Return True if running in an ephemeral one-shot environment."""
56
+ # The npm launcher often runs deepctl through `uv tool run`, which looks
57
+ # one-shot, but the npm install itself persists and is pinned to one
58
+ # version, so its users do need to hear about updates.
59
+ if launched_via_npm():
60
+ return False
47
61
  # uvx
48
62
  if "UV_INTERNAL__PARENT_INTERPRETER" in os.environ:
49
63
  return True
@@ -200,7 +214,7 @@ def print_pending_notification() -> None:
200
214
  try:
201
215
  sys.stderr.write(
202
216
  f"\n\033[33m Update available: {current} → {latest}"
203
- f" — run \033[1mdg update\033[0m\033[33m to upgrade\033[0m\n\n"
217
+ f" — run \033[1m{upgrade_hint()}\033[0m\033[33m to upgrade\033[0m\n\n"
204
218
  )
205
219
  except (BrokenPipeError, OSError, ValueError):
206
220
  # The output stream closed (e.g. an MCP host disconnected stdio
@@ -7,6 +7,8 @@ from deepctl_core import Config
7
7
  from packaging import version
8
8
  from pydantic import BaseModel, Field
9
9
 
10
+ from .startup_check import upgrade_hint
11
+
10
12
 
11
13
  class VersionInfo(BaseModel):
12
14
  """Version information from PyPI."""
@@ -245,7 +247,7 @@ class VersionChecker:
245
247
  self.config._set_config_value(
246
248
  "update.cached_version_info", info.model_dump(mode="json")
247
249
  )
248
- self.config.save()
250
+ self.config.save(implicit=True)
249
251
 
250
252
 
251
253
  def format_version_message(info: VersionInfo) -> str:
@@ -270,5 +272,5 @@ def format_version_message(info: VersionInfo) -> str:
270
272
  else:
271
273
  message += f" (released {days_old} days ago)"
272
274
 
273
- message += "\nRun 'dg update' to upgrade"
275
+ message += f"\nRun '{upgrade_hint()}' to upgrade"
274
276
  return message
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepctl-cmd-update
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Update command for deepctl
5
5
  Author-email: Deepgram <devrel@deepgram.com>
6
6
  Maintainer-email: Deepgram <devrel@deepgram.com>
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/markdown
18
- Requires-Dist: deepctl-core>=0.1.10
18
+ Requires-Dist: deepctl-core>=0.3.1
19
19
  Requires-Dist: click>=8.0.0
20
20
  Requires-Dist: pydantic>=2.0.0
21
21
  Requires-Dist: rich>=13.0.0
@@ -1,4 +1,4 @@
1
- deepctl-core>=0.1.10
1
+ deepctl-core>=0.3.1
2
2
  click>=8.0.0
3
3
  pydantic>=2.0.0
4
4
  rich>=13.0.0