devlaunch 0.0.5__tar.gz → 0.0.6__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.
- {devlaunch-0.0.5 → devlaunch-0.0.6}/PKG-INFO +2 -2
- {devlaunch-0.0.5 → devlaunch-0.0.6}/README.md +1 -1
- {devlaunch-0.0.5 → devlaunch-0.0.6}/devlaunch/completions/dl.bash +48 -5
- {devlaunch-0.0.5 → devlaunch-0.0.6}/devlaunch/dl.py +46 -9
- {devlaunch-0.0.5 → devlaunch-0.0.6}/pyproject.toml +1 -2
- {devlaunch-0.0.5 → devlaunch-0.0.6}/.gitignore +0 -0
- {devlaunch-0.0.5 → devlaunch-0.0.6}/LICENSE +0 -0
- {devlaunch-0.0.5 → devlaunch-0.0.6}/devlaunch/__init__.py +0 -0
- {devlaunch-0.0.5 → devlaunch-0.0.6}/devlaunch/completion.py +0 -0
- {devlaunch-0.0.5 → devlaunch-0.0.6}/devlaunch/completion_loader.py +0 -0
- {devlaunch-0.0.5 → devlaunch-0.0.6}/devlaunch/completions/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devlaunch
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: DevLaunch - A streamlined CLI for devpod workspaces
|
|
5
5
|
Project-URL: Source, https://github.com/blooop/devlaunch
|
|
6
6
|
Project-URL: Home, https://github.com/blooop/devlaunch
|
|
@@ -31,7 +31,7 @@ A streamlined CLI for [devpod](https://devpod.sh) with intuitive autocomplete an
|
|
|
31
31
|
[](https://github.com/blooop/devlaunch/pulls?q=is%3Amerged)
|
|
32
32
|
[](https://GitHub.com/blooop/devlaunch/releases/)
|
|
33
33
|
[](https://pypi.org/project/devlaunch/)
|
|
34
|
-
[](https://prefix.dev/channels/blooop/packages/devlaunch)
|
|
35
35
|
[](https://opensource.org/license/mit/)
|
|
36
36
|
[](https://www.python.org/downloads/)
|
|
37
37
|
[](https://pixi.sh)
|
|
@@ -10,7 +10,7 @@ A streamlined CLI for [devpod](https://devpod.sh) with intuitive autocomplete an
|
|
|
10
10
|
[](https://github.com/blooop/devlaunch/pulls?q=is%3Amerged)
|
|
11
11
|
[](https://GitHub.com/blooop/devlaunch/releases/)
|
|
12
12
|
[](https://pypi.org/project/devlaunch/)
|
|
13
|
-
[](https://prefix.dev/channels/blooop/packages/devlaunch)
|
|
14
14
|
[](https://opensource.org/license/mit/)
|
|
15
15
|
[](https://www.python.org/downloads/)
|
|
16
16
|
[](https://pixi.sh)
|
|
@@ -1,9 +1,48 @@
|
|
|
1
1
|
# dl completion
|
|
2
|
+
# Note: This completion function does not support quoted arguments or escaped spaces.
|
|
3
|
+
# All arguments are treated as literal strings separated by whitespace.
|
|
4
|
+
# This is acceptable because GitHub usernames, repo names, and workspace names
|
|
5
|
+
# do not contain spaces or special characters that would require quoting.
|
|
6
|
+
#
|
|
7
|
+
# Implementation note: We parse COMP_LINE directly instead of adjusting COMP_WORDBREAKS
|
|
8
|
+
# because temporary COMP_WORDBREAKS modification can have side effects with bash's
|
|
9
|
+
# internal completion state and doesn't reliably prevent word splitting in all
|
|
10
|
+
# bash versions. Direct parsing gives us full control over word boundaries.
|
|
2
11
|
_dl_completion() {
|
|
3
12
|
local cur prev opts
|
|
4
13
|
COMPREPLY=()
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
|
|
15
|
+
# Extract current line from COMP_LINE instead of COMP_WORDS
|
|
16
|
+
# This avoids issues with COMP_WORDBREAKS treating dashes as word boundaries
|
|
17
|
+
local line="${COMP_LINE:0:COMP_POINT}"
|
|
18
|
+
|
|
19
|
+
# Parse the line into an array of words (pure bash, no external processes)
|
|
20
|
+
local words
|
|
21
|
+
read -r -a words <<< "$line"
|
|
22
|
+
local word_count=${#words[@]}
|
|
23
|
+
|
|
24
|
+
# Extract current and previous words from the parsed array
|
|
25
|
+
if (( word_count > 0 )); then
|
|
26
|
+
cur="${words[word_count-1]}"
|
|
27
|
+
else
|
|
28
|
+
cur=""
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
if (( word_count > 1 )); then
|
|
32
|
+
prev="${words[word_count-2]}"
|
|
33
|
+
else
|
|
34
|
+
prev=""
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# If line ends with whitespace, we're starting a new word
|
|
38
|
+
if [[ "$line" =~ [[:space:]]$ ]]; then
|
|
39
|
+
((word_count++))
|
|
40
|
+
cur=""
|
|
41
|
+
# Update prev when starting a new word
|
|
42
|
+
if (( ${#words[@]} > 0 )); then
|
|
43
|
+
prev="${words[-1]}"
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
7
46
|
|
|
8
47
|
# Global command options (only valid as first arg)
|
|
9
48
|
local global_opts="--ls --install --help -h --version"
|
|
@@ -27,7 +66,7 @@ _dl_completion() {
|
|
|
27
66
|
fi
|
|
28
67
|
|
|
29
68
|
# First argument: global flags, workspaces, repos, owners, or paths
|
|
30
|
-
if [[ ${
|
|
69
|
+
if [[ ${word_count} -eq 2 ]]; then
|
|
31
70
|
# Global flags
|
|
32
71
|
if [[ ${cur} == -* ]]; then
|
|
33
72
|
COMPREPLY=( $(compgen -W "${global_opts}" -- ${cur}) )
|
|
@@ -76,9 +115,13 @@ _dl_completion() {
|
|
|
76
115
|
fi
|
|
77
116
|
|
|
78
117
|
# Second argument (after workspace): subcommands
|
|
79
|
-
if [[ ${
|
|
118
|
+
if [[ ${word_count} -eq 3 ]]; then
|
|
80
119
|
# Don't complete after global flags
|
|
81
|
-
|
|
120
|
+
# Extract the first argument (word after "dl") from the words array
|
|
121
|
+
local first=""
|
|
122
|
+
if (( ${#words[@]} > 1 )); then
|
|
123
|
+
first="${words[1]}"
|
|
124
|
+
fi
|
|
82
125
|
if [[ "$first" == --* ]]; then
|
|
83
126
|
return 0
|
|
84
127
|
fi
|
|
@@ -79,8 +79,12 @@ def write_completion_cache(data: Dict[str, Any]) -> None:
|
|
|
79
79
|
cache_path = get_cache_path()
|
|
80
80
|
try:
|
|
81
81
|
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
-
|
|
82
|
+
# Write to temp file first, then atomic rename
|
|
83
|
+
temp_path = cache_path.with_suffix(".tmp")
|
|
84
|
+
with open(temp_path, "w", encoding="utf-8") as f:
|
|
83
85
|
json.dump(data, f)
|
|
86
|
+
# Atomic rename (on POSIX systems)
|
|
87
|
+
temp_path.replace(cache_path)
|
|
84
88
|
except OSError:
|
|
85
89
|
pass
|
|
86
90
|
|
|
@@ -100,8 +104,12 @@ def write_bash_completion_cache(data: Dict[str, Any]) -> None:
|
|
|
100
104
|
f'DL_OWNERS="{owners}"',
|
|
101
105
|
f'DL_BRANCHES="{branches}"',
|
|
102
106
|
]
|
|
103
|
-
|
|
107
|
+
# Write to temp file first, then atomic rename
|
|
108
|
+
temp_path = BASH_CACHE_FILE.with_suffix(".tmp")
|
|
109
|
+
with open(temp_path, "w", encoding="utf-8") as f:
|
|
104
110
|
f.write("\n".join(lines) + "\n")
|
|
111
|
+
# Atomic rename (on POSIX systems)
|
|
112
|
+
temp_path.replace(BASH_CACHE_FILE)
|
|
105
113
|
except OSError:
|
|
106
114
|
pass
|
|
107
115
|
|
|
@@ -220,16 +228,23 @@ def is_git_spec(spec: str) -> bool:
|
|
|
220
228
|
|
|
221
229
|
|
|
222
230
|
def expand_workspace_spec(spec: str) -> str:
|
|
223
|
-
"""Expand owner/repo[@branch] to github.com
|
|
231
|
+
"""Expand owner/repo[@branch] to git@github.com:owner/repo.git[@branch] for devpod."""
|
|
224
232
|
# Don't expand if it's a path
|
|
225
233
|
if is_path_spec(spec):
|
|
226
234
|
return spec
|
|
227
235
|
# Don't expand if it already looks like a URL
|
|
228
236
|
if "://" in spec or spec.startswith("github.com/") or spec.startswith("gitlab.com/"):
|
|
229
237
|
return spec
|
|
230
|
-
#
|
|
238
|
+
# Don't expand if it's already an SSH URL (more specific pattern: user@host:path)
|
|
239
|
+
# Matches patterns like git@github.com:, git@gitlab.com:, etc.
|
|
240
|
+
if re.match(r"^[^@]+@[^:]+:", spec):
|
|
241
|
+
return spec
|
|
242
|
+
# Check if it matches owner/repo[@branch] pattern - use SSH URL format for GitHub
|
|
231
243
|
if OWNER_REPO_PATTERN.match(spec):
|
|
232
|
-
|
|
244
|
+
if "@" in spec:
|
|
245
|
+
owner_repo, branch = spec.split("@", 1)
|
|
246
|
+
return f"git@github.com:{owner_repo}.git@{branch}"
|
|
247
|
+
return f"git@github.com:{spec}.git"
|
|
233
248
|
# Otherwise return as-is (existing workspace name)
|
|
234
249
|
return spec
|
|
235
250
|
|
|
@@ -277,12 +292,16 @@ def spec_to_workspace_id(spec: str) -> str:
|
|
|
277
292
|
# Strip protocol prefix if present
|
|
278
293
|
if "://" in full_source:
|
|
279
294
|
full_source = full_source.split("://", 1)[1]
|
|
295
|
+
# Strip SSH URL prefix (user@host:) if present
|
|
296
|
+
ssh_match = re.match(r"^[^@]+@([^:]+):(.*)", full_source)
|
|
297
|
+
if ssh_match:
|
|
298
|
+
full_source = f"{ssh_match.group(1)}/{ssh_match.group(2)}"
|
|
280
299
|
# Strip .git suffix if present
|
|
281
300
|
if full_source.endswith(".git"):
|
|
282
301
|
full_source = full_source[:-4]
|
|
283
|
-
# Devpod sanitizes: lowercase, replace . and / with -, remove _
|
|
302
|
+
# Devpod sanitizes: lowercase, replace . and / and : with -, remove _
|
|
284
303
|
workspace_id = full_source.lower()
|
|
285
|
-
workspace_id = workspace_id.replace(".", "-").replace("/", "-")
|
|
304
|
+
workspace_id = workspace_id.replace(".", "-").replace("/", "-").replace(":", "-")
|
|
286
305
|
workspace_id = workspace_id.replace("_", "")
|
|
287
306
|
# Remove trailing - if any
|
|
288
307
|
workspace_id = workspace_id.rstrip("-")
|
|
@@ -696,12 +715,16 @@ def workspace_ssh(workspace: str, command: Optional[str] = None) -> int:
|
|
|
696
715
|
def workspace_stop(workspace: str) -> int:
|
|
697
716
|
"""Stop a workspace."""
|
|
698
717
|
result = run_devpod(["stop", workspace])
|
|
718
|
+
# Update cache after stopping workspace
|
|
719
|
+
update_cache_background()
|
|
699
720
|
return result.returncode
|
|
700
721
|
|
|
701
722
|
|
|
702
723
|
def workspace_delete(workspace: str) -> int:
|
|
703
724
|
"""Delete a workspace."""
|
|
704
725
|
result = run_devpod(["delete", workspace])
|
|
726
|
+
# Update cache after deleting workspace
|
|
727
|
+
update_cache_background()
|
|
705
728
|
return result.returncode
|
|
706
729
|
|
|
707
730
|
|
|
@@ -739,6 +762,7 @@ Workspace commands:
|
|
|
739
762
|
Global commands:
|
|
740
763
|
dl --ls List all workspaces
|
|
741
764
|
dl --install Install shell completions
|
|
765
|
+
dl --refresh Refresh completion cache
|
|
742
766
|
dl --help, -h Show this help
|
|
743
767
|
dl --version Show version
|
|
744
768
|
|
|
@@ -759,6 +783,12 @@ def main() -> int:
|
|
|
759
783
|
"""Main entry point for dl CLI."""
|
|
760
784
|
args = sys.argv[1:]
|
|
761
785
|
|
|
786
|
+
# Always update cache in background (unless we're the update process)
|
|
787
|
+
if args and args[0] in ["--update-cache"]:
|
|
788
|
+
pass # Don't recursively update
|
|
789
|
+
else:
|
|
790
|
+
update_cache_background()
|
|
791
|
+
|
|
762
792
|
# No args - try fzf selection
|
|
763
793
|
if not args:
|
|
764
794
|
selected = fuzzy_select_workspace()
|
|
@@ -793,10 +823,17 @@ def main() -> int:
|
|
|
793
823
|
return 0
|
|
794
824
|
|
|
795
825
|
if args[0] == "--update-cache":
|
|
796
|
-
#
|
|
826
|
+
# Silent background update
|
|
797
827
|
update_completion_cache()
|
|
798
828
|
return 0
|
|
799
829
|
|
|
830
|
+
if args[0] == "--refresh":
|
|
831
|
+
# Manual refresh with feedback
|
|
832
|
+
print("Refreshing completion cache...")
|
|
833
|
+
data = update_completion_cache()
|
|
834
|
+
print(f"Cache updated: {len(data.get('workspaces', []))} workspaces found")
|
|
835
|
+
return 0
|
|
836
|
+
|
|
800
837
|
if args[0] == "--completion-data":
|
|
801
838
|
# Output all completion data as JSON (fast, from cache)
|
|
802
839
|
cache = read_completion_cache()
|
|
@@ -898,7 +935,7 @@ def main() -> int:
|
|
|
898
935
|
# Attach to workspace
|
|
899
936
|
ret = workspace_ssh(workspace_id, shell_command)
|
|
900
937
|
|
|
901
|
-
# Update cache
|
|
938
|
+
# Update cache after workspace operations
|
|
902
939
|
update_cache_background()
|
|
903
940
|
|
|
904
941
|
return ret
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "devlaunch"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.6"
|
|
4
4
|
authors = [{ name = "Austin Gregg-Smith", email = "blooop@gmail.com" }]
|
|
5
5
|
description = "DevLaunch - A streamlined CLI for devpod workspaces"
|
|
6
6
|
readme = "README.md"
|
|
@@ -23,7 +23,6 @@ python = ">=3.10"
|
|
|
23
23
|
shellcheck = ">=0.10.0,<0.11"
|
|
24
24
|
devpod = ">=0.8.0,<0.9"
|
|
25
25
|
gh = ">=2.83.0,<3.0"
|
|
26
|
-
ralph-claude-code = ">=0.1.0"
|
|
27
26
|
|
|
28
27
|
[tool.pixi.feature.py310.dependencies]
|
|
29
28
|
python = "3.10.*"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|