keepassxc-cli 1.1.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.
Files changed (38) hide show
  1. {keepassxc_cli-1.1.0/keepassxc_cli.egg-info → keepassxc_cli-1.3.0}/PKG-INFO +37 -4
  2. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/README.md +35 -2
  3. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/__main__.py +3 -1
  4. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/add.py +0 -2
  5. keepassxc_cli-1.3.0/keepassxc_cli/commands/group_uuid.py +58 -0
  6. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/mkdir.py +5 -3
  7. keepassxc_cli-1.3.0/keepassxc_cli/commands/version.py +31 -0
  8. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0/keepassxc_cli.egg-info}/PKG-INFO +37 -4
  9. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/SOURCES.txt +2 -0
  10. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/requires.txt +1 -1
  11. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/pyproject.toml +1 -1
  12. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/tests/conftest.py +1 -0
  13. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/tests/test_commands.py +97 -7
  14. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/auto-merge-dependabot.yml +0 -0
  15. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/auto-release.yml +0 -0
  16. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/lint_and_test.yml +0 -0
  17. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/pypi.yml +0 -0
  18. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.gitignore +0 -0
  19. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/CLAUDE.md +0 -0
  20. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/LICENSE +0 -0
  21. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/__init__.py +0 -0
  22. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/__init__.py +0 -0
  23. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/clip.py +0 -0
  24. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/edit.py +0 -0
  25. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/lock.py +0 -0
  26. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/rm.py +0 -0
  27. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/setup.py +0 -0
  28. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/show.py +0 -0
  29. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/status.py +0 -0
  30. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/totp.py +0 -0
  31. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/config.py +0 -0
  32. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/output.py +0 -0
  33. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/dependency_links.txt +0 -0
  34. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/entry_points.txt +0 -0
  35. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/top_level.txt +0 -0
  36. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/setup.cfg +0 -0
  37. {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/tests/test_config.py +0 -0
  38. {keepassxc_cli-1.1.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.1.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.0.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"
@@ -121,11 +121,15 @@ keepassxc-cli clip totp https://github.com
121
121
  #### `add` — Add a new entry
122
122
 
123
123
  ```bash
124
- keepassxc-cli add --url https://example.com --username user@example.com --title "Example"
125
124
  # Password is prompted securely if --password is not given
125
+ keepassxc-cli add --url https://example.com --username user@example.com
126
126
  keepassxc-cli add --url https://example.com --username user --password mypass
127
+ # Place the entry in a specific group by UUID
128
+ keepassxc-cli add --url https://example.com --username user --group-uuid <group-uuid>
127
129
  ```
128
130
 
131
+ > **Note**: The entry title is always derived from the URL hostname by KeePassXC. The protocol has no field to set a custom title.
132
+
129
133
  #### `edit` — Edit an entry
130
134
 
131
135
  ```bash
@@ -154,9 +158,38 @@ keepassxc-cli lock
154
158
 
155
159
  ```bash
156
160
  keepassxc-cli mkdir "Work"
157
- keepassxc-cli mkdir "Projects" --parent-uuid <parent-group-uuid>
161
+ keepassxc-cli mkdir "Work/Projects" # create Projects inside Work
158
162
  ```
159
163
 
164
+ Use `/`-separated paths to create nested groups. KeePassXC creates any missing path segments automatically.
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
+
160
193
  ## Configuration
161
194
 
162
195
  ### CLI config (`~/.keepassxc/cli.json`)
@@ -106,11 +106,15 @@ keepassxc-cli clip totp https://github.com
106
106
  #### `add` — Add a new entry
107
107
 
108
108
  ```bash
109
- keepassxc-cli add --url https://example.com --username user@example.com --title "Example"
110
109
  # Password is prompted securely if --password is not given
110
+ keepassxc-cli add --url https://example.com --username user@example.com
111
111
  keepassxc-cli add --url https://example.com --username user --password mypass
112
+ # Place the entry in a specific group by UUID
113
+ keepassxc-cli add --url https://example.com --username user --group-uuid <group-uuid>
112
114
  ```
113
115
 
116
+ > **Note**: The entry title is always derived from the URL hostname by KeePassXC. The protocol has no field to set a custom title.
117
+
114
118
  #### `edit` — Edit an entry
115
119
 
116
120
  ```bash
@@ -139,9 +143,38 @@ keepassxc-cli lock
139
143
 
140
144
  ```bash
141
145
  keepassxc-cli mkdir "Work"
142
- keepassxc-cli mkdir "Projects" --parent-uuid <parent-group-uuid>
146
+ keepassxc-cli mkdir "Work/Projects" # create Projects inside Work
143
147
  ```
144
148
 
149
+ Use `/`-separated paths to create nested groups. KeePassXC creates any missing path segments automatically.
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
+
145
178
  ## Configuration
146
179
 
147
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
 
@@ -15,7 +15,6 @@ def add_parser(subparsers: argparse._SubParsersAction) -> None:
15
15
  p.add_argument("--url", required=True, help="Entry URL")
16
16
  p.add_argument("--username", required=True, help="Username")
17
17
  p.add_argument("--password", default=None, help="Password (prompted if omitted)")
18
- p.add_argument("--title", default="", help="Entry title")
19
18
  p.add_argument("--group-uuid", default="", help="Target group UUID")
20
19
  p.set_defaults(func=run)
21
20
 
@@ -37,7 +36,6 @@ def run(
37
36
  url=args.url,
38
37
  username=args.username,
39
38
  password=password,
40
- title=args.title,
41
39
  group_uuid=args.group_uuid,
42
40
  )
43
41
  if success:
@@ -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
@@ -11,8 +11,10 @@ from keepassxc_cli.config import CliConfig
11
11
 
12
12
  def add_parser(subparsers: argparse._SubParsersAction) -> None:
13
13
  p = subparsers.add_parser("mkdir", help="Create a new group")
14
- p.add_argument("name", help="Group name")
15
- p.add_argument("--parent-uuid", default="", help="Parent group UUID")
14
+ p.add_argument(
15
+ "name",
16
+ help="Group name or path. Use '/' to create nested groups (e.g. 'Work/Projects').",
17
+ )
16
18
  p.set_defaults(func=run)
17
19
 
18
20
 
@@ -25,7 +27,7 @@ def run(
25
27
  *,
26
28
  fmt: str = "table",
27
29
  ) -> int:
28
- group = client.create_group(args.name, parent_group_uuid=args.parent_uuid)
30
+ group = client.create_group(args.name)
29
31
  if group is None:
30
32
  print("Failed to create group.", file=sys.stderr)
31
33
  return 1
@@ -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,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keepassxc-cli
3
- Version: 1.1.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.0.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"
@@ -121,11 +121,15 @@ keepassxc-cli clip totp https://github.com
121
121
  #### `add` — Add a new entry
122
122
 
123
123
  ```bash
124
- keepassxc-cli add --url https://example.com --username user@example.com --title "Example"
125
124
  # Password is prompted securely if --password is not given
125
+ keepassxc-cli add --url https://example.com --username user@example.com
126
126
  keepassxc-cli add --url https://example.com --username user --password mypass
127
+ # Place the entry in a specific group by UUID
128
+ keepassxc-cli add --url https://example.com --username user --group-uuid <group-uuid>
127
129
  ```
128
130
 
131
+ > **Note**: The entry title is always derived from the URL hostname by KeePassXC. The protocol has no field to set a custom title.
132
+
129
133
  #### `edit` — Edit an entry
130
134
 
131
135
  ```bash
@@ -154,9 +158,38 @@ keepassxc-cli lock
154
158
 
155
159
  ```bash
156
160
  keepassxc-cli mkdir "Work"
157
- keepassxc-cli mkdir "Projects" --parent-uuid <parent-group-uuid>
161
+ keepassxc-cli mkdir "Work/Projects" # create Projects inside Work
158
162
  ```
159
163
 
164
+ Use `/`-separated paths to create nested groups. KeePassXC creates any missing path segments automatically.
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
+
160
193
  ## Configuration
161
194
 
162
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.0.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.0.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
 
@@ -38,11 +38,10 @@ def make_args(**kwargs) -> argparse.Namespace:
38
38
  "url": "https://example.com",
39
39
  "username": "user",
40
40
  "password": "pass",
41
- "title": "Example",
42
41
  "group_uuid": "",
43
42
  "uuid": "abcdef12-0000-0000-0000-000000000000",
44
43
  "name": "NewGroup",
45
- "parent_uuid": "",
44
+ "path": "Work",
46
45
  }
47
46
  defaults.update(kwargs)
48
47
  return argparse.Namespace(**defaults)
@@ -142,7 +141,7 @@ class TestShowCommand:
142
141
  class TestAddCommand:
143
142
  def test_success(self, mock_client, cli_config, browser_config, browser_config_path, capsys):
144
143
  mock_client.set_login.return_value = True
145
- args = make_args(url="https://example.com", username="u", password="p", title="T", group_uuid="")
144
+ args = make_args(url="https://example.com", username="u", password="p", group_uuid="")
146
145
  rc = add.run(mock_client, args, cli_config, browser_config, browser_config_path)
147
146
  assert rc == 0
148
147
  mock_client.set_login.assert_called_once()
@@ -150,7 +149,7 @@ class TestAddCommand:
150
149
 
151
150
  def test_failure(self, mock_client, cli_config, browser_config, browser_config_path, capsys):
152
151
  mock_client.set_login.return_value = False
153
- args = make_args(url="https://example.com", username="u", password="p", title="T", group_uuid="")
152
+ args = make_args(url="https://example.com", username="u", password="p", group_uuid="")
154
153
  rc = add.run(mock_client, args, cli_config, browser_config, browser_config_path)
155
154
  assert rc == 1
156
155
 
@@ -270,14 +269,105 @@ class TestMkdirCommand:
270
269
  def test_success(self, mock_client, cli_config, browser_config, browser_config_path, capsys, mock_group):
271
270
  new_group = mock_group(uuid="new-uuid", name="MyGroup")
272
271
  mock_client.create_group.return_value = new_group
273
- args = make_args(name="MyGroup", parent_uuid="")
272
+ args = make_args(name="MyGroup")
274
273
  rc = mkdir.run(mock_client, args, cli_config, browser_config, browser_config_path)
275
274
  assert rc == 0
276
275
  out = capsys.readouterr().out
277
276
  assert "MyGroup" in out
278
277
 
278
+ def test_path_syntax(self, mock_client, cli_config, browser_config, browser_config_path, capsys, mock_group):
279
+ new_group = mock_group(uuid="new-uuid", name="Projects")
280
+ mock_client.create_group.return_value = new_group
281
+ args = make_args(name="Work/Projects")
282
+ rc = mkdir.run(mock_client, args, cli_config, browser_config, browser_config_path)
283
+ assert rc == 0
284
+ mock_client.create_group.assert_called_once_with("Work/Projects")
285
+
279
286
  def test_failure(self, mock_client, cli_config, browser_config, browser_config_path, capsys):
280
287
  mock_client.create_group.return_value = None
281
- args = make_args(name="MyGroup", parent_uuid="")
288
+ args = make_args(name="MyGroup")
282
289
  rc = mkdir.run(mock_client, args, cli_config, browser_config, browser_config_path)
283
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