agent-first-data 0.23.0__tar.gz → 0.25.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.
Files changed (17) hide show
  1. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/PKG-INFO +3 -3
  2. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/README.md +2 -2
  3. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data/__init__.py +2 -0
  4. agent_first_data-0.25.0/agent_first_data/assets/cli-help-v1.schema.json +227 -0
  5. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data/cli.py +6 -13
  6. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data/format.py +57 -0
  7. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data.egg-info/PKG-INFO +3 -3
  8. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data.egg-info/SOURCES.txt +1 -0
  9. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/pyproject.toml +1 -1
  10. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/tests/test_cli.py +25 -10
  11. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/tests/test_format.py +18 -0
  12. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data/assets/protocol-v1.schema.json +0 -0
  13. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data/assets/registry.json +0 -0
  14. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data.egg-info/dependency_links.txt +0 -0
  15. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/agent_first_data.egg-info/top_level.txt +0 -0
  16. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/setup.cfg +0 -0
  17. {agent_first_data-0.23.0 → agent_first_data-0.25.0}/tests/test_no_stderr_policy.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-first-data
3
- Version: 0.23.0
3
+ Version: 0.25.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,7 +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`.
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`, `redact_argv`, `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
32
 
33
33
  ## Behavior Notes
34
34
 
@@ -37,7 +37,7 @@ Useful names use Python casing: `render` (the single value x format x options ->
37
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.
38
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.
39
39
  - `build_cli_error(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
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.
40
+ - Use `cli_handle_version_or_continue()` before argument parsing so `--version` always answers with a structured protocol-v1 `kind:"result"` version event. An explicit `--output` wins; pass the command's normal output as `default_format` (JSON when omitted), plus its value-taking global flag names.
41
41
 
42
42
  ## Reference
43
43
 
@@ -19,7 +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`.
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`, `redact_argv`, `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
23
 
24
24
  ## Behavior Notes
25
25
 
@@ -28,7 +28,7 @@ Useful names use Python casing: `render` (the single value x format x options ->
28
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.
29
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.
30
30
  - `build_cli_error(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
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.
31
+ - Use `cli_handle_version_or_continue()` before argument parsing so `--version` always answers with a structured protocol-v1 `kind:"result"` version event. An explicit `--output` wins; pass the command's normal output as `default_format` (JSON when omitted), plus its value-taking global flag names.
32
32
 
33
33
  ## Reference
34
34
 
@@ -24,6 +24,7 @@ from agent_first_data.format import (
24
24
  PlainStyle,
25
25
  OutputOptions,
26
26
  redacted_value,
27
+ redact_argv,
27
28
  redact_url_secrets,
28
29
  normalize_utc_offset,
29
30
  is_valid_bcp47,
@@ -70,6 +71,7 @@ __all__ = [
70
71
  "PlainStyle",
71
72
  "OutputOptions",
72
73
  "redacted_value",
74
+ "redact_argv",
73
75
  "redact_url_secrets",
74
76
  "normalize_utc_offset",
75
77
  "is_valid_bcp47",
@@ -0,0 +1,227 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://agentfirstkit.org/schemas/agent-first-data/cli-help-v1.schema.json",
4
+ "title": "AFDATA CLI help v1 event",
5
+ "description": "Compact, progressively disclosed CLI help carried by an AFDATA result event.",
6
+ "type": "object",
7
+ "required": [
8
+ "kind",
9
+ "result",
10
+ "trace"
11
+ ],
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "kind": {
15
+ "const": "result"
16
+ },
17
+ "result": {
18
+ "type": "object",
19
+ "required": [
20
+ "code",
21
+ "help"
22
+ ],
23
+ "additionalProperties": false,
24
+ "properties": {
25
+ "code": {
26
+ "const": "help"
27
+ },
28
+ "help": {
29
+ "$ref": "#/$defs/help"
30
+ }
31
+ }
32
+ },
33
+ "trace": {
34
+ "type": "object"
35
+ }
36
+ },
37
+ "$defs": {
38
+ "argument": {
39
+ "type": "object",
40
+ "required": [
41
+ "name"
42
+ ],
43
+ "additionalProperties": false,
44
+ "properties": {
45
+ "name": {
46
+ "type": "string",
47
+ "minLength": 1
48
+ },
49
+ "short": {
50
+ "type": "string",
51
+ "pattern": "^-[^-]$"
52
+ },
53
+ "help": {
54
+ "type": "string",
55
+ "minLength": 1
56
+ },
57
+ "required": {
58
+ "const": true
59
+ },
60
+ "global": {
61
+ "const": true
62
+ },
63
+ "repeatable": {
64
+ "const": true
65
+ },
66
+ "value": {
67
+ "type": "string",
68
+ "minLength": 1
69
+ },
70
+ "values": {
71
+ "type": "array",
72
+ "minItems": 1,
73
+ "items": {
74
+ "type": "string",
75
+ "minLength": 1
76
+ }
77
+ },
78
+ "default": {
79
+ "type": "string"
80
+ },
81
+ "defaults": {
82
+ "type": "array",
83
+ "minItems": 1,
84
+ "items": {
85
+ "type": "string"
86
+ }
87
+ }
88
+ },
89
+ "allOf": [
90
+ {
91
+ "if": {
92
+ "properties": {
93
+ "name": {
94
+ "pattern": "^-"
95
+ }
96
+ }
97
+ },
98
+ "else": {
99
+ "not": {
100
+ "anyOf": [
101
+ {
102
+ "required": [
103
+ "value"
104
+ ]
105
+ },
106
+ {
107
+ "required": [
108
+ "values"
109
+ ]
110
+ }
111
+ ]
112
+ }
113
+ }
114
+ },
115
+ {
116
+ "not": {
117
+ "required": [
118
+ "value",
119
+ "values"
120
+ ]
121
+ }
122
+ },
123
+ {
124
+ "not": {
125
+ "required": [
126
+ "default",
127
+ "defaults"
128
+ ]
129
+ }
130
+ }
131
+ ]
132
+ },
133
+ "command": {
134
+ "type": "object",
135
+ "required": [
136
+ "name"
137
+ ],
138
+ "additionalProperties": false,
139
+ "properties": {
140
+ "name": {
141
+ "type": "string",
142
+ "minLength": 1
143
+ },
144
+ "about": {
145
+ "type": "string",
146
+ "minLength": 1
147
+ },
148
+ "usage": {
149
+ "type": "string",
150
+ "minLength": 1,
151
+ "pattern": "^[^\\n\\r]+$"
152
+ },
153
+ "arguments": {
154
+ "type": "array",
155
+ "minItems": 1,
156
+ "items": {
157
+ "$ref": "#/$defs/argument"
158
+ }
159
+ },
160
+ "subcommands": {
161
+ "type": "array",
162
+ "minItems": 1,
163
+ "items": {
164
+ "$ref": "#/$defs/command"
165
+ }
166
+ }
167
+ }
168
+ },
169
+ "help": {
170
+ "type": "object",
171
+ "required": [
172
+ "scope",
173
+ "command_path",
174
+ "name"
175
+ ],
176
+ "additionalProperties": false,
177
+ "properties": {
178
+ "scope": {
179
+ "enum": [
180
+ "one_level",
181
+ "recursive"
182
+ ]
183
+ },
184
+ "command_path": {
185
+ "type": "string",
186
+ "minLength": 1
187
+ },
188
+ "inherited_arguments_from": {
189
+ "type": "array",
190
+ "minItems": 1,
191
+ "uniqueItems": true,
192
+ "items": {
193
+ "type": "string",
194
+ "minLength": 1
195
+ }
196
+ },
197
+ "name": {
198
+ "type": "string",
199
+ "minLength": 1
200
+ },
201
+ "about": {
202
+ "type": "string",
203
+ "minLength": 1
204
+ },
205
+ "usage": {
206
+ "type": "string",
207
+ "minLength": 1,
208
+ "pattern": "^[^\\n\\r]+$"
209
+ },
210
+ "arguments": {
211
+ "type": "array",
212
+ "minItems": 1,
213
+ "items": {
214
+ "$ref": "#/$defs/argument"
215
+ }
216
+ },
217
+ "subcommands": {
218
+ "type": "array",
219
+ "minItems": 1,
220
+ "items": {
221
+ "$ref": "#/$defs/command"
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
@@ -451,6 +451,8 @@ def cli_handle_version_or_continue(
451
451
  display_name: str | None,
452
452
  version: str,
453
453
  build: str | None,
454
+ *,
455
+ default_format: OutputFormat = OutputFormat.JSON,
454
456
  ) -> str | None:
455
457
  """Render version output if --version/-V is present; otherwise return None.
456
458
 
@@ -461,8 +463,8 @@ def cli_handle_version_or_continue(
461
463
 
462
464
  The one blessed behavior: ``--version``/``-V`` always answers with a
463
465
  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.
466
+ An explicit ``--output <json|yaml|plain>`` wins; otherwise
467
+ ``default_format`` should be the command's normal output default.
466
468
 
467
469
  Only a top-level version request is recognized: scanning stops at the first
468
470
  positional argument (the subcommand), so ``tool sub --version <value>``
@@ -490,19 +492,10 @@ def cli_handle_version_or_continue(
490
492
  break
491
493
 
492
494
  flag_name, inline_value = _split_flag(arg)
493
- if arg in ("--version", "-V"):
495
+ if arg == "--version":
494
496
  version_requested = True
495
497
  i += 1
496
498
  continue
497
- if arg == "--json":
498
- if output_format is not None and output_format is not OutputFormat.JSON:
499
- output_error = ValueError(
500
- "conflicting output formats: --json conflicts with previous output format"
501
- )
502
- else:
503
- output_format = OutputFormat.JSON
504
- i += 1
505
- continue
506
499
  # `--output-to` takes a value but does not affect version output. Consume
507
500
  # its space-separated value so it is not mistaken for the subcommand
508
501
  # boundary (which would hide a later `--version`/`--output`).
@@ -560,7 +553,7 @@ def cli_handle_version_or_continue(
560
553
  display_name,
561
554
  version,
562
555
  build,
563
- output_format if output_format is not None else OutputFormat.JSON,
556
+ output_format if output_format is not None else default_format,
564
557
  )
565
558
 
566
559
 
@@ -613,6 +613,63 @@ def redact_url_secrets(url: str, *, secret_names: Sequence[str] = ()) -> str:
613
613
  return redacted if redacted is not None else url
614
614
 
615
615
 
616
+ def redact_argv(
617
+ args: Sequence[str],
618
+ *,
619
+ secret_names: Sequence[str] = (),
620
+ policy: RedactionPolicy | None = None,
621
+ ) -> list[str]:
622
+ """Redact secret values out of a command line.
623
+
624
+ A long flag whose name is secret by AFDATA naming (``--api-key-secret``, or
625
+ an exact ``secret_names`` entry) has its value replaced by ``***``, in both
626
+ ``--flag=value`` and ``--flag value`` spellings. Everything else is
627
+ preserved byte-for-byte.
628
+
629
+ Free text is deliberately never scanned: a bare ``api_key_secret=sk-live``
630
+ positional, or a secret-looking token after a non-secret flag, is left
631
+ alone. AFDATA decides sensitivity from the *field name*, and argv is no
632
+ exception — rename the flag rather than pattern-matching values. A flag with
633
+ no value (end of argv, or followed by another flag) is likewise left
634
+ inspectable.
635
+
636
+ Only long (``--``) flags are recognized, matching the convention's
637
+ long-flags-only rule.
638
+
639
+ Intended for CLIs that record their own invocation — startup diagnostics,
640
+ audit trails, crash reports — where writing argv verbatim would put a
641
+ credential in the log.
642
+ """
643
+ if policy is RedactionPolicy.Off:
644
+ return list(args)
645
+ context = _RedactionContext.from_names(secret_names)
646
+ out: list[str] = []
647
+ redact_next = False
648
+ for arg in args:
649
+ if redact_next:
650
+ redact_next = False
651
+ if not arg.startswith("-"):
652
+ out.append("***")
653
+ continue
654
+ if arg.startswith("--"):
655
+ rest = arg[2:]
656
+ name, sep, _value = rest.partition("=")
657
+ if sep:
658
+ if _is_secret_flag_name(name, context):
659
+ out.append(f"--{name}=***")
660
+ continue
661
+ elif _is_secret_flag_name(rest, context):
662
+ redact_next = True
663
+ out.append(arg)
664
+ return out
665
+
666
+
667
+ def _is_secret_flag_name(flag_name: str, context: _RedactionContext) -> bool:
668
+ """Whether a long flag's name is secret, normalizing kebab-case to snake_case."""
669
+ normalized = flag_name.replace("-", "_")
670
+ return context.is_secret_key(normalized) or context.is_secret_key(flag_name)
671
+
672
+
616
673
  def _apply_redaction(value: Any, secret_names: Sequence[str], policy: RedactionPolicy | None) -> None:
617
674
  context = _RedactionContext.from_names(secret_names)
618
675
  _apply_redaction_policy_with_context(value, policy, context)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-first-data
3
- Version: 0.23.0
3
+ Version: 0.25.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,7 +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`.
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`, `redact_argv`, `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
32
 
33
33
  ## Behavior Notes
34
34
 
@@ -37,7 +37,7 @@ Useful names use Python casing: `render` (the single value x format x options ->
37
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.
38
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.
39
39
  - `build_cli_error(message, hint?)` returns a strict-ready CLI error with `error.retryable:false` and `trace:{}`.
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.
40
+ - Use `cli_handle_version_or_continue()` before argument parsing so `--version` always answers with a structured protocol-v1 `kind:"result"` version event. An explicit `--output` wins; pass the command's normal output as `default_format` (JSON when omitted), plus its value-taking global flag names.
41
41
 
42
42
  ## Reference
43
43
 
@@ -7,6 +7,7 @@ agent_first_data.egg-info/PKG-INFO
7
7
  agent_first_data.egg-info/SOURCES.txt
8
8
  agent_first_data.egg-info/dependency_links.txt
9
9
  agent_first_data.egg-info/top_level.txt
10
+ agent_first_data/assets/cli-help-v1.schema.json
10
11
  agent_first_data/assets/protocol-v1.schema.json
11
12
  agent_first_data/assets/registry.json
12
13
  tests/test_cli.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-first-data"
3
- version = "0.23.0"
3
+ version = "0.25.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"
@@ -520,9 +520,9 @@ def test_cli_render_version_renders_json():
520
520
  assert '"version":"1.2.3"' in out
521
521
 
522
522
 
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.
523
+ def test_cli_handle_version_bare_inherits_json_default():
524
+ # `--version` answers with a protocol-v1 event and follows the command's
525
+ # declared output default.
526
526
  out = cli_handle_version_or_continue(
527
527
  ["--version"],
528
528
  VERSION_VALUE_FLAGS,
@@ -542,6 +542,21 @@ def test_cli_handle_version_bare_defaults_to_json():
542
542
  assert parsed["trace"] == {}
543
543
 
544
544
 
545
+ def test_cli_handle_version_bare_inherits_plain_default():
546
+ out = cli_handle_version_or_continue(
547
+ ["--version"],
548
+ VERSION_VALUE_FLAGS,
549
+ "agent-cli",
550
+ None,
551
+ "1.2.3",
552
+ None,
553
+ default_format=OutputFormat.PLAIN,
554
+ )
555
+ assert out is not None
556
+ assert "result.code=version" in out
557
+ assert "result.version=1.2.3" in out
558
+
559
+
545
560
  def test_cli_handle_version_honors_explicit_plain_output():
546
561
  out = cli_handle_version_or_continue(
547
562
  ["--version", "--output", "plain"],
@@ -656,9 +671,11 @@ def test_cli_handle_version_supports_inline_output_format():
656
671
  assert 'version: "1.2.3"' in out
657
672
 
658
673
 
659
- def test_cli_handle_version_json_alias():
674
+ def test_cli_handle_version_ignores_json_flag():
675
+ # --json belongs to the application: it must not select a format, and it
676
+ # must not conflict with one. --output yaml still wins.
660
677
  out = cli_handle_version_or_continue(
661
- ["--version", "--json"],
678
+ ["--version", "--json", "--output", "yaml"],
662
679
  VERSION_VALUE_FLAGS,
663
680
  "agent-cli",
664
681
  None,
@@ -666,15 +683,13 @@ def test_cli_handle_version_json_alias():
666
683
  None,
667
684
  )
668
685
  assert out is not None
669
- parsed = json.loads(out.strip())
670
- assert parsed["kind"] == "result"
671
- assert parsed["result"]["version"] == "1.2.3"
686
+ assert out.startswith("---\n")
672
687
 
673
688
 
674
- def test_cli_handle_version_json_alias_conflict():
689
+ def test_cli_handle_version_conflicting_output_formats():
675
690
  with pytest.raises(ValueError, match="conflicting output formats"):
676
691
  cli_handle_version_or_continue(
677
- ["--version", "--json", "--output", "yaml"],
692
+ ["--version", "--output", "json", "--output", "yaml"],
678
693
  VERSION_VALUE_FLAGS,
679
694
  "agent-cli",
680
695
  None,
@@ -25,6 +25,7 @@ from agent_first_data import (
25
25
  OutputOptions,
26
26
  OutputFormat,
27
27
  redacted_value,
28
+ redact_argv,
28
29
  redact_url_secrets,
29
30
  render,
30
31
  normalize_utc_offset,
@@ -64,6 +65,23 @@ def test_redact_url_fixtures():
64
65
  assert got == case["expected"], f"[redact_url/{name}] got {got!r}"
65
66
 
66
67
 
68
+ def test_redact_argv_fixtures():
69
+ for case in _load("redact_argv.json"):
70
+ name = case["name"]
71
+ options = _redaction_options(case)
72
+ got = redact_argv(
73
+ case["input"],
74
+ secret_names=options.secret_names,
75
+ policy=options.policy,
76
+ )
77
+ assert got == case["expected"], f"[redact_argv/{name}] got {got!r}"
78
+
79
+
80
+ def test_redact_argv_default_matches_explicit_defaults():
81
+ args = ["tool", "--api-key-secret=sk-live"]
82
+ assert redact_argv(args) == redact_argv(args, secret_names=(), policy=None)
83
+
84
+
67
85
  def test_redaction_options_fixtures():
68
86
  for case in _load("redaction_options.json"):
69
87
  name = case["name"]