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 +70 -23
- devs/core/integration.py +164 -84
- {devs_cli-4.0.3.dist-info → devs_cli-4.0.7.dist-info}/METADATA +1 -1
- devs_cli-4.0.7.dist-info/RECORD +13 -0
- devs_cli-4.0.3.dist-info/RECORD +0 -13
- {devs_cli-4.0.3.dist-info → devs_cli-4.0.7.dist-info}/WHEEL +0 -0
- {devs_cli-4.0.3.dist-info → devs_cli-4.0.7.dist-info}/entry_points.txt +0 -0
- {devs_cli-4.0.3.dist-info → devs_cli-4.0.7.dist-info}/licenses/LICENSE +0 -0
- {devs_cli-4.0.3.dist-info → devs_cli-4.0.7.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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 =
|
|
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
|
|
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) ->
|
|
30
|
-
"""Check if VS Code CLI is available.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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(
|
|
123
|
-
|
|
124
|
-
cmd.extend([
|
|
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,
|
|
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
|
-
|
|
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(
|
|
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:
|
|
@@ -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,,
|
devs_cli-4.0.3.dist-info/RECORD
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|