keepassxc-cli 1.2.0__tar.gz → 1.3.0__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.

Potentially problematic release.


This version of keepassxc-cli might be problematic. Click here for more details.

Files changed (38) hide show
  1. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/PKG-INFO +29 -2
  2. keepassxc_cli-1.2.0/keepassxc_cli.egg-info/PKG-INFO → keepassxc_cli-1.3.0/README.md +27 -15
  3. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/__main__.py +3 -1
  4. keepassxc_cli-1.3.0/keepassxc_cli/commands/group_uuid.py +58 -0
  5. keepassxc_cli-1.3.0/keepassxc_cli/commands/version.py +31 -0
  6. keepassxc_cli-1.2.0/README.md → keepassxc_cli-1.3.0/keepassxc_cli.egg-info/PKG-INFO +42 -0
  7. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/SOURCES.txt +2 -0
  8. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/requires.txt +1 -1
  9. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/pyproject.toml +1 -1
  10. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/tests/conftest.py +1 -0
  11. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/tests/test_commands.py +85 -1
  12. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/.github/workflows/auto-merge-dependabot.yml +0 -0
  13. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/.github/workflows/auto-release.yml +0 -0
  14. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/.github/workflows/lint_and_test.yml +0 -0
  15. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/.github/workflows/pypi.yml +0 -0
  16. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/.gitignore +0 -0
  17. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/CLAUDE.md +0 -0
  18. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/LICENSE +0 -0
  19. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/__init__.py +0 -0
  20. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/__init__.py +0 -0
  21. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/add.py +0 -0
  22. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/clip.py +0 -0
  23. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/edit.py +0 -0
  24. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/lock.py +0 -0
  25. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/mkdir.py +0 -0
  26. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/rm.py +0 -0
  27. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/setup.py +0 -0
  28. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/show.py +0 -0
  29. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/status.py +0 -0
  30. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/totp.py +0 -0
  31. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/config.py +0 -0
  32. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli/output.py +0 -0
  33. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/dependency_links.txt +0 -0
  34. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/entry_points.txt +0 -0
  35. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/top_level.txt +0 -0
  36. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/setup.cfg +0 -0
  37. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/tests/test_config.py +0 -0
  38. {keepassxc_cli-1.2.0 → keepassxc_cli-1.3.0}/tests/test_output.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keepassxc-cli
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: CLI for KeePassXC using the browser extension protocol with biometric unlock
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
- Requires-Dist: keepassxc-browser-api==1.1.0
9
+ Requires-Dist: keepassxc-browser-api==1.2.0
10
10
  Requires-Dist: pyperclip==1.8.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: pytest>=7.0; extra == "dev"
@@ -163,6 +163,33 @@ keepassxc-cli mkdir "Work/Projects" # create Projects inside Work
163
163
 
164
164
  Use `/`-separated paths to create nested groups. KeePassXC creates any missing path segments automatically.
165
165
 
166
+ #### `group-uuid` — Look up a group's UUID by path
167
+
168
+ ```bash
169
+ keepassxc-cli group-uuid "Work"
170
+ keepassxc-cli group-uuid "Work/Projects"
171
+ keepassxc-cli group-uuid "Work/Projects" -j
172
+ ```
173
+
174
+ Returns the UUID for the group at the given path (relative to the database root). Useful for scripting — pipe the UUID into `add --group-uuid`.
175
+
176
+ JSON output (`-j`):
177
+ ```json
178
+ {
179
+ "path": "Work/Projects",
180
+ "name": "Projects",
181
+ "uuid": "<uuid>"
182
+ }
183
+ ```
184
+
185
+ #### `version` — Show the CLI version
186
+
187
+ ```bash
188
+ keepassxc-cli version
189
+ ```
190
+
191
+ Does not require a running KeePassXC instance.
192
+
166
193
  ## Configuration
167
194
 
168
195
  ### CLI config (`~/.keepassxc/cli.json`)
@@ -1,18 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: keepassxc-cli
3
- Version: 1.2.0
4
- Summary: CLI for KeePassXC using the browser extension protocol with biometric unlock
5
- License-Expression: MIT
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Requires-Dist: keepassxc-browser-api==1.1.0
10
- Requires-Dist: pyperclip==1.8.0
11
- Provides-Extra: dev
12
- Requires-Dist: pytest>=7.0; extra == "dev"
13
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
14
- Dynamic: license-file
15
-
16
1
  # keepassxc-cli
17
2
 
