launchline 0.1.0__tar.gz → 0.2.1.dev0__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.
- launchline-0.2.1.dev0/.generated-go-semantic-release-changelog.md +23 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.github/workflows/release.yml +8 -1
- {launchline-0.1.0 → launchline-0.2.1.dev0}/CHANGELOG.md +15 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/PKG-INFO +19 -12
- {launchline-0.1.0 → launchline-0.2.1.dev0}/README.md +18 -11
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/__main__.py +5 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/cli.py +6 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/config.py +17 -3
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tests/test_cli.py +60 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tests/test_config.py +57 -0
- launchline-0.1.0/.generated-go-semantic-release-changelog.md +0 -46
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.github/workflows/ci.yml +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.gitignore +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.markdownlint.json +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.python-version +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.semrel/linux_amd64/changelog-generator-default/1.15.0/changelog-generator-default_v1.15.0_linux_amd64 +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.semrel/linux_amd64/commit-analyzer-default/1.15.0/commit-analyzer-cz_v1.15.0_linux_amd64 +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.semrel/linux_amd64/condition-github/1.11.0/condition-github_v1.11.0_linux_amd64 +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.semrel/linux_amd64/files-updater-npm/1.11.0/files-updater-npm_v1.11.0_linux_amd64 +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/.semrel/linux_amd64/provider-github/1.19.0/provider-github_v1.19.0_linux_amd64 +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/LICENSE +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/assets/launchline.ico +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/assets/launchline.png +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/assets/launchline.svg +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/docs/images/demo.gif +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/docs/images/hero.png +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/docs/images/navigate.png +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/docs/images/search.png +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/pyproject.toml +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/__init__.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/assets/launchline.ico +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/exceptions.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/fuzzy.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/keys.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/runner.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/src/launchline/ui.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tests/__init__.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tests/conftest.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tests/test_cross_platform.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tests/test_runner.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tests/test_ui.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tools/generate_icon.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/tools/generate_screenshots.py +0 -0
- {launchline-0.1.0 → launchline-0.2.1.dev0}/uv.lock +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## 0.2.0 (2026-03-30)
|
|
2
|
+
|
|
3
|
+
#### Feature
|
|
4
|
+
|
|
5
|
+
* accept command as string or array in config (db1b1857)
|
|
6
|
+
* add --config-path flag to print active config file location (fe98125a)
|
|
7
|
+
|
|
8
|
+
#### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* apply ruff format to test_cli.py (45b9375b)
|
|
11
|
+
* capitalize docstrings to satisfy ruff D403 (8e51bff1)
|
|
12
|
+
* wrap long lines in test_cli.py to satisfy ruff E501 (9eef319e)
|
|
13
|
+
* use absolute URLs for images in README for PyPI rendering (7dc2878d)
|
|
14
|
+
* restrict release asset glob to dist archives only (827cbec5)
|
|
15
|
+
|
|
16
|
+
#### Documentation
|
|
17
|
+
|
|
18
|
+
* update changelog for v0.2.0 (557d5475)
|
|
19
|
+
|
|
20
|
+
#### Build
|
|
21
|
+
|
|
22
|
+
* auto-rewrite README image paths during release (90e2ec08)
|
|
23
|
+
|
|
@@ -34,6 +34,11 @@ jobs:
|
|
|
34
34
|
- uses: astral-sh/setup-uv@v6
|
|
35
35
|
- run: uv sync --group dev
|
|
36
36
|
- run: uv run pytest
|
|
37
|
+
|
|
38
|
+
- name: Rewrite README image paths for PyPI
|
|
39
|
+
run: |
|
|
40
|
+
sed -i "s|(docs/|(https://raw.githubusercontent.com/${{ github.repository }}/v${{ steps.semrel.outputs.version }}/docs/|g" README.md
|
|
41
|
+
|
|
37
42
|
- run: uv build
|
|
38
43
|
|
|
39
44
|
- uses: actions/upload-artifact@v4
|
|
@@ -49,7 +54,9 @@ jobs:
|
|
|
49
54
|
with:
|
|
50
55
|
tag_name: "v${{ steps.semrel.outputs.version }}"
|
|
51
56
|
generate_release_notes: true
|
|
52
|
-
files:
|
|
57
|
+
files: |
|
|
58
|
+
dist/*.tar.gz
|
|
59
|
+
dist/*.whl
|
|
53
60
|
|
|
54
61
|
pypi:
|
|
55
62
|
needs: release
|
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `command` config key now accepts a list of strings (e.g.,
|
|
13
|
+
`command = ["ssh", "-t", "my server"]`) for easier argument handling,
|
|
14
|
+
especially with values containing spaces
|
|
15
|
+
- `--config-path` CLI flag to print the active configuration file path
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- PyPI package page now renders README images correctly (paths are rewritten
|
|
20
|
+
to absolute URLs during the release build)
|
|
21
|
+
- Release artifacts no longer accidentally include non-distribution files
|
|
22
|
+
|
|
8
23
|
## [0.1.0] - 2026-03-30
|
|
9
24
|
|
|
10
25
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: launchline
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.2.1.dev0
|
|
4
4
|
Summary: A lightweight terminal launcher for interactive CLI tools.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mikejhill/launchline
|
|
6
6
|
Project-URL: Repository, https://github.com/mikejhill/launchline
|
|
@@ -29,7 +29,7 @@ A lightweight terminal launcher for interactive CLI tools. Define your frequentl
|
|
|
29
29
|
used commands in a TOML config file, then pick them from a fuzzy-searchable menu
|
|
30
30
|
instead of remembering flags and paths.
|
|
31
31
|
|
|
32
|
-

|
|
32
|
+

|
|
33
33
|
|
|
34
34
|
## Why
|
|
35
35
|
|
|
@@ -50,7 +50,7 @@ instead.
|
|
|
50
50
|
|
|
51
51
|
| Fuzzy search | Arrow-key navigation |
|
|
52
52
|
| :-------------------------------: | :-----------------------------------: |
|
|
53
|
-
|  |  |
|
|
53
|
+
|  |  |
|
|
54
54
|
|
|
55
55
|
## Installation
|
|
56
56
|
|
|
@@ -123,21 +123,24 @@ Config file format is [TOML](https://toml.io). The file has an optional
|
|
|
123
123
|
|
|
124
124
|
Each `[[entries]]` table defines one launchable tool:
|
|
125
125
|
|
|
126
|
-
| Key | Type
|
|
127
|
-
| ------------------- |
|
|
128
|
-
| `name` | string
|
|
129
|
-
| `command` | string
|
|
130
|
-
| `args` | list of strings
|
|
131
|
-
| `description` | string
|
|
132
|
-
| `working_directory` | string
|
|
133
|
-
| `env` | table
|
|
126
|
+
| Key | Type | Required | Default | Description |
|
|
127
|
+
| ------------------- | ------------------------- | -------- | ------- | -------------------------------------------------------------- |
|
|
128
|
+
| `name` | string | yes | — | Display name shown in the menu |
|
|
129
|
+
| `command` | string or list of strings | yes | — | Executable to run (with optional inline arguments) |
|
|
130
|
+
| `args` | list of strings | no | `[]` | Additional arguments appended after any inline command args |
|
|
131
|
+
| `description` | string | no | `""` | Short description shown next to the name |
|
|
132
|
+
| `working_directory` | string | no | — | Working directory for the subprocess |
|
|
133
|
+
| `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
|
|
134
134
|
|
|
135
135
|
### Validation Rules
|
|
136
136
|
|
|
137
137
|
- At least one `[[entries]]` table is required.
|
|
138
138
|
- Every entry must have both `name` and `command`.
|
|
139
|
+
- `command` may be a string (`"pwsh"`) or a list (`["ssh", "-t", "host"]`).
|
|
140
|
+
When a list, the first element is the executable and the rest are arguments.
|
|
139
141
|
- `on_exit` must be `"restart"` or `"exit"`.
|
|
140
|
-
- `args` must be a list (not a bare string).
|
|
142
|
+
- `args` must be a list (not a bare string). These are appended after any
|
|
143
|
+
arguments provided inline in a `command` list.
|
|
141
144
|
- `env` must be a TOML table (not a string or list).
|
|
142
145
|
- If `working_directory` does not exist at load time, it is silently reset to
|
|
143
146
|
`None` (a warning is logged).
|
|
@@ -168,6 +171,10 @@ name = "PowerShell"
|
|
|
168
171
|
command = "pwsh"
|
|
169
172
|
args = ["-NoProfile"]
|
|
170
173
|
env = { TERM = "xterm-256color" }
|
|
174
|
+
|
|
175
|
+
[[entries]]
|
|
176
|
+
name = "Remote Server"
|
|
177
|
+
command = ["ssh", "-t", "my server", "bash"]
|
|
171
178
|
```
|
|
172
179
|
|
|
173
180
|
## Usage
|
|
@@ -7,7 +7,7 @@ A lightweight terminal launcher for interactive CLI tools. Define your frequentl
|
|
|
7
7
|
used commands in a TOML config file, then pick them from a fuzzy-searchable menu
|
|
8
8
|
instead of remembering flags and paths.
|
|
9
9
|
|
|
10
|
-

|
|
10
|
+

|
|
11
11
|
|
|
12
12
|
## Why
|
|
13
13
|
|
|
@@ -28,7 +28,7 @@ instead.
|
|
|
28
28
|
|
|
29
29
|
| Fuzzy search | Arrow-key navigation |
|
|
30
30
|
| :-------------------------------: | :-----------------------------------: |
|
|
31
|
-
|  |  |
|
|
31
|
+
|  |  |
|
|
32
32
|
|
|
33
33
|
## Installation
|
|
34
34
|
|
|
@@ -101,21 +101,24 @@ Config file format is [TOML](https://toml.io). The file has an optional
|
|
|
101
101
|
|
|
102
102
|
Each `[[entries]]` table defines one launchable tool:
|
|
103
103
|
|
|
104
|
-
| Key | Type
|
|
105
|
-
| ------------------- |
|
|
106
|
-
| `name` | string
|
|
107
|
-
| `command` | string
|
|
108
|
-
| `args` | list of strings
|
|
109
|
-
| `description` | string
|
|
110
|
-
| `working_directory` | string
|
|
111
|
-
| `env` | table
|
|
104
|
+
| Key | Type | Required | Default | Description |
|
|
105
|
+
| ------------------- | ------------------------- | -------- | ------- | -------------------------------------------------------------- |
|
|
106
|
+
| `name` | string | yes | — | Display name shown in the menu |
|
|
107
|
+
| `command` | string or list of strings | yes | — | Executable to run (with optional inline arguments) |
|
|
108
|
+
| `args` | list of strings | no | `[]` | Additional arguments appended after any inline command args |
|
|
109
|
+
| `description` | string | no | `""` | Short description shown next to the name |
|
|
110
|
+
| `working_directory` | string | no | — | Working directory for the subprocess |
|
|
111
|
+
| `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
|
|
112
112
|
|
|
113
113
|
### Validation Rules
|
|
114
114
|
|
|
115
115
|
- At least one `[[entries]]` table is required.
|
|
116
116
|
- Every entry must have both `name` and `command`.
|
|
117
|
+
- `command` may be a string (`"pwsh"`) or a list (`["ssh", "-t", "host"]`).
|
|
118
|
+
When a list, the first element is the executable and the rest are arguments.
|
|
117
119
|
- `on_exit` must be `"restart"` or `"exit"`.
|
|
118
|
-
- `args` must be a list (not a bare string).
|
|
120
|
+
- `args` must be a list (not a bare string). These are appended after any
|
|
121
|
+
arguments provided inline in a `command` list.
|
|
119
122
|
- `env` must be a TOML table (not a string or list).
|
|
120
123
|
- If `working_directory` does not exist at load time, it is silently reset to
|
|
121
124
|
`None` (a warning is logged).
|
|
@@ -146,6 +149,10 @@ name = "PowerShell"
|
|
|
146
149
|
command = "pwsh"
|
|
147
150
|
args = ["-NoProfile"]
|
|
148
151
|
env = { TERM = "xterm-256color" }
|
|
152
|
+
|
|
153
|
+
[[entries]]
|
|
154
|
+
name = "Remote Server"
|
|
155
|
+
command = ["ssh", "-t", "my server", "bash"]
|
|
149
156
|
```
|
|
150
157
|
|
|
151
158
|
## Usage
|
|
@@ -80,6 +80,11 @@ def main() -> None:
|
|
|
80
80
|
print(CommandLineInterface.icon_path())
|
|
81
81
|
return
|
|
82
82
|
|
|
83
|
+
if args.config_path:
|
|
84
|
+
config_path = ConfigLoader.resolve_path(args.config)
|
|
85
|
+
print(config_path)
|
|
86
|
+
return
|
|
87
|
+
|
|
83
88
|
logging.basicConfig(
|
|
84
89
|
level=args.log_level,
|
|
85
90
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
@@ -64,6 +64,12 @@ class CommandLineInterface:
|
|
|
64
64
|
default=False,
|
|
65
65
|
help="Print the path to the bundled icon and exit.",
|
|
66
66
|
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--config-path",
|
|
69
|
+
action="store_true",
|
|
70
|
+
default=False,
|
|
71
|
+
help="Print the path to the active config file and exit.",
|
|
72
|
+
)
|
|
67
73
|
|
|
68
74
|
args = parser.parse_args(argv)
|
|
69
75
|
args.log_level = getattr(logging, args.log_level)
|
|
@@ -188,7 +188,19 @@ description = "PowerShell 7"
|
|
|
188
188
|
entries: list[EntryConfig] = []
|
|
189
189
|
for i, entry_raw in enumerate(raw_entries, 1):
|
|
190
190
|
name = entry_raw.get("name", "")
|
|
191
|
-
|
|
191
|
+
raw_command = entry_raw.get("command", "")
|
|
192
|
+
|
|
193
|
+
if isinstance(raw_command, list):
|
|
194
|
+
if not raw_command:
|
|
195
|
+
raise ConfigurationError(
|
|
196
|
+
f"Entry {i}: 'command' list must not be empty."
|
|
197
|
+
)
|
|
198
|
+
command = str(raw_command[0])
|
|
199
|
+
command_args = tuple(str(a) for a in raw_command[1:])
|
|
200
|
+
else:
|
|
201
|
+
command = str(raw_command)
|
|
202
|
+
command_args = ()
|
|
203
|
+
|
|
192
204
|
if not name or not command:
|
|
193
205
|
raise ConfigurationError(
|
|
194
206
|
f"Entry {i}: 'name' and 'command' are required."
|
|
@@ -200,6 +212,8 @@ description = "PowerShell 7"
|
|
|
200
212
|
f"Entry {i}: 'args' must be a list of strings."
|
|
201
213
|
)
|
|
202
214
|
|
|
215
|
+
combined_args = command_args + tuple(str(a) for a in raw_args)
|
|
216
|
+
|
|
203
217
|
wd_raw = entry_raw.get("working_directory")
|
|
204
218
|
wd: Path | None = None
|
|
205
219
|
if wd_raw:
|
|
@@ -221,8 +235,8 @@ description = "PowerShell 7"
|
|
|
221
235
|
entries.append(
|
|
222
236
|
EntryConfig(
|
|
223
237
|
name=str(name),
|
|
224
|
-
command=
|
|
225
|
-
args=
|
|
238
|
+
command=command,
|
|
239
|
+
args=combined_args,
|
|
226
240
|
description=str(entry_raw.get("description", "")),
|
|
227
241
|
working_directory=wd,
|
|
228
242
|
env={str(k): str(v) for k, v in raw_env.items()},
|
|
@@ -99,6 +99,20 @@ class TestParseArgs:
|
|
|
99
99
|
f"Expected icon_path=False by default, got {args.icon_path!r}"
|
|
100
100
|
)
|
|
101
101
|
|
|
102
|
+
def test_config_path_flag_parsed(self) -> None:
|
|
103
|
+
"""--config-path flag is stored as boolean True."""
|
|
104
|
+
args = CommandLineInterface.parse_args(["--config-path"])
|
|
105
|
+
assert args.config_path is True, (
|
|
106
|
+
f"Expected config_path=True, got {args.config_path!r}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def test_config_path_defaults_to_false(self) -> None:
|
|
110
|
+
"""config_path defaults to False when not specified."""
|
|
111
|
+
args = CommandLineInterface.parse_args([])
|
|
112
|
+
assert args.config_path is False, (
|
|
113
|
+
f"Expected config_path=False by default, got {args.config_path!r}"
|
|
114
|
+
)
|
|
115
|
+
|
|
102
116
|
|
|
103
117
|
class TestIconPath:
|
|
104
118
|
"""Tests for CommandLineInterface.icon_path."""
|
|
@@ -108,3 +122,49 @@ class TestIconPath:
|
|
|
108
122
|
path = CommandLineInterface.icon_path()
|
|
109
123
|
assert path.exists(), f"Bundled icon not found at {path}"
|
|
110
124
|
assert path.suffix == ".ico", f"Expected .ico file, got {path.suffix}"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestConfigPathFlag:
|
|
128
|
+
"""Tests for --config-path flag behaviour via main()."""
|
|
129
|
+
|
|
130
|
+
def test_prints_explicit_config_path(
|
|
131
|
+
self,
|
|
132
|
+
tmp_path: Path,
|
|
133
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
134
|
+
capsys: pytest.CaptureFixture[str],
|
|
135
|
+
) -> None:
|
|
136
|
+
"""--config-path with --config prints the explicit path."""
|
|
137
|
+
cfg = tmp_path / "custom.toml"
|
|
138
|
+
cfg.write_text("[settings]\n[[entries]]\nname='x'\ncommand='y'\n")
|
|
139
|
+
|
|
140
|
+
from launchline.__main__ import main
|
|
141
|
+
|
|
142
|
+
monkeypatch.setattr(
|
|
143
|
+
"sys.argv",
|
|
144
|
+
["launchline", "--config-path", "--config", str(cfg)],
|
|
145
|
+
)
|
|
146
|
+
main()
|
|
147
|
+
assert capsys.readouterr().out.strip() == str(cfg), (
|
|
148
|
+
"Expected --config-path to print the explicit config file path"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def test_prints_default_config_path(
|
|
152
|
+
self,
|
|
153
|
+
tmp_path: Path,
|
|
154
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
155
|
+
capsys: pytest.CaptureFixture[str],
|
|
156
|
+
) -> None:
|
|
157
|
+
"""--config-path without --config resolves via standard lookup."""
|
|
158
|
+
cfg = tmp_path / "config.toml"
|
|
159
|
+
cfg.write_text("[settings]\n[[entries]]\nname='x'\ncommand='y'\n")
|
|
160
|
+
|
|
161
|
+
from launchline.__main__ import main
|
|
162
|
+
from launchline.config import ConfigLoader
|
|
163
|
+
|
|
164
|
+
monkeypatch.setattr(ConfigLoader, "DEFAULT_PATH", cfg)
|
|
165
|
+
monkeypatch.setattr("sys.argv", ["launchline", "--config-path"])
|
|
166
|
+
monkeypatch.delenv("LAUNCHLINE_CONFIG", raising=False)
|
|
167
|
+
main()
|
|
168
|
+
assert capsys.readouterr().out.strip() == str(cfg), (
|
|
169
|
+
"Expected --config-path to print the default config file path"
|
|
170
|
+
)
|
|
@@ -152,6 +152,63 @@ class TestLoadConfig:
|
|
|
152
152
|
with pytest.raises(ConfigurationError, match="'args' must be a list"):
|
|
153
153
|
ConfigLoader.load(cfg)
|
|
154
154
|
|
|
155
|
+
def test_command_as_array(self, tmp_path: Path) -> None:
|
|
156
|
+
"""Command as list splits into executable + args."""
|
|
157
|
+
cfg = tmp_path / "config.toml"
|
|
158
|
+
cfg.write_text(
|
|
159
|
+
'[[entries]]\nname = "X"\ncommand = ["ssh", "-t", "my server"]\n',
|
|
160
|
+
encoding="utf-8",
|
|
161
|
+
)
|
|
162
|
+
config = ConfigLoader.load(cfg)
|
|
163
|
+
assert config.entries[0].command == "ssh", (
|
|
164
|
+
f"Expected command='ssh', got {config.entries[0].command!r}"
|
|
165
|
+
)
|
|
166
|
+
assert config.entries[0].args == ("-t", "my server"), (
|
|
167
|
+
f"Expected args=('-t', 'my server'), got {config.entries[0].args!r}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def test_command_array_with_extra_args(self, tmp_path: Path) -> None:
|
|
171
|
+
"""Command array args are prepended before explicit args."""
|
|
172
|
+
cfg = tmp_path / "config.toml"
|
|
173
|
+
cfg.write_text(
|
|
174
|
+
'[[entries]]\nname = "X"\n'
|
|
175
|
+
'command = ["ssh", "-t"]\n'
|
|
176
|
+
'args = ["my server", "bash"]\n',
|
|
177
|
+
encoding="utf-8",
|
|
178
|
+
)
|
|
179
|
+
config = ConfigLoader.load(cfg)
|
|
180
|
+
assert config.entries[0].command == "ssh", (
|
|
181
|
+
f"Expected command='ssh', got {config.entries[0].command!r}"
|
|
182
|
+
)
|
|
183
|
+
assert config.entries[0].args == ("-t", "my server", "bash"), (
|
|
184
|
+
f"Expected combined args, got {config.entries[0].args!r}"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def test_command_array_single_element(self, tmp_path: Path) -> None:
|
|
188
|
+
"""Single-element command array is valid."""
|
|
189
|
+
cfg = tmp_path / "config.toml"
|
|
190
|
+
cfg.write_text(
|
|
191
|
+
'[[entries]]\nname = "X"\ncommand = ["pwsh"]\n',
|
|
192
|
+
encoding="utf-8",
|
|
193
|
+
)
|
|
194
|
+
config = ConfigLoader.load(cfg)
|
|
195
|
+
assert config.entries[0].command == "pwsh", (
|
|
196
|
+
f"Expected command='pwsh', got {config.entries[0].command!r}"
|
|
197
|
+
)
|
|
198
|
+
assert config.entries[0].args == (), (
|
|
199
|
+
f"Expected empty args, got {config.entries[0].args!r}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def test_command_empty_array_rejected(self, tmp_path: Path) -> None:
|
|
203
|
+
"""Empty command array is an error."""
|
|
204
|
+
cfg = tmp_path / "config.toml"
|
|
205
|
+
cfg.write_text(
|
|
206
|
+
'[[entries]]\nname = "X"\ncommand = []\n',
|
|
207
|
+
encoding="utf-8",
|
|
208
|
+
)
|
|
209
|
+
with pytest.raises(ConfigurationError, match="must not be empty"):
|
|
210
|
+
ConfigLoader.load(cfg)
|
|
211
|
+
|
|
155
212
|
def test_rejects_invalid_env_type(self, tmp_path: Path) -> None:
|
|
156
213
|
cfg = tmp_path / "config.toml"
|
|
157
214
|
cfg.write_text(
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
## 0.1.0 (2026-03-30)
|
|
2
|
-
|
|
3
|
-
#### Feature
|
|
4
|
-
|
|
5
|
-
* replace entry icon with title_prefix and bundle icon in package (10583207)
|
|
6
|
-
* add icon assets, entry icon config, fix URLs and changelog (b29358bb)
|
|
7
|
-
* enable ghost_text by default, restore original screenshots (470ff45e)
|
|
8
|
-
* add ghost_text and instant_numeric_launch feature flags, cross-platform classifier (b1d412c4)
|
|
9
|
-
* initial LaunchLine implementation (df1e8602)
|
|
10
|
-
|
|
11
|
-
#### Bug Fixes
|
|
12
|
-
|
|
13
|
-
* sync icon generator gray color with latest SVG (#94A3B8) (1aae2dc7)
|
|
14
|
-
* regenerate icons with proper rendering, add generate_icon tool (4e3410b1)
|
|
15
|
-
* make tests and mypy cross-platform for Linux CI (cd5accfa)
|
|
16
|
-
|
|
17
|
-
#### Documentation
|
|
18
|
-
|
|
19
|
-
* add screenshot/GIF generation tool and README images (4bc81d70)
|
|
20
|
-
* fix markdown table alignment for MD060 (59b5ab59)
|
|
21
|
-
* update AI disclaimer to info block with simplified text (89de06d1)
|
|
22
|
-
* add workflow status badges to README (ff5890e0)
|
|
23
|
-
* add AI-generated project disclaimer to README (49ba23c6)
|
|
24
|
-
|
|
25
|
-
#### Tests
|
|
26
|
-
|
|
27
|
-
* add CLI and cross-platform tests, improve suite quality (3dc2fd6e)
|
|
28
|
-
|
|
29
|
-
#### Code Refactoring
|
|
30
|
-
|
|
31
|
-
* remove title_prefix feature from entry configuration (b2fcce7b)
|
|
32
|
-
* rename instant_numeric_launch to numeric_trigger, reorder settings (898cf209)
|
|
33
|
-
* improve source documentation and organize code into classes (8dedef8c)
|
|
34
|
-
|
|
35
|
-
#### Chores
|
|
36
|
-
|
|
37
|
-
* set v0.1.0 release date (a70d982b)
|
|
38
|
-
* add pytest-cov and enforce 85% coverage threshold (dc5616b5)
|
|
39
|
-
* add PyPI publish job to release workflow (92df1a40)
|
|
40
|
-
* add MIT license (1cc39ec2)
|
|
41
|
-
|
|
42
|
-
#### Build
|
|
43
|
-
|
|
44
|
-
* consolidate release into single workflow with dry-run safety (22314e02)
|
|
45
|
-
* switch to hatch-vcs for dynamic version from git tags (f03de3d3)
|
|
46
|
-
|
|
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
|
|
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
|