jev-cli 0.4.1__py3-none-any.whl

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.
jev_cli/__init__.py ADDED
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env python3
2
+ """Small, dependency-free CLI for TypeSafe Jev."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import getpass
8
+ import json
9
+ import os
10
+ import shutil
11
+ import sys
12
+ import tempfile
13
+ import urllib.error
14
+ import urllib.request
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ API_URL = "https://api.typesafe.ai/v1/systemone"
19
+ CREDENTIALS_FILE = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "jev-cli" / "credentials.json"
20
+ BUNDLED_SKILLS = Path(__file__).with_name("bundled_skills")
21
+ INSTALL_MARKER = ".jev-cli-managed"
22
+
23
+
24
+ class CliError(Exception):
25
+ def __init__(self, message: str, exit_code: int = 2) -> None:
26
+ super().__init__(message)
27
+ self.exit_code = exit_code
28
+
29
+
30
+ def api_key() -> str:
31
+ if value := os.environ.get("TYPESAFE_API_KEY"):
32
+ return value
33
+ try:
34
+ data = json.loads(CREDENTIALS_FILE.read_text())
35
+ value = data["api_key"]
36
+ except (OSError, json.JSONDecodeError, KeyError, TypeError) as exc:
37
+ raise CliError(
38
+ "TypeSafe API key is not stored; run: jev auth set",
39
+ 3,
40
+ ) from exc
41
+ if not isinstance(value, str) or not value:
42
+ raise CliError("stored TypeSafe API key is empty", 3)
43
+ return value
44
+
45
+
46
+ def set_api_key() -> None:
47
+ value = getpass.getpass("TypeSafe API key: ").strip() if sys.stdin.isatty() else sys.stdin.read().strip()
48
+ if not value:
49
+ raise CliError("API key is empty")
50
+ CREDENTIALS_FILE.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
51
+ try:
52
+ fd, temporary = tempfile.mkstemp(dir=CREDENTIALS_FILE.parent)
53
+ with os.fdopen(fd, "w") as file:
54
+ json.dump({"api_key": value}, file)
55
+ file.write("\n")
56
+ os.chmod(temporary, 0o600)
57
+ os.replace(temporary, CREDENTIALS_FILE)
58
+ except OSError as exc:
59
+ raise CliError("could not write the Jev credential store", 3) from exc
60
+
61
+
62
+ def install_skills(
63
+ *, global_install: bool, claude: bool, cwd: Path | None = None, home: Path | None = None
64
+ ) -> dict[str, Any]:
65
+ base = (home or Path.home()) if global_install else (cwd or Path.cwd())
66
+ flavor = ".claude" if claude else ".agents"
67
+ destination_root = base / flavor / "skills"
68
+ installed: list[str] = []
69
+ paths: list[str] = []
70
+ for source in sorted(BUNDLED_SKILLS.iterdir()):
71
+ if not source.is_dir() or not (source / "SKILL.md").is_file():
72
+ continue
73
+ destination = destination_root / source.name
74
+ if destination.exists() and not (destination / INSTALL_MARKER).is_file():
75
+ raise CliError(f"refusing to overwrite unmanaged skill: {destination}")
76
+ destination_root.mkdir(parents=True, exist_ok=True)
77
+ staged = Path(tempfile.mkdtemp(prefix=f".{source.name}-", dir=destination_root))
78
+ try:
79
+ shutil.copytree(source, staged, dirs_exist_ok=True)
80
+ (staged / INSTALL_MARKER).write_text("managed by jev install-skills\n")
81
+ if destination.exists():
82
+ shutil.rmtree(destination)
83
+ staged.replace(destination)
84
+ except OSError as exc:
85
+ shutil.rmtree(staged, ignore_errors=True)
86
+ raise CliError(f"could not install skill: {exc}") from exc
87
+ installed.append(source.name)
88
+ paths.append(str(destination))
89
+ return {
90
+ "ok": True,
91
+ "scope": "global" if global_install else "local",
92
+ "flavor": "claude" if claude else "agents",
93
+ "destination": str(destination_root),
94
+ "installed": installed,
95
+ "paths": paths,
96
+ }
97
+
98
+
99
+ def read_text(value: str | None) -> str:
100
+ if value is None or value == "-":
101
+ if sys.stdin.isatty():
102
+ raise CliError("state is required as an argument, @file, or stdin")
103
+ return sys.stdin.read()
104
+ if value.startswith("@"):
105
+ try:
106
+ return Path(value[1:]).read_text()
107
+ except OSError as exc:
108
+ raise CliError(f"cannot read state file: {exc}") from exc
109
+ return value
110
+
111
+
112
+ def state_value(value: str | None, force_json: bool) -> Any:
113
+ text = read_text(value)
114
+ if force_json:
115
+ try:
116
+ return json.loads(text)
117
+ except json.JSONDecodeError as exc:
118
+ raise CliError(f"invalid JSON state: {exc}") from exc
119
+ return text
120
+
121
+
122
+ def split_pair(value: str) -> tuple[str, str]:
123
+ key, separator, description = value.partition("=")
124
+ if not separator or not key or not description:
125
+ raise argparse.ArgumentTypeError("expected KEY=DESCRIPTION")
126
+ return key, description
127
+
128
+
129
+ def load_request(path: str) -> dict[str, Any]:
130
+ text = read_text("-" if path == "-" else f"@{path}")
131
+ try:
132
+ payload = json.loads(text)
133
+ except json.JSONDecodeError as exc:
134
+ raise CliError(f"invalid request JSON: {exc}") from exc
135
+ if not isinstance(payload, dict) or "state" not in payload or "questions" not in payload:
136
+ raise CliError("request must be an object containing state and questions")
137
+ return payload
138
+
139
+
140
+ def call(payload: dict[str, Any], endpoint: str) -> dict[str, Any]:
141
+ request = urllib.request.Request(
142
+ endpoint,
143
+ data=json.dumps(payload, ensure_ascii=False).encode(),
144
+ headers={
145
+ "Authorization": f"Bearer {api_key()}",
146
+ "Content-Type": "application/json",
147
+ "User-Agent": "jev-cli/0.4.1",
148
+ },
149
+ method="POST",
150
+ )
151
+ try:
152
+ with urllib.request.urlopen(request, timeout=60) as response:
153
+ result = json.load(response)
154
+ except urllib.error.HTTPError as exc:
155
+ body = exc.read().decode(errors="replace")
156
+ try:
157
+ detail = json.loads(body)
158
+ except json.JSONDecodeError:
159
+ detail = body
160
+ code = 3 if exc.code in (401, 403) else 4 if exc.code in (429, 500, 502, 503, 504) else 1
161
+ raise CliError(f"API HTTP {exc.code}: {json.dumps(detail, ensure_ascii=False)}", code) from exc
162
+ except (urllib.error.URLError, TimeoutError) as exc:
163
+ raise CliError(f"API connection failed: {exc}", 4) from exc
164
+ if not isinstance(result, dict):
165
+ raise CliError("API returned a non-object response", 1)
166
+ return result
167
+
168
+
169
+ def common_parser() -> argparse.ArgumentParser:
170
+ parser = argparse.ArgumentParser(add_help=False)
171
+ parser.add_argument("--model", default="jev-latest", help="model name (default: jev-latest)")
172
+ parser.add_argument("--json-state", action="store_true", help="parse state as JSON")
173
+ parser.add_argument("--pretty", action="store_true", help="pretty-print JSON")
174
+ parser.add_argument("--value", action="store_true", help="print only the primary answer value")
175
+ parser.add_argument("--endpoint", default=os.environ.get("TYPESAFE_API_URL", API_URL), help=argparse.SUPPRESS)
176
+ return parser
177
+
178
+
179
+ def parser() -> argparse.ArgumentParser:
180
+ root = argparse.ArgumentParser(
181
+ prog="jev",
182
+ description="Evaluate text or JSON with TypeSafe Jev. Uses its own local credential store.",
183
+ )
184
+ root.add_argument("--version", action="version", version="jev 0.4.1")
185
+ sub = root.add_subparsers(dest="command", required=True)
186
+ common = common_parser()
187
+
188
+ auth = sub.add_parser("auth", help="manage the API key in the Jev credential store")
189
+ auth_sub = auth.add_subparsers(dest="auth_command", required=True)
190
+ auth_sub.add_parser("set", help="store an API key from a hidden prompt or stdin")
191
+ auth_sub.add_parser("status", help="check whether an API key is stored")
192
+ auth_sub.add_parser("test", help="connect to Jev and verify the API key")
193
+
194
+ skills = sub.add_parser("install-skills", help="install bundled agent skills")
195
+ skills.add_argument("-g", "--global", dest="global_install", action="store_true", help="install in the user home")
196
+ skills.add_argument("--claude", action="store_true", help="install for Claude instead of .agents")
197
+
198
+ noul = sub.add_parser("noul", parents=[common], help="answer one yes/no question with a probability")
199
+ noul.add_argument("-q", "--question", required=True, help="question to answer")
200
+ noul.add_argument("-s", "--state", help="text, @file, or - for stdin (default: stdin)")
201
+
202
+ choice = sub.add_parser("choice", parents=[common], help="choose one option")
203
+ choice.add_argument("-q", "--question", required=True, help="question to answer")
204
+ choice.add_argument("-s", "--state", help="text, @file, or - for stdin (default: stdin)")
205
+ choice.add_argument("-o", "--option", action="append", type=split_pair, required=True, metavar="KEY=DESCRIPTION")
206
+
207
+ score = sub.add_parser("score", parents=[common], help="score against ordered levels")
208
+ score.add_argument("-q", "--question", required=True, help="question to answer")
209
+ score.add_argument("-s", "--state", help="text, @file, or - for stdin (default: stdin)")
210
+ score.add_argument("-l", "--level", action="append", required=True, metavar="DESCRIPTION")
211
+
212
+ run = sub.add_parser("run", parents=[common], help="send a complete request JSON for batched questions")
213
+ run.add_argument("request", help="JSON file or - for stdin")
214
+ return root
215
+
216
+
217
+ def request_for(args: argparse.Namespace) -> tuple[dict[str, Any], str | None]:
218
+ if args.command == "run":
219
+ payload = load_request(args.request)
220
+ payload.setdefault("model", args.model)
221
+ return payload, None
222
+
223
+ question: dict[str, Any] = {"type": args.command, "instructions": args.question}
224
+ if args.command == "choice":
225
+ question["criteria"] = dict(args.option)
226
+ elif args.command == "score":
227
+ question["criteria"] = args.level
228
+ payload = {
229
+ "state": state_value(args.state, args.json_state),
230
+ "model": args.model,
231
+ "questions": {"answer": question},
232
+ }
233
+ return payload, args.command
234
+
235
+
236
+ def primary_value(result: dict[str, Any], kind: str | None) -> Any:
237
+ if kind is None:
238
+ raise CliError("--value is available only for noul, choice, and score")
239
+ answer = result["answers"]["answer"]
240
+ return answer[{"noul": "noul", "choice": "choice", "score": "score"}[kind]]
241
+
242
+
243
+ def main() -> int:
244
+ try:
245
+ args = parser().parse_args()
246
+ if args.command == "auth":
247
+ if args.auth_command == "set":
248
+ set_api_key()
249
+ print(json.dumps({"ok": True, "stored": True, "store": str(CREDENTIALS_FILE)}))
250
+ elif args.auth_command == "test":
251
+ result = call(
252
+ {
253
+ "state": "authentication test",
254
+ "model": "jev-latest",
255
+ "questions": {
256
+ "answer": {
257
+ "type": "noul",
258
+ "instructions": "Is this an authentication test?",
259
+ }
260
+ },
261
+ },
262
+ API_URL,
263
+ )
264
+ print(json.dumps({"ok": True, "valid": True, "model": result.get("model")}))
265
+ else:
266
+ api_key()
267
+ print(json.dumps({"ok": True, "stored": True, "store": str(CREDENTIALS_FILE)}))
268
+ return 0
269
+ if args.command == "install-skills":
270
+ print(json.dumps(install_skills(global_install=args.global_install, claude=args.claude)))
271
+ return 0
272
+ payload, kind = request_for(args)
273
+ result = call(payload, args.endpoint)
274
+ if args.value:
275
+ print(primary_value(result, kind))
276
+ else:
277
+ print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None))
278
+ return 0
279
+ except CliError as exc:
280
+ print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False), file=sys.stderr)
281
+ return exc.exit_code
282
+ except (KeyError, TypeError) as exc:
283
+ print(json.dumps({"ok": False, "error": f"unexpected API response: {exc}"}), file=sys.stderr)
284
+ return 1
285
+
286
+
287
+ if __name__ == "__main__":
288
+ raise SystemExit(main())
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: jev-cli
3
+ description: Use when classifying or scoring state with the `jev` CLI.
4
+ version: 0.1.0
5
+ author: tumf
6
+ license: MIT
7
+ metadata:
8
+ hermes:
9
+ tags: [jev, cli, classification, scoring]
10
+ related_skills: []
11
+ ---
12
+
13
+ # jev-cli
14
+
15
+ ## Overview
16
+
17
+ Use the `jev` command to ask TypeSafe Jev typed questions about text or JSON state. Prefer this skill when a task needs a compact machine-readable probability, one choice from explicit criteria, an ordered score, or several typed answers in one request.
18
+
19
+ The package and repository are named `jev-cli`; the installed command is `jev`.
20
+
21
+ Install this bundled skill with `jev install-skills`. Add `--global` for the user-wide target and `--claude` for Claude skill directories.
22
+
23
+ ## Workflow
24
+
25
+ 1. Confirm the command is installed with `jev --version`. If it is missing, install from the repository root with `make install`. Completion: `jev --version` exits successfully.
26
+ 2. Confirm key availability with `jev auth status`, then use `jev auth test` when API validity must be verified. If absent or invalid, ask the user to configure a TypeSafe API key; never print, infer, or commit it. Completion: the selected command returns `ok: true`.
27
+ 3. Choose the narrowest question type:
28
+ - `noul` for a yes/no probability.
29
+ - `choice` for one key from explicit options.
30
+ - `score` for an ordered numeric level.
31
+ - `run` for a complete request object or multiple questions.
32
+ 4. Send only the state needed for the judgment. TypeSafe receives the submitted state and questions, so do not send secrets or private data without explicit authorization.
33
+ 5. Use default JSON output for automation. Use `--value` only when a scalar is sufficient. Completion: parse the result and account for the exit code before acting on it.
34
+
35
+ Read [references/cli.md](references/cli.md) for copy-paste command forms, inputs, output controls, and exit codes.
36
+
37
+ ## Decision rules
38
+
39
+ - Give `choice` options stable keys and descriptions that make the categories mutually understandable.
40
+ - Give `score` levels in ascending order; the returned number may be fractional.
41
+ - Use `--json-state` only when the state must retain JSON structure.
42
+ - Use `@file` or stdin for long input instead of embedding it in shell arguments.
43
+ - Set `TYPESAFE_API_KEY` only for process-level overrides. The normal credential store remains under the user's config directory.
44
+ - Treat model output as a structured judgment, not verified fact. Keep consequential actions behind the caller's own validation and authorization rules.
45
+
46
+ ## Common pitfalls
47
+
48
+ 1. **Calling the executable `jev-cli`.** The package is `jev-cli`, but the executable is `jev`.
49
+ 2. **Using `--value` with `run`.** Batched requests require the JSON response.
50
+ 3. **Treating transport failures as negative answers.** Exit code `4` means retryable API or network failure, not a model judgment.
51
+ 4. **Leaking state through convenience.** Inspect file and stdin content before submission when it may contain credentials or private records.
52
+ 5. **Embedding an API key in examples.** Use `jev auth set` through stdin or the environment variable without writing its value into scripts or documentation.
53
+
54
+ ## Verification checklist
55
+
56
+ - [ ] `jev --version` succeeds.
57
+ - [ ] `jev auth status` or `jev auth test` succeeds without exposing the key.
58
+ - [ ] The selected question type matches the desired answer shape.
59
+ - [ ] Submitted state contains no unauthorized secret or private data.
60
+ - [ ] The caller handles JSON and nonzero exit codes.
61
+ - [ ] Consequential follow-up actions retain their own authorization checks.
@@ -0,0 +1,142 @@
1
+ # CLI reference
2
+
3
+ ## Authentication
4
+
5
+ Check whether authentication is available without printing the key:
6
+
7
+ ```bash
8
+ jev auth status
9
+ ```
10
+
11
+ Connect to Jev and verify that the resolved API key is accepted:
12
+
13
+ ```bash
14
+ jev auth test
15
+ ```
16
+
17
+ In a terminal, enter the API key at the hidden prompt:
18
+
19
+ ```bash
20
+ jev auth set
21
+ ```
22
+
23
+ For non-interactive automation, stdin remains supported:
24
+
25
+ ```bash
26
+ printf '%s' "$TYPESAFE_API_KEY" | jev auth set
27
+ ```
28
+
29
+ Resolution order:
30
+
31
+ 1. `TYPESAFE_API_KEY`
32
+ 2. `$XDG_CONFIG_HOME/jev-cli/credentials.json`
33
+ 3. `~/.config/jev-cli/credentials.json`
34
+
35
+ ## Typed questions
36
+
37
+ Use `-q` for `--question` and `-s` for `--state` when a shorter command is useful. `--value` has no short form.
38
+
39
+ Return a yes/no probability from `0` to `1`:
40
+
41
+ ```bash
42
+ jev noul --question 'Does this message require an urgent response?' --state 'Please respond today.'
43
+ ```
44
+
45
+ Choose one stable key from explicit criteria:
46
+
47
+ ```bash
48
+ jev choice --question 'Which team should receive this?' --state 'Checkout returns HTTP 500.' \
49
+ -o engineering='Software defect or technical failure' \
50
+ -o sales='Purchase or pricing question'
51
+ ```
52
+
53
+ Score against ordered levels:
54
+
55
+ ```bash
56
+ jev score --question 'How dissatisfied is this customer?' --state 'This is the third failure.' \
57
+ -l 'Not dissatisfied' \
58
+ -l 'Slightly dissatisfied' \
59
+ -l 'Clearly dissatisfied' \
60
+ -l 'Extremely dissatisfied'
61
+ ```
62
+
63
+ Send a complete request from a file:
64
+
65
+ ```bash
66
+ jev run request.json
67
+ ```
68
+
69
+ ## Input forms
70
+
71
+ Literal text:
72
+
73
+ ```bash
74
+ jev noul --question 'Does this require action?' --state 'Please investigate.'
75
+ ```
76
+
77
+ File content:
78
+
79
+ ```bash
80
+ jev noul --question 'Does this require action?' --state @message.txt
81
+ ```
82
+
83
+ Standard input:
84
+
85
+ ```bash
86
+ printf '%s' 'Please investigate.' | jev noul --question 'Does this require action?' --state -
87
+ ```
88
+
89
+ Structured JSON state:
90
+
91
+ ```bash
92
+ printf '%s' '{"message":"Please investigate","priority":"high"}' | \
93
+ jev noul --question 'Does this require action?' --state - --json-state
94
+ ```
95
+
96
+ ## Output controls
97
+
98
+ Pretty JSON:
99
+
100
+ ```bash
101
+ jev noul --question 'Does this require action?' --state 'Please investigate.' --pretty
102
+ ```
103
+
104
+ Primary scalar only:
105
+
106
+ ```bash
107
+ jev noul --question 'Does this require action?' --state 'Please investigate.' --value
108
+ ```
109
+
110
+ `--value` applies to `noul`, `choice`, and `score`, not `run`.
111
+
112
+ ## Exit codes
113
+
114
+ | Code | Meaning |
115
+ |---:|---|
116
+ | `0` | Success |
117
+ | `1` | Invalid response or non-retryable API error |
118
+ | `2` | Local input or credential error |
119
+ | `3` | Authentication or authorization failure |
120
+ | `4` | Retryable network, rate-limit, or server failure |
121
+
122
+ Read stderr as JSON on failure. Do not interpret a nonzero exit as a model answer.
123
+
124
+ ## Installation
125
+
126
+ From the repository root:
127
+
128
+ ```bash
129
+ make install
130
+ jev --version
131
+ ```
132
+
133
+ The project requires Python 3.13 or later and `uv`.
134
+
135
+ Install the bundled `jev-cli` Agent Skill:
136
+
137
+ ```bash
138
+ jev install-skills
139
+ jev install-skills --global
140
+ jev install-skills --claude
141
+ jev install-skills --global --claude
142
+ ```
jev_cli/py.typed ADDED
File without changes
@@ -0,0 +1,302 @@
1
+ Metadata-Version: 2.4
2
+ Name: jev-cli
3
+ Version: 0.4.1
4
+ Summary: Small dependency-free CLI for TypeSafe Jev
5
+ Keywords: typesafe,jev,cli,ai
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: >=3.13
14
+ Project-URL: Homepage, https://github.com/tumf/jev-cli
15
+ Project-URL: Issues, https://github.com/tumf/jev-cli/issues
16
+ Project-URL: Repository, https://github.com/tumf/jev-cli
17
+ Description-Content-Type: text/markdown
18
+
19
+ # jev-cli
20
+
21
+ A small, dependency-free CLI for [TypeSafe Jev](https://docs.typesafe.ai/introduction). Send text or JSON state, ask typed questions, and receive machine-readable `noul`, `choice`, or `score` answers.
22
+
23
+ The `jev` command is useful when application code needs a fast classification or judgment instead of generated prose.
24
+
25
+ > **Unofficial:** This is an independent community project. It is not affiliated with, maintained by, or endorsed by TypeSafe AI.
26
+
27
+ ## Features
28
+
29
+ - Supports all three Jev primitives: `noul`, `choice`, and `score`
30
+ - Sends multiple questions in one request with `run`
31
+ - Accepts text, JSON, files, and stdin
32
+ - Emits compact JSON by default
33
+ - Can print only the primary value for shell scripts
34
+ - Uses structured stderr errors and meaningful exit codes
35
+ - Has no runtime dependencies outside Python's standard library
36
+
37
+ ## Requirements
38
+
39
+ - Python 3.13 or later
40
+ - [uv](https://docs.astral.sh/uv/)
41
+ - GNU Make
42
+ - A TypeSafe API key
43
+
44
+ ## Install
45
+
46
+ Clone the repository and install the command with `uv tool` through the Makefile. This installs `jev` into uv's executable directory and verifies the installed version.
47
+
48
+ ```bash
49
+ git clone https://github.com/tumf/jev-cli.git
50
+ cd jev-cli
51
+ make install
52
+ ```
53
+
54
+ Verify that the command is available:
55
+
56
+ ```bash
57
+ jev --version
58
+ ```
59
+
60
+ Expected output:
61
+
62
+ ```text
63
+ jev 0.4.1
64
+ ```
65
+
66
+ ## Authentication
67
+
68
+ The recommended approach for automation is the `TYPESAFE_API_KEY` environment variable. It takes precedence over the credential file.
69
+
70
+ ```bash
71
+ export TYPESAFE_API_KEY='your-api-key'
72
+ jev auth status
73
+ ```
74
+
75
+ For local use, enter the key at the hidden prompt. `auth set` does not accept the key as a command-line argument, which keeps it out of process arguments and shell history.
76
+
77
+ ```bash
78
+ jev auth set
79
+ jev auth status
80
+ jev auth test
81
+ ```
82
+
83
+ For non-interactive automation, piping the key to `jev auth set` remains supported.
84
+
85
+ `auth status` reports only whether a key is available. `auth test` sends a minimal request to Jev and verifies that the key is accepted. Neither command prints the key.
86
+
87
+ The fallback credential path follows XDG conventions:
88
+
89
+ - `$XDG_CONFIG_HOME/jev-cli/credentials.json` when `XDG_CONFIG_HOME` is set
90
+ - `~/.config/jev-cli/credentials.json` otherwise
91
+
92
+ The credential directory is created with mode `0700`; the file is written atomically with mode `0600`.
93
+
94
+ ## Quick start
95
+
96
+ `--question` and `--state` also accept the short forms `-q` and `-s`. `--value` has no short form.
97
+
98
+ ## Install the bundled Agent Skill
99
+
100
+ `jev-cli` currently bundles the `jev-cli` skill. Install it for the current project or globally:
101
+
102
+ ```bash
103
+ jev install-skills
104
+ jev install-skills --global
105
+ ```
106
+
107
+ Use `--claude` to target Claude's skill directory instead:
108
+
109
+ ```bash
110
+ jev install-skills --claude
111
+ jev install-skills --global --claude
112
+ ```
113
+
114
+ The command prints JSON. It refreshes only copies it previously installed and refuses to overwrite an unmanaged skill directory.
115
+
116
+ Ask whether a message expresses urgency. `--value` prints only the resulting probability from `0` to `1`.
117
+
118
+ ```bash
119
+ jev noul \
120
+ --question 'Does this message express urgency?' \
121
+ --state 'Please restore service today.' \
122
+ --value
123
+ ```
124
+
125
+ Example output:
126
+
127
+ ```text
128
+ 0.98
129
+ ```
130
+
131
+ Without `--value`, the command returns the complete API response as JSON, including model and token usage.
132
+
133
+ ```bash
134
+ jev noul \
135
+ --question 'Does this message express urgency?' \
136
+ --state 'Please restore service today.' \
137
+ --pretty
138
+ ```
139
+
140
+ ## Question types
141
+
142
+ ### Noul: yes/no probability
143
+
144
+ Use `noul` for one focused yes/no judgment. The value is the probability that the answer is yes.
145
+
146
+ ```bash
147
+ jev noul \
148
+ --question 'Does this message request a refund?' \
149
+ --state 'The integration is broken, but I do not want a refund.' \
150
+ --value
151
+ ```
152
+
153
+ ### Choice: select one option
154
+
155
+ Use `choice` when the answer must be one of a known set. Each option uses `KEY=DESCRIPTION` syntax.
156
+
157
+ ```bash
158
+ jev choice \
159
+ --question 'Which team should handle this?' \
160
+ --state 'The payment integration keeps failing.' \
161
+ -o 'billing=Payment, charge, or refund issues' \
162
+ -o 'technical=Bugs or integration failures' \
163
+ -o 'other=None of these' \
164
+ --pretty
165
+ ```
166
+
167
+ ### Score: evaluate ordered levels
168
+
169
+ Use `score` for an ordered scale. Levels are numbered from zero in the order supplied.
170
+
171
+ ```bash
172
+ jev score \
173
+ --question 'How frustrated is the customer?' \
174
+ --state 'This has failed for three days. Please help.' \
175
+ -l 'Calm' \
176
+ -l 'Concerned but civil' \
177
+ -l 'Very angry' \
178
+ --value
179
+ ```
180
+
181
+ ## Input formats
182
+
183
+ ### Standard input
184
+
185
+ Omit the state or pass `-` to read it from stdin. This is useful for pipelines and avoids putting sensitive input in shell history.
186
+
187
+ ```bash
188
+ printf '%s' 'Please resolve this today.' | \
189
+ jev noul --question 'Does this message express urgency?' --value
190
+ ```
191
+
192
+ ### File input
193
+
194
+ Prefix a path with `@` to read its contents.
195
+
196
+ ```bash
197
+ jev noul \
198
+ --question 'Does this document mention security risks?' \
199
+ --state @document.txt \
200
+ --value
201
+ ```
202
+
203
+ ### JSON state
204
+
205
+ Use `--json-state` to parse the state as JSON. Instructions can refer to named fields.
206
+
207
+ ```bash
208
+ printf '%s' '{"message":"Please respond today"}' | \
209
+ jev noul \
210
+ --question 'Does `message` express urgency?' \
211
+ --json-state \
212
+ --value
213
+ ```
214
+
215
+ ## Batch questions
216
+
217
+ Jev evaluates questions independently against the same state. Use `run` to send a complete System One request and avoid one API call per question.
218
+
219
+ Create `request.json`:
220
+
221
+ ```json
222
+ {
223
+ "state": {
224
+ "message": "The payment integration has failed for three days. Please fix it today."
225
+ },
226
+ "model": "jev-latest",
227
+ "questions": {
228
+ "department": {
229
+ "type": "choice",
230
+ "instructions": "Which team should handle `message`?",
231
+ "criteria": {
232
+ "billing": "Payment, charge, or refund issues",
233
+ "technical": "Bugs or integration failures",
234
+ "other": "None of these"
235
+ }
236
+ },
237
+ "urgent": {
238
+ "type": "noul",
239
+ "instructions": "Does `message` express urgency?"
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ Send it in one request:
246
+
247
+ ```bash
248
+ jev run request.json --pretty
249
+ ```
250
+
251
+ A request can also be piped through stdin:
252
+
253
+ ```bash
254
+ cat request.json | jev run - --pretty
255
+ ```
256
+
257
+ ## Output and automation
258
+
259
+ The default stdout is one JSON object. Logs and structured errors go to stderr, so stdout can be piped directly into another program.
260
+
261
+ Use `--value` with `noul`, `choice`, or `score` when a script needs only the primary answer:
262
+
263
+ ```bash
264
+ if awk 'BEGIN { exit !(ARGV[1] >= 0.9) }' \
265
+ "$(jev noul --question 'Is this urgent?' --state 'Restore service today.' --value)"; then
266
+ echo urgent
267
+ fi
268
+ ```
269
+
270
+ Use `--model` to select another model available to the account:
271
+
272
+ ```bash
273
+ jev noul --question 'Is this urgent?' --state 'Restore service today.' \
274
+ --model jev-latest \
275
+ --pretty
276
+ ```
277
+
278
+ ## Exit codes
279
+
280
+ | Code | Meaning |
281
+ |---:|---|
282
+ | `0` | Success |
283
+ | `1` | Unexpected API response or other error |
284
+ | `2` | Invalid arguments or input |
285
+ | `3` | Missing or rejected authentication |
286
+ | `4` | Connection, rate-limit, or transient server error |
287
+
288
+ An error is emitted as JSON on stderr:
289
+
290
+ ```json
291
+ {"ok": false, "error": "TypeSafe API key is not stored; run: jev auth set"}
292
+ ```
293
+
294
+ ## Scope and limitations
295
+
296
+ The `jev` command is a thin client for focused System One judgments. It does not generate prose, perform arithmetic, compare dates, or replace application-level validation. Keep deterministic work in code and use Jev for semantic judgments.
297
+
298
+ The CLI sends the supplied state and questions to the TypeSafe API. Do not submit data that your organization is not permitted to send to that service.
299
+
300
+ ## License
301
+
302
+ [MIT](LICENSE)
@@ -0,0 +1,9 @@
1
+ jev_cli/__init__.py,sha256=7HUg9ytx1qciSS6pktmaSj0dJWU0qD9z1-YIM0R06P8,11869
2
+ jev_cli/bundled_skills/jev-cli/SKILL.md,sha256=w6yne7l1IA02c3rlhldNtJTN650GdE6-0N19duHkgiA,3550
3
+ jev_cli/bundled_skills/jev-cli/references/cli.md,sha256=3t7z046QqYovrRjvsl7_MDwMf_KnH0o-Unx3ADF0dc0,2931
4
+ jev_cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ jev_cli-0.4.1.dist-info/licenses/LICENSE,sha256=oKoTGKD5xnfr8R9BogCNrZGMoJ9oCYg-tLqJxoeDYwQ,1061
6
+ jev_cli-0.4.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
7
+ jev_cli-0.4.1.dist-info/entry_points.txt,sha256=PJeuUSh7E8GY0ifzE742Z7NL_42yPSw_XPYapVdfd0I,38
8
+ jev_cli-0.4.1.dist-info/METADATA,sha256=Dt0i_f3cdc9DLznd4o2f9qqj3v0vV8Y7GVoqtmlBZ3Q,8155
9
+ jev_cli-0.4.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ jev = jev_cli:main
3
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tumf
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.