devs-cli 4.0.4__py3-none-any.whl → 5.0.0__py3-none-any.whl

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.
devs/cli.py CHANGED
@@ -246,12 +246,13 @@ def start(dev_names: tuple, rebuild: bool, rebuild_if_changed: bool, live: bool,
246
246
  default=None,
247
247
  envvar='DEVS_SSH_HOST',
248
248
  help=(
249
- 'Attach VS Code to a container ALREADY RUNNING on a remote SSH host '
250
- '(e.g. Tailscale or any SSH-reachable Docker host). This is connection-only: '
251
- 'it does NOT provision anything. '
252
- 'It does NOT create, start, or sync the container — you must have already '
253
- 'started it on the remote host yourself (e.g. run `devs start` over there). '
254
- 'Only builds the VS Code Remote-SSH + attach URI. '
249
+ 'Open VS Code via direct Remote-SSH into a container ALREADY RUNNING and '
250
+ 'reachable at this SSH host (e.g. its Tailscale name). The container is its '
251
+ 'own ssh host, so this opens its /workspaces folder directly — no attach. '
252
+ 'Connection-only: it does NOT create, start, or sync the container — start it '
253
+ 'yourself first (e.g. `devs start` on the host). '
254
+ 'Usually unnecessary: without --ssh, `devs vscode <dev>` auto-discovers the '
255
+ "container's tailnet name via the handshake start-tailscale.sh writes. "
255
256
  'Can also be set via the DEVS_SSH_HOST env var or ssh_host in DEVS.yml.'
256
257
  ),
257
258
  )
