devs-cli 4.0.3__py3-none-any.whl → 4.0.7__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
@@ -3,6 +3,7 @@
3
3
  import os
4
4
  import sys
5
5
  import subprocess
6
+ import traceback
6
7
  from functools import wraps
7
8
  from importlib.metadata import version, PackageNotFoundError
8
9
 
@@ -239,64 +240,112 @@ def start(dev_names: tuple, rebuild: bool, rebuild_if_changed: bool, live: bool,
239
240
  @click.option('--delay', default=2.0, help='Delay between opening VS Code windows (seconds)')
240
241
  @click.option('--live', is_flag=True, help='Start containers with current directory mounted as workspace')
241
242
  @click.option('--env', multiple=True, help='Environment variables to pass to container (format: VAR=value)')
243
+ @click.option(
244
+ '--ssh',
245
+ 'ssh_host',
246
+ default=None,
247
+ envvar='DEVS_SSH_HOST',
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. '
255
+ 'Can also be set via the DEVS_SSH_HOST env var or ssh_host in DEVS.yml.'
256
+ ),
257
+ )
242
258
  @debug_option
243
- def vscode(dev_names: tuple, delay: float, live: bool, env: tuple, debug: bool) -> None:
259
+ def vscode(dev_names: tuple, delay: float, live: bool, env: tuple, ssh_host: str, debug: bool) -> None:
244
260
  """Open devcontainers in VS Code.
245
-
261
+
246
262
  DEV_NAMES: One or more development environment names to open
247
-
263
+
248
264
  Example: devs vscode sally bob
249
265
  Example: devs vscode sally --live # Start with current directory mounted
250
266
  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
251
268
  """
252
269
  check_dependencies()
253
270
  project = get_project()
254
-
271
+
272
+ # If --ssh not given on CLI or env, check DEVS.yml
273
+ if not ssh_host:
274
+ ssh_host = DevsConfigLoader.load_ssh_host(project.info.name) or None
275
+
276
+ vscode_integration = VSCodeIntegration(project)
277
+
278
+ 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
+ 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
+
293
+ try:
294
+ success_count = vscode_integration.launch_multiple_devcontainers(
295
+ workspace_dirs,
296
+ valid_dev_names,
297
+ delay_between_windows=delay,
298
+ live=live,
299
+ ssh_host=ssh_host,
300
+ )
301
+ if success_count == 0:
302
+ console.print("❌ Failed to open any VS Code windows")
303
+ except VSCodeError as e:
304
+ console.print(f"❌ VS Code integration error: {e}")
305
+ return
306
+
307
+ # Local mode: manage containers and workspaces as normal
255
308
  container_manager = ContainerManager(project, config)
256
309
  workspace_manager = WorkspaceManager(project, config)
257
- vscode = VSCodeIntegration(project)
258
-
310
+
259
311
  workspace_dirs = []
260
312
  valid_dev_names = []
261
-
313
+
262
314
  for dev_name in dev_names:
263
315
  console.print(f" Preparing: {dev_name}")
264
-
265
- # Load environment variables from DEVS.yml and merge with CLI --env flags
316
+
266
317
  devs_env = DevsConfigLoader.load_env_vars(dev_name, project.info.name)
267
318
  cli_env = parse_env_vars(env) if env else {}
268
319
  extra_env = merge_env_vars(devs_env, cli_env) if devs_env or cli_env else None
269
-
320
+
270
321
  if extra_env:
271
322
  console.print(f"🔧 Environment variables: {', '.join(f'{k}={v}' for k, v in extra_env.items())}")
272
-
323
+
273
324
  try:
274
- # Ensure workspace exists (handles live mode internally)
275
325
  workspace_dir = workspace_manager.create_workspace(dev_name, live=live)
276
-
277
- # Ensure container is running before launching VS Code (no auto-rebuild in CLI)
326
+
278
327
  if container_manager.ensure_container_running(dev_name, workspace_dir, check_rebuild=False, debug=debug, live=live, extra_env=extra_env):
279
328
  workspace_dirs.append(workspace_dir)
280
329
  valid_dev_names.append(dev_name)
281
330
  else:
282
331
  console.print(f" ❌ Failed to start container for {dev_name}, skipping...")
283
-
332
+
284
333
  except (ContainerError, WorkspaceError) as e:
285
334
  console.print(f" ❌ Error preparing {dev_name}: {e}")
286
335
  continue
