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.
- {devlaunch-0.0.7 → devlaunch-0.0.9}/.gitignore +0 -12
- {devlaunch-0.0.7 → devlaunch-0.0.9}/PKG-INFO +87 -10
- {devlaunch-0.0.7 → devlaunch-0.0.9}/README.md +86 -9
- devlaunch-0.0.9/devlaunch/aid.py +212 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/completion.py +1 -1
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/completions/dl.bash +17 -2
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/dl.py +92 -47
- devlaunch-0.0.9/devlaunch/gh_auth.py +171 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/worktree/branch_manager.py +0 -49
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/worktree/config.py +0 -10
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/worktree/models.py +28 -6
- devlaunch-0.0.9/devlaunch/worktree/storage.py +348 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/worktree/workspace_clone.py +0 -2
- {devlaunch-0.0.7 → devlaunch-0.0.9}/pyproject.toml +2 -1
- devlaunch-0.0.7/devlaunch/worktree/storage.py +0 -131
- {devlaunch-0.0.7 → devlaunch-0.0.9}/LICENSE +0 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/__init__.py +0 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/completion_loader.py +0 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/completions/__init__.py +0 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/worktree/__init__.py +0 -0
- {devlaunch-0.0.7 → devlaunch-0.0.9}/devlaunch/worktree/repo_manager.py +0 -0
|
@@ -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.
|
|
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
|
[](https://github.com/blooop/devlaunch/pulls?q=is%3Amerged)
|
|
34
34
|
[](https://GitHub.com/blooop/devlaunch/releases/)
|
|
35
35
|
[](https://pypi.org/project/devlaunch/)
|
|
36
|
-
[](https://prefix.dev/channels/blooop/packages/devlaunch)
|
|
37
37
|
[](https://opensource.org/license/mit/)
|
|
38
38
|
[](https://www.python.org/downloads/)
|
|
39
39
|
[](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
|
[](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)
|
|
@@ -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
|
-
|
|
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
|