@@ -264,7 +265,8 @@ def vscode(dev_names: tuple, delay: float, live: bool, env: tuple, ssh_host: str
264
265
  Example: devs vscode sally bob
265
266
  Example: devs vscode sally --live # Start with current directory mounted
266
267
  Example: devs vscode sally --env QUART_PORT=5001
267
- Example: devs vscode sally --ssh myhost.tailnet.ts.net # Attach to a container ALREADY running on myhost
268
+ Example: devs vscode sally # Auto: direct Remote-SSH if sally's container is on the tailnet, else local
269
+ Example: devs vscode sally --ssh devs-myorg-myrepo-sally # Force direct Remote-SSH into that node
268
270
  """
269
271
  check_dependencies()
270
272
  project = get_project()
@@ -275,79 +277,88 @@ def vscode(dev_names: tuple, delay: float, live: bool, env: tuple, ssh_host: str
275
277
 
276
278
  vscode_integration = VSCodeIntegration(project)
277
279
 
280
+ # Decide, per dev, whether to open via direct Remote-SSH (the container is its
281
+ # own tailnet ssh host) or to manage it locally. An explicit --ssh / env / DEVS.yml
282
+ # host forces SSH for every dev; otherwise auto-discover via the tailnet handshake
283
+ # that start-tailscale.sh writes (set only when the container is up + Tailscale SSH
284
+ # is on). Devs that don't resolve fall through to normal local mode.
278
285
  if ssh_host:
279
- # SSH mode is attach-only: we do NOT provision anything. The container must
280
- # already be running on the remote host (start it there yourself, e.g. by
281
- # running `devs start` on the remote machine). We skip ContainerManager /
282
- # WorkspaceManager entirely and only need the workspace path to construct the
283
- # URI; derive it from the project name and dev name (no filesystem access needed).
284
- workspace_dirs = []
285
- valid_dev_names = []
286
+ ssh_hosts = {dev_name: ssh_host for dev_name in dev_names}
287
+ else:
288
+ ssh_hosts = {}
286
289
  for dev_name in dev_names:
287
- workspace_name = project.get_workspace_name(dev_name)
288
- # Use a synthetic Path so generate_devcontainer_uri can derive workspace_name.
289
- # The path itself is never accessed locally in SSH mode.
290
- workspace_dirs.append(config.workspaces_dir / workspace_name)
291
- valid_dev_names.append(dev_name)
292
-
290
+ host = vscode_integration.resolve_tailnet_ssh_host(dev_name)
291
+ if host:
292
+ ssh_hosts[dev_name] = host
293
+
294
+ ssh_dev_names = [d for d in dev_names if d in ssh_hosts]
295
+ local_dev_names = [d for d in dev_names if d not in ssh_hosts]
296
+
297
+ # --- Direct Remote-SSH (attach-only: container already running on its host) ---
298
+ if ssh_dev_names:
299
+ for dev_name in ssh_dev_names:
300
+ console.print(f" 🔗 {dev_name} → ssh://{ssh_hosts[dev_name]} (direct Remote-SSH into container)")
301
+ # Synthetic workspace Paths: only the name is used to build the URI; the path
302
+ # is never accessed locally in SSH mode.
303
+ workspace_dirs = [config.workspaces_dir / project.get_workspace_name(d) for d in ssh_dev_names]
293
304
  try:
294
305
  success_count = vscode_integration.launch_multiple_devcontainers(
295
306
  workspace_dirs,
296
- valid_dev_names,
307
+ ssh_dev_names,
297
308
  delay_between_windows=delay,
298
309
  live=live,
299
- ssh_host=ssh_host,
310
+ ssh_hosts=ssh_hosts,
300
311
  )
301
312
  if success_count == 0:
302
- console.print("❌ Failed to open any VS Code windows")
313
+ console.print("❌ Failed to open any VS Code windows (Remote-SSH)")
303
314
  except VSCodeError as e:
304
315
  console.print(f"❌ VS Code integration error: {e}")
305
- return
306
316
 
307
- # Local mode: manage containers and workspaces as normal
308
- container_manager = ContainerManager(project, config)
309
- workspace_manager = WorkspaceManager(project, config)
317
+ # --- Local mode: manage containers and workspaces as normal ---
318
+ if local_dev_names:
319
+ container_manager = ContainerManager(project, config)
320
+ workspace_manager = WorkspaceManager(project, config)
310
321
 
311
- workspace_dirs = []
312
- valid_dev_names = []
322
+ workspace_dirs = []
323
+ valid_dev_names = []
313
324
 
314
- for dev_name in dev_names:
315
- console.print(f" Preparing: {dev_name}")
325
+ for dev_name in local_dev_names:
326
+ console.print(f" Preparing: {dev_name}")
316
327
 
317
- devs_env = DevsConfigLoader.load_env_vars(dev_name, project.info.name)
318
- cli_env = parse_env_vars(env) if env else {}
319
- extra_env = merge_env_vars(devs_env, cli_env) if devs_env or cli_env else None
328
+ devs_env = DevsConfigLoader.load_env_vars(dev_name, project.info.name)
329
+ cli_env = parse_env_vars(env) if env else {}
330
+ extra_env = merge_env_vars(devs_env, cli_env) if devs_env or cli_env else None
320
331
 
321
- if extra_env:
322
- console.print(f"🔧 Environment variables: {', '.join(f'{k}={v}' for k, v in extra_env.items())}")
332
+ if extra_env:
333
+ console.print(f"🔧 Environment variables: {', '.join(f'{k}={v}' for k, v in extra_env.items())}")
323
334
 
324
- try:
325
- workspace_dir = workspace_manager.create_workspace(dev_name, live=live)
335
+ try:
336
+ workspace_dir = workspace_manager.create_workspace(dev_name, live=live)
326
337
 
327
- if container_manager.ensure_container_running(dev_name, workspace_dir, check_rebuild=False, debug=debug, live=live, extra_env=extra_env):
328
- workspace_dirs.append(workspace_dir)
329
- valid_dev_names.append(dev_name)
330
- else:
331
- console.print(f" ❌ Failed to start container for {dev_name}, skipping...")
338
+ if container_manager.ensure_container_running(dev_name, workspace_dir, check_rebuild=False, debug=debug, live=live, extra_env=extra_env):
339
+ workspace_dirs.append(workspace_dir)
340
+ valid_dev_names.append(dev_name)
341
+ else:
342
+ console.print(f" ❌ Failed to start container for {dev_name}, skipping...")
332
343
 
333
- except (ContainerError, WorkspaceError) as e:
334
- console.print(f" ❌ Error preparing {dev_name}: {e}")
335
- continue
344
+ except (ContainerError, WorkspaceError) as e:
345
+ console.print(f" ❌ Error preparing {dev_name}: {e}")
346
+ continue
336
347
 
337
- if workspace_dirs:
338
- try:
339
- success_count = vscode_integration.launch_multiple_devcontainers(
340
- workspace_dirs,
341
- valid_dev_names,
342
- delay_between_windows=delay,
343
- live=live,
344
- )
348
+ if workspace_dirs:
349
+ try:
350
+ success_count = vscode_integration.launch_multiple_devcontainers(
351
+ workspace_dirs,
352
+ valid_dev_names,
353
+ delay_between_windows=delay,
354
+ live=live,
355
+ )
345
356
 
346
- if success_count == 0:
347
- console.print("❌ Failed to open any VS Code windows")
357
+ if success_count == 0:
358
+ console.print("❌ Failed to open any VS Code windows")
348
359
 
349
- except VSCodeError as e:
350
- console.print(f"❌ VS Code integration error: {e}")
360
+ except VSCodeError as e:
361
+ console.print(f"❌ VS Code integration error: {e}")
351
362
 
352
363
 
353
364
  @cli.command()
devs/core/integration.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """VS Code and external tool integrations."""
2
2
 
3
3
  import json
4
+ import shlex
4
5
  import subprocess
5
6
  import time
6
7
  from pathlib import Path
@@ -8,8 +9,9 @@ from typing import List, Optional
8
9
 
9
10
  from rich.console import Console
10
11
 
11
- from ..exceptions import VSCodeError, DependencyError
12
+ from ..exceptions import VSCodeError
12
13
  from devs_common.core.project import Project
14
+ from devs_common.utils.config_hash import get_env_mount_path
13
15
  from devs_common.utils.devcontainer import prepare_devcontainer_environment
14
16
 
15
17
  console = Console()
@@ -25,13 +27,19 @@ class VSCodeIntegration:
25
27
  project: Project instance
26
28
  """
27
29
  self.project = project
28
- self._check_vscode_cli()
29
-
30
- def _check_vscode_cli(self) -> None:
31
- """Check if VS Code CLI is available.
32
-
33
- Raises:
34
- DependencyError: If code command is not found
30
+ self.code_available = self._check_vscode_cli()
31
+
32
+ def _check_vscode_cli(self) -> bool:
33
+ """Check if the VS Code 'code' CLI is available.
34
+
35
+ This is intentionally non-fatal: on a remote/headless dev box (e.g. one you
36
+ only ever reach over SSH) there may be no local 'code' command, but it is still
37
+ useful to start the container and print the URI / command you'd run from a
38
+ machine that does have VS Code. Callers consult ``self.code_available`` and fall
39
+ back to printing the command instead of launching.
40
+
41
+ Returns:
42
+ True if the 'code' command is available, False otherwise.
35
43
  """
36
44
  try:
37
45
  result = subprocess.run(
@@ -40,17 +48,45 @@ class VSCodeIntegration:
40
48
  text=True,
41
49
  check=False
42
50
  )
43
- if result.returncode != 0:
44
- raise DependencyError(
45
- "VS Code 'code' command not found. Make sure VS Code is installed "
46
- "and the 'code' command is available in your PATH."
47
- )
51
+ return result.returncode == 0
48
52
  except FileNotFoundError:
49
- raise DependencyError(
50
- "VS Code 'code' command not found. Make sure VS Code is installed "
51
- "and the 'code' command is available in your PATH."
52
- )
53
+ return False
53
54
 
55
+ def resolve_tailnet_ssh_host(self, dev_name: str) -> Optional[str]:
56
+ """Discover a container's own tailnet SSH name via the writeback handshake.
57
+
58
+ Joins docker <-> tailnet by docker container id, WITHOUT `docker exec`:
59
+ ``docker inspect`` the container for its hostname (== its short container id,
60
+ == the ``$HOSTNAME`` start-tailscale.sh keyed its handshake file by), then read
61
+ that file out of the host-shared env mount. Returns the short MagicDNS name
62
+ (e.g. ``devs-acme-app-eamonn-1``) only when the container is up AND Tailscale
63
+ SSH is on; otherwise None (caller falls back to local mode).
64
+ """
65
+ container_name = self.project.get_container_name(dev_name)
66
+ try:
67
+ inspect = subprocess.run(
68
+ ["docker", "inspect", "--format", "{{.Config.Hostname}}", container_name],
69
+ capture_output=True, text=True, check=False,
70
+ )
71
+ except FileNotFoundError:
72
+ return None # no docker here
73
+ if inspect.returncode != 0:
74
+ return None # container doesn't exist / not inspectable
75
+ container_id = inspect.stdout.strip()
76
+ if not container_id:
77
+ return None
78
+
79
+ handshake = get_env_mount_path(self.project.info.name) / "ts-nodes" / f"{container_id}.json"
80
+ if not handshake.exists():
81
+ return None
82
+ try:
83
+ data = json.loads(handshake.read_text())
84
+ except (OSError, ValueError):
85
+ return None
86
+ if not data.get("ssh"):
87
+ return None # on the tailnet but not accepting SSH
88
+ return data.get("short") or None
89
+
54
90
  def generate_devcontainer_uri(
55
91
  self,
56
92
  workspace_dir: Path,
@@ -66,39 +102,81 @@ class VSCodeIntegration:
66
102
  dev_name: Development environment name
67
103
  live: Whether to use live mode (mount current directory)
68
104
  attach_to_existing: Whether to attach to existing container (vs create new one)
69
- ssh_host: If set, generate a Remote-SSH + attached-container URI for this host.
70
- The host can be a Tailscale MagicDNS name or any SSH-reachable hostname.
105
+ ssh_host: If set, generate a plain Remote-SSH folder URI for this host.
106
+ In the devs+Tailscale model the container IS its own SSH host
107
+ (you SSH straight into it — there's no separate Docker daemon to
108
+ attach through), so this is NOT an attached-container URI.
71
109
 
72
110
  Returns:
73
111
  VS Code devcontainer URI
74
112
  """
113
+ # IMPORTANT: In live mode, use the actual host folder name because devcontainer CLI
114
+ # mounts the host directory directly and VS Code must match that path.
115
+ workspace_name = workspace_dir.name if live else self.project.get_workspace_name(dev_name)
116
+
117
+ if ssh_host:
118
+ # Direct Remote-SSH INTO the container. With Tailscale the container is its
119
+ # own tailnet node running Tailscale SSH, so VS Code opens the in-container
120
+ # workspace folder over SSH — no Docker host, no attach. Wrapping this in an
121
+ # attached-container URI would make VS Code SSH in and then hunt for a Docker
122
+ # daemon that isn't there.
123
+ return f"vscode-remote://ssh-remote+{ssh_host}/workspaces/{workspace_name}"
124
+
75
125
  if attach_to_existing:
76
126
  container_name = self.project.get_container_name(dev_name)
77
- workspace_name = workspace_dir.name if live else self.project.get_workspace_name(dev_name)
127
+ container_hex = container_name.encode("utf-8").hex()
128
+ return f"vscode-remote://attached-container+{container_hex}/workspaces/{workspace_name}"
78
129
 
79
- if ssh_host:
80
- # JSON-encoded container info that includes the SSH host so VS Code's
81
- # Dev Containers extension connects through Remote-SSH first.
82
- # Container name is prefixed with "/" as Docker returns it in Names field.
83
- container_info = {
84
- "containerName": f"/{container_name}",
85
- "settings": {"host": f"ssh://{ssh_host}"},
86
- }
87
- container_hex = json.dumps(container_info, separators=(",", ":")).encode("utf-8").hex()
88
- else:
89
- container_hex = container_name.encode("utf-8").hex()
130
+ # Create new container from devcontainer.json
131
+ workspace_hex = workspace_dir.as_posix().encode("utf-8").hex()
132
+ return f"vscode-remote://dev-container+{workspace_hex}/workspaces/{workspace_name}"
133
+
134
+ def _format_code_command(
135
+ self,
136
+ workspace_dir: Path,
137
+ dev_name: str,
138
+ live: bool,
139
+ new_window: bool,
140
+ ssh_host: Optional[str],
141
+ ) -> str:
142
+ """Build the 'code' command string for opening a container.
143
+
144
+ Returned as a copy/paste-ready, shell-quoted string so it can be printed and
145
+ run from another machine that has VS Code installed.
146
+ """
147
+ vscode_uri = self.generate_devcontainer_uri(
148
+ workspace_dir, dev_name, live, attach_to_existing=True, ssh_host=ssh_host
149
+ )
150
+ cmd = ["code"]
151
+ if new_window:
152
+ cmd.append("--new-window")
153
+ cmd.extend(["--folder-uri", vscode_uri])
154
+ return " ".join(shlex.quote(part) for part in cmd)
155
+
156
+ def _print_code_commands(
157
+ self,
158
+ workspace_dir: Path,
159
+ dev_name: str,
160
+ live: bool,
161
+ new_window: bool,
162
+ ssh_host: Optional[str],
163
+ ) -> None:
164
+ """Print the 'code' command for this container.
165
+
166
+ When an SSH host is set we print only the Remote-SSH command (the plain
167
+ attached-container command wouldn't work from another machine and is just
168
+ noise). Otherwise we print the local attached-container command.
169
+ """
170
+ if ssh_host:
171
+ ssh_cmd = self._format_code_command(workspace_dir, dev_name, live, new_window, ssh_host)
172
+ console.print(f" 📋 VS Code command (Remote-SSH: {ssh_host}):")
173
+ console.print(f" {ssh_cmd}")
174
+ return
175
+
176
+ plain_cmd = self._format_code_command(workspace_dir, dev_name, live, new_window, None)
177
+ console.print(f" 📋 VS Code command:")
178
+ console.print(f" {plain_cmd}")
90
179
 
91
- vscode_uri = f"vscode-remote://attached-container+{container_hex}/workspaces/{workspace_name}"
92
- else:
93
- # Original behavior: create new container from devcontainer.json
94
- workspace_hex = workspace_dir.as_posix().encode("utf-8").hex()
95
- # IMPORTANT: In live mode, use the actual host folder name because devcontainer CLI
96
- # mounts the host directory directly and VS Code must match that path.
97
- workspace_name = workspace_dir.name if live else self.project.get_workspace_name(dev_name)
98
- vscode_uri = f"vscode-remote://dev-container+{workspace_hex}/workspaces/{workspace_name}"
99
-
100
- return vscode_uri
101
-
102
180
  def launch_devcontainer(
103
181
  self,
104
182
  workspace_dir: Path,
@@ -127,11 +205,6 @@ class VSCodeIntegration:
127
205
  workspace_dir, dev_name, live, attach_to_existing=True, ssh_host=ssh_host
128
206
  )
129
207
 
130
- if ssh_host:
131
- console.print(f" 🚀 Opening VS Code for: {dev_name} (via SSH: {ssh_host})")
132
- else:
133
- console.print(f" 🚀 Opening VS Code for: {dev_name}")
134
-
135
208
  cmd = ["code"]
136
209
 
137
210
  if new_window:
@@ -139,6 +212,34 @@ class VSCodeIntegration:
139
212
 
140
213
  cmd.extend(["--folder-uri", vscode_uri])
141
214
 
215
+ # Always print the command(s) so they can be copied and run elsewhere.
216
+ self._print_code_commands(workspace_dir, dev_name, live, new_window, ssh_host)
217
+
218
+ # No local 'code' command (e.g. a headless remote dev box reached over SSH):
219
+ # don't fail — just point to the command printed above and stop here.
220
+ if not self.code_available:
221
+ if ssh_host:
222
+ # SSH mode is attach-only: we never created/started the container,
223
+ # so we can't claim it's ready. The user must ensure it's running
224
+ # on the remote host themselves.
225
+ console.print(
226
+ f" ⚠️ VS Code 'code' command not found here — printed the attach "
227
+ f"URI only. Make sure the container is already running on '{ssh_host}', "
228
+ "then run the command above from a machine with VS Code installed."
229
+ )
230
+ else:
231
+ console.print(
232
+ f" ⚠️ VS Code 'code' command not found here — container is ready, "
233
+ "but VS Code can't be launched from this machine. "
234
+ "Run the command above from a machine with VS Code installed."
235
+ )
236
+ return True
237
+
238
+ if ssh_host:
239
+ console.print(f" 🚀 Opening VS Code for: {dev_name} (via SSH: {ssh_host})")
240
+ else:
241
+ console.print(f" 🚀 Opening VS Code for: {dev_name}")
242
+
142
243
  # Set environment variables using shared function
143
244
  container_workspace_name = self.project.get_workspace_name(dev_name)
144
245
  env = prepare_devcontainer_environment(
@@ -177,6 +278,7 @@ class VSCodeIntegration:
177
278
  delay_between_windows: float = 2.0,
178
279
  live: bool = False,
179
280
  ssh_host: Optional[str] = None,
281
+ ssh_hosts: Optional[dict] = None,
180
282
  ) -> int:
181
283
  """Launch multiple devcontainers in separate VS Code windows.
182
284
 
@@ -185,7 +287,10 @@ class VSCodeIntegration:
185
287
  dev_names: List of development environment names
186
288
  delay_between_windows: Delay between opening windows (seconds)
187
289
  live: Whether to use live mode (mount current directory)
188
- ssh_host: If set, connect via Remote-SSH to this host then attach to each container.
290
+ ssh_host: If set, connect every dev via direct Remote-SSH to this host.
291
+ ssh_hosts: Optional per-dev override mapping ``dev_name -> ssh host``
292
+ (each container is its own tailnet host, so they differ). Falls back
293
+ to ``ssh_host`` for any dev not present in the map.
189
294
 
190
295
  Returns:
191
296
  Number of successfully launched windows
@@ -193,10 +298,11 @@ class VSCodeIntegration:
193
298
  if len(workspace_dirs) != len(dev_names):
194
299
  raise VSCodeError("Workspace directories and dev names lists must have same length")
195
300
 
196
- if ssh_host:
301
+ ssh_hosts = ssh_hosts or {}
302
+ if ssh_host or ssh_hosts:
197
303
  console.print(
198
304
  f"📂 Opening {len(dev_names)} devcontainers in VS Code "
199
- f"(via SSH: {ssh_host}) for project: {self.project.info.name}"
305
+ f"(via Remote-SSH) for project: {self.project.info.name}"
200
306
  )
201
307
  else:
202
308
  console.print(f"📂 Opening {len(dev_names)} devcontainers in VS Code for project: {self.project.info.name}")
@@ -204,9 +310,10 @@ class VSCodeIntegration:
204
310
  success_count = 0
205
311
 
206
312
  for workspace_dir, dev_name in zip(workspace_dirs, dev_names):
313
+ host = ssh_hosts.get(dev_name, ssh_host)
207
314
  try:
208
315
  if self.launch_devcontainer(
209
- workspace_dir, dev_name, new_window=True, live=live, ssh_host=ssh_host
316
+ workspace_dir, dev_name, new_window=True, live=live, ssh_host=host
210
317
  ):
211
318
  success_count += 1
212
319
 
@@ -217,7 +324,7 @@ class VSCodeIntegration:
217
324
  console.print(f" ❌ Failed to launch {dev_name}: {e}")
218
325
  continue
219
326
 
220
- if success_count > 0:
327
+ if success_count > 0 and self.code_available:
221
328
  console.print("")
222
329
  console.print(f"💡 VS Code windows should open shortly with titles: '<dev-name> - {self.project.info.directory.name}'")
223
330
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-cli
3
- Version: 4.0.4
3
+ Version: 5.0.0
4
4
  Summary: DevContainer Management Tool - Manage multiple named devcontainers for any project
5
5
  Author: Dan Lester
6
6
  License-Expression: MIT
@@ -0,0 +1,13 @@
1
+ devs/__init__.py,sha256=dMk0J_JrmBmknPvt6HNUa04qxgHYpxhnIXa3PZFk_N0,571
2
+ devs/cli.py,sha256=inWDnumsDeaQcvY8y42HcNPY-HJxYkCOUGHgsjYx4nI,44985
3
+ devs/config.py,sha256=DaS2_1x0h63cHn34RmWbEhUx_1u3-sAo16tM3AXMKfQ,1561
4
+ devs/exceptions.py,sha256=7xO7ihJu_U6CupZPMv89B4N5EBSUcdj2OdA6GPAFPEE,490
5
+ devs/core/__init__.py,sha256=TPy3eZi-AEztci_QZ37YAAl2zvUQlwBALpyiQx2ED7Q,363
6
+ devs/core/integration.py,sha256=_zT5kJi_L34uU3yhLinLI7bbjI5Q27dX9ABxgwHkx2Y,16557
7
+ devs/utils/__init__.py,sha256=f-sEWETPfW2Xkaxgd-l6FS-jIQAorLlQZOicIZvp-W0,638
8
+ devs_cli-5.0.0.dist-info/licenses/LICENSE,sha256=bi2EUiv-lmC_quQVkNqzTYXJpjVarkPsVKfqhJl7ccQ,1067
9
+ devs_cli-5.0.0.dist-info/METADATA,sha256=3YGHAfTQZrjl-5eJ7w2a_49LqHjPhcdO6Z2bRdF1orQ,5282
10
+ devs_cli-5.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
11
+ devs_cli-5.0.0.dist-info/entry_points.txt,sha256=s8-pgqZ1QvzPJgHP9vNxwBYdezmGsFdRUHacJPZ4WOs,39
12
+ devs_cli-5.0.0.dist-info/top_level.txt,sha256=9RIVUPVGuOdO84qkBh_9XcKoTV7773o3py78wz2-IZk,5
13
+ devs_cli-5.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (83.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,13 +0,0 @@
1
- devs/__init__.py,sha256=dMk0J_JrmBmknPvt6HNUa04qxgHYpxhnIXa3PZFk_N0,571
2
- devs/cli.py,sha256=wRqKwdyGYAqemrabrH7NhMpBYX4iFJpowWxwDqptYmk,44125
3
- devs/config.py,sha256=DaS2_1x0h63cHn34RmWbEhUx_1u3-sAo16tM3AXMKfQ,1561
4
- devs/exceptions.py,sha256=7xO7ihJu_U6CupZPMv89B4N5EBSUcdj2OdA6GPAFPEE,490
5
- devs/core/__init__.py,sha256=TPy3eZi-AEztci_QZ37YAAl2zvUQlwBALpyiQx2ED7Q,363
6
- devs/core/integration.py,sha256=1x-6gbFRqR95dykdLyt0RmrhUxeFzlIq_yBitDGd1qI,11471
7
- devs/utils/__init__.py,sha256=f-sEWETPfW2Xkaxgd-l6FS-jIQAorLlQZOicIZvp-W0,638
8
- devs_cli-4.0.4.dist-info/licenses/LICENSE,sha256=bi2EUiv-lmC_quQVkNqzTYXJpjVarkPsVKfqhJl7ccQ,1067
9
- devs_cli-4.0.4.dist-info/METADATA,sha256=pbkj80f2KAnueazHgj0bE2JcISS2k6xr_TvWUudR2ho,5282
10
- devs_cli-4.0.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
- devs_cli-4.0.4.dist-info/entry_points.txt,sha256=s8-pgqZ1QvzPJgHP9vNxwBYdezmGsFdRUHacJPZ4WOs,39
12
- devs_cli-4.0.4.dist-info/top_level.txt,sha256=9RIVUPVGuOdO84qkBh_9XcKoTV7773o3py78wz2-IZk,5
13
- devs_cli-4.0.4.dist-info/RECORD,,