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.
- {keepassxc_cli-1.1.0/keepassxc_cli.egg-info → keepassxc_cli-1.3.0}/PKG-INFO +37 -4
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/README.md +35 -2
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/__main__.py +3 -1
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/add.py +0 -2
- keepassxc_cli-1.3.0/keepassxc_cli/commands/group_uuid.py +58 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/mkdir.py +5 -3
- keepassxc_cli-1.3.0/keepassxc_cli/commands/version.py +31 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0/keepassxc_cli.egg-info}/PKG-INFO +37 -4
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/SOURCES.txt +2 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/requires.txt +1 -1
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/pyproject.toml +1 -1
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/tests/conftest.py +1 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/tests/test_commands.py +97 -7
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/auto-merge-dependabot.yml +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/auto-release.yml +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/lint_and_test.yml +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.github/workflows/pypi.yml +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/.gitignore +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/CLAUDE.md +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/LICENSE +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/__init__.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/__init__.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/clip.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/edit.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/lock.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/rm.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/setup.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/show.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/status.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/commands/totp.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/config.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli/output.py +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/dependency_links.txt +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/entry_points.txt +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/keepassxc_cli.egg-info/top_level.txt +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/setup.cfg +0 -0
- {keepassxc_cli-1.1.0 → keepassxc_cli-1.3.0}/tests/test_config.py +0 -0
- {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.
|
|
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.
|
|
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"
|
|
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"
|
|
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(
|
|
15
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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"
|
|
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
|
|
@@ -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
|
-
"
|
|
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",
|
|
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",
|
|
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"
|
|
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"
|
|
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
|
|
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
|