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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devlaunch
3
- Version: 0.0.5
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
  [![GitHub pull-requests merged](https://badgen.net/github/merged-prs/blooop/devlaunch)](https://github.com/blooop/devlaunch/pulls?q=is%3Amerged)
32
32
  [![GitHub release](https://img.shields.io/github/release/blooop/devlaunch.svg)](https://GitHub.com/blooop/devlaunch/releases/)
33
33
  [![PyPI](https://img.shields.io/pypi/v/devlaunch)](https://pypi.org/project/devlaunch/)
34
- [![Conda](https://img.shields.io/badge/conda-blooop-green?logo=conda-forge)](https://prefix.dev/channels/blooop/packages/devlaunch)
34
+ [![Conda](https://img.shields.io/badge/conda-v0.0.5-brightgreen?logo=anaconda)](https://prefix.dev/channels/blooop/packages/devlaunch)
35
35
  [![License](https://img.shields.io/github/license/blooop/devlaunch)](https://opensource.org/license/mit/)
36
36
  [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://www.python.org/downloads/)
37
37
  [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh)
@@ -10,7 +10,7 @@ A streamlined CLI for [devpod](https://devpod.sh) with intuitive autocomplete an
10
10
  [![GitHub pull-requests merged](https://badgen.net/github/merged-prs/blooop/devlaunch)](https://github.com/blooop/devlaunch/pulls?q=is%3Amerged)
11
11
  [![GitHub release](https://img.shields.io/github/release/blooop/devlaunch.svg)](https://GitHub.com/blooop/devlaunch/releases/)
12
12
  [![PyPI](https://img.shields.io/pypi/v/devlaunch)](https://pypi.org/project/devlaunch/)
13
- [![Conda](https://img.shields.io/badge/conda-blooop-green?logo=conda-forge)](https://prefix.dev/channels/blooop/packages/devlaunch)
13
+ [![Conda](https://img.shields.io/badge/conda-v0.0.5-brightgreen?logo=anaconda)](https://prefix.dev/channels/blooop/packages/devlaunch)
14
14
  [![License](https://img.shields.io/github/license/blooop/devlaunch)](https://opensource.org/license/mit/)
15
15
  [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](https://www.python.org/downloads/)
16
16
  [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](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
- cur="${COMP_WORDS[COMP_CWORD]}"
6
- prev="${COMP_WORDS[COMP_CWORD-1]}"
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 [[ ${COMP_CWORD} -eq 1 ]]; then
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 [[ ${COMP_CWORD} -eq 2 ]]; then
118
+ if [[ ${word_count} -eq 3 ]]; then
80
119
  # Don't complete after global flags
81
- local first="${COMP_WORDS[1]}"
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
- with open(cache_path, "w", encoding="utf-8") as f:
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
- with open(BASH_CACHE_FILE, "w", encoding="utf-8") as f:
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/owner/repo[@branch] for devpod."""
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
- # Check if it matches owner/repo[@branch] pattern
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
- return f"github.com/{spec}"
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
- # Update completion cache (called in background)
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 in background after workspace operations
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.5"
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