jep-cli 0.6.1__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.
jep_cli-0.6.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Human Judgment Systems Foundation Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ # Notice
2
+
3
+ This repository contains an experimental CLI seed for the JEP v0.6 API.
4
+
5
+ Internet-Draft text and excerpts are governed by the IETF Trust Legal Provisions and BCP 78 / BCP 79.
6
+
7
+ Implementation code, examples, and tests are provided under the repository license unless otherwise stated.
jep_cli-0.6.1/PKG-INFO ADDED
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: jep-cli
3
+ Version: 0.6.1
4
+ Summary: Command-line tool for creating and verifying JEP v0.6 events through the JEP API
5
+ Author-email: HJS Spec <signal@humanjudgment.org>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/hjs-spec/cli
8
+ Project-URL: Repository, https://github.com/hjs-spec/cli
9
+ Project-URL: Issues, https://github.com/hjs-spec/cli/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ License-File: NOTICE.md
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
20
+ Dynamic: license-file
21
+
22
+ # JEP CLI v0.6
23
+
24
+ Command-line tool for creating and verifying JEP v0.6 events through the JEP API.
25
+
26
+ This CLI targets:
27
+
28
+ ```text
29
+ POST /events/create
30
+ POST /events/verify
31
+ GET /health
32
+ ```
33
+
34
+ It is aligned with:
35
+
36
+ - `draft-wang-jep-judgment-event-protocol-06`
37
+ - `draft-wang-jep-profiles-00`
38
+ - `draft-wang-jep-conformance-00`
39
+ - `hjs-spec/jep-api`
40
+
41
+ ## Status
42
+
43
+ Experimental implementation seed.
44
+
45
+ This CLI does not define new JEP-Core semantics and does not determine legal liability, factual truth, regulatory compliance, or complete-log availability.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install -e ".[dev]"
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ Use flags:
56
+
57
+ ```bash
58
+ jep --base-url http://127.0.0.1:8000 health
59
+ ```
60
+
61
+ Or environment variables:
62
+
63
+ ```bash
64
+ export JEP_API_URL=http://127.0.0.1:8000
65
+ export JEP_API_KEY=
66
+ ```
67
+
68
+ ## Commands
69
+
70
+ ### Health
71
+
72
+ ```bash
73
+ jep health
74
+ ```
75
+
76
+ ### Create a Judgment event
77
+
78
+ ```bash
79
+ jep create \
80
+ --verb J \
81
+ --who did:example:agent-789 \
82
+ --what '{"claim":"approve","subject":"demo"}' \
83
+ --aud https://api.example.org
84
+ ```
85
+
86
+ ### Create with extensions
87
+
88
+ ```bash
89
+ jep create \
90
+ --verb J \
91
+ --who did:example:agent-789 \
92
+ --what '{"claim":"approve"}' \
93
+ --ext 'https://example.org/profile={"name":"demo"}' \
94
+ --ext-crit https://example.org/profile
95
+ ```
96
+
97
+ ### Verify an event from a file
98
+
99
+ ```bash
100
+ jep verify event.json --mode archival
101
+ ```
102
+
103
+ ### Pipe create response into verify
104
+
105
+ ```bash
106
+ jep create --verb J --who did:example:agent --what '{"claim":"approve"}' > response.json
107
+ jep extract-event response.json > event.json
108
+ jep verify event.json
109
+ ```
110
+
111
+ ## Input Rules
112
+
113
+ `--what` and event arguments may be:
114
+
115
+ - inline JSON;
116
+ - a plain string;
117
+ - a file path;
118
+ - `-` for stdin.
119
+
120
+ ## Related Repositories
121
+
122
+ - JEP v0.6: https://github.com/hjs-spec/jep-v06
123
+ - JEP API v0.6: https://github.com/hjs-spec/jep-api
124
+ - JEP Python SDK v0.6: https://github.com/hjs-spec/jep-sdk-py
125
+ - JEP Go SDK v0.6: https://github.com/hjs-spec/jep-sdk-go
126
+ - HJS v0.5: https://github.com/hjs-spec/hjs-05
127
+ - JAC v0.5: https://github.com/hjs-spec/jac-agent-02
128
+
129
+ ## Public Drafts
130
+
131
+ - JEP-Core: https://datatracker.ietf.org/doc/draft-wang-jep-judgment-event-protocol/
132
+ - JEP-Profiles: https://datatracker.ietf.org/doc/draft-wang-jep-profiles/
133
+ - JEP-Conformance: https://datatracker.ietf.org/doc/draft-wang-jep-conformance/
134
+
135
+ ## License
136
+
137
+ MIT
138
+
139
+ ## Runtime and verification notes
140
+
141
+ See [HARDENING.md](HARDENING.md) for supported behavior, regression checks, and compatibility boundaries.
@@ -0,0 +1,120 @@
1
+ # JEP CLI v0.6
2
+
3
+ Command-line tool for creating and verifying JEP v0.6 events through the JEP API.
4
+
5
+ This CLI targets:
6
+
7
+ ```text
8
+ POST /events/create
9
+ POST /events/verify
10
+ GET /health
11
+ ```
12
+
13
+ It is aligned with:
14
+
15
+ - `draft-wang-jep-judgment-event-protocol-06`
16
+ - `draft-wang-jep-profiles-00`
17
+ - `draft-wang-jep-conformance-00`
18
+ - `hjs-spec/jep-api`
19
+
20
+ ## Status
21
+
22
+ Experimental implementation seed.
23
+
24
+ This CLI does not define new JEP-Core semantics and does not determine legal liability, factual truth, regulatory compliance, or complete-log availability.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install -e ".[dev]"
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ Use flags:
35
+
36
+ ```bash
37
+ jep --base-url http://127.0.0.1:8000 health
38
+ ```
39
+
40
+ Or environment variables:
41
+
42
+ ```bash
43
+ export JEP_API_URL=http://127.0.0.1:8000
44
+ export JEP_API_KEY=
45
+ ```
46
+
47
+ ## Commands
48
+
49
+ ### Health
50
+
51
+ ```bash
52
+ jep health
53
+ ```
54
+
55
+ ### Create a Judgment event
56
+
57
+ ```bash
58
+ jep create \
59
+ --verb J \
60
+ --who did:example:agent-789 \
61
+ --what '{"claim":"approve","subject":"demo"}' \
62
+ --aud https://api.example.org
63
+ ```
64
+
65
+ ### Create with extensions
66
+
67
+ ```bash
68
+ jep create \
69
+ --verb J \
70
+ --who did:example:agent-789 \
71
+ --what '{"claim":"approve"}' \
72
+ --ext 'https://example.org/profile={"name":"demo"}' \
73
+ --ext-crit https://example.org/profile
74
+ ```
75
+
76
+ ### Verify an event from a file
77
+
78
+ ```bash
79
+ jep verify event.json --mode archival
80
+ ```
81
+
82
+ ### Pipe create response into verify
83
+
84
+ ```bash
85
+ jep create --verb J --who did:example:agent --what '{"claim":"approve"}' > response.json
86
+ jep extract-event response.json > event.json
87
+ jep verify event.json
88
+ ```
89
+
90
+ ## Input Rules
91
+
92
+ `--what` and event arguments may be:
93
+
94
+ - inline JSON;
95
+ - a plain string;
96
+ - a file path;
97
+ - `-` for stdin.
98
+
99
+ ## Related Repositories
100
+
101
+ - JEP v0.6: https://github.com/hjs-spec/jep-v06
102
+ - JEP API v0.6: https://github.com/hjs-spec/jep-api
103
+ - JEP Python SDK v0.6: https://github.com/hjs-spec/jep-sdk-py
104
+ - JEP Go SDK v0.6: https://github.com/hjs-spec/jep-sdk-go
105
+ - HJS v0.5: https://github.com/hjs-spec/hjs-05
106
+ - JAC v0.5: https://github.com/hjs-spec/jac-agent-02
107
+
108
+ ## Public Drafts
109
+
110
+ - JEP-Core: https://datatracker.ietf.org/doc/draft-wang-jep-judgment-event-protocol/
111
+ - JEP-Profiles: https://datatracker.ietf.org/doc/draft-wang-jep-profiles/
112
+ - JEP-Conformance: https://datatracker.ietf.org/doc/draft-wang-jep-conformance/
113
+
114
+ ## License
115
+
116
+ MIT
117
+
118
+ ## Runtime and verification notes
119
+
120
+ See [HARDENING.md](HARDENING.md) for supported behavior, regression checks, and compatibility boundaries.
@@ -0,0 +1,3 @@
1
+ """JEP CLI v0.6."""
2
+
3
+ __version__ = "0.6.0"
@@ -0,0 +1,61 @@
1
+ """Small HTTP client for JEP CLI v0.6."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Any, Dict, Optional
7
+ from urllib import request, error
8
+
9
+
10
+ class JEPCLIError(Exception):
11
+ pass
12
+
13
+
14
+ class JEPAPIError(JEPCLIError):
15
+ def __init__(self, status: int, message: str, payload: Optional[Dict[str, Any]] = None):
16
+ super().__init__(f"JEP API error ({status}): {message}")
17
+ self.status = status
18
+ self.message = message
19
+ self.payload = payload or {}
20
+
21
+
22
+ class JEPClient:
23
+ def __init__(self, base_url: str = "http://127.0.0.1:8000", api_key: str = "", timeout: float = 30.0):
24
+ self.base_url = base_url.rstrip("/")
25
+ self.api_key = api_key
26
+ self.timeout = timeout
27
+
28
+ def create_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
29
+ return self._json("POST", "/events/create", payload)
30
+
31
+ def verify_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
32
+ return self._json("POST", "/events/verify", payload)
33
+
34
+ def health(self) -> Dict[str, Any]:
35
+ return self._json("GET", "/health", None)
36
+
37
+ def _json(self, method: str, path: str, payload: Optional[Dict[str, Any]]) -> Dict[str, Any]:
38
+ data = None
39
+ headers = {
40
+ "content-type": "application/json",
41
+ "user-agent": "JEP-CLI/0.6.0",
42
+ }
43
+ if self.api_key:
44
+ headers["authorization"] = f"Bearer {self.api_key}"
45
+ headers["x-api-key"] = self.api_key
46
+ if payload is not None:
47
+ data = json.dumps(payload).encode("utf-8")
48
+
49
+ req = request.Request(self.base_url + path, data=data, headers=headers, method=method)
50
+ try:
51
+ with request.urlopen(req, timeout=self.timeout) as resp:
52
+ body = resp.read().decode("utf-8")
53
+ return json.loads(body) if body else {}
54
+ except error.HTTPError as exc:
55
+ body = exc.read().decode("utf-8")
56
+ try:
57
+ parsed = json.loads(body)
58
+ except Exception:
59
+ parsed = {"message": body}
60
+ msg = parsed.get("message") or parsed.get("error") or body
61
+ raise JEPAPIError(exc.code, msg, parsed) from exc
@@ -0,0 +1,168 @@
1
+ """JEP CLI v0.6 command line interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Any, Dict
11
+
12
+ from .client import JEPClient, JEPCLIError
13
+
14
+
15
+ VALID_VERBS = {"J", "D", "T", "V"}
16
+
17
+
18
+ def parse_json_value(value: str) -> Any:
19
+ if value == "-":
20
+ return json.load(sys.stdin)
21
+
22
+ path = Path(value)
23
+ if path.exists() and path.is_file():
24
+ return json.loads(path.read_text(encoding="utf-8"))
25
+
26
+ try:
27
+ return json.loads(value)
28
+ except json.JSONDecodeError:
29
+ return value
30
+
31
+
32
+ def parse_ext_items(items: list[str]) -> Dict[str, Any]:
33
+ ext: Dict[str, Any] = {}
34
+ for item in items:
35
+ if "=" not in item:
36
+ raise ValueError(f"extension must be key=json_or_string, got: {item}")
37
+ key, raw = item.split("=", 1)
38
+ ext[key] = parse_json_value(raw)
39
+ return ext
40
+
41
+
42
+ def print_json(data: Any) -> None:
43
+ print(json.dumps(data, indent=2, ensure_ascii=False))
44
+
45
+
46
+ def build_client(args: argparse.Namespace) -> JEPClient:
47
+ base_url = args.base_url or os.environ.get("JEP_API_URL", "http://127.0.0.1:8000")
48
+ api_key = args.api_key or os.environ.get("JEP_API_KEY", "")
49
+ return JEPClient(base_url=base_url, api_key=api_key, timeout=args.timeout)
50
+
51
+
52
+ def cmd_create(args: argparse.Namespace) -> int:
53
+ if args.verb not in VALID_VERBS:
54
+ raise ValueError("verb must be J, D, T, or V")
55
+
56
+ payload: Dict[str, Any] = {
57
+ "verb": args.verb,
58
+ "what": parse_json_value(args.what),
59
+ }
60
+ if args.who:
61
+ payload["who"] = args.who
62
+ if args.aud:
63
+ payload["aud"] = args.aud
64
+ if args.ref:
65
+ payload["ref"] = parse_json_value(args.ref)
66
+ if args.ttl_minutes is not None:
67
+ payload["ttl_minutes"] = args.ttl_minutes
68
+ if args.digest_only_who:
69
+ payload["digest_only_who"] = True
70
+ if args.ext:
71
+ payload["ext"] = parse_ext_items(args.ext)
72
+ if args.ext_crit:
73
+ payload["ext_crit"] = args.ext_crit
74
+
75
+ result = build_client(args).create_event(payload)
76
+ print_json(result)
77
+ return 0
78
+
79
+
80
+ def cmd_verify(args: argparse.Namespace) -> int:
81
+ event = parse_json_value(args.event)
82
+ if not isinstance(event, dict):
83
+ raise ValueError("event must be JSON object or path to JSON event file")
84
+ payload = {
85
+ "event": event,
86
+ "mode": args.mode,
87
+ "consume_nonce": args.consume_nonce,
88
+ }
89
+ if args.expected_audience:
90
+ payload["expected_audience"] = args.expected_audience
91
+ if args.mode == "acceptance" and not args.expected_audience:
92
+ raise ValueError("acceptance mode requires --expected-audience")
93
+ result = build_client(args).verify_event(payload)
94
+ print_json(result)
95
+ return 0 if result.get("valid") is True else 2
96
+
97
+
98
+ def cmd_health(args: argparse.Namespace) -> int:
99
+ result = build_client(args).health()
100
+ print_json(result)
101
+ return 0
102
+
103
+
104
+ def cmd_extract_event(args: argparse.Namespace) -> int:
105
+ data = parse_json_value(args.response)
106
+ if not isinstance(data, dict) or "event" not in data:
107
+ raise ValueError("response must contain an event field")
108
+ print_json(data["event"])
109
+ return 0
110
+
111
+
112
+ def build_parser() -> argparse.ArgumentParser:
113
+ parser = argparse.ArgumentParser(
114
+ prog="jep",
115
+ description="JEP CLI v0.6 for creating and verifying JEP-Core events through the JEP API.",
116
+ )
117
+ parser.add_argument("--base-url", default="", help="JEP API base URL. Default: JEP_API_URL or http://127.0.0.1:8000")
118
+ parser.add_argument("--api-key", default="", help="API key. Default: JEP_API_KEY")
119
+ parser.add_argument("--timeout", type=float, default=30.0, help="HTTP timeout in seconds")
120
+
121
+ sub = parser.add_subparsers(dest="command", required=True)
122
+
123
+ create = sub.add_parser("create", help="Create a JEP event")
124
+ create.add_argument("--verb", required=True, choices=sorted(VALID_VERBS), help="JEP verb: J, D, T, or V")
125
+ create.add_argument("--who", default="", help="Actor identifier")
126
+ create.add_argument("--what", required=True, help="JSON value, string, file path, or '-' for stdin")
127
+ create.add_argument("--aud", default="", help="Audience / validation context")
128
+ create.add_argument("--ref", default="", help="Reference event hash")
129
+ create.add_argument("--ttl-minutes", type=int, default=None, help="Optional TTL extension in minutes")
130
+ create.add_argument("--digest-only-who", action="store_true", help="Request digest-only privacy extension")
131
+ create.add_argument("--ext", action="append", default=[], help="Extension key=json_or_string. May be repeated")
132
+ create.add_argument("--ext-crit", action="append", default=[], help="Critical extension URI. May be repeated")
133
+ create.set_defaults(func=cmd_create)
134
+
135
+ verify = sub.add_parser("verify", help="Verify a JEP event")
136
+ verify.add_argument("event", help="Event JSON object, event JSON file path, or '-' for stdin")
137
+ verify.add_argument("--mode", default="archival", choices=["archival", "acceptance"], help="Validation mode")
138
+ verify.add_argument("--expected-audience", default="", help="Required receiving audience for acceptance")
139
+ verify.add_argument("--consume-nonce", action="store_true", help="Ask verifier to consume nonce")
140
+ verify.set_defaults(func=cmd_verify)
141
+
142
+ health = sub.add_parser("health", help="Check JEP API health")
143
+ health.set_defaults(func=cmd_health)
144
+
145
+ extract = sub.add_parser("extract-event", help="Extract event object from /events/create response")
146
+ extract.add_argument("response", help="Response JSON object, response JSON file path, or '-' for stdin")
147
+ extract.set_defaults(func=cmd_extract_event)
148
+
149
+ return parser
150
+
151
+
152
+ def main(argv: list[str] | None = None) -> int:
153
+ parser = build_parser()
154
+ try:
155
+ args = parser.parse_args(argv)
156
+ return int(args.func(args) or 0)
157
+ except SystemExit as exc:
158
+ # Allows tests and embedded callers to receive an exit code instead of
159
+ # terminating the Python process. The console entry point still exits
160
+ # via the __main__ block below.
161
+ return int(exc.code or 0)
162
+ except (JEPCLIError, ValueError, OSError, json.JSONDecodeError) as exc:
163
+ print(f"jep: error: {exc}", file=sys.stderr)
164
+ return 1
165
+
166
+
167
+ if __name__ == "__main__":
168
+ raise SystemExit(main())
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: jep-cli
3
+ Version: 0.6.1
4
+ Summary: Command-line tool for creating and verifying JEP v0.6 events through the JEP API
5
+ Author-email: HJS Spec <signal@humanjudgment.org>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/hjs-spec/cli
8
+ Project-URL: Repository, https://github.com/hjs-spec/cli
9
+ Project-URL: Issues, https://github.com/hjs-spec/cli/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ License-File: NOTICE.md
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
20
+ Dynamic: license-file
21
+
22
+ # JEP CLI v0.6
23
+
24
+ Command-line tool for creating and verifying JEP v0.6 events through the JEP API.
25
+
26
+ This CLI targets:
27
+
28
+ ```text
29
+ POST /events/create
30
+ POST /events/verify
31
+ GET /health
32
+ ```
33
+
34
+ It is aligned with:
35
+
36
+ - `draft-wang-jep-judgment-event-protocol-06`
37
+ - `draft-wang-jep-profiles-00`
38
+ - `draft-wang-jep-conformance-00`
39
+ - `hjs-spec/jep-api`
40
+
41
+ ## Status
42
+
43
+ Experimental implementation seed.
44
+
45
+ This CLI does not define new JEP-Core semantics and does not determine legal liability, factual truth, regulatory compliance, or complete-log availability.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ pip install -e ".[dev]"
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ Use flags:
56
+
57
+ ```bash
58
+ jep --base-url http://127.0.0.1:8000 health
59
+ ```
60
+
61
+ Or environment variables:
62
+
63
+ ```bash
64
+ export JEP_API_URL=http://127.0.0.1:8000
65
+ export JEP_API_KEY=
66
+ ```
67
+
68
+ ## Commands
69
+
70
+ ### Health
71
+
72
+ ```bash
73
+ jep health
74
+ ```
75
+
76
+ ### Create a Judgment event
77
+
78
+ ```bash
79
+ jep create \
80
+ --verb J \
81
+ --who did:example:agent-789 \
82
+ --what '{"claim":"approve","subject":"demo"}' \
83
+ --aud https://api.example.org
84
+ ```
85
+
86
+ ### Create with extensions
87
+
88
+ ```bash
89
+ jep create \
90
+ --verb J \
91
+ --who did:example:agent-789 \
92
+ --what '{"claim":"approve"}' \
93
+ --ext 'https://example.org/profile={"name":"demo"}' \
94
+ --ext-crit https://example.org/profile
95
+ ```
96
+
97
+ ### Verify an event from a file
98
+
99
+ ```bash
100
+ jep verify event.json --mode archival
101
+ ```
102
+
103
+ ### Pipe create response into verify
104
+
105
+ ```bash
106
+ jep create --verb J --who did:example:agent --what '{"claim":"approve"}' > response.json
107
+ jep extract-event response.json > event.json
108
+ jep verify event.json
109
+ ```
110
+
111
+ ## Input Rules
112
+
113
+ `--what` and event arguments may be:
114
+
115
+ - inline JSON;
116
+ - a plain string;
117
+ - a file path;
118
+ - `-` for stdin.
119
+
120
+ ## Related Repositories
121
+
122
+ - JEP v0.6: https://github.com/hjs-spec/jep-v06
123
+ - JEP API v0.6: https://github.com/hjs-spec/jep-api
124
+ - JEP Python SDK v0.6: https://github.com/hjs-spec/jep-sdk-py
125
+ - JEP Go SDK v0.6: https://github.com/hjs-spec/jep-sdk-go
126
+ - HJS v0.5: https://github.com/hjs-spec/hjs-05
127
+ - JAC v0.5: https://github.com/hjs-spec/jac-agent-02
128
+
129
+ ## Public Drafts
130
+
131
+ - JEP-Core: https://datatracker.ietf.org/doc/draft-wang-jep-judgment-event-protocol/
132
+ - JEP-Profiles: https://datatracker.ietf.org/doc/draft-wang-jep-profiles/
133
+ - JEP-Conformance: https://datatracker.ietf.org/doc/draft-wang-jep-conformance/
134
+
135
+ ## License
136
+
137
+ MIT
138
+
139
+ ## Runtime and verification notes
140
+
141
+ See [HARDENING.md](HARDENING.md) for supported behavior, regression checks, and compatibility boundaries.
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ NOTICE.md
3
+ README.md
4
+ pyproject.toml
5
+ jep_cli/__init__.py
6
+ jep_cli/client.py
7
+ jep_cli/main.py
8
+ jep_cli.egg-info/PKG-INFO
9
+ jep_cli.egg-info/SOURCES.txt
10
+ jep_cli.egg-info/dependency_links.txt
11
+ jep_cli.egg-info/entry_points.txt
12
+ jep_cli.egg-info/requires.txt
13
+ jep_cli.egg-info/top_level.txt
14
+ tests/test_acceptance.py
15
+ tests/test_cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ jep = jep_cli.main:main
@@ -0,0 +1,3 @@
1
+
2
+ [dev]
3
+ pytest>=7.0.0
@@ -0,0 +1 @@
1
+ jep_cli
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "jep-cli"
7
+ version = "0.6.1"
8
+ description = "Command-line tool for creating and verifying JEP v0.6 events through the JEP API"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [
12
+ { name = "HJS Spec", email = "signal@humanjudgment.org" }
13
+ ]
14
+ requires-python = ">=3.9"
15
+ dependencies = []
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ ]
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "pytest>=7.0.0",
26
+ ]
27
+
28
+ [project.scripts]
29
+ jep = "jep_cli.main:main"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/hjs-spec/cli"
33
+ Repository = "https://github.com/hjs-spec/cli"
34
+ Issues = "https://github.com/hjs-spec/cli/issues"
35
+
36
+ [tool.setuptools.packages.find]
37
+ include = ["jep_cli*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,16 @@
1
+ import json
2
+ from jep_cli import main as module
3
+
4
+
5
+ def test_acceptance_requires_expected_audience(capsys):
6
+ assert module.main(["verify", '{"jep":"1"}', "--mode", "acceptance"]) == 1
7
+ assert "expected-audience" in capsys.readouterr().err
8
+
9
+
10
+ def test_verification_failure_returns_nonzero(monkeypatch):
11
+ class Client:
12
+ def verify_event(self, payload):
13
+ assert payload["expected_audience"] == "receiver"
14
+ return {"valid": False}
15
+ monkeypatch.setattr(module, "build_client", lambda args: Client())
16
+ assert module.main(["verify", '{}', "--mode", "acceptance", "--expected-audience", "receiver"]) == 2
@@ -0,0 +1,154 @@
1
+ """Tests for JEP CLI v0.6."""
2
+
3
+ import json
4
+ import threading
5
+ from http.server import BaseHTTPRequestHandler, HTTPServer
6
+
7
+ import pytest
8
+
9
+ from jep_cli.main import main, parse_json_value, parse_ext_items
10
+
11
+
12
+ class Handler(BaseHTTPRequestHandler):
13
+ def _json(self, status, payload):
14
+ body = json.dumps(payload).encode()
15
+ self.send_response(status)
16
+ self.send_header("content-type", "application/json")
17
+ self.send_header("content-length", str(len(body)))
18
+ self.end_headers()
19
+ self.wfile.write(body)
20
+
21
+ def do_GET(self):
22
+ if self.path == "/health":
23
+ self._json(200, {"ok": True, "profile": "jep-core-0.6"})
24
+ else:
25
+ self._json(404, {"message": "not found"})
26
+
27
+ def do_POST(self):
28
+ length = int(self.headers.get("content-length", "0"))
29
+ payload = json.loads(self.rfile.read(length) or b"{}")
30
+ if self.path == "/events/create":
31
+ event = {
32
+ "jep": "1",
33
+ "verb": payload["verb"],
34
+ "who": payload.get("who", "did:example:agent"),
35
+ "when": 1234567890,
36
+ "what": payload.get("what"),
37
+ "nonce": "nonce-1",
38
+ "aud": payload.get("aud"),
39
+ "ref": payload.get("ref"),
40
+ "ext": payload.get("ext"),
41
+ "ext_crit": payload.get("ext_crit"),
42
+ "sig": "header..sig",
43
+ }
44
+ self._json(200, {
45
+ "event": event,
46
+ "event_hash": "sha256:abc",
47
+ "validation": {
48
+ "valid": True,
49
+ "level": 1,
50
+ "mode": "archival",
51
+ "profile": "jep-core-0.6",
52
+ "event_hash": "sha256:abc",
53
+ "warnings": [],
54
+ "errors": [],
55
+ }
56
+ })
57
+ return
58
+ if self.path == "/events/verify":
59
+ self._json(200, {
60
+ "valid": True,
61
+ "level": 1,
62
+ "mode": payload.get("mode", "archival"),
63
+ "profile": "jep-core-0.6",
64
+ "event_hash": "sha256:def",
65
+ "warnings": [],
66
+ "errors": [],
67
+ })
68
+ return
69
+ self._json(404, {"message": "not found"})
70
+
71
+ def log_message(self, *args):
72
+ pass
73
+
74
+
75
+ @pytest.fixture()
76
+ def api_server():
77
+ server = HTTPServer(("127.0.0.1", 0), Handler)
78
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
79
+ thread.start()
80
+ try:
81
+ yield f"http://127.0.0.1:{server.server_port}"
82
+ finally:
83
+ server.shutdown()
84
+ thread.join()
85
+
86
+
87
+ def test_parse_json_value_string_and_json(tmp_path):
88
+ assert parse_json_value('{"a":1}') == {"a": 1}
89
+ assert parse_json_value("plain") == "plain"
90
+ f = tmp_path / "data.json"
91
+ f.write_text('{"b":2}')
92
+ assert parse_json_value(str(f)) == {"b": 2}
93
+
94
+
95
+ def test_parse_ext_items():
96
+ ext = parse_ext_items(['https://example.org/x={"a":1}', 'https://example.org/y=plain'])
97
+ assert ext["https://example.org/x"] == {"a": 1}
98
+ assert ext["https://example.org/y"] == "plain"
99
+
100
+
101
+ def test_health(api_server, capsys):
102
+ code = main(["--base-url", api_server, "health"])
103
+ out = capsys.readouterr().out
104
+ assert code == 0
105
+ assert '"ok": true' in out
106
+
107
+
108
+ def test_create(api_server, capsys):
109
+ code = main([
110
+ "--base-url", api_server,
111
+ "create",
112
+ "--verb", "J",
113
+ "--who", "did:example:agent",
114
+ "--what", '{"claim":"approve"}',
115
+ "--ext", 'https://example.org/profile={"name":"demo"}',
116
+ "--ext-crit", "https://example.org/profile",
117
+ ])
118
+ out = json.loads(capsys.readouterr().out)
119
+ assert code == 0
120
+ assert out["event_hash"] == "sha256:abc"
121
+ assert out["event"]["verb"] == "J"
122
+ assert out["event"]["ext"]["https://example.org/profile"]["name"] == "demo"
123
+
124
+
125
+ def test_verify(api_server, capsys, tmp_path):
126
+ event = {
127
+ "jep": "1",
128
+ "verb": "J",
129
+ "who": "did:example:agent",
130
+ "when": 123,
131
+ "what": "sha256:abc",
132
+ "nonce": "nonce-1",
133
+ "sig": "header..sig",
134
+ }
135
+ p = tmp_path / "event.json"
136
+ p.write_text(json.dumps(event))
137
+ code = main(["--base-url", api_server, "verify", str(p)])
138
+ out = json.loads(capsys.readouterr().out)
139
+ assert code == 0
140
+ assert out["valid"] is True
141
+
142
+
143
+ def test_extract_event(capsys, tmp_path):
144
+ p = tmp_path / "response.json"
145
+ p.write_text(json.dumps({"event": {"jep": "1", "verb": "J"}}))
146
+ code = main(["extract-event", str(p)])
147
+ out = json.loads(capsys.readouterr().out)
148
+ assert code == 0
149
+ assert out["verb"] == "J"
150
+
151
+
152
+ def test_invalid_verb(capsys):
153
+ code = main(["create", "--verb", "X", "--what", "x"])
154
+ assert code != 0