devlaunch 0.0.14__tar.gz → 0.0.16__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.14 → devlaunch-0.0.16}/PKG-INFO +20 -2
- {devlaunch-0.0.14 → devlaunch-0.0.16}/README.md +19 -1
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/aid.py +43 -12
- devlaunch-0.0.16/devlaunch/devpod_provider.py +116 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/gh_auth.py +33 -6
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/config.py +3 -2
- devlaunch-0.0.16/devlaunch/xdg.py +21 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/pyproject.toml +2 -2
- {devlaunch-0.0.14 → devlaunch-0.0.16}/.gitignore +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/LICENSE +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/__init__.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/completion.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/completion_loader.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/completions/__init__.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/completions/dl.bash +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/devpod_ssh.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/dl.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/tools.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/tty_session.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/workspace_id.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/__init__.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/branch_manager.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/migration.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/models.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/repo_manager.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/storage.py +0 -0
- {devlaunch-0.0.14 → devlaunch-0.0.16}/devlaunch/worktree/workspace_clone.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.16
|
|
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
|
|
@@ -105,13 +105,26 @@ aid blooop/devlaunch@fix/42 fix the flaky test
|
|
|
105
105
|
is exactly
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
dl blooop/devlaunch@fix/42 -- claude 'fix the flaky test'
|
|
108
|
+
dl blooop/devlaunch@fix/42 -- IS_SANDBOX=1 claude --dangerously-skip-permissions 'fix the flaky test'
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
That means an `aid` workspace *is* the `dl` workspace: same clone, same workspace
|
|
112
112
|
id, same container — started if stopped, attached to if already running, and never
|
|
113
113
|
rebuilt just because `aid` asked for it. Anything `dl` learns, `aid` gets.
|
|
114
114
|
|
|
115
|
+
`claude` is started with `--dangerously-skip-permissions`. The agent is already
|
|
116
|
+
inside a disposable container holding only this repo, so the per-tool prompts it
|
|
117
|
+
would ask on the host protect nothing here and would stall an unattended run.
|
|
118
|
+
`IS_SANDBOX=1` rides along because `claude` otherwise refuses that flag outright
|
|
119
|
+
under `uid 0`, and devcontainers that run as root are ordinary. The variable is
|
|
120
|
+
scoped to the agent process, not exported into your shell.
|
|
121
|
+
|
|
122
|
+
The trade is worth stating plainly: an agent started this way edits, runs and
|
|
123
|
+
deletes inside the container without asking. It cannot reach your host, but it can
|
|
124
|
+
rewrite the checkout it is in, so review an `aid` workspace before pushing rather
|
|
125
|
+
than treating it as a sandbox that will stop it for you. `--codex` and `--gemini`
|
|
126
|
+
are unaffected, and `dl <ws> -- claude` still runs exactly what you typed.
|
|
127
|
+
|
|
115
128
|
| Option | Description |
|
|
116
129
|
|--------|-------------|
|
|
117
130
|
| `--claude`, `--codex`, `--gemini` | Pick the agent (default: `claude`) |
|
|
@@ -254,6 +267,11 @@ something to be spent on whatever the image ships. Check a workspace with:
|
|
|
254
267
|
dl <workspace> -- gh auth status
|
|
255
268
|
```
|
|
256
269
|
|
|
270
|
+
If no token can be found, `dl` warns on stderr and opens the workspace anyway rather
|
|
271
|
+
than failing — and the warning names the config directory `gh` consulted, because the
|
|
272
|
+
usual cause is a shell that scoped `XDG_CONFIG_HOME` somewhere `gh` has no login,
|
|
273
|
+
not a host that is actually logged out.
|
|
274
|
+
|
|
257
275
|
### Who gets the token
|
|
258
276
|
|
|
259
277
|
Everything running in the container does — including a `postCreateCommand` from a
|
|
@@ -83,13 +83,26 @@ aid blooop/devlaunch@fix/42 fix the flaky test
|
|
|
83
83
|
is exactly
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
dl blooop/devlaunch@fix/42 -- claude 'fix the flaky test'
|
|
86
|
+
dl blooop/devlaunch@fix/42 -- IS_SANDBOX=1 claude --dangerously-skip-permissions 'fix the flaky test'
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
That means an `aid` workspace *is* the `dl` workspace: same clone, same workspace
|
|
90
90
|
id, same container — started if stopped, attached to if already running, and never
|
|
91
91
|
rebuilt just because `aid` asked for it. Anything `dl` learns, `aid` gets.
|
|
92
92
|
|
|
93
|
+
`claude` is started with `--dangerously-skip-permissions`. The agent is already
|
|
94
|
+
inside a disposable container holding only this repo, so the per-tool prompts it
|
|
95
|
+
would ask on the host protect nothing here and would stall an unattended run.
|
|
96
|
+
`IS_SANDBOX=1` rides along because `claude` otherwise refuses that flag outright
|
|
97
|
+
under `uid 0`, and devcontainers that run as root are ordinary. The variable is
|
|
98
|
+
scoped to the agent process, not exported into your shell.
|
|
99
|
+
|
|
100
|
+
The trade is worth stating plainly: an agent started this way edits, runs and
|
|
101
|
+
deletes inside the container without asking. It cannot reach your host, but it can
|
|
102
|
+
rewrite the checkout it is in, so review an `aid` workspace before pushing rather
|
|
103
|
+
than treating it as a sandbox that will stop it for you. `--codex` and `--gemini`
|
|
104
|
+
are unaffected, and `dl <ws> -- claude` still runs exactly what you typed.
|
|
105
|
+
|
|
93
106
|
| Option | Description |
|
|
94
107
|
|--------|-------------|
|
|
95
108
|
| `--claude`, `--codex`, `--gemini` | Pick the agent (default: `claude`) |
|
|
@@ -232,6 +245,11 @@ something to be spent on whatever the image ships. Check a workspace with:
|
|
|
232
245
|
dl <workspace> -- gh auth status
|
|
233
246
|
```
|
|
234
247
|
|
|
248
|
+
If no token can be found, `dl` warns on stderr and opens the workspace anyway rather
|
|
249
|
+
than failing — and the warning names the config directory `gh` consulted, because the
|
|
250
|
+
usual cause is a shell that scoped `XDG_CONFIG_HOME` somewhere `gh` has no login,
|
|
251
|
+
not a host that is actually logged out.
|
|
252
|
+
|
|
235
253
|
### Who gets the token
|
|
236
254
|
|
|
237
255
|
Everything running in the container does — including a `postCreateCommand` from a
|
|
@@ -7,7 +7,7 @@ a `dl` one and hands that to :func:`devlaunch.dl.main`, so:
|
|
|
7
7
|
|
|
8
8
|
is exactly
|
|
9
9
|
|
|
10
|
-
dl owner/repo@branch -- claude 'fix the flaky test'
|
|
10
|
+
dl owner/repo@branch -- claude --dangerously-skip-permissions 'fix the flaky test'
|
|
11
11
|
|
|
12
12
|
Everything that decides how a workspace is obtained — the bare repo cache, the
|
|
13
13
|
worktree clone, the workspace id, the devpod container, the fast attach to one
|
|
@@ -27,13 +27,41 @@ from typing import Dict, List, Optional
|
|
|
27
27
|
|
|
28
28
|
from . import dl
|
|
29
29
|
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class Agent:
|
|
33
|
+
"""How one coding agent is started inside the workspace.
|
|
34
|
+
|
|
35
|
+
Split three ways because not every part of the line belongs everywhere:
|
|
36
|
+
`env` and `command` always run, while `prompt_flags` only joins them when
|
|
37
|
+
there is a prompt — gemini's way of taking an initial prompt is a flag that
|
|
38
|
+
is a syntax error without one.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
command: List[str]
|
|
42
|
+
prompt_flags: List[str] = field(default_factory=list)
|
|
43
|
+
env: Dict[str, str] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
|
|
30
46
|
# Base command per agent. The prompt, when there is one, is appended as a single
|
|
31
47
|
# quoted argument; each of these CLIs takes an initial prompt that way and then
|
|
32
48
|
# drops into its interactive session.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
#
|
|
50
|
+
# claude is started with --dangerously-skip-permissions: the whole point of a dl
|
|
51
|
+
# workspace is that the agent is already inside a disposable container with only
|
|
52
|
+
# this repo in it, so the per-tool prompts it would otherwise ask on the host buy
|
|
53
|
+
# nothing and stop an unattended `aid owner/repo fix the bug` dead.
|
|
54
|
+
#
|
|
55
|
+
# IS_SANDBOX=1 is what makes that flag usable at all here. claude refuses it
|
|
56
|
+
# outright under uid 0 -- "cannot be used with root/sudo privileges", exit 1 --
|
|
57
|
+
# and plenty of devcontainers run as root, so without this aid would not start
|
|
58
|
+
# in them at all. The variable is claude's own way of being told the refusal is
|
|
59
|
+
# answering for a machine that isn't there, which is exactly a dl workspace: a
|
|
60
|
+
# disposable container holding one repo.
|
|
61
|
+
AGENT_COMMANDS: Dict[str, Agent] = {
|
|
62
|
+
"claude": Agent(["claude", "--dangerously-skip-permissions"], env={"IS_SANDBOX": "1"}),
|
|
63
|
+
"codex": Agent(["codex"]),
|
|
64
|
+
"gemini": Agent(["gemini"], prompt_flags=["--prompt-interactive"]),
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
# Flags that pick the agent, e.g. `aid --gemini owner/repo ...`.
|
|
@@ -122,18 +150,21 @@ def build_agent_command(agent: str, prompt: str = "") -> str:
|
|
|
122
150
|
takes. The prompt is quoted here rather than reassembled by the caller, so
|
|
123
151
|
the words the user typed reach the agent as the single argument they meant.
|
|
124
152
|
"""
|
|
153
|
+
# Named for the agent, not `spec`, which is the workspace everywhere else here.
|
|
125
154
|
try:
|
|
126
|
-
|
|
155
|
+
started = AGENT_COMMANDS[agent]
|
|
127
156
|
except KeyError:
|
|
128
157
|
raise UsageError(
|
|
129
158
|
f"Unknown agent {agent!r}. Choose one of: {', '.join(sorted(AGENT_COMMANDS))}."
|
|
130
159
|
) from None
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
# No prompt to be interactive about: start the agent's plain session, without
|
|
161
|
+
# the flags that only make sense alongside one.
|
|
162
|
+
words = [*started.command, *started.prompt_flags, prompt] if prompt else list(started.command)
|
|
163
|
+
# Assignments prefixing a command set the variables for that command only, so
|
|
164
|
+
# the agent is the one process that sees them and nothing in the login shell
|
|
165
|
+
# dl runs this under is changed.
|
|
166
|
+
assignments = [f"{name}={shlex.quote(value)}" for name, value in sorted(started.env.items())]
|
|
167
|
+
return " ".join([*assignments, shlex.join(words)])
|
|
137
168
|
|
|
138
169
|
|
|
139
170
|
def build_dl_args(parsed: AidArgs) -> List[str]:
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Answer "is this devpod provider already registered?" from devpod itself.
|
|
2
|
+
|
|
3
|
+
The question used to be asked by grepping devpod's human-facing table:
|
|
4
|
+
|
|
5
|
+
devpod provider list | grep -qw docker || devpod provider add docker
|
|
6
|
+
|
|
7
|
+
devpod v0.26.1 colourises that table unconditionally and emits the reset
|
|
8
|
+
sequence ``ESC[m`` immediately before each cell, so the character preceding
|
|
9
|
+
``docker`` is the letter ``m``, ``grep -w``'s left word boundary never matches,
|
|
10
|
+
and the guard reported "absent" on every machine that had already registered the
|
|
11
|
+
provider. ``NO_COLOR=1`` does not help.
|
|
12
|
+
|
|
13
|
+
Dropping ``-w`` would have fixed today's rendering and left us reading a table
|
|
14
|
+
devpod is free to re-render tomorrow. ``provider list --output json`` is the
|
|
15
|
+
same information in a form that has no rendering to change: an object keyed by
|
|
16
|
+
provider name.
|
|
17
|
+
|
|
18
|
+
The other half of the old guard's failure is the part worth keeping out on
|
|
19
|
+
purpose. Output it could not read came back as an empty set of providers, and an
|
|
20
|
+
empty set of providers means "go add one" -- so an unreadable answer turned into
|
|
21
|
+
an action. Here, unreadable is its own outcome: `UnreadableProviderList`. The
|
|
22
|
+
caller finds out, rather than being told something false.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import subprocess
|
|
27
|
+
from typing import Callable, Optional, Sequence, Set
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UnreadableProviderList(RuntimeError):
|
|
31
|
+
"""devpod's provider listing could not be read.
|
|
32
|
+
|
|
33
|
+
Distinct from "no providers are registered", which is a listing that reads
|
|
34
|
+
fine and is empty.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def parse_provider_names(listing: str) -> Set[str]:
|
|
39
|
+
"""The names of every registered provider in a `--output json` listing."""
|
|
40
|
+
try:
|
|
41
|
+
parsed = json.loads(listing)
|
|
42
|
+
except json.JSONDecodeError as exc:
|
|
43
|
+
raise UnreadableProviderList(
|
|
44
|
+
f"devpod's provider listing is not JSON: {listing[:120]!r}"
|
|
45
|
+
) from exc
|
|
46
|
+
if not isinstance(parsed, dict):
|
|
47
|
+
raise UnreadableProviderList(
|
|
48
|
+
f"expected devpod to list providers by name, got {type(parsed).__name__}"
|
|
49
|
+
)
|
|
50
|
+
return set(parsed)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def list_provider_names(
|
|
54
|
+
run: Callable[..., subprocess.CompletedProcess] = subprocess.run,
|
|
55
|
+
) -> Set[str]:
|
|
56
|
+
"""Ask devpod which providers are registered."""
|
|
57
|
+
result = run(
|
|
58
|
+
["devpod", "provider", "list", "--output", "json"],
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
check=False,
|
|
62
|
+
)
|
|
63
|
+
if result.returncode != 0:
|
|
64
|
+
raise UnreadableProviderList(
|
|
65
|
+
f"`devpod provider list` exited {result.returncode}: {(result.stderr or '').strip()[:200]}"
|
|
66
|
+
)
|
|
67
|
+
return parse_provider_names(result.stdout or "")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def ensure_provider(
|
|
71
|
+
name: str, run: Callable[..., subprocess.CompletedProcess] = subprocess.run
|
|
72
|
+
) -> bool:
|
|
73
|
+
"""Register `name` with devpod unless it is already registered.
|
|
74
|
+
|
|
75
|
+
Returns True if it had to be added, False if it was already there. Raises
|
|
76
|
+
`UnreadableProviderList` rather than guessing when devpod's answer cannot
|
|
77
|
+
be read.
|
|
78
|
+
"""
|
|
79
|
+
if name in list_provider_names(run=run):
|
|
80
|
+
return False
|
|
81
|
+
result = run(["devpod", "provider", "add", name], check=False)
|
|
82
|
+
if result.returncode != 0:
|
|
83
|
+
raise RuntimeError(f"`devpod provider add {name}` exited {result.returncode}")
|
|
84
|
+
return True
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main(
|
|
88
|
+
argv: Optional[Sequence[str]] = None,
|
|
89
|
+
run: Callable[..., subprocess.CompletedProcess] = subprocess.run,
|
|
90
|
+
) -> int:
|
|
91
|
+
"""CLI entry point: `python -m devlaunch.devpod_provider <name>`.
|
|
92
|
+
|
|
93
|
+
This is what the `dev-add-docker` pixi task runs, so a devpod that cannot be
|
|
94
|
+
read has to stop the task rather than let it carry on as if the provider
|
|
95
|
+
were missing.
|
|
96
|
+
"""
|
|
97
|
+
import argparse # pylint: disable=import-outside-toplevel
|
|
98
|
+
|
|
99
|
+
parser = argparse.ArgumentParser(
|
|
100
|
+
prog="python -m devlaunch.devpod_provider",
|
|
101
|
+
description="Register a devpod provider unless it is already registered.",
|
|
102
|
+
)
|
|
103
|
+
parser.add_argument("name", help="provider name, e.g. docker")
|
|
104
|
+
args = parser.parse_args(argv)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
added = ensure_provider(args.name, run=run)
|
|
108
|
+
except (UnreadableProviderList, RuntimeError) as exc:
|
|
109
|
+
print(f"error: {exc}")
|
|
110
|
+
return 1
|
|
111
|
+
print(f"devpod provider {args.name}: {'added' if added else 'already registered'}")
|
|
112
|
+
return 0
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
raise SystemExit(main())
|
|
@@ -27,6 +27,8 @@ import subprocess
|
|
|
27
27
|
import tempfile
|
|
28
28
|
from typing import Dict, Iterator, List, Optional, Tuple
|
|
29
29
|
|
|
30
|
+
from devlaunch.xdg import config_home
|
|
31
|
+
|
|
30
32
|
# The variable set inside the container. gh consults it before its config file.
|
|
31
33
|
TOKEN_VAR = "GH_TOKEN"
|
|
32
34
|
|
|
@@ -74,14 +76,31 @@ def _token_from_gh_cli() -> Optional[str]:
|
|
|
74
76
|
timeout=_GH_TIMEOUT_SECONDS,
|
|
75
77
|
)
|
|
76
78
|
except (OSError, subprocess.SubprocessError) as e:
|
|
77
|
-
logging.
|
|
79
|
+
logging.warning(
|
|
80
|
+
"Could not read a GitHub token from gh (%s), so this workspace opens "
|
|
81
|
+
"without a GitHub login.",
|
|
82
|
+
e,
|
|
83
|
+
)
|
|
78
84
|
return None
|
|
79
85
|
if result.returncode != 0:
|
|
80
|
-
|
|
86
|
+
# Name the config dir: a run that scoped XDG_CONFIG_HOME to a scratch
|
|
87
|
+
# directory hides the host's gh login, so gh refuses even though the user
|
|
88
|
+
# is logged in, and `gh auth login` is exactly the wrong remedy for it.
|
|
89
|
+
logging.warning(
|
|
90
|
+
"gh auth token exited %s, so this workspace opens without a GitHub login. "
|
|
91
|
+
"gh read its config from %s -- if you are logged in on this host, that "
|
|
92
|
+
"directory is the thing to check before `gh auth login`.",
|
|
93
|
+
result.returncode,
|
|
94
|
+
config_home(),
|
|
95
|
+
)
|
|
81
96
|
return None
|
|
82
|
-
# Never log the value itself, only whether it was usable
|
|
97
|
+
# Never log the value itself, only whether it was usable: what gh printed may
|
|
98
|
+
# be a malformed credential, and a warning is not a place to put one.
|
|
83
99
|
if not _is_token(result.stdout.strip()):
|
|
84
|
-
logging.
|
|
100
|
+
logging.warning(
|
|
101
|
+
"gh auth token printed something that is not a token, so this workspace "
|
|
102
|
+
"opens without a GitHub login."
|
|
103
|
+
)
|
|
85
104
|
return None
|
|
86
105
|
return result.stdout.strip()
|
|
87
106
|
|
|
@@ -112,13 +131,21 @@ def _stage_token_file(token: str) -> Optional[str]:
|
|
|
112
131
|
try:
|
|
113
132
|
fd, path = tempfile.mkstemp(prefix="devlaunch-gh-", suffix=".env")
|
|
114
133
|
except OSError as e:
|
|
115
|
-
logging.
|
|
134
|
+
logging.warning(
|
|
135
|
+
"Could not create a file to pass the GitHub token to devpod (%s), so this "
|
|
136
|
+
"workspace opens without a GitHub login.",
|
|
137
|
+
e,
|
|
138
|
+
)
|
|
116
139
|
return None
|
|
117
140
|
try:
|
|
118
141
|
with os.fdopen(fd, "w") as handle:
|
|
119
142
|
handle.write(f"{TOKEN_VAR}={token}\n")
|
|
120
143
|
except OSError as e:
|
|
121
|
-
logging.
|
|
144
|
+
logging.warning(
|
|
145
|
+
"Could not write the GitHub token for devpod (%s), so this workspace "
|
|
146
|
+
"opens without a GitHub login.",
|
|
147
|
+
e,
|
|
148
|
+
)
|
|
122
149
|
with contextlib.suppress(OSError):
|
|
123
150
|
os.unlink(path)
|
|
124
151
|
return None
|
|
@@ -7,6 +7,8 @@ from typing import Dict, Optional, Union
|
|
|
7
7
|
|
|
8
8
|
import tomli
|
|
9
9
|
|
|
10
|
+
from devlaunch.xdg import config_home
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
def _get_cache_base() -> Path:
|
|
12
14
|
"""Get the base cache directory, honoring XDG_CACHE_HOME."""
|
|
@@ -86,8 +88,7 @@ class WorktreeConfig:
|
|
|
86
88
|
|
|
87
89
|
def get_config_path() -> Path:
|
|
88
90
|
"""Get the path to the config file."""
|
|
89
|
-
|
|
90
|
-
return config_dir / "devlaunch" / "config.toml"
|
|
91
|
+
return config_home() / "devlaunch" / "config.toml"
|
|
91
92
|
|
|
92
93
|
|
|
93
94
|
def load_config() -> Dict:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Where the XDG base directories point on this machine.
|
|
2
|
+
|
|
3
|
+
Two unrelated places ask for the config home: the worktree loader, which reads
|
|
4
|
+
`config.toml` under it, and the gh-token warning, which names it so a user whose
|
|
5
|
+
shell scoped the variable can see why `gh auth token` refused. Those two have to
|
|
6
|
+
agree -- a warning that names one directory while the loader reads another is
|
|
7
|
+
worse than no warning -- so they share the answer rather than each spelling it.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def config_home() -> Path:
|
|
15
|
+
"""`$XDG_CONFIG_HOME`, or the `~/.config` the spec falls back to.
|
|
16
|
+
|
|
17
|
+
An empty value counts as unset, which is what the XDG basedir spec says and
|
|
18
|
+
what a shell exporting the variable with no value means. Reading it any other
|
|
19
|
+
way resolves the config path relative to the working directory instead.
|
|
20
|
+
"""
|
|
21
|
+
return Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "devlaunch"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.16"
|
|
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"
|
|
@@ -74,7 +74,7 @@ py312 = ["py312", "test"]
|
|
|
74
74
|
py313 = ["py313", "test"]
|
|
75
75
|
|
|
76
76
|
[tool.pixi.tasks]
|
|
77
|
-
dev-add-docker = "
|
|
77
|
+
dev-add-docker = "python -m devlaunch.devpod_provider docker"
|
|
78
78
|
dev = { cmd = "devpod up . --ide none && ssh pythontemplate.devpod", depends-on = ["dev-add-docker"] }
|
|
79
79
|
dev-vs = { cmd = "devpod up . --ide vscode", depends-on = ["dev-add-docker"] }
|
|
80
80
|
dev-restart = { cmd = "devpod up . --recreate --ide none && ssh pythontemplate.devpod", depends-on = ["dev-add-docker"] }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|