agent-first-data 0.20.0__tar.gz → 0.22.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.
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/PKG-INFO +3 -13
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/README.md +2 -12
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data/cli.py +98 -26
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data.egg-info/PKG-INFO +3 -13
- agent_first_data-0.22.0/agent_first_data.egg-info/SOURCES.txt +14 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/pyproject.toml +1 -4
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/tests/test_cli.py +177 -13
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/tests/test_no_stderr_policy.py +0 -2
- agent_first_data-0.20.0/agent_first_data/assets/skills/agent-first-data/SKILL.md +0 -280
- agent_first_data-0.20.0/agent_first_data/assets/skills/agent-first-data/references/protocol-v1.schema.json +0 -66
- agent_first_data-0.20.0/agent_first_data/assets/skills/agent-first-data/references/registry.json +0 -251
- agent_first_data-0.20.0/agent_first_data/assets/skills/agent-first-data/references/rules.md +0 -358
- agent_first_data-0.20.0/agent_first_data/skill.py +0 -663
- agent_first_data-0.20.0/agent_first_data/stream_redirect.py +0 -176
- agent_first_data-0.20.0/agent_first_data.egg-info/SOURCES.txt +0 -22
- agent_first_data-0.20.0/tests/test_skill.py +0 -267
- agent_first_data-0.20.0/tests/test_stream_redirect.py +0 -64
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data/__init__.py +0 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data/assets/protocol-v1.schema.json +0 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data/assets/registry.json +0 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data/format.py +0 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data.egg-info/dependency_links.txt +0 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/agent_first_data.egg-info/top_level.txt +0 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/setup.cfg +0 -0
- {agent_first_data-0.20.0 → agent_first_data-0.22.0}/tests/test_format.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
4
4
|
Summary: A naming convention that lets AI agents understand your data without being told what it means, plus a CLI and library for reading and safely editing structured JSON, TOML, YAML, dotenv, and INI documents.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
@@ -28,15 +28,7 @@ print(render(value, OutputFormat.JSON))
|
|
|
28
28
|
print(render(value, OutputFormat.PLAIN))
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Useful names use Python casing: `render` (the single value x format x options -> str entry point; takes an `OutputFormat` and an optional keyword-only `options=`), `redacted_value`, `redact_url_secrets`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `is_valid_rfc3339`, `is_valid_bcp47`, `cli_parse_output`, `build_cli_error`, `build_cli_version`, `cli_handle_version_or_continue`, `decode_protocol_event`.
|
|
32
|
-
|
|
33
|
-
Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
|
|
34
|
-
|
|
35
|
-
```python
|
|
36
|
-
from agent_first_data import init_logging_json
|
|
37
|
-
|
|
38
|
-
init_logging_json("INFO", secret_names=("authorization",))
|
|
39
|
-
```
|
|
31
|
+
Useful names use Python casing: `render` (the single value x format x options -> str entry point; takes an `OutputFormat` and an optional keyword-only `options=`), `redacted_value`, `redact_url_secrets`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `is_valid_rfc3339`, `is_valid_bcp47`, `cli_parse_output`, `build_cli_error`, `build_cli_version`, `cli_handle_version_or_continue`, `decode_protocol_event`.
|
|
40
32
|
|
|
41
33
|
## Behavior Notes
|
|
42
34
|
|
|
@@ -45,12 +37,10 @@ init_logging_json("INFO", secret_names=("authorization",))
|
|
|
45
37
|
- YAML keeps original keys and values (structure-preserving, like JSON), sorting keys by UTF-16 code unit order and quoting/escaping unsafe keys and string scalars. Plain strips formatting suffixes, formats values, sorts the same way, and renders nested objects/arrays as canonical JSON.
|
|
46
38
|
- Logging records use `kind:"log"` with a nested `log` payload and a separate `level` field, so error-level logs are not terminal protocol errors.
|
|
47
39
|
- `build_cli_error(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
|
|
48
|
-
- Use `cli_handle_version_or_continue()` before argument parsing so
|
|
49
|
-
- Use `agent_first_data.stream_redirect.install_from_raw_args()` before version/help handling if a CLI exposes `--stdout-file` or `--stderr-file`; stderr is redirected as native diagnostics, not JSON.
|
|
40
|
+
- Use `cli_handle_version_or_continue()` before argument parsing so `--version`/`-V` always answers with a structured protocol-v1 `kind:"result"` version event — JSON by default, or `--output yaml|plain`/`--json` for another format; there is no conventional bare-text form. Pass your own value-taking global flag names so their value is not mistaken for the subcommand boundary.
|
|
50
41
|
|
|
51
42
|
## Reference
|
|
52
43
|
|
|
53
|
-
- Full convention and API groups: [docs/overview.md](https://github.com/agentfirstkit/agent-first-data/blob/main/docs/overview.md)
|
|
54
44
|
- Formal cross-language contract: [spec/agent-first-data.md](https://github.com/agentfirstkit/agent-first-data/blob/main/spec/agent-first-data.md)
|
|
55
45
|
- Conformance fixtures: [spec/fixtures](https://github.com/agentfirstkit/agent-first-data/tree/main/spec/fixtures)
|
|
56
46
|
- Agent skill: [skills/agent-first-data/SKILL.md](https://github.com/agentfirstkit/agent-first-data/blob/main/skills/agent-first-data/SKILL.md)
|
|
@@ -19,15 +19,7 @@ print(render(value, OutputFormat.JSON))
|
|
|
19
19
|
print(render(value, OutputFormat.PLAIN))
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Useful names use Python casing: `render` (the single value x format x options -> str entry point; takes an `OutputFormat` and an optional keyword-only `options=`), `redacted_value`, `redact_url_secrets`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `is_valid_rfc3339`, `is_valid_bcp47`, `cli_parse_output`, `build_cli_error`, `build_cli_version`, `cli_handle_version_or_continue`, `decode_protocol_event`.
|
|
23
|
-
|
|
24
|
-
Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
|
|
25
|
-
|
|
26
|
-
```python
|
|
27
|
-
from agent_first_data import init_logging_json
|
|
28
|
-
|
|
29
|
-
init_logging_json("INFO", secret_names=("authorization",))
|
|
30
|
-
```
|
|
22
|
+
Useful names use Python casing: `render` (the single value x format x options -> str entry point; takes an `OutputFormat` and an optional keyword-only `options=`), `redacted_value`, `redact_url_secrets`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `is_valid_rfc3339`, `is_valid_bcp47`, `cli_parse_output`, `build_cli_error`, `build_cli_version`, `cli_handle_version_or_continue`, `decode_protocol_event`.
|
|
31
23
|
|
|
32
24
|
## Behavior Notes
|
|
33
25
|
|
|
@@ -36,12 +28,10 @@ init_logging_json("INFO", secret_names=("authorization",))
|
|
|
36
28
|
- YAML keeps original keys and values (structure-preserving, like JSON), sorting keys by UTF-16 code unit order and quoting/escaping unsafe keys and string scalars. Plain strips formatting suffixes, formats values, sorts the same way, and renders nested objects/arrays as canonical JSON.
|
|
37
29
|
- Logging records use `kind:"log"` with a nested `log` payload and a separate `level` field, so error-level logs are not terminal protocol errors.
|
|
38
30
|
- `build_cli_error(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
|
|
39
|
-
- Use `cli_handle_version_or_continue()` before argument parsing so
|
|
40
|
-
- Use `agent_first_data.stream_redirect.install_from_raw_args()` before version/help handling if a CLI exposes `--stdout-file` or `--stderr-file`; stderr is redirected as native diagnostics, not JSON.
|
|
31
|
+
- Use `cli_handle_version_or_continue()` before argument parsing so `--version`/`-V` always answers with a structured protocol-v1 `kind:"result"` version event — JSON by default, or `--output yaml|plain`/`--json` for another format; there is no conventional bare-text form. Pass your own value-taking global flag names so their value is not mistaken for the subcommand boundary.
|
|
41
32
|
|
|
42
33
|
## Reference
|
|
43
34
|
|
|
44
|
-
- Full convention and API groups: [docs/overview.md](https://github.com/agentfirstkit/agent-first-data/blob/main/docs/overview.md)
|
|
45
35
|
- Formal cross-language contract: [spec/agent-first-data.md](https://github.com/agentfirstkit/agent-first-data/blob/main/spec/agent-first-data.md)
|
|
46
36
|
- Conformance fixtures: [spec/fixtures](https://github.com/agentfirstkit/agent-first-data/tree/main/spec/fixtures)
|
|
47
37
|
- Agent skill: [skills/agent-first-data/SKILL.md](https://github.com/agentfirstkit/agent-first-data/blob/main/skills/agent-first-data/SKILL.md)
|
|
@@ -385,39 +385,84 @@ class CliEmitter:
|
|
|
385
385
|
return self.finish(json_result(payload).build(), 0)
|
|
386
386
|
|
|
387
387
|
|
|
388
|
-
def
|
|
388
|
+
def _split_flag(arg: str) -> tuple[str | None, str | None]:
|
|
389
|
+
"""Split a flag token into its long name and optional inline ``=value``.
|
|
390
|
+
|
|
391
|
+
Mirrors the Rust pre-parser: leading dashes are stripped from the name and a
|
|
392
|
+
bare ``-`` (or an empty name) yields ``(None, None)``. ``--flag`` →
|
|
393
|
+
``("flag", None)``; ``--flag=x`` → ``("flag", "x")`` (``x`` may be ``""``).
|
|
394
|
+
"""
|
|
395
|
+
if not arg.startswith("-") or arg == "-":
|
|
396
|
+
return (None, None)
|
|
397
|
+
flag, sep, value = arg.partition("=")
|
|
398
|
+
name = flag.lstrip("-")
|
|
399
|
+
if not name:
|
|
400
|
+
return (None, None)
|
|
401
|
+
if sep:
|
|
402
|
+
return (name, value)
|
|
403
|
+
return (name, None)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def build_cli_version(
|
|
407
|
+
name: str,
|
|
408
|
+
display_name: str | None,
|
|
409
|
+
version: str,
|
|
410
|
+
build: str | None,
|
|
411
|
+
) -> dict:
|
|
389
412
|
"""Build a standard CLI version result event.
|
|
390
413
|
|
|
391
|
-
The result payload always carries ``code: "version"`` alongside
|
|
392
|
-
``version``, so structured consumers can dispatch on ``code`` the same
|
|
393
|
-
|
|
414
|
+
The result payload always carries ``code: "version"`` alongside ``name`` and
|
|
415
|
+
``version``, so structured consumers can dispatch on ``code`` the same way
|
|
416
|
+
they would for any other result shape. ``name`` is the short/bin identity
|
|
417
|
+
(e.g. ``"afdata"``); ``display_name`` is an optional human-facing product
|
|
418
|
+
name (e.g. ``"Agent-First Data"``); ``build`` is an opaque caller-supplied
|
|
419
|
+
identifier (a git commit SHA, for example) — its meaning is entirely up to
|
|
420
|
+
the caller. Both ``display_name`` and ``build`` are ``None`` when
|
|
421
|
+
unavailable, and simply absent from the payload.
|
|
394
422
|
"""
|
|
395
|
-
|
|
423
|
+
payload: dict[str, Any] = {"code": "version", "name": name, "version": version}
|
|
424
|
+
if display_name is not None:
|
|
425
|
+
payload["display_name"] = display_name
|
|
426
|
+
if build is not None:
|
|
427
|
+
payload["build"] = build
|
|
428
|
+
return json_result(payload).build().to_dict()
|
|
396
429
|
|
|
397
430
|
|
|
398
431
|
def cli_render_version(
|
|
399
432
|
name: str,
|
|
433
|
+
display_name: str | None,
|
|
400
434
|
version: str,
|
|
401
|
-
|
|
435
|
+
build: str | None,
|
|
436
|
+
format: OutputFormat,
|
|
402
437
|
) -> str:
|
|
403
|
-
"""Render CLI version
|
|
438
|
+
"""Render a CLI version response as a protocol-v1 event in ``format``.
|
|
404
439
|
|
|
405
|
-
|
|
406
|
-
|
|
440
|
+
There is no conventional ``"<name> <version>"`` path: ``--version`` always
|
|
441
|
+
answers with the structured event (see :func:`build_cli_version`).
|
|
407
442
|
"""
|
|
408
|
-
rendered = (
|
|
409
|
-
f"{name} {version}" if format is None else render(build_cli_version(version), format)
|
|
410
|
-
)
|
|
443
|
+
rendered = render(build_cli_version(name, display_name, version, build), format)
|
|
411
444
|
return rendered.rstrip("\n") + "\n"
|
|
412
445
|
|
|
413
446
|
|
|
414
|
-
def cli_handle_version_or_continue(
|
|
447
|
+
def cli_handle_version_or_continue(
|
|
448
|
+
raw_args: list[str],
|
|
449
|
+
value_flags: list[str],
|
|
450
|
+
name: str,
|
|
451
|
+
display_name: str | None,
|
|
452
|
+
version: str,
|
|
453
|
+
build: str | None,
|
|
454
|
+
) -> str | None:
|
|
415
455
|
"""Render version output if --version/-V is present; otherwise return None.
|
|
416
456
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
457
|
+
``raw_args`` is ``sys.argv[1:]`` (no program name); scanning starts at index
|
|
458
|
+
0. ``value_flags`` names the caller's own value-taking global flags (with or
|
|
459
|
+
without leading dashes) so their value is never mistaken for the subcommand
|
|
460
|
+
boundary — the Python stand-in for Rust's ``&clap::Command`` lookup.
|
|
461
|
+
|
|
462
|
+
The one blessed behavior: ``--version``/``-V`` always answers with a
|
|
463
|
+
protocol-v1 ``kind:"result"`` version event (see :func:`build_cli_version`) —
|
|
464
|
+
JSON by default, or ``--json`` / ``--output <json|yaml|plain>`` to select
|
|
465
|
+
another format.
|
|
421
466
|
|
|
422
467
|
Only a top-level version request is recognized: scanning stops at the first
|
|
423
468
|
positional argument (the subcommand), so ``tool sub --version <value>``
|
|
@@ -428,6 +473,7 @@ def cli_handle_version_or_continue(raw_args: list[str], name: str, version: str)
|
|
|
428
473
|
``--version --output xml``. The caller should convert that to a CLI error
|
|
429
474
|
with ``build_cli_error``.
|
|
430
475
|
"""
|
|
476
|
+
value_flag_names = {vf.lstrip("-") for vf in value_flags}
|
|
431
477
|
version_requested = False
|
|
432
478
|
output_format: OutputFormat | None = None
|
|
433
479
|
output_error: ValueError | None = None
|
|
@@ -442,6 +488,8 @@ def cli_handle_version_or_continue(raw_args: list[str], name: str, version: str)
|
|
|
442
488
|
# git/cargo/clap: this pre-parser only owns a top-level version request.
|
|
443
489
|
if not arg.startswith("-"):
|
|
444
490
|
break
|
|
491
|
+
|
|
492
|
+
flag_name, inline_value = _split_flag(arg)
|
|
445
493
|
if arg in ("--version", "-V"):
|
|
446
494
|
version_requested = True
|
|
447
495
|
i += 1
|
|
@@ -455,17 +503,25 @@ def cli_handle_version_or_continue(raw_args: list[str], name: str, version: str)
|
|
|
455
503
|
output_format = OutputFormat.JSON
|
|
456
504
|
i += 1
|
|
457
505
|
continue
|
|
458
|
-
|
|
506
|
+
# `--output-to` takes a value but does not affect version output. Consume
|
|
507
|
+
# its space-separated value so it is not mistaken for the subcommand
|
|
508
|
+
# boundary (which would hide a later `--version`/`--output`).
|
|
509
|
+
if flag_name == "output-to":
|
|
510
|
+
has_space_value = (
|
|
511
|
+
inline_value is None
|
|
512
|
+
and i + 1 < len(raw_args)
|
|
513
|
+
and not raw_args[i + 1].startswith("-")
|
|
514
|
+
)
|
|
515
|
+
i += 2 if has_space_value else 1
|
|
516
|
+
continue
|
|
517
|
+
if flag_name == "output":
|
|
459
518
|
value: str | None
|
|
460
|
-
if
|
|
461
|
-
value =
|
|
462
|
-
step = 1
|
|
519
|
+
if inline_value is not None:
|
|
520
|
+
value = inline_value
|
|
463
521
|
elif i + 1 < len(raw_args) and not raw_args[i + 1].startswith("-"):
|
|
464
522
|
value = raw_args[i + 1]
|
|
465
|
-
step = 2
|
|
466
523
|
else:
|
|
467
524
|
value = None
|
|
468
|
-
step = 1
|
|
469
525
|
if value is None:
|
|
470
526
|
output_error = ValueError(
|
|
471
527
|
"missing value for --output: expected json, yaml, or plain"
|
|
@@ -481,15 +537,31 @@ def cli_handle_version_or_continue(raw_args: list[str], name: str, version: str)
|
|
|
481
537
|
output_format = parsed_output
|
|
482
538
|
except ValueError as e:
|
|
483
539
|
output_error = e
|
|
484
|
-
i +=
|
|
540
|
+
i += 1 if (inline_value is not None or value is None) else 2
|
|
485
541
|
continue
|
|
486
|
-
|
|
542
|
+
|
|
543
|
+
# Any other flag: consume a space-separated value only if the caller
|
|
544
|
+
# listed this flag as value-taking, there is no inline `=value`, and the
|
|
545
|
+
# next arg exists and doesn't start with `-`. Otherwise it is a boolean
|
|
546
|
+
# flag and consumes only itself.
|
|
547
|
+
has_space_value = (
|
|
548
|
+
inline_value is None
|
|
549
|
+
and i + 1 < len(raw_args)
|
|
550
|
+
and not raw_args[i + 1].startswith("-")
|
|
551
|
+
)
|
|
552
|
+
i += 2 if (has_space_value and flag_name in value_flag_names) else 1
|
|
487
553
|
|
|
488
554
|
if not version_requested:
|
|
489
555
|
return None
|
|
490
556
|
if output_error is not None:
|
|
491
557
|
raise output_error
|
|
492
|
-
return cli_render_version(
|
|
558
|
+
return cli_render_version(
|
|
559
|
+
name,
|
|
560
|
+
display_name,
|
|
561
|
+
version,
|
|
562
|
+
build,
|
|
563
|
+
output_format if output_format is not None else OutputFormat.JSON,
|
|
564
|
+
)
|
|
493
565
|
|
|
494
566
|
|
|
495
567
|
def build_cli_error(message: str, hint: str | None = None) -> dict | Event:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.22.0
|
|
4
4
|
Summary: A naming convention that lets AI agents understand your data without being told what it means, plus a CLI and library for reading and safely editing structured JSON, TOML, YAML, dotenv, and INI documents.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
|
|
@@ -28,15 +28,7 @@ print(render(value, OutputFormat.JSON))
|
|
|
28
28
|
print(render(value, OutputFormat.PLAIN))
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
Useful names use Python casing: `render` (the single value x format x options -> str entry point; takes an `OutputFormat` and an optional keyword-only `options=`), `redacted_value`, `redact_url_secrets`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `is_valid_rfc3339`, `is_valid_bcp47`, `cli_parse_output`, `build_cli_error`, `build_cli_version`, `cli_handle_version_or_continue`, `decode_protocol_event`.
|
|
32
|
-
|
|
33
|
-
Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
|
|
34
|
-
|
|
35
|
-
```python
|
|
36
|
-
from agent_first_data import init_logging_json
|
|
37
|
-
|
|
38
|
-
init_logging_json("INFO", secret_names=("authorization",))
|
|
39
|
-
```
|
|
31
|
+
Useful names use Python casing: `render` (the single value x format x options -> str entry point; takes an `OutputFormat` and an optional keyword-only `options=`), `redacted_value`, `redact_url_secrets`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `is_valid_rfc3339`, `is_valid_bcp47`, `cli_parse_output`, `build_cli_error`, `build_cli_version`, `cli_handle_version_or_continue`, `decode_protocol_event`.
|
|
40
32
|
|
|
41
33
|
## Behavior Notes
|
|
42
34
|
|
|
@@ -45,12 +37,10 @@ init_logging_json("INFO", secret_names=("authorization",))
|
|
|
45
37
|
- YAML keeps original keys and values (structure-preserving, like JSON), sorting keys by UTF-16 code unit order and quoting/escaping unsafe keys and string scalars. Plain strips formatting suffixes, formats values, sorts the same way, and renders nested objects/arrays as canonical JSON.
|
|
46
38
|
- Logging records use `kind:"log"` with a nested `log` payload and a separate `level` field, so error-level logs are not terminal protocol errors.
|
|
47
39
|
- `build_cli_error(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
|
|
48
|
-
- Use `cli_handle_version_or_continue()` before argument parsing so
|
|
49
|
-
- Use `agent_first_data.stream_redirect.install_from_raw_args()` before version/help handling if a CLI exposes `--stdout-file` or `--stderr-file`; stderr is redirected as native diagnostics, not JSON.
|
|
40
|
+
- Use `cli_handle_version_or_continue()` before argument parsing so `--version`/`-V` always answers with a structured protocol-v1 `kind:"result"` version event — JSON by default, or `--output yaml|plain`/`--json` for another format; there is no conventional bare-text form. Pass your own value-taking global flag names so their value is not mistaken for the subcommand boundary.
|
|
50
41
|
|
|
51
42
|
## Reference
|
|
52
43
|
|
|
53
|
-
- Full convention and API groups: [docs/overview.md](https://github.com/agentfirstkit/agent-first-data/blob/main/docs/overview.md)
|
|
54
44
|
- Formal cross-language contract: [spec/agent-first-data.md](https://github.com/agentfirstkit/agent-first-data/blob/main/spec/agent-first-data.md)
|
|
55
45
|
- Conformance fixtures: [spec/fixtures](https://github.com/agentfirstkit/agent-first-data/tree/main/spec/fixtures)
|
|
56
46
|
- Agent skill: [skills/agent-first-data/SKILL.md](https://github.com/agentfirstkit/agent-first-data/blob/main/skills/agent-first-data/SKILL.md)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
agent_first_data/__init__.py
|
|
4
|
+
agent_first_data/cli.py
|
|
5
|
+
agent_first_data/format.py
|
|
6
|
+
agent_first_data.egg-info/PKG-INFO
|
|
7
|
+
agent_first_data.egg-info/SOURCES.txt
|
|
8
|
+
agent_first_data.egg-info/dependency_links.txt
|
|
9
|
+
agent_first_data.egg-info/top_level.txt
|
|
10
|
+
agent_first_data/assets/protocol-v1.schema.json
|
|
11
|
+
agent_first_data/assets/registry.json
|
|
12
|
+
tests/test_cli.py
|
|
13
|
+
tests/test_format.py
|
|
14
|
+
tests/test_no_stderr_policy.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agent-first-data"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.22.0"
|
|
4
4
|
description = "A naming convention that lets AI agents understand your data without being told what it means, plus a CLI and library for reading and safely editing structured JSON, TOML, YAML, dotenv, and INI documents."
|
|
5
5
|
license = "MIT"
|
|
6
6
|
readme = "README.md"
|
|
@@ -17,7 +17,4 @@ build-backend = "setuptools.build_meta"
|
|
|
17
17
|
[tool.setuptools.package-data]
|
|
18
18
|
agent_first_data = [
|
|
19
19
|
"assets/*.json",
|
|
20
|
-
"assets/skills/agent-first-data/*.md",
|
|
21
|
-
"assets/skills/agent-first-data/references/*.md",
|
|
22
|
-
"assets/skills/agent-first-data/references/*.json",
|
|
23
20
|
]
|
|
@@ -484,36 +484,72 @@ def test_finish_returns_4_on_lifecycle_violation():
|
|
|
484
484
|
|
|
485
485
|
# ── version helpers ───────────────────────────────────────────────────────────
|
|
486
486
|
|
|
487
|
+
# The value-taking global flags a caller passes through so their space-separated
|
|
488
|
+
# value is not mistaken for the subcommand boundary (the Python stand-in for the
|
|
489
|
+
# Rust tests' `version_test_command()` with its `--stdout-file`/`--stderr-file`).
|
|
490
|
+
VERSION_VALUE_FLAGS = ["--stdout-file", "--stderr-file"]
|
|
491
|
+
|
|
492
|
+
|
|
487
493
|
def test_build_cli_version_standard_shape():
|
|
488
|
-
v = build_cli_version("1.2.3")
|
|
494
|
+
v = build_cli_version("agent-cli", "Agent CLI Example", "1.2.3", "abc1234")
|
|
489
495
|
assert v["kind"] == "result"
|
|
490
496
|
assert v["result"]["code"] == "version"
|
|
497
|
+
assert v["result"]["name"] == "agent-cli"
|
|
498
|
+
assert v["result"]["display_name"] == "Agent CLI Example"
|
|
491
499
|
assert v["result"]["version"] == "1.2.3"
|
|
500
|
+
assert v["result"]["build"] == "abc1234"
|
|
492
501
|
# 0.16 spec: all events have trace by default
|
|
493
502
|
assert v["trace"] == {}
|
|
494
503
|
|
|
495
504
|
|
|
496
|
-
def
|
|
497
|
-
|
|
505
|
+
def test_build_cli_version_omits_absent_display_name_and_build():
|
|
506
|
+
v = build_cli_version("agent-cli", None, "1.2.3", None)
|
|
507
|
+
result = v["result"]
|
|
508
|
+
assert result["name"] == "agent-cli"
|
|
509
|
+
assert result["version"] == "1.2.3"
|
|
510
|
+
assert "display_name" not in result
|
|
511
|
+
assert "build" not in result
|
|
498
512
|
|
|
499
513
|
|
|
500
|
-
def
|
|
501
|
-
out = cli_render_version("agent-cli", "1.2.3", OutputFormat.JSON)
|
|
514
|
+
def test_cli_render_version_renders_json():
|
|
515
|
+
out = cli_render_version("agent-cli", None, "1.2.3", None, OutputFormat.JSON)
|
|
502
516
|
assert out.endswith("\n")
|
|
503
517
|
assert '"kind":"result"' in out
|
|
504
518
|
assert '"code":"version"' in out
|
|
519
|
+
assert '"name":"agent-cli"' in out
|
|
505
520
|
assert '"version":"1.2.3"' in out
|
|
506
521
|
|
|
507
522
|
|
|
508
|
-
def
|
|
509
|
-
|
|
523
|
+
def test_cli_handle_version_bare_defaults_to_json():
|
|
524
|
+
# The one blessed behavior: `--version` always answers with a protocol-v1
|
|
525
|
+
# event, JSON by default — no more conventional bare-text special case.
|
|
526
|
+
out = cli_handle_version_or_continue(
|
|
527
|
+
["--version"],
|
|
528
|
+
VERSION_VALUE_FLAGS,
|
|
529
|
+
"agent-cli",
|
|
530
|
+
"Agent CLI Example",
|
|
531
|
+
"1.2.3",
|
|
532
|
+
None,
|
|
533
|
+
)
|
|
534
|
+
assert out is not None
|
|
535
|
+
parsed = json.loads(out.strip())
|
|
536
|
+
assert parsed["kind"] == "result"
|
|
537
|
+
assert parsed["result"]["code"] == "version"
|
|
538
|
+
assert parsed["result"]["name"] == "agent-cli"
|
|
539
|
+
assert parsed["result"]["display_name"] == "Agent CLI Example"
|
|
540
|
+
assert parsed["result"]["version"] == "1.2.3"
|
|
541
|
+
assert "build" not in parsed["result"]
|
|
542
|
+
assert parsed["trace"] == {}
|
|
510
543
|
|
|
511
544
|
|
|
512
|
-
def
|
|
545
|
+
def test_cli_handle_version_honors_explicit_plain_output():
|
|
513
546
|
out = cli_handle_version_or_continue(
|
|
514
547
|
["--version", "--output", "plain"],
|
|
548
|
+
VERSION_VALUE_FLAGS,
|
|
515
549
|
"agent-cli",
|
|
550
|
+
None,
|
|
516
551
|
"1.2.3",
|
|
552
|
+
None,
|
|
517
553
|
)
|
|
518
554
|
assert out is not None
|
|
519
555
|
assert "kind=result" in out
|
|
@@ -521,37 +557,155 @@ def test_cli_handle_version_honors_output_flag():
|
|
|
521
557
|
assert "result.version=1.2.3" in out
|
|
522
558
|
|
|
523
559
|
|
|
560
|
+
def test_cli_handle_version_skips_output_to_space_value():
|
|
561
|
+
# A preceding `--output-to <value>` (space form) must not be mistaken for
|
|
562
|
+
# the subcommand boundary; the later `--version --output json` must still be
|
|
563
|
+
# detected.
|
|
564
|
+
out = cli_handle_version_or_continue(
|
|
565
|
+
["--output-to", "stdout", "--version", "--output", "json"],
|
|
566
|
+
VERSION_VALUE_FLAGS,
|
|
567
|
+
"agent-cli",
|
|
568
|
+
None,
|
|
569
|
+
"1.2.3",
|
|
570
|
+
None,
|
|
571
|
+
)
|
|
572
|
+
assert out is not None
|
|
573
|
+
parsed = json.loads(out.strip())
|
|
574
|
+
assert parsed["kind"] == "result"
|
|
575
|
+
assert parsed["result"]["version"] == "1.2.3"
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def test_cli_handle_version_skips_caller_value_flag_space_value():
|
|
579
|
+
# A caller's own value-taking global flag (here `--stdout-file`/
|
|
580
|
+
# `--stderr-file`): the path value must not be mistaken for the subcommand
|
|
581
|
+
# boundary either.
|
|
582
|
+
out = cli_handle_version_or_continue(
|
|
583
|
+
["--stdout-file", "/tmp/out.log", "--stderr-file", "/tmp/err.log", "--version"],
|
|
584
|
+
VERSION_VALUE_FLAGS,
|
|
585
|
+
"agent-cli",
|
|
586
|
+
None,
|
|
587
|
+
"1.2.3",
|
|
588
|
+
None,
|
|
589
|
+
)
|
|
590
|
+
assert out is not None
|
|
591
|
+
parsed = json.loads(out.strip())
|
|
592
|
+
assert parsed["result"]["name"] == "agent-cli"
|
|
593
|
+
assert parsed["result"]["version"] == "1.2.3"
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def test_cli_handle_version_skips_caller_value_flag_inline_value():
|
|
597
|
+
out = cli_handle_version_or_continue(
|
|
598
|
+
["--stdout-file=/tmp/out.log", "--version"],
|
|
599
|
+
VERSION_VALUE_FLAGS,
|
|
600
|
+
"agent-cli",
|
|
601
|
+
None,
|
|
602
|
+
"1.2.3",
|
|
603
|
+
None,
|
|
604
|
+
)
|
|
605
|
+
assert out is not None
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def test_cli_handle_version_skips_caller_defined_value_flag():
|
|
609
|
+
# A consumer's *own* value-taking global flag the pre-parser has no special
|
|
610
|
+
# knowledge of — here a hypha-style comma-list `--log` — must have its
|
|
611
|
+
# space-separated value recognized through `value_flags`, not a hardcoded
|
|
612
|
+
# allowlist. Otherwise `request,startup` would be read as the subcommand
|
|
613
|
+
# boundary and `--version` dropped.
|
|
614
|
+
out = cli_handle_version_or_continue(
|
|
615
|
+
["--log", "request,startup", "--version"],
|
|
616
|
+
["--log"],
|
|
617
|
+
"hypha",
|
|
618
|
+
None,
|
|
619
|
+
"1.2.3",
|
|
620
|
+
None,
|
|
621
|
+
)
|
|
622
|
+
assert out is not None
|
|
623
|
+
parsed = json.loads(out.strip())
|
|
624
|
+
assert parsed["result"]["name"] == "hypha"
|
|
625
|
+
assert parsed["result"]["version"] == "1.2.3"
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def test_cli_handle_version_boolean_global_flag_does_not_over_consume():
|
|
629
|
+
# The mirror of the case above: a caller's boolean global flag (absent from
|
|
630
|
+
# value_flags) takes no value, so the following positional is the subcommand
|
|
631
|
+
# boundary and a `--version` after it belongs to the subcommand.
|
|
632
|
+
assert (
|
|
633
|
+
cli_handle_version_or_continue(
|
|
634
|
+
["--verbose", "sense", "--version"],
|
|
635
|
+
["--log"],
|
|
636
|
+
"hypha",
|
|
637
|
+
None,
|
|
638
|
+
"1.2.3",
|
|
639
|
+
None,
|
|
640
|
+
)
|
|
641
|
+
is None
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def test_cli_handle_version_supports_inline_output_format():
|
|
646
|
+
out = cli_handle_version_or_continue(
|
|
647
|
+
["--output=yaml", "--version"],
|
|
648
|
+
VERSION_VALUE_FLAGS,
|
|
649
|
+
"agent-cli",
|
|
650
|
+
None,
|
|
651
|
+
"1.2.3",
|
|
652
|
+
None,
|
|
653
|
+
)
|
|
654
|
+
assert out is not None
|
|
655
|
+
assert out.startswith("---\n")
|
|
656
|
+
assert 'version: "1.2.3"' in out
|
|
657
|
+
|
|
658
|
+
|
|
524
659
|
def test_cli_handle_version_json_alias():
|
|
525
660
|
out = cli_handle_version_or_continue(
|
|
526
661
|
["--version", "--json"],
|
|
662
|
+
VERSION_VALUE_FLAGS,
|
|
527
663
|
"agent-cli",
|
|
664
|
+
None,
|
|
528
665
|
"1.2.3",
|
|
666
|
+
None,
|
|
529
667
|
)
|
|
530
668
|
assert out is not None
|
|
531
|
-
|
|
532
|
-
assert
|
|
533
|
-
assert
|
|
669
|
+
parsed = json.loads(out.strip())
|
|
670
|
+
assert parsed["kind"] == "result"
|
|
671
|
+
assert parsed["result"]["version"] == "1.2.3"
|
|
534
672
|
|
|
535
673
|
|
|
536
674
|
def test_cli_handle_version_json_alias_conflict():
|
|
537
675
|
with pytest.raises(ValueError, match="conflicting output formats"):
|
|
538
676
|
cli_handle_version_or_continue(
|
|
539
677
|
["--version", "--json", "--output", "yaml"],
|
|
678
|
+
VERSION_VALUE_FLAGS,
|
|
540
679
|
"agent-cli",
|
|
680
|
+
None,
|
|
541
681
|
"1.2.3",
|
|
682
|
+
None,
|
|
542
683
|
)
|
|
543
684
|
|
|
544
685
|
|
|
545
686
|
def test_cli_handle_version_returns_none_without_version():
|
|
546
|
-
assert
|
|
687
|
+
assert (
|
|
688
|
+
cli_handle_version_or_continue(
|
|
689
|
+
["ping"],
|
|
690
|
+
VERSION_VALUE_FLAGS,
|
|
691
|
+
"agent-cli",
|
|
692
|
+
None,
|
|
693
|
+
"1.2.3",
|
|
694
|
+
None,
|
|
695
|
+
)
|
|
696
|
+
is None
|
|
697
|
+
)
|
|
547
698
|
|
|
548
699
|
|
|
549
700
|
def test_cli_handle_version_rejects_invalid_output():
|
|
550
701
|
with pytest.raises(ValueError, match="xml"):
|
|
551
702
|
cli_handle_version_or_continue(
|
|
552
703
|
["--version", "--output", "xml"],
|
|
704
|
+
VERSION_VALUE_FLAGS,
|
|
553
705
|
"agent-cli",
|
|
706
|
+
None,
|
|
554
707
|
"1.2.3",
|
|
708
|
+
None,
|
|
555
709
|
)
|
|
556
710
|
|
|
557
711
|
|
|
@@ -561,16 +715,22 @@ def test_cli_handle_version_ignores_version_flag_after_subcommand():
|
|
|
561
715
|
assert (
|
|
562
716
|
cli_handle_version_or_continue(
|
|
563
717
|
["hatch", "--version", "1.3.0"],
|
|
718
|
+
VERSION_VALUE_FLAGS,
|
|
564
719
|
"agent-cli",
|
|
720
|
+
None,
|
|
565
721
|
"1.2.3",
|
|
722
|
+
None,
|
|
566
723
|
)
|
|
567
724
|
is None
|
|
568
725
|
)
|
|
569
726
|
assert (
|
|
570
727
|
cli_handle_version_or_continue(
|
|
571
728
|
["hatch", "-V", "1.3.0"],
|
|
729
|
+
VERSION_VALUE_FLAGS,
|
|
572
730
|
"agent-cli",
|
|
731
|
+
None,
|
|
573
732
|
"1.2.3",
|
|
733
|
+
None,
|
|
574
734
|
)
|
|
575
735
|
is None
|
|
576
736
|
)
|
|
@@ -581,8 +741,12 @@ def test_cli_handle_version_honors_output_flag_before_top_level_version():
|
|
|
581
741
|
# --version is still recognized.
|
|
582
742
|
out = cli_handle_version_or_continue(
|
|
583
743
|
["--output", "json", "--version"],
|
|
744
|
+
VERSION_VALUE_FLAGS,
|
|
584
745
|
"agent-cli",
|
|
746
|
+
None,
|
|
585
747
|
"1.2.3",
|
|
748
|
+
None,
|
|
586
749
|
)
|
|
587
750
|
assert out is not None
|
|
588
|
-
|
|
751
|
+
parsed = json.loads(out.strip())
|
|
752
|
+
assert parsed["result"]["version"] == "1.2.3"
|
|
@@ -28,8 +28,6 @@ def test_no_ad_hoc_stderr_writes_in_runtime_sources() -> None:
|
|
|
28
28
|
|
|
29
29
|
violations: list[str] = []
|
|
30
30
|
for path in files:
|
|
31
|
-
if path.name == "stream_redirect.py":
|
|
32
|
-
continue
|
|
33
31
|
for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
|
|
34
32
|
if DISALLOWED.search(line):
|
|
35
33
|
violations.append(f"{path.name}:{lineno}: {line.strip()}")
|