287
-
336
+
288
337
  if workspace_dirs:
289
338
  try:
290
- success_count = vscode.launch_multiple_devcontainers(
291
- workspace_dirs,
339
+ success_count = vscode_integration.launch_multiple_devcontainers(
340
+ workspace_dirs,
292
341
  valid_dev_names,
293
342
  delay_between_windows=delay,
294
- live=live
343
+ live=live,
295
344
  )
296
-
345
+
297
346
  if success_count == 0:
298
347
  console.print("❌ Failed to open any VS Code windows")
299
-
348
+
300
349
  except VSCodeError as e:
301
350
  console.print(f"❌ VS Code integration error: {e}")
302
351
 
@@ -459,7 +508,6 @@ def claude(dev_name: str, prompt: str, auth: bool, reset_workspace: bool, live:
459
508
  except Exception as e:
460
509
  console.print(f"❌ Failed to configure Claude authentication: {e}")
461
510
  if debug:
462
- import traceback
463
511
  console.print(traceback.format_exc())
464
512
  sys.exit(1)
465
513
 
@@ -632,7 +680,6 @@ def _handle_codex_auth(api_key: str, debug: bool) -> None:
632
680
  except Exception as e:
633
681
  console.print(f"❌ Failed to configure Codex authentication: {e}")
634
682
  if debug:
635
- import traceback
636
683
  console.print(traceback.format_exc())
637
684
  sys.exit(1)
638
685
 
devs/core/integration.py CHANGED
@@ -1,13 +1,15 @@
1
1
  """VS Code and external tool integrations."""
2
2
 
3
+ import json
4
+ import shlex
3
5
  import subprocess
4
6
  import time
5
7
  from pathlib import Path
6
- from typing import List
8
+ from typing import List, Optional
7
9
 
8
10
  from rich.console import Console
9
11
 
10
- from ..exceptions import VSCodeError, DependencyError
12
+ from ..exceptions import VSCodeError
11
13
  from devs_common.core.project import Project
12
14
  from devs_common.utils.devcontainer import prepare_devcontainer_environment
13
15
 
@@ -24,13 +26,19 @@ class VSCodeIntegration:
24
26
  project: Project instance
25
27
  """
26
28
  self.project = project
27
- self._check_vscode_cli()
28
-
29
- def _check_vscode_cli(self) -> None:
30
- """Check if VS Code CLI is available.
31
-
32
- Raises:
33
- DependencyError: If code command is not found
29
+ self.code_available = self._check_vscode_cli()
30
+
31
+ def _check_vscode_cli(self) -> bool:
32
+ """Check if the VS Code 'code' CLI is available.
33
+
34
+ This is intentionally non-fatal: on a remote/headless dev box (e.g. one you
35
+ only ever reach over SSH) there may be no local 'code' command, but it is still
36
+ useful to start the container and print the URI / command you'd run from a
37
+ machine that does have VS Code. Callers consult ``self.code_available`` and fall
38
+ back to printing the command instead of launching.
39
+
40
+ Returns:
41
+ True if the 'code' command is available, False otherwise.
34
42
  """
35
43
  try:
36
44
  result = subprocess.run(
@@ -39,90 +47,156 @@ class VSCodeIntegration:
39
47
  text=True,
40
48
  check=False
41
49
  )
42
- if result.returncode != 0:
43
- raise DependencyError(
44
- "VS Code 'code' command not found. Make sure VS Code is installed "
45
- "and the 'code' command is available in your PATH."
46
- )
50
+ return result.returncode == 0
47
51
  except FileNotFoundError:
48
- raise DependencyError(
49
- "VS Code 'code' command not found. Make sure VS Code is installed "
50
- "and the 'code' command is available in your PATH."
51
- )
52
+ return False
52
53
 
53
- def generate_devcontainer_uri(self, workspace_dir: Path, dev_name: str, live: bool = False, attach_to_existing: bool = True) -> str:
54
+ def generate_devcontainer_uri(
55
+ self,
56
+ workspace_dir: Path,
57
+ dev_name: str,
58
+ live: bool = False,
59
+ attach_to_existing: bool = True,
60
+ ssh_host: Optional[str] = None,
61
+ ) -> str:
54
62
  """Generate VS Code devcontainer URI.
55
-
63
+
56
64
  Args:
57
65
  workspace_dir: Workspace directory path
58
66
  dev_name: Development environment name
59
67
  live: Whether to use live mode (mount current directory)
60
68
  attach_to_existing: Whether to attach to existing container (vs create new one)
61
-
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.
71
+
62
72
  Returns:
63
73
  VS Code devcontainer URI
64
74
  """
65
75
  if attach_to_existing:
66
- # Generate container name to attach to
67
76
  container_name = self.project.get_container_name(dev_name)
68
- # Use attached-container URI format to connect to existing container
69
- # Encode container name for URI
70
- container_hex = container_name.encode('utf-8').hex()
71
-
72
- # Generate workspace path inside container
73
77
  workspace_name = workspace_dir.name if live else self.project.get_workspace_name(dev_name)
78
+
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()
90
+
74
91
  vscode_uri = f"vscode-remote://attached-container+{container_hex}/workspaces/{workspace_name}"
75
92
  else:
76
93
  # Original behavior: create new container from devcontainer.json
77
- # Convert workspace path to hex for VS Code URI
78
- workspace_hex = workspace_dir.as_posix().encode('utf-8').hex()
79
-
80
- # Generate workspace name inside container
81
- # IMPORTANT: In live mode, we must use the actual host folder name (e.g. "workstuff")
82
- # because devcontainer CLI mounts the host directory directly, preserving its name.
83
- # VS Code needs to connect to /workspaces/<host-folder-name>, not our constructed name.
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.
84
97
  workspace_name = workspace_dir.name if live else self.project.get_workspace_name(dev_name)
85
-
86
- # Build VS Code devcontainer URI
87
98
  vscode_uri = f"vscode-remote://dev-container+{workspace_hex}/workspaces/{workspace_name}"
88
-
99
+
89
100
  return vscode_uri
90
-
101
+
102
+ def _format_code_command(
103
+ self,
104
+ workspace_dir: Path,
105
+ dev_name: str,
106
+ live: bool,
107
+ new_window: bool,
108
+ ssh_host: Optional[str],
109
+ ) -> str:
110
+ """Build the 'code' command string for opening a container.
111
+
112
+ Returned as a copy/paste-ready, shell-quoted string so it can be printed and
113
+ run from another machine that has VS Code installed.
114
+ """
115
+ vscode_uri = self.generate_devcontainer_uri(
116
+ workspace_dir, dev_name, live, attach_to_existing=True, ssh_host=ssh_host
117
+ )
118
+ cmd = ["code"]
119
+ if new_window:
120
+ cmd.append("--new-window")
121
+ cmd.extend(["--folder-uri", vscode_uri])
122
+ return " ".join(shlex.quote(part) for part in cmd)
123
+
124
+ def _print_code_commands(
125
+ self,
126
+ workspace_dir: Path,
127
+ dev_name: str,
128
+ live: bool,
129
+ new_window: bool,
130
+ ssh_host: Optional[str],
131
+ ) -> None:
132
+ """Print the 'code' command(s) for this container.
133
+
134
+ Always prints the plain (non-SSH) command. When an SSH host is set, the SSH
135
+ form is printed too, so you can copy whichever matches where you're running it.
136
+ """
137
+ if ssh_host:
138
+ ssh_cmd = self._format_code_command(workspace_dir, dev_name, live, new_window, ssh_host)
139
+ console.print(f" 📋 VS Code command (via SSH: {ssh_host}):")
140
+ console.print(f" {ssh_cmd}")
141
+
142
+ plain_cmd = self._format_code_command(workspace_dir, dev_name, live, new_window, None)
143
+ console.print(f" 📋 VS Code command:")
144
+ console.print(f" {plain_cmd}")
145
+
91
146
  def launch_devcontainer(
92
- self,
93
- workspace_dir: Path,
147
+ self,
148
+ workspace_dir: Path,
94
149
  dev_name: str,
95
150
  new_window: bool = True,
96
- live: bool = False
151
+ live: bool = False,
152
+ ssh_host: Optional[str] = None,
97
153
  ) -> bool:
98
154
  """Launch a devcontainer in VS Code.
99
-
155
+
100
156
  Args:
101
157
  workspace_dir: Workspace directory path
102
- dev_name: Development environment name
158
+ dev_name: Development environment name
103
159
  new_window: Whether to open in a new window
104
160
  live: Whether to use live mode (mount current directory)
105
-
161
+ ssh_host: If set, connect via Remote-SSH to this host then attach to the container.
162
+
106
163
  Returns:
107
164
  True if VS Code launched successfully
108
-
165
+
109
166
  Raises:
110
167
  VSCodeError: If VS Code launch fails
111
168
  """
112
169
  try:
113
- # Always attach to existing container (since we ensure it's running in CLI)
114
- vscode_uri = self.generate_devcontainer_uri(workspace_dir, dev_name, live, attach_to_existing=True)
115
-
116
- console.print(f" 🚀 Opening VS Code for: {dev_name}")
117
-
118
- # Build VS Code command
119
- cmd = ['code']
120
-
170
+ vscode_uri = self.generate_devcontainer_uri(
171
+ workspace_dir, dev_name, live, attach_to_existing=True, ssh_host=ssh_host
172
+ )
173
+
174
+ cmd = ["code"]
175
+
121
176
  if new_window:
122
- cmd.append('--new-window')
123
-
124
- cmd.extend(['--folder-uri', vscode_uri])
125
-
177
+ cmd.append("--new-window")
178
+
179
+ cmd.extend(["--folder-uri", vscode_uri])
180
+
181
+ # Always print the command(s) so they can be copied and run elsewhere.
182
+ self._print_code_commands(workspace_dir, dev_name, live, new_window, ssh_host)
183
+
184
+ # No local 'code' command (e.g. a headless remote dev box reached over SSH):
185
+ # don't fail — the container is already prepared, so just point to the
186
+ # command printed above and stop here.
187
+ if not self.code_available:
188
+ console.print(
189
+ f" ⚠️ VS Code 'code' command not found here — container is ready, "
190
+ "but VS Code can't be launched from this machine. "
191
+ "Run the command above from a machine with VS Code installed."
192
+ )
193
+ return True
194
+
195
+ if ssh_host:
196
+ console.print(f" 🚀 Opening VS Code for: {dev_name} (via SSH: {ssh_host})")
197
+ else:
198
+ console.print(f" 🚀 Opening VS Code for: {dev_name}")
199
+
126
200
  # Set environment variables using shared function
127
201
  container_workspace_name = self.project.get_workspace_name(dev_name)
128
202
  env = prepare_devcontainer_environment(
@@ -131,74 +205,80 @@ class VSCodeIntegration:
131
205
  workspace_folder=workspace_dir,
132
206
  container_workspace_name=container_workspace_name,
133
207
  git_remote_url=self.project.info.git_remote_url,
134
- debug=False, # VS Code launch doesn't need debug mode
135
- live=live
208
+ debug=False,
209
+ live=live,
136
210
  )
137
-
138
- # Launch VS Code in background
211
+
139
212
  process = subprocess.Popen(
140
213
  cmd,
141
214
  env=env,
142
215
  stdout=subprocess.DEVNULL,
143
216
  stderr=subprocess.DEVNULL,
144
- start_new_session=True
217
+ start_new_session=True,
145
218
  )
146
-
147
- # Give it a moment to start
219
+
148
220
  time.sleep(1)
149
-
150
- # Check if process is still running (not immediately failed)
221
+
151
222
  if process.poll() is not None and process.returncode != 0:
152
223
  raise VSCodeError(f"VS Code process exited with code {process.returncode}")
153
-
224
+
154
225
  console.print(f" ✅ Launched VS Code for: {dev_name}")
155
226
  return True
156
-
227
+
157
228
  except subprocess.SubprocessError as e:
158
229
  raise VSCodeError(f"Failed to launch VS Code for {dev_name}: {e}")
159
230
 
160
231
  def launch_multiple_devcontainers(
161
- self,
162
- workspace_dirs: List[Path],
232
+ self,
233
+ workspace_dirs: List[Path],
163
234
  dev_names: List[str],
164
235
  delay_between_windows: float = 2.0,
165
- live: bool = False
236
+ live: bool = False,
237
+ ssh_host: Optional[str] = None,
166
238
  ) -> int:
167
239
  """Launch multiple devcontainers in separate VS Code windows.
168
-
240
+
169
241
  Args:
170
242
  workspace_dirs: List of workspace directory paths
171
243
  dev_names: List of development environment names
172
244
  delay_between_windows: Delay between opening windows (seconds)
173
245
  live: Whether to use live mode (mount current directory)
174
-
246
+ ssh_host: If set, connect via Remote-SSH to this host then attach to each container.
247
+
175
248
  Returns:
176
249
  Number of successfully launched windows
177
250
  """
178
251
  if len(workspace_dirs) != len(dev_names):
179
252
  raise VSCodeError("Workspace directories and dev names lists must have same length")
180
-
181
- console.print(f"📂 Opening {len(dev_names)} devcontainers in VS Code for project: {self.project.info.name}")
182
-
253
+
254
+ if ssh_host:
255
+ console.print(
256
+ f"📂 Opening {len(dev_names)} devcontainers in VS Code "
257
+ f"(via SSH: {ssh_host}) for project: {self.project.info.name}"
258
+ )
259
+ else:
260
+ console.print(f"📂 Opening {len(dev_names)} devcontainers in VS Code for project: {self.project.info.name}")
261
+
183
262
  success_count = 0
184
-
263
+
185
264
  for workspace_dir, dev_name in zip(workspace_dirs, dev_names):
186
265
  try:
187
- if self.launch_devcontainer(workspace_dir, dev_name, new_window=True, live=live):
266
+ if self.launch_devcontainer(
267
+ workspace_dir, dev_name, new_window=True, live=live, ssh_host=ssh_host
268
+ ):
188
269
  success_count += 1
189
-
190
- # Add delay between windows to ensure they open separately
270
+
191
271
  if delay_between_windows > 0:
192
272
  time.sleep(delay_between_windows)
193
-
273
+
194
274
  except VSCodeError as e:
195
275
  console.print(f" ❌ Failed to launch {dev_name}: {e}")
196
276
  continue
197
-
198
- if success_count > 0:
277
+
278
+ if success_count > 0 and self.code_available:
199
279
  console.print("")
200
280
  console.print(f"💡 VS Code windows should open shortly with titles: '<dev-name> - {self.project.info.directory.name}'")
201
-
281
+
202
282
  return success_count
203
283
 
204
284
  class ExternalToolIntegration:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-cli
3
- Version: 4.0.3
3
+ Version: 4.0.7
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=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=cM3bIok06Z4faIkTyF8h35FeKdHpnx3_h2mXnJ1SaW0,13935
7
+ devs/utils/__init__.py,sha256=f-sEWETPfW2Xkaxgd-l6FS-jIQAorLlQZOicIZvp-W0,638
8
+ devs_cli-4.0.7.dist-info/licenses/LICENSE,sha256=bi2EUiv-lmC_quQVkNqzTYXJpjVarkPsVKfqhJl7ccQ,1067
9
+ devs_cli-4.0.7.dist-info/METADATA,sha256=kak8KcJlQeXQGLObaeobYBGZWS9CsCFOOaRYMu9j6B0,5282
10
+ devs_cli-4.0.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ devs_cli-4.0.7.dist-info/entry_points.txt,sha256=s8-pgqZ1QvzPJgHP9vNxwBYdezmGsFdRUHacJPZ4WOs,39
12
+ devs_cli-4.0.7.dist-info/top_level.txt,sha256=9RIVUPVGuOdO84qkBh_9XcKoTV7773o3py78wz2-IZk,5
13
+ devs_cli-4.0.7.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- devs/__init__.py,sha256=dMk0J_JrmBmknPvt6HNUa04qxgHYpxhnIXa3PZFk_N0,571
2
- devs/cli.py,sha256=tlmKeM-sk3SRRXhRegbL35hm0PR3NFq-N-UgPzNE3gA,42127
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=YOz03TvS5Rk8YYnPT_rbEB_EMjpkOY4Z7OEzeP-6ZZQ,11055
7
- devs/utils/__init__.py,sha256=f-sEWETPfW2Xkaxgd-l6FS-jIQAorLlQZOicIZvp-W0,638
8
- devs_cli-4.0.3.dist-info/licenses/LICENSE,sha256=bi2EUiv-lmC_quQVkNqzTYXJpjVarkPsVKfqhJl7ccQ,1067
9
- devs_cli-4.0.3.dist-info/METADATA,sha256=-Pi2rvXvVojes91djeFRVFExTqE4gvqHYls5yUeN168,5282
10
- devs_cli-4.0.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
- devs_cli-4.0.3.dist-info/entry_points.txt,sha256=s8-pgqZ1QvzPJgHP9vNxwBYdezmGsFdRUHacJPZ4WOs,39
12
- devs_cli-4.0.3.dist-info/top_level.txt,sha256=9RIVUPVGuOdO84qkBh_9XcKoTV7773o3py78wz2-IZk,5
13
- devs_cli-4.0.3.dist-info/RECORD,,