18
3
  A command-line interface for [KeePassXC](https://keepassxc.org/) that communicates via the browser extension protocol, supporting biometric (TouchID/fingerprint) unlock on supported platforms.
@@ -163,6 +148,33 @@ keepassxc-cli mkdir "Work/Projects" # create Projects inside Work
163
148
 
164
149
  Use `/`-separated paths to create nested groups. KeePassXC creates any missing path segments automatically.
165
150
 
151
+ #### `group-uuid` — Look up a group's UUID by path
152
+
153
+ ```bash
154
+ keepassxc-cli group-uuid "Work"
155
+ keepassxc-cli group-uuid "Work/Projects"
156
+ keepassxc-cli group-uuid "Work/Projects" -j
157
+ ```
158
+
159
+ Returns the UUID for the group at the given path (relative to the database root). Useful for scripting — pipe the UUID into `add --group-uuid`.
160
+
161
+ JSON output (`-j`):
162
+ ```json
163
+ {
164
+ "path": "Work/Projects",
165
+ "name": "Projects",
166
+ "uuid": "<uuid>"
167
+ }
168
+ ```
169
+
170
+ #### `version` — Show the CLI version
171
+
172
+ ```bash
173
+ keepassxc-cli version
174
+ ```
175
+
176
+ Does not require a running KeePassXC instance.
177
+
166
178
  ## Configuration
167
179
 
168
180
  ### CLI config (`~/.keepassxc/cli.json`)
@@ -11,7 +11,7 @@ from keepassxc_browser_api import BrowserClient, BrowserConfig
11
11
  from keepassxc_browser_api.exceptions import KeePassXCError, ConnectionError
12
12
 
13
13
  from .config import CliConfig, DEFAULT_CLI_CONFIG_PATH
14
- from .commands import setup, status, show, add, edit, rm, totp, clip, lock, mkdir
14
+ from .commands import setup, status, show, add, edit, rm, totp, clip, lock, mkdir, group_uuid, version
15
15
 
16
16
  # Shared parent parser that injects -j/--json into each subparser that supports it.
17
17
  # Defined at module level so command modules can import it if needed.
@@ -55,6 +55,8 @@ def main() -> None:
55
55
  clip.add_parser(subparsers)
56
56
  lock.add_parser(subparsers)
57
57
  mkdir.add_parser(subparsers)
58
+ group_uuid.add_parser(subparsers, fmt_parent)
59
+ version.add_parser(subparsers)
58
60
 
59
61
  args = parser.parse_args()
60
62
 
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from keepassxc_browser_api import BrowserClient, BrowserConfig
9
+
10
+ from keepassxc_cli.config import CliConfig
11
+
12
+
13
+ def add_parser(subparsers: argparse._SubParsersAction, fmt_parent: argparse.ArgumentParser | None = None) -> None:
14
+ parents = [fmt_parent] if fmt_parent else []
15
+ p = subparsers.add_parser(
16
+ "group-uuid",
17
+ parents=parents,
18
+ help="Look up the UUID of a group by its path",
19
+ )
20
+ p.add_argument(
21
+ "path",
22
+ help="Group path relative to the database root (e.g. 'Work/Projects')",
23
+ )
24
+ p.set_defaults(func=run)
25
+
26
+
27
+ def run(
28
+ client: BrowserClient,
29
+ args: argparse.Namespace,
30
+ cli_config: CliConfig,
31
+ browser_config: BrowserConfig,
32
+ browser_config_path: Path,
33
+ *,
34
+ fmt: str = "table",
35
+ ) -> int:
36
+ groups = client.get_database_groups()
37
+ if not groups:
38
+ print("Failed to retrieve group tree.", file=sys.stderr)
39
+ return 1
40
+
41
+ root = groups[0]
42
+ parts = args.path.split("/")
43
+
44
+ # Paths are root-relative: traverse root.children, not the root itself.
45
+ current = root.children
46
+ matched = None
47
+ for part in parts:
48
+ matched = next((g for g in current if g.name == part), None)
49
+ if matched is None:
50
+ print(f"Group not found: {args.path!r}", file=sys.stderr)
51
+ return 1
52
+ current = matched.children
53
+
54
+ if fmt == "json":
55
+ print(json.dumps({"path": args.path, "name": matched.name, "uuid": matched.uuid}, indent=2))
56
+ else:
57
+ print(f"{args.path} [{matched.uuid}]")
58
+ return 0
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from importlib.metadata import PackageNotFoundError, version
5
+ from pathlib import Path
6
+
7
+ from keepassxc_browser_api import BrowserClient, BrowserConfig
8
+
9
+ from keepassxc_cli.config import CliConfig
10
+
11
+
12
+ def add_parser(subparsers: argparse._SubParsersAction) -> None:
13
+ p = subparsers.add_parser("version", help="Show the keepassxc-cli version")
14
+ p.set_defaults(func=run)
15
+
16
+
17
+ def run(
18
+ client: BrowserClient,
19
+ args: argparse.Namespace,
20
+ cli_config: CliConfig,
21
+ browser_config: BrowserConfig,
22
+ browser_config_path: Path,
23
+ *,
24
+ fmt: str = "table",
25
+ ) -> int:
26
+ try:
27
+ ver = version("keepassxc-cli")
28
+ except PackageNotFoundError:
29
+ ver = "unknown"
30
+ print(f"keepassxc-cli {ver}")
31
+ return 0
@@ -1,3 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: keepassxc-cli
3
+ Version: 1.3.0
4
+ Summary: CLI for KeePassXC using the browser extension protocol with biometric unlock
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: keepassxc-browser-api==1.2.0
10
+ Requires-Dist: pyperclip==1.8.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
14
+ Dynamic: license-file
15
+
1
16
  # keepassxc-cli
2
17
 
3
18
  A command-line interface for [KeePassXC](https://keepassxc.org/) that communicates via the browser extension protocol, supporting biometric (TouchID/fingerprint) unlock on supported platforms.
@@ -148,6 +163,33 @@ keepassxc-cli mkdir "Work/Projects" # create Projects inside Work
148
163
 
149
164
  Use `/`-separated paths to create nested groups. KeePassXC creates any missing path segments automatically.
150
165
 
166
+ #### `group-uuid` — Look up a group's UUID by path
167
+
168
+ ```bash
169
+ keepassxc-cli group-uuid "Work"
170
+ keepassxc-cli group-uuid "Work/Projects"
171
+ keepassxc-cli group-uuid "Work/Projects" -j
172
+ ```
173
+
174
+ Returns the UUID for the group at the given path (relative to the database root). Useful for scripting — pipe the UUID into `add --group-uuid`.
175
+
176
+ JSON output (`-j`):
177
+ ```json
178
+ {
179
+ "path": "Work/Projects",
180
+ "name": "Projects",
181
+ "uuid": "<uuid>"
182
+ }
183
+ ```
184
+
185
+ #### `version` — Show the CLI version
186
+
187
+ ```bash
188
+ keepassxc-cli version
189
+ ```
190
+
191
+ Does not require a running KeePassXC instance.
192
+
151
193
  ## Configuration
152
194
 
153
195
  ### CLI config (`~/.keepassxc/cli.json`)
@@ -21,6 +21,7 @@ keepassxc_cli/commands/__init__.py
21
21
  keepassxc_cli/commands/add.py
22
22
  keepassxc_cli/commands/clip.py
23
23
  keepassxc_cli/commands/edit.py
24
+ keepassxc_cli/commands/group_uuid.py
24
25
  keepassxc_cli/commands/lock.py
25
26
  keepassxc_cli/commands/mkdir.py
26
27
  keepassxc_cli/commands/rm.py
@@ -28,6 +29,7 @@ keepassxc_cli/commands/setup.py
28
29
  keepassxc_cli/commands/show.py
29
30
  keepassxc_cli/commands/status.py
30
31
  keepassxc_cli/commands/totp.py
32
+ keepassxc_cli/commands/version.py
31
33
  tests/conftest.py
32
34
  tests/test_commands.py
33
35
  tests/test_config.py
@@ -1,4 +1,4 @@
1
- keepassxc-browser-api==1.1.0
1
+ keepassxc-browser-api==1.2.0
2
2
  pyperclip==1.8.0
3
3
 
4
4
  [dev]
@@ -10,7 +10,7 @@ readme = "README.md"
10
10
  requires-python = ">=3.10"
11
11
  license = "MIT"
12
12
  dependencies = [
13
- "keepassxc-browser-api==1.1.0",
13
+ "keepassxc-browser-api==1.2.0",
14
14
  "pyperclip==1.8.0",
15
15
  ]
16
16
 
@@ -71,6 +71,7 @@ def mock_client():
71
71
  client.get_logins.return_value = [make_entry()]
72
72
  client.set_login.return_value = True
73
73
  client.create_group.return_value = make_group(uuid="new-uuid", name="NewGroup")
74
+ client.get_database_groups.return_value = [make_group()]
74
75
  client.get_totp.return_value = "123456"
75
76
  client.delete_entry.return_value = True
76
77
  client.lock_database.return_value = True
@@ -10,7 +10,7 @@ import pytest
10
10
  from keepassxc_browser_api import Entry, BrowserConfig, Association
11
11
  from keepassxc_cli.config import CliConfig
12
12
  from keepassxc_cli.commands import (
13
- setup, status, show, add, edit, rm, totp, clip, lock, mkdir,
13
+ setup, status, show, add, edit, rm, totp, clip, lock, mkdir, group_uuid, version,
14
14
  )
15
15
 
16
16
 
@@ -41,6 +41,7 @@ def make_args(**kwargs) -> argparse.Namespace:
41
41
  "group_uuid": "",
42
42
  "uuid": "abcdef12-0000-0000-0000-000000000000",
43
43
  "name": "NewGroup",
44
+ "path": "Work",
44
45
  }
45
46
  defaults.update(kwargs)
46
47
  return argparse.Namespace(**defaults)
@@ -287,3 +288,86 @@ class TestMkdirCommand:
287
288
  args = make_args(name="MyGroup")
288
289
  rc = mkdir.run(mock_client, args, cli_config, browser_config, browser_config_path)
289
290
  assert rc == 1
291
+
292
+
293
+ # --- group-uuid ---
294
+
295
+
296
+ class TestGroupUuidCommand:
297
+ def _make_tree(self, mock_group):
298
+ from keepassxc_browser_api import Group
299
+ projects = mock_group(uuid="projects-uuid", name="Projects")
300
+ work = mock_group(uuid="work-uuid", name="Work", children=[projects])
301
+ root = mock_group(uuid="root-uuid", name="Root", children=[work])
302
+ return [root]
303
+
304
+ def test_found_top_level(self, mock_client, cli_config, browser_config, browser_config_path, capsys, mock_group):
305
+ mock_client.get_database_groups.return_value = self._make_tree(mock_group)
306
+ args = make_args(path="Work")
307
+ rc = group_uuid.run(mock_client, args, cli_config, browser_config, browser_config_path)
308
+ assert rc == 0
309
+ out = capsys.readouterr().out
310
+ assert "work-uuid" in out
311
+ assert "Work" in out
312
+
313
+ def test_found_nested_path(self, mock_client, cli_config, browser_config, browser_config_path, capsys, mock_group):
314
+ mock_client.get_database_groups.return_value = self._make_tree(mock_group)
315
+ args = make_args(path="Work/Projects")
316
+ rc = group_uuid.run(mock_client, args, cli_config, browser_config, browser_config_path)
317
+ assert rc == 0
318
+ out = capsys.readouterr().out
319
+ assert "projects-uuid" in out
320
+
321
+ def test_not_found(self, mock_client, cli_config, browser_config, browser_config_path, capsys, mock_group):
322
+ mock_client.get_database_groups.return_value = self._make_tree(mock_group)
323
+ args = make_args(path="Nonexistent")
324
+ rc = group_uuid.run(mock_client, args, cli_config, browser_config, browser_config_path)
325
+ assert rc == 1
326
+ err = capsys.readouterr().err
327
+ assert "not found" in err.lower()
328
+
329
+ def test_not_found_mid_path(self, mock_client, cli_config, browser_config, browser_config_path, capsys, mock_group):
330
+ mock_client.get_database_groups.return_value = self._make_tree(mock_group)
331
+ args = make_args(path="Work/Nope/Projects")
332
+ rc = group_uuid.run(mock_client, args, cli_config, browser_config, browser_config_path)
333
+ assert rc == 1
334
+
335
+ def test_json_output(self, mock_client, cli_config, browser_config, browser_config_path, capsys, mock_group):
336
+ import json
337
+ mock_client.get_database_groups.return_value = self._make_tree(mock_group)
338
+ args = make_args(path="Work/Projects")
339
+ rc = group_uuid.run(mock_client, args, cli_config, browser_config, browser_config_path, fmt="json")
340
+ assert rc == 0
341
+ data = json.loads(capsys.readouterr().out)
342
+ assert data["path"] == "Work/Projects"
343
+ assert data["name"] == "Projects"
344
+ assert data["uuid"] == "projects-uuid"
345
+
346
+ def test_get_database_groups_failure(self, mock_client, cli_config, browser_config, browser_config_path, capsys):
347
+ mock_client.get_database_groups.return_value = []
348
+ args = make_args(path="Work")
349
+ rc = group_uuid.run(mock_client, args, cli_config, browser_config, browser_config_path)
350
+ assert rc == 1
351
+
352
+
353
+ # --- version ---
354
+
355
+
356
+ class TestVersionCommand:
357
+ def test_prints_version(self, mock_client, cli_config, browser_config, browser_config_path, capsys):
358
+ with patch("keepassxc_cli.commands.version.version", return_value="1.3.0"):
359
+ args = make_args()
360
+ rc = version.run(mock_client, args, cli_config, browser_config, browser_config_path)
361
+ assert rc == 0
362
+ out = capsys.readouterr().out
363
+ assert "keepassxc-cli" in out
364
+ assert "1.3.0" in out
365
+
366
+ def test_package_not_found(self, mock_client, cli_config, browser_config, browser_config_path, capsys):
367
+ from importlib.metadata import PackageNotFoundError
368
+ with patch("keepassxc_cli.commands.version.version", side_effect=PackageNotFoundError):
369
+ args = make_args()
370
+ rc = version.run(mock_client, args, cli_config, browser_config, browser_config_path)
371
+ assert rc == 0
372
+ out = capsys.readouterr().out
373
+ assert "unknown" in out
File without changes
File without changes
File without changes
File without changes