signbuddy 1.0.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.
- signbuddy-1.0.0/PKG-INFO +53 -0
- signbuddy-1.0.0/README.md +44 -0
- signbuddy-1.0.0/pyproject.toml +25 -0
- signbuddy-1.0.0/setup.cfg +4 -0
- signbuddy-1.0.0/src/signbuddy/__init__.py +3 -0
- signbuddy-1.0.0/src/signbuddy/__main__.py +5 -0
- signbuddy-1.0.0/src/signbuddy/cli.py +138 -0
- signbuddy-1.0.0/src/signbuddy.egg-info/PKG-INFO +53 -0
- signbuddy-1.0.0/src/signbuddy.egg-info/SOURCES.txt +11 -0
- signbuddy-1.0.0/src/signbuddy.egg-info/dependency_links.txt +1 -0
- signbuddy-1.0.0/src/signbuddy.egg-info/entry_points.txt +2 -0
- signbuddy-1.0.0/src/signbuddy.egg-info/requires.txt +1 -0
- signbuddy-1.0.0/src/signbuddy.egg-info/top_level.txt +1 -0
signbuddy-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: signbuddy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI for verifying SignBuddy-signed PDF documents
|
|
5
|
+
Author: SignBuddy
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.31.0
|
|
9
|
+
|
|
10
|
+
# SignBuddy CLI
|
|
11
|
+
|
|
12
|
+
Verify whether a PDF was signed and recorded through [SignBuddy](https://signbuddy.eu).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install signbuddy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or from local source:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone <your-repo-url>
|
|
24
|
+
cd signbuddy
|
|
25
|
+
pip install .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
No setup required — the CLI talks to the SignBuddy API out of the box.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
signbuddy --verify AGR-1234567890 /path/to/document.pdf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The contract number is printed on the signed document (format `AGR-` followed by 10 digits).
|
|
37
|
+
|
|
38
|
+
### Exit codes
|
|
39
|
+
|
|
40
|
+
| Code | Meaning |
|
|
41
|
+
| ---- | ---------------------------------------- |
|
|
42
|
+
| `0` | Document verified |
|
|
43
|
+
| `1` | Document not verified |
|
|
44
|
+
| `2` | Invalid input (file missing / not a PDF) |
|
|
45
|
+
| `3` | Could not reach the API / API error |
|
|
46
|
+
|
|
47
|
+
## Options
|
|
48
|
+
|
|
49
|
+
| Option | Description |
|
|
50
|
+
| -------------------- | ------------------------------------ |
|
|
51
|
+
| `--timeout <secs>` | HTTP timeout in seconds (default 30) |
|
|
52
|
+
| `--version` | Print the CLI version |
|
|
53
|
+
| `-h`, `--help` | Show help |
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# SignBuddy CLI
|
|
2
|
+
|
|
3
|
+
Verify whether a PDF was signed and recorded through [SignBuddy](https://signbuddy.eu).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install signbuddy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or from local source:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git clone <your-repo-url>
|
|
15
|
+
cd signbuddy
|
|
16
|
+
pip install .
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
No setup required — the CLI talks to the SignBuddy API out of the box.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
signbuddy --verify AGR-1234567890 /path/to/document.pdf
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The contract number is printed on the signed document (format `AGR-` followed by 10 digits).
|
|
28
|
+
|
|
29
|
+
### Exit codes
|
|
30
|
+
|
|
31
|
+
| Code | Meaning |
|
|
32
|
+
| ---- | ---------------------------------------- |
|
|
33
|
+
| `0` | Document verified |
|
|
34
|
+
| `1` | Document not verified |
|
|
35
|
+
| `2` | Invalid input (file missing / not a PDF) |
|
|
36
|
+
| `3` | Could not reach the API / API error |
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
| Option | Description |
|
|
41
|
+
| -------------------- | ------------------------------------ |
|
|
42
|
+
| `--timeout <secs>` | HTTP timeout in seconds (default 30) |
|
|
43
|
+
| `--version` | Print the CLI version |
|
|
44
|
+
| `-h`, `--help` | Show help |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "signbuddy"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "CLI for verifying SignBuddy-signed PDF documents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "SignBuddy" }
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"requests>=2.31.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
signbuddy = "signbuddy.cli:main"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
package-dir = {"" = "src"}
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages.find]
|
|
25
|
+
where = ["src"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from signbuddy import __version__
|
|
8
|
+
|
|
9
|
+
API_BASE_URL = "https://api.signbuddy.eu"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
prog="signbuddy",
|
|
15
|
+
description="Verify whether a PDF was signed and recorded through SignBuddy.",
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--version",
|
|
19
|
+
action="version",
|
|
20
|
+
version=f"%(prog)s {__version__}",
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--timeout",
|
|
24
|
+
type=float,
|
|
25
|
+
default=30.0,
|
|
26
|
+
help="HTTP timeout in seconds. Default: 30",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--verify",
|
|
30
|
+
nargs=2,
|
|
31
|
+
metavar=("CONTRACT_NUMBER", "FILEPATH"),
|
|
32
|
+
help="Verify a PDF by contract number and file path.",
|
|
33
|
+
)
|
|
34
|
+
return parser
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> int:
|
|
38
|
+
parser = build_parser()
|
|
39
|
+
args = parser.parse_args()
|
|
40
|
+
|
|
41
|
+
if not args.verify:
|
|
42
|
+
parser.error("the following argument is required: --verify")
|
|
43
|
+
|
|
44
|
+
contract_number, file_path = args.verify
|
|
45
|
+
pdf_path = Path(file_path).expanduser().resolve()
|
|
46
|
+
|
|
47
|
+
if not pdf_path.exists():
|
|
48
|
+
print(f"Error: file not found: {pdf_path}")
|
|
49
|
+
return 2
|
|
50
|
+
if not pdf_path.is_file():
|
|
51
|
+
print(f"Error: not a file: {pdf_path}")
|
|
52
|
+
return 2
|
|
53
|
+
if pdf_path.suffix.lower() != ".pdf":
|
|
54
|
+
print(f"Error: expected a PDF file, got: {pdf_path.name}")
|
|
55
|
+
return 2
|
|
56
|
+
|
|
57
|
+
verify_url = f"{API_BASE_URL}/api/v1/verify"
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
with pdf_path.open("rb") as handle:
|
|
61
|
+
response = requests.post(
|
|
62
|
+
verify_url,
|
|
63
|
+
data={"contract_number": contract_number},
|
|
64
|
+
files={"pdf_file": (pdf_path.name, handle, "application/pdf")},
|
|
65
|
+
timeout=args.timeout,
|
|
66
|
+
)
|
|
67
|
+
except requests.RequestException as exc:
|
|
68
|
+
print(f"Error: failed to reach SignBuddy API at {verify_url}")
|
|
69
|
+
print(f"Detail: {exc}")
|
|
70
|
+
return 3
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
payload = response.json()
|
|
74
|
+
except json.JSONDecodeError:
|
|
75
|
+
print(f"Error: SignBuddy API returned non-JSON response (HTTP {response.status_code})")
|
|
76
|
+
return 3
|
|
77
|
+
|
|
78
|
+
if response.status_code >= 400:
|
|
79
|
+
print(f"Error: SignBuddy API returned HTTP {response.status_code}")
|
|
80
|
+
detail = payload.get("detail")
|
|
81
|
+
if detail:
|
|
82
|
+
print(f"Detail: {detail}")
|
|
83
|
+
else:
|
|
84
|
+
print(json.dumps(payload, indent=2))
|
|
85
|
+
return 3
|
|
86
|
+
|
|
87
|
+
_print_result(payload, pdf_path)
|
|
88
|
+
return 0 if payload.get("verified") else 1
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _print_result(payload: dict, pdf_path: Path) -> None:
|
|
92
|
+
verified = bool(payload.get("verified"))
|
|
93
|
+
found = bool(payload.get("found"))
|
|
94
|
+
|
|
95
|
+
print(f"File: {pdf_path}")
|
|
96
|
+
print(f"Contract Number: {payload.get('contract_number') or 'N/A'}")
|
|
97
|
+
print(f"Verified: {'YES' if verified else 'NO'}")
|
|
98
|
+
print(f"Found in SignBuddy: {'YES' if found else 'NO'}")
|
|
99
|
+
|
|
100
|
+
if payload.get("contract_title"):
|
|
101
|
+
print(f"Contract Title: {payload['contract_title']}")
|
|
102
|
+
|
|
103
|
+
if payload.get("message"):
|
|
104
|
+
print(f"Message: {payload['message']}")
|
|
105
|
+
|
|
106
|
+
if payload.get("hash_mismatch"):
|
|
107
|
+
print("Hash Mismatch: YES (uploaded file does not match the recorded document)")
|
|
108
|
+
|
|
109
|
+
if payload.get("pdf_hash"):
|
|
110
|
+
print(f"Stored Hash: {payload['pdf_hash']}")
|
|
111
|
+
if payload.get("searched_hash"):
|
|
112
|
+
print(f"Uploaded Hash: {payload['searched_hash']}")
|
|
113
|
+
if payload.get("expected_hash"):
|
|
114
|
+
print(f"Expected Hash: {payload['expected_hash']}")
|
|
115
|
+
|
|
116
|
+
if payload.get("rekor_status"):
|
|
117
|
+
print(f"Rekor: {payload['rekor_status']}")
|
|
118
|
+
if payload.get("log_index") is not None:
|
|
119
|
+
print(f"Rekor Log Index: {payload['log_index']}")
|
|
120
|
+
if payload.get("entry_uuid"):
|
|
121
|
+
print(f"Rekor Entry UUID: {payload['entry_uuid']}")
|
|
122
|
+
if payload.get("integrated_time_formatted"):
|
|
123
|
+
print(f"Rekor Integrated Time: {payload['integrated_time_formatted']}")
|
|
124
|
+
if payload.get("ots_status"):
|
|
125
|
+
print(f"OpenTimestamps: {payload['ots_status']}")
|
|
126
|
+
if payload.get("bitcoin_block_height") is not None:
|
|
127
|
+
print(f"Bitcoin Block Height: {payload['bitcoin_block_height']}")
|
|
128
|
+
if payload.get("bitcoin_confirmed_at"):
|
|
129
|
+
print(f"Bitcoin Confirmed At: {payload['bitcoin_confirmed_at']}")
|
|
130
|
+
|
|
131
|
+
signers = payload.get("signers") or []
|
|
132
|
+
if signers:
|
|
133
|
+
print("Signers:")
|
|
134
|
+
for signer in signers:
|
|
135
|
+
name = signer.get("name") or "Unknown"
|
|
136
|
+
email = signer.get("email") or "N/A"
|
|
137
|
+
signed_at = signer.get("signed_at") or "N/A"
|
|
138
|
+
print(f" - {name} <{email}> signed_at={signed_at}")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: signbuddy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI for verifying SignBuddy-signed PDF documents
|
|
5
|
+
Author: SignBuddy
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.31.0
|
|
9
|
+
|
|
10
|
+
# SignBuddy CLI
|
|
11
|
+
|
|
12
|
+
Verify whether a PDF was signed and recorded through [SignBuddy](https://signbuddy.eu).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install signbuddy
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or from local source:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone <your-repo-url>
|
|
24
|
+
cd signbuddy
|
|
25
|
+
pip install .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
No setup required — the CLI talks to the SignBuddy API out of the box.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
signbuddy --verify AGR-1234567890 /path/to/document.pdf
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The contract number is printed on the signed document (format `AGR-` followed by 10 digits).
|
|
37
|
+
|
|
38
|
+
### Exit codes
|
|
39
|
+
|
|
40
|
+
| Code | Meaning |
|
|
41
|
+
| ---- | ---------------------------------------- |
|
|
42
|
+
| `0` | Document verified |
|
|
43
|
+
| `1` | Document not verified |
|
|
44
|
+
| `2` | Invalid input (file missing / not a PDF) |
|
|
45
|
+
| `3` | Could not reach the API / API error |
|
|
46
|
+
|
|
47
|
+
## Options
|
|
48
|
+
|
|
49
|
+
| Option | Description |
|
|
50
|
+
| -------------------- | ------------------------------------ |
|
|
51
|
+
| `--timeout <secs>` | HTTP timeout in seconds (default 30) |
|
|
52
|
+
| `--version` | Print the CLI version |
|
|
53
|
+
| `-h`, `--help` | Show help |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/signbuddy/__init__.py
|
|
4
|
+
src/signbuddy/__main__.py
|
|
5
|
+
src/signbuddy/cli.py
|
|
6
|
+
src/signbuddy.egg-info/PKG-INFO
|
|
7
|
+
src/signbuddy.egg-info/SOURCES.txt
|
|
8
|
+
src/signbuddy.egg-info/dependency_links.txt
|
|
9
|
+
src/signbuddy.egg-info/entry_points.txt
|
|
10
|
+
src/signbuddy.egg-info/requires.txt
|
|
11
|
+
src/signbuddy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.31.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
signbuddy
|