devlaunch 0.0.7__tar.gz → 0.0.9__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.
@@ -179,20 +179,8 @@ test_suite_analysis/metadata.json
179
179
  # Claude Code local settings (personal, not shared)
180
180
  .claude/settings.local.json
181
181
 
182
- # Ralph autonomous agent state files
183
- .call_count
184
- .circuit_breaker_history
185
- .circuit_breaker_state
186
- .exit_signals
187
- .last_reset
188
- .ralph_session
189
- .ralph_session_history
190
- .response_analysis
191
182
  .claude_session_id
192
- progress.json
193
- status.json
194
183
  logs/
195
- .ralph/
196
184
 
197
185
  # uv is not this project's package manager (pixi.lock is authoritative)
198
186
  uv.lock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devlaunch
3
- Version: 0.0.7
3
+ Version: 0.0.9
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
@@ -33,7 +33,7 @@ A streamlined CLI for [devpod](https://devpod.sh) with intuitive autocomplete an
33
33
  [![GitHub pull-requests merged](https://badgen.net/github/merged-prs/blooop/devlaunch)](https://github.com/blooop/devlaunch/pulls?q=is%3Amerged)
34
34
  [![GitHub release](https://img.shields.io/github/release/blooop/devlaunch.svg)](https://GitHub.com/blooop/devlaunch/releases/)
35
35
  [![PyPI](https://img.shields.io/pypi/v/devlaunch)](https://pypi.org/project/devlaunch/)
36
- [![Conda](https://img.shields.io/badge/conda-v0.0.7-brightgreen?logo=anaconda)](https://prefix.dev/channels/blooop/packages/devlaunch)
36
+ [![Conda](https://img.shields.io/badge/conda-v0.0.9-brightgreen?logo=anaconda)](https://prefix.dev/channels/blooop/packages/devlaunch)
37
37
  [![License](https://img.shields.io/github/license/blooop/devlaunch)](https://opensource.org/license/mit/)
38
38
  [![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/)
39
39
  [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh)
@@ -55,10 +55,12 @@ pip install devlaunch
55
55
  ```
56
56
 
57
57
  Note: When using pip, you must install [devpod](https://devpod.sh/docs/getting-started/install) separately.
58
+ If `devpod` is not on `PATH`, every command that needs it prints a single install hint on stderr and exits `127`
59
+ (the shell's "command not found" code). `dl --help` and `dl --version` keep working without it.
58
60
 
59
61
  ### Shell Completions
60
62
 
61
- After installation, set up shell completions:
63
+ After installation, set up shell completions for `dl` and `aid`:
62
64
 
63
65
  ```bash
64
66
  dl --install
@@ -74,6 +76,44 @@ dl <user/repo> <cmd> # Run workspace command (stop, code, etc.)
74
76
  dl <user/repo> -- <command> # Run shell command in workspace
75
77
  ```
76
78
 
79
+ ## aid: start a coding agent in a workspace
80
+
81
+ `aid` is `dl` with a coding agent started for you:
82
+
83
+ ```bash
84
+ aid <user/repo>[@branch] [prompt...] # Open the workspace, start the agent
85
+ ```
86
+
87
+ It is a shortcut, not a second launcher. `aid` rewrites its command line into a
88
+ `dl` one and hands it to `dl` itself, so
89
+
90
+ ```bash
91
+ aid blooop/devlaunch@fix/42 fix the flaky test
92
+ ```
93
+
94
+ is exactly
95
+
96
+ ```bash
97
+ dl blooop/devlaunch@fix/42 -- claude 'fix the flaky test'
98
+ ```
99
+
100
+ That means an `aid` workspace *is* the `dl` workspace: same clone, same workspace
101
+ id, same container — started if stopped, attached to if already running, and never
102
+ rebuilt just because `aid` asked for it. Anything `dl` learns, `aid` gets.
103
+
104
+ | Option | Description |
105
+ |--------|-------------|
106
+ | `--claude`, `--codex`, `--gemini` | Pick the agent (default: `claude`) |
107
+ | `--devcontainer <variant\|path>` | Passed through to `dl` |
108
+ | `DEVLAUNCH_AID_AGENT=<agent>` | Change the default agent |
109
+
110
+ Everything after the workspace is the prompt, flags and all, so it never needs
111
+ quoting to survive `aid`'s own parsing. Managing workspaces — listing, stopping,
112
+ deleting, VS Code — stays with `dl`.
113
+
114
+ The agent's CLI has to be installed in the container; `aid` runs it there, it does
115
+ not install it.
116
+
77
117
  ## Workspace Sources
78
118
 
79
119
  ```bash
@@ -105,6 +145,50 @@ Projects with demanding devcontainers — several variants, compose sidecars, or
105
145
  host-side `initializeCommand` that has to tell branch workspaces apart — are
106
146
  covered in [docs/devcontainer-projects.md](docs/devcontainer-projects.md).
107
147
 
148
+ ## GitHub Authentication
149
+
150
+ Every workspace `dl` opens inherits the host's GitHub login, so `gh` is already
151
+ authenticated inside the container and the devcontainer.json does not have to
152
+ arrange anything for it. devpod forwards the ssh agent and git credentials on its
153
+ own, but nothing else carries `gh`.
154
+
155
+ devlaunch takes the token from `GH_TOKEN`, `GITHUB_TOKEN`, or `gh auth token`,
156
+ whichever answers first, and hands it to the container as `GH_TOKEN`. That reaches
157
+ any image and any container user, unlike a bind-mount of `~/.config/gh`, and it
158
+ works whether the host keeps its token in `hosts.yml` or in a keyring. The token
159
+ is passed to devpod through a private file and through devpod's own environment,
160
+ never on a command line, so it does not appear in `ps`. The container still needs
161
+ `gh` installed for the login to be of any use. Check a workspace with:
162
+
163
+ ```bash
164
+ dl <workspace> -- gh auth status
165
+ ```
166
+
167
+ ### Who gets the token
168
+
169
+ Everything running in the container does — including a `postCreateCommand` from a
170
+ repo you did not write. `dl someone/repo` builds and runs that project's
171
+ devcontainer with your GitHub token in its environment, and a `gh auth login` token
172
+ usually carries `repo`, `workflow`, `gist` and `read:org` scopes. devpod already
173
+ forwards the ssh agent to every workspace, so this is not a new trust boundary, but
174
+ it is a wider one. Skip it for a repo you have not read:
175
+
176
+ ```bash
177
+ DEVLAUNCH_NO_GH_TOKEN=1 dl someone/repo
178
+ ```
179
+
180
+ | Variable | Description |
181
+ |----------|-------------|
182
+ | `DEVLAUNCH_NO_GH_TOKEN=1` | Do not forward the host's GitHub login into workspaces |
183
+
184
+ ### When the token changes
185
+
186
+ `dl` refreshes the token on every start, so rotating it on the host is enough for
187
+ any workspace that gets started or restarted afterwards. Attaching to a workspace
188
+ that is *already running* skips that step, and the token it was given at startup
189
+ stays in place — including one it was given before you set
190
+ `DEVLAUNCH_NO_GH_TOKEN`. Run `dl <workspace> restart` to replace it.
191
+
108
192
  ## Global Commands
109
193
 
110
194
  | Command | Description |
@@ -163,13 +247,6 @@ Use `--warm` to prepare a workspace without attaching a shell:
163
247
  dl --warm owner/repo@branch # Creates container in background
164
248
  ```
165
249
 
166
- ### Backend Selection
167
-
168
- ```bash
169
- dl --backend devpod owner/repo # Force legacy DevPod backend
170
- DEVLAUNCH_BACKEND=devpod dl owner/repo # Use environment variable
171
- ```
172
-
173
250
  ## Shell Completion
174
251
 
175
252
  After running `dl --install`, you get intelligent tab completion:
@@ -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-v0.0.7-brightgreen?logo=anaconda)](https://prefix.dev/channels/blooop/packages/devlaunch)
13
+ [![Conda](https://img.shields.io/badge/conda-v0.0.9-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)
@@ -32,10 +32,12 @@ pip install devlaunch
32
32
  ```
33
33
 
34
34
  Note: When using pip, you must install [devpod](https://devpod.sh/docs/getting-started/install) separately.
35
+ If `devpod` is not on `PATH`, every command that needs it prints a single install hint on stderr and exits `127`
36
+ (the shell's "command not found" code). `dl --help` and `dl --version` keep working without it.
35
37
 
36
38
  ### Shell Completions
37
39
 
38
- After installation, set up shell completions:
40
+ After installation, set up shell completions for `dl` and `aid`:
39
41
 
40
42
  ```bash
41
43
  dl --install
@@ -51,6 +53,44 @@ dl <user/repo> <cmd> # Run workspace command (stop, code, etc.)
51
53
  dl <user/repo> -- <command> # Run shell command in workspace
52
54
  ```
53
55
 
56
+ ## aid: start a coding agent in a workspace
57
+
58
+ `aid` is `dl` with a coding agent started for you:
59
+
60
+ ```bash
61
+ aid <user/repo>[@branch] [prompt...] # Open the workspace, start the agent
62
+ ```
63
+
64
+ It is a shortcut, not a second launcher. `aid` rewrites its command line into a
65
+ `dl` one and hands it to `dl` itself, so
66
+
67
+ ```bash
68
+ aid blooop/devlaunch@fix/42 fix the flaky test
69
+ ```
70
+
71
+ is exactly
72
+
73
+ ```bash
74
+ dl blooop/devlaunch@fix/42 -- claude 'fix the flaky test'
75
+ ```
76
+
77
+ That means an `aid` workspace *is* the `dl` workspace: same clone, same workspace
78
+ id, same container — started if stopped, attached to if already running, and never
79
+ rebuilt just because `aid` asked for it. Anything `dl` learns, `aid` gets.
80
+
81
+ | Option | Description |
82
+ |--------|-------------|
83
+ | `--claude`, `--codex`, `--gemini` | Pick the agent (default: `claude`) |
84
+ | `--devcontainer <variant\|path>` | Passed through to `dl` |
85
+ | `DEVLAUNCH_AID_AGENT=<agent>` | Change the default agent |
86
+
87
+ Everything after the workspace is the prompt, flags and all, so it never needs
88
+ quoting to survive `aid`'s own parsing. Managing workspaces — listing, stopping,
89
+ deleting, VS Code — stays with `dl`.
90
+
91
+ The agent's CLI has to be installed in the container; `aid` runs it there, it does
92
+ not install it.
93
+
54
94
  ## Workspace Sources
55
95
 
56
96
  ```bash
@@ -82,6 +122,50 @@ Projects with demanding devcontainers — several variants, compose sidecars, or
82
122
  host-side `initializeCommand` that has to tell branch workspaces apart — are
83
123
  covered in [docs/devcontainer-projects.md](docs/devcontainer-projects.md).
84
124
 
125
+ ## GitHub Authentication
126
+
127
+ Every workspace `dl` opens inherits the host's GitHub login, so `gh` is already
128
+ authenticated inside the container and the devcontainer.json does not have to
129
+ arrange anything for it. devpod forwards the ssh agent and git credentials on its
130
+ own, but nothing else carries `gh`.
131
+
132
+ devlaunch takes the token from `GH_TOKEN`, `GITHUB_TOKEN`, or `gh auth token`,
133
+ whichever answers first, and hands it to the container as `GH_TOKEN`. That reaches
134
+ any image and any container user, unlike a bind-mount of `~/.config/gh`, and it
135
+ works whether the host keeps its token in `hosts.yml` or in a keyring. The token
136
+ is passed to devpod through a private file and through devpod's own environment,
137
+ never on a command line, so it does not appear in `ps`. The container still needs
138
+ `gh` installed for the login to be of any use. Check a workspace with:
139
+
140
+ ```bash
141
+ dl <workspace> -- gh auth status
142
+ ```
143
+
144
+ ### Who gets the token
145
+
146
+ Everything running in the container does — including a `postCreateCommand` from a
147
+ repo you did not write. `dl someone/repo` builds and runs that project's
148
+ devcontainer with your GitHub token in its environment, and a `gh auth login` token
149
+ usually carries `repo`, `workflow`, `gist` and `read:org` scopes. devpod already
150
+ forwards the ssh agent to every workspace, so this is not a new trust boundary, but
151
+ it is a wider one. Skip it for a repo you have not read:
152
+
153
+ ```bash
154
+ DEVLAUNCH_NO_GH_TOKEN=1 dl someone/repo
155
+ ```
156
+
157
+ | Variable | Description |
158
+ |----------|-------------|
159
+ | `DEVLAUNCH_NO_GH_TOKEN=1` | Do not forward the host's GitHub login into workspaces |
160
+
161
+ ### When the token changes
162
+
163
+ `dl` refreshes the token on every start, so rotating it on the host is enough for
164
+ any workspace that gets started or restarted afterwards. Attaching to a workspace
165
+ that is *already running* skips that step, and the token it was given at startup
166
+ stays in place — including one it was given before you set
167
+ `DEVLAUNCH_NO_GH_TOKEN`. Run `dl <workspace> restart` to replace it.
168
+
85
169
  ## Global Commands
86
170
 
87
171
  | Command | Description |
@@ -140,13 +224,6 @@ Use `--warm` to prepare a workspace without attaching a shell:
140
224
  dl --warm owner/repo@branch # Creates container in background
141
225
  ```
142
226
 
143
- ### Backend Selection
144
-
145
- ```bash
146
- dl --backend devpod owner/repo # Force legacy DevPod backend
147
- DEVLAUNCH_BACKEND=devpod dl owner/repo # Use environment variable
148
- ```
149
-
150
227
  ## Shell Completion
151
228
 
152
229
  After running `dl --install`, you get intelligent tab completion:
@@ -0,0 +1,212 @@
1
+ """aid - AI Develop: `dl`, with a coding agent started for you.
2
+
3
+ aid is a shortcut, not a second launcher. It rewrites its own command line into
4
+ a `dl` one and hands that to :func:`devlaunch.dl.main`, so:
5
+
6
+ aid owner/repo@branch fix the flaky test
7
+
8
+ is exactly
9
+
10
+ dl owner/repo@branch -- claude 'fix the flaky test'
11
+
12
+ Everything that decides how a workspace is obtained — the bare repo cache, the
13
+ worktree clone, the workspace id, the devpod container, the fast attach to one
14
+ that is already running, the forwarded gh login — happens inside dl, once. There
15
+ is no container machinery in this module, deliberately: an aid that built its own
16
+ would drift from dl and start rebuilding containers dl would have reused.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import logging
22
+ import os
23
+ import shlex
24
+ import sys
25
+ from dataclasses import dataclass, field
26
+ from typing import Dict, List, Optional
27
+
28
+ from . import dl
29
+
30
+ # Base command per agent. The prompt, when there is one, is appended as a single
31
+ # quoted argument; each of these CLIs takes an initial prompt that way and then
32
+ # drops into its interactive session.
33
+ AGENT_COMMANDS: Dict[str, List[str]] = {
34
+ "claude": ["claude"],
35
+ "codex": ["codex"],
36
+ "gemini": ["gemini", "--prompt-interactive"],
37
+ }
38
+
39
+ # Flags that pick the agent, e.g. `aid --gemini owner/repo ...`.
40
+ AGENT_FLAGS: Dict[str, str] = {f"--{name}": name for name in AGENT_COMMANDS}
41
+
42
+ DEFAULT_AGENT = "claude"
43
+
44
+ # Overrides the default agent for people who do not want to type a flag every
45
+ # time. A --flag on the command line still wins.
46
+ AGENT_ENV_VAR = "DEVLAUNCH_AID_AGENT"
47
+
48
+
49
+ class UsageError(Exception):
50
+ """The aid command line could not be understood."""
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class AidArgs:
55
+ """An aid command line, split into the pieces the dl one is built from.
56
+
57
+ Only ever built by parse_aid_args, which refuses a command line without a
58
+ workspace, so spec is a str here and every field is ready to use.
59
+ """
60
+
61
+ spec: str
62
+ agent: str = DEFAULT_AGENT
63
+ # dl options seen before the spec (`--devcontainer x`), passed through as-is.
64
+ dl_options: List[str] = field(default_factory=list)
65
+ prompt: str = ""
66
+
67
+
68
+ def default_agent(env: Optional[Dict[str, str]] = None) -> str:
69
+ """Return the agent to use when no flag picks one."""
70
+ environ = os.environ if env is None else env
71
+ name = environ.get(AGENT_ENV_VAR, "").strip()
72
+ if not name:
73
+ return DEFAULT_AGENT
74
+ if name not in AGENT_COMMANDS:
75
+ raise UsageError(
76
+ f"{AGENT_ENV_VAR}={name!r} is not a known agent. "
77
+ f"Choose one of: {', '.join(sorted(AGENT_COMMANDS))}."
78
+ )
79
+ return name
80
+
81
+
82
+ def parse_aid_args(argv: List[str], env: Optional[Dict[str, str]] = None) -> AidArgs:
83
+ """Split an aid command line into agent, dl options, workspace spec and prompt.
84
+
85
+ The first argument that is neither an agent flag nor a dl option is the
86
+ workspace spec; everything after it is the prompt, flags and all, so a
87
+ prompt never has to be quoted to protect it from aid's own parsing.
88
+ """
89
+ agent = default_agent(env)
90
+ dl_options: List[str] = []
91
+ spec: Optional[str] = None
92
+ i = 0
93
+ while i < len(argv):
94
+ arg = argv[i]
95
+ if arg in AGENT_FLAGS:
96
+ agent = AGENT_FLAGS[arg]
97
+ i += 1
98
+ continue
99
+ if arg in dl.DL_VALUE_OPTIONS:
100
+ # Take the value with it; dl reports a missing one.
101
+ dl_options.extend(argv[i : i + 2])
102
+ i += 2
103
+ continue
104
+ if arg.startswith("-"):
105
+ dl_options.append(arg)
106
+ i += 1
107
+ continue
108
+ spec = arg
109
+ i += 1
110
+ break
111
+
112
+ if spec is None:
113
+ raise UsageError("aid needs a workspace: aid <user/repo>[@branch] [prompt]")
114
+
115
+ return AidArgs(spec=spec, agent=agent, dl_options=dl_options, prompt=" ".join(argv[i:]))
116
+
117
+
118
+ def build_agent_command(agent: str, prompt: str = "") -> str:
119
+ """Build the shell command that starts the agent inside the workspace.
120
+
121
+ Returned as one shell string because that is what dl's `-- <command>` form
122
+ takes. The prompt is quoted here rather than reassembled by the caller, so
123
+ the words the user typed reach the agent as the single argument they meant.
124
+ """
125
+ try:
126
+ command = list(AGENT_COMMANDS[agent])
127
+ except KeyError:
128
+ raise UsageError(
129
+ f"Unknown agent {agent!r}. Choose one of: {', '.join(sorted(AGENT_COMMANDS))}."
130
+ ) from None
131
+ if not prompt:
132
+ # No prompt to be interactive about: start the agent's plain session.
133
+ # gemini's --prompt-interactive would be a syntax error without one.
134
+ return shlex.quote(command[0])
135
+ command.append(prompt)
136
+ return shlex.join(command)
137
+
138
+
139
+ def build_dl_args(parsed: AidArgs) -> List[str]:
140
+ """Turn a parsed aid command line into the dl one that does the work."""
141
+ return [
142
+ *parsed.dl_options,
143
+ parsed.spec,
144
+ "--",
145
+ build_agent_command(parsed.agent, parsed.prompt),
146
+ ]
147
+
148
+
149
+ def print_help() -> None:
150
+ """Print usage help."""
151
+ agents = ", ".join(f"--{name}" for name in sorted(AGENT_COMMANDS))
152
+ print(
153
+ f"""aid - AI Develop: start a coding agent in a devlaunch workspace
154
+
155
+ aid is a shortcut for `dl <workspace> -- <agent> '<prompt>'`. The workspace is
156
+ opened by dl itself, so it is the same workspace, container and clone that
157
+ `dl <workspace>` gives you — started if it is stopped, attached to if it is
158
+ already running, and never rebuilt just because aid asked for it.
159
+
160
+ Usage:
161
+ aid <user/repo>[@branch] [prompt...] Open the workspace and start the agent
162
+ aid <workspace> [prompt...] Same, for an existing workspace or ./path
163
+
164
+ Options:
165
+ {agents}
166
+ Pick the agent (default: {DEFAULT_AGENT})
167
+ --devcontainer <variant|path> Passed through to dl
168
+ --help, -h Show this help
169
+ --version Show version
170
+
171
+ Environment:
172
+ {AGENT_ENV_VAR}=<agent> Change the default agent
173
+
174
+ Examples:
175
+ aid blooop/devlaunch # Start {DEFAULT_AGENT} in the workspace
176
+ aid blooop/devlaunch@fix/42 fix the bug # Open the branch, hand over the prompt
177
+ aid --gemini ./my-project explain this # Pick a different agent
178
+
179
+ Everything else — listing, stopping, deleting, VS Code — is dl's job:
180
+ dl --help
181
+ """
182
+ )
183
+
184
+
185
+ def main(argv: Optional[List[str]] = None) -> int:
186
+ """Entry point for the aid command."""
187
+ args = sys.argv[1:] if argv is None else list(argv)
188
+
189
+ if not args or args[0] in ("--help", "-h"):
190
+ print_help()
191
+ return 0 if args else 1
192
+
193
+ if args[0] == "--version":
194
+ print(f"aid {dl.get_version()}")
195
+ return 0
196
+
197
+ try:
198
+ parsed = parse_aid_args(args)
199
+ dl_args = build_dl_args(parsed)
200
+ except UsageError as e:
201
+ logging.error(str(e))
202
+ return 1
203
+
204
+ logging.info("aid -> dl %s", shlex.join(dl_args))
205
+ return dl.main(dl_args)
206
+
207
+
208
+ if __name__ == "__main__":
209
+ try:
210
+ sys.exit(main())
211
+ except KeyboardInterrupt:
212
+ sys.exit(130)
@@ -34,7 +34,7 @@ def _completion_file_path() -> pathlib.Path:
34
34
 
35
35
 
36
36
  def install_completions(rc_path: Optional[pathlib.Path] = None) -> int:
37
- """Install or refresh completion scripts for dl."""
37
+ """Install or refresh completion scripts for dl and aid."""
38
38
  completion_path = _completion_file_path().expanduser()
39
39
  rc_target = (rc_path if rc_path is not None else pathlib.Path.home() / ".bashrc").expanduser()
40
40
 
@@ -8,6 +8,10 @@
8
8
  # because temporary COMP_WORDBREAKS modification can have side effects with bash's
9
9
  # internal completion state and doesn't reliably prevent word splitting in all
10
10
  # bash versions. Direct parsing gives us full control over word boundaries.
11
+ #
12
+ # The same function serves `aid`, whose first argument is a dl workspace spec
13
+ # too. Only the flag list and what follows the spec differ, so the two places
14
+ # that care branch on $cmd rather than the script being copied for aid.
11
15
  _dl_completion() {
12
16
  local cur prev opts
13
17
  COMPREPLY=()
@@ -44,8 +48,17 @@ _dl_completion() {
44
48
  fi
45
49
  fi
46
50
 
51
+ # The command being completed: dl or aid.
52
+ local cmd=""
53
+ if (( ${#words[@]} > 0 )); then
54
+ cmd="${words[0]##*/}"
55
+ fi
56
+
47
57
  # Global command options (only valid as first arg)
48
58
  local global_opts="--ls --install --help -h --version --devcontainer"
59
+ if [[ "$cmd" == aid ]]; then
60
+ global_opts="--claude --codex --gemini --devcontainer --help -h --version"
61
+ fi
49
62
 
50
63
  # Workspace subcommands
51
64
  local ws_cmds="stop rm code restart recreate reset --"
@@ -133,8 +146,9 @@ _dl_completion() {
133
146
  return 0
134
147
  fi
135
148
 
136
- # Second argument (after workspace): subcommands
137
- if [[ ${word_count} -eq 3 ]]; then
149
+ # Second argument (after workspace): subcommands. Everything after an aid
150
+ # workspace is the prompt, so there is nothing to offer there.
151
+ if [[ ${word_count} -eq 3 && "$cmd" != aid ]]; then
138
152
  # Don't complete after global flags
139
153
  # Extract the first argument (word after "dl") from the words array
140
154
  local first=""
@@ -155,4 +169,5 @@ _dl_completion() {
155
169
 
156
170
  # Use -o default for better completion behavior
157
171
  complete -o default -F _dl_completion dl
172
+ complete -o default -F _dl_completion aid
158
173
  # end dl completion