agent-first-data 0.13.0__tar.gz → 0.13.2__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 (18) hide show
  1. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/PKG-INFO +3 -2
  2. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/README.md +2 -1
  3. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data/__init__.py +10 -0
  4. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data/cli.py +84 -0
  5. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data/format.py +55 -3
  6. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data.egg-info/PKG-INFO +3 -2
  7. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/pyproject.toml +1 -1
  8. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/tests/test_cli.py +51 -0
  9. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/tests/test_format.py +8 -0
  10. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data/afdata_logging.py +0 -0
  11. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data/skill.py +0 -0
  12. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data.egg-info/SOURCES.txt +0 -0
  13. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data.egg-info/dependency_links.txt +0 -0
  14. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/agent_first_data.egg-info/top_level.txt +0 -0
  15. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/setup.cfg +0 -0
  16. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/tests/test_afdata_logging.py +0 -0
  17. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/tests/test_no_stderr_policy.py +0 -0
  18. {agent_first_data-0.13.0 → agent_first_data-0.13.2}/tests/test_skill.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-first-data
3
- Version: 0.13.0
3
+ Version: 0.13.2
4
4
  Summary: A naming convention that lets AI agents understand your data without being told what it means.
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
@@ -29,7 +29,7 @@ print(output_json(value))
29
29
  print(output_plain(value))
30
30
  ```
31
31
 
32
- Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `cli_parse_output`, `cli_output`, and `build_cli_error`.
32
+ Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `cli_parse_output`, `cli_output`, `build_cli_error`, `build_cli_version`, and `cli_handle_version_or_continue`.
33
33
 
34
34
  Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
35
35
 
@@ -46,6 +46,7 @@ init_logging_json("INFO", secret_names=("authorization",))
46
46
  - YAML/plain quote and escape keys as well as values, sort by UTF-16 code unit order, and render nested objects in arrays as canonical JSON.
47
47
  - Logging records use `code: "log"` plus a separate `level` field, so error-level logs are not terminal protocol errors.
48
48
  - `build_cli_error(message, hint?)` returns `{code:"error", error: message, hint?}` only.
49
+ - Use `cli_handle_version_or_continue()` before argument parsing so bare `--version` stays conventional and `--version --output json|yaml|plain` stays structured.
49
50
 
50
51
  ## Reference
51
52
 
@@ -20,7 +20,7 @@ print(output_json(value))
20
20
  print(output_plain(value))
21
21
  ```
22
22
 
23
- Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `cli_parse_output`, `cli_output`, and `build_cli_error`.
23
+ Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `cli_parse_output`, `cli_output`, `build_cli_error`, `build_cli_version`, and `cli_handle_version_or_continue`.
24
24
 
25
25
  Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
26
26
 
@@ -37,6 +37,7 @@ init_logging_json("INFO", secret_names=("authorization",))
37
37
  - YAML/plain quote and escape keys as well as values, sort by UTF-16 code unit order, and render nested objects in arrays as canonical JSON.
38
38
  - Logging records use `code: "log"` plus a separate `level` field, so error-level logs are not terminal protocol errors.
39
39
  - `build_cli_error(message, hint?)` returns `{code:"error", error: message, hint?}` only.
40
+ - Use `cli_handle_version_or_continue()` before argument parsing so bare `--version` stays conventional and `--version --output json|yaml|plain` stays structured.
40
41
 
41
42
  ## Reference
42
43
 
@@ -24,6 +24,8 @@ from agent_first_data.format import (
24
24
  redact_url_secrets_with_options,
25
25
  parse_size,
26
26
  normalize_utc_offset,
27
+ is_valid_rfc3339_date,
28
+ is_valid_rfc3339_time,
27
29
  )
28
30
 
29
31
  from agent_first_data.afdata_logging import (
@@ -42,6 +44,9 @@ from agent_first_data.cli import (
42
44
  cli_output,
43
45
  cli_output_with_options,
44
46
  build_cli_error,
47
+ build_cli_version,
48
+ cli_render_version,
49
+ cli_handle_version_or_continue,
45
50
  )
46
51
 
47
52
  from agent_first_data.skill import (
@@ -84,6 +89,8 @@ __all__ = [
84
89
  "redact_url_secrets_with_options",
85
90
  "parse_size",
86
91
  "normalize_utc_offset",
92
+ "is_valid_rfc3339_date",
93
+ "is_valid_rfc3339_time",
87
94
  "AfdataHandler",
88
95
  "init_logging_json",
89
96
  "init_logging_plain",
@@ -96,6 +103,9 @@ __all__ = [
96
103
  "cli_output",
97
104
  "cli_output_with_options",
98
105
  "build_cli_error",
106
+ "build_cli_version",
107
+ "cli_render_version",
108
+ "cli_handle_version_or_continue",
99
109
  "SkillSpec",
100
110
  "SkillAction",
101
111
  "SkillScope",
@@ -7,6 +7,7 @@ from typing import Any
7
7
 
8
8
  from agent_first_data.format import (
9
9
  OutputOptions,
10
+ build_json,
10
11
  output_json,
11
12
  output_json_with_options,
12
13
  output_yaml,
@@ -93,6 +94,89 @@ def cli_output_with_options(
93
94
  return output_json_with_options(value, output_options)
94
95
 
95
96
 
97
+ def build_cli_version(version: str) -> dict:
98
+ """Build a standard CLI version value."""
99
+ return build_json("version", {"version": version})
100
+
101
+
102
+ def cli_render_version(
103
+ name: str,
104
+ version: str,
105
+ format: OutputFormat | None = None,
106
+ ) -> str:
107
+ """Render CLI version output.
108
+
109
+ Pass an OutputFormat for AFDATA JSON/YAML/plain. Pass None to preserve
110
+ conventional "<name> <version>" text.
111
+ """
112
+ rendered = (
113
+ f"{name} {version}" if format is None else cli_output(build_cli_version(version), format)
114
+ )
115
+ return rendered.rstrip("\n") + "\n"
116
+
117
+
118
+ def cli_handle_version_or_continue(
119
+ raw_args: list[str],
120
+ name: str,
121
+ version: str,
122
+ default_output: OutputFormat | None = None,
123
+ output_flag: str = "--output",
124
+ allow_output_format: bool = True,
125
+ ) -> str | None:
126
+ """Render version output if --version/-V is present; otherwise return None.
127
+
128
+ Raises ValueError for malformed version requests, for example
129
+ ``--version --output xml``. The caller should convert that to a CLI error
130
+ with ``build_cli_error``.
131
+ """
132
+ version_requested = False
133
+ output_format: OutputFormat | None = None
134
+ output_error: ValueError | None = None
135
+
136
+ i = 0
137
+ while i < len(raw_args):
138
+ arg = raw_args[i]
139
+ if arg == "--":
140
+ break
141
+ if arg in ("--version", "-V"):
142
+ version_requested = True
143
+ i += 1
144
+ continue
145
+ if allow_output_format and (arg == output_flag or arg.startswith(f"{output_flag}=")):
146
+ value: str | None
147
+ if arg.startswith(f"{output_flag}="):
148
+ value = arg.split("=", 1)[1]
149
+ step = 1
150
+ elif i + 1 < len(raw_args) and not raw_args[i + 1].startswith("-"):
151
+ value = raw_args[i + 1]
152
+ step = 2
153
+ else:
154
+ value = None
155
+ step = 1
156
+ if value is None:
157
+ output_error = ValueError(
158
+ f"missing value for {output_flag}: expected json, yaml, or plain"
159
+ )
160
+ else:
161
+ try:
162
+ output_format = cli_parse_output(value)
163
+ except ValueError as e:
164
+ output_error = e
165
+ i += step
166
+ continue
167
+ i += 1
168
+
169
+ if not version_requested:
170
+ return None
171
+ if output_error is not None:
172
+ raise output_error
173
+ return cli_render_version(
174
+ name,
175
+ version,
176
+ output_format if allow_output_format and output_format is not None else default_output,
177
+ )
178
+
179
+
96
180
  def build_cli_error(message: str, hint: str | None = None) -> dict:
97
181
  """Build a standard CLI parse error value.
98
182
 
@@ -1,10 +1,10 @@
1
1
  """AFDATA output formatting and protocol templates.
2
2
 
3
- 19 public APIs and 4 types: protocol builders, value redactors (copy and
3
+ 21 public APIs and 4 types: protocol builders, value redactors (copy and
4
4
  in-place; cover _secret and _url fields), output formatters, URL-string
5
5
  redactors (redact_url_secrets / _with_options), parse_size,
6
- normalize_utc_offset, RedactionPolicy, RedactionOptions, OutputStyle, and
7
- OutputOptions.
6
+ normalize_utc_offset, is_valid_rfc3339_date, is_valid_rfc3339_time,
7
+ RedactionPolicy, RedactionOptions, OutputStyle, and OutputOptions.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
@@ -293,6 +293,38 @@ def normalize_utc_offset(value: str) -> str | None:
293
293
  return f"{s[0]}{hours:02d}:{minutes:02d}"
294
294
 
295
295
 
296
+ def is_valid_rfc3339_date(value: str) -> bool:
297
+ """Return true when value is an RFC 3339 full-date (YYYY-MM-DD)."""
298
+ if not isinstance(value, str):
299
+ return False
300
+ if len(value) != 10 or value[4] != "-" or value[7] != "-":
301
+ return False
302
+ year = _parse_ascii_int(value[0:4])
303
+ month = _parse_ascii_int(value[5:7])
304
+ day = _parse_ascii_int(value[8:10])
305
+ if year is None or month is None or day is None:
306
+ return False
307
+ return 1 <= month <= 12 and 1 <= day <= _days_in_month(year, month)
308
+
309
+
310
+ def is_valid_rfc3339_time(value: str) -> bool:
311
+ """Return true when value is an RFC 3339 partial-time (HH:MM:SS[.fraction])."""
312
+ if not isinstance(value, str):
313
+ return False
314
+ if len(value) < 8 or value[2] != ":" or value[5] != ":":
315
+ return False
316
+ hour = _parse_ascii_int(value[0:2])
317
+ minute = _parse_ascii_int(value[3:5])
318
+ second = _parse_ascii_int(value[6:8])
319
+ if hour is None or minute is None or second is None:
320
+ return False
321
+ if hour > 23 or minute > 59 or second > 59:
322
+ return False
323
+ if len(value) == 8:
324
+ return True
325
+ return value[8] == "." and len(value) > 9 and value[9:].isdigit()
326
+
327
+
296
328
  def _parse_utc_offset_body(body: str) -> tuple[int, int] | None:
297
329
  if not body:
298
330
  return None
@@ -315,6 +347,26 @@ def _parse_utc_offset_body(body: str) -> tuple[int, int] | None:
315
347
  return None
316
348
 
317
349
 
350
+ def _parse_ascii_int(value: str) -> int | None:
351
+ if not value or not (value.isascii() and value.isdigit()):
352
+ return None
353
+ return int(value)
354
+
355
+
356
+ def _days_in_month(year: int, month: int) -> int:
357
+ if month in (1, 3, 5, 7, 8, 10, 12):
358
+ return 31
359
+ if month in (4, 6, 9, 11):
360
+ return 30
361
+ if month == 2:
362
+ return 29 if _is_leap_year(year) else 28
363
+ return 0
364
+
365
+
366
+ def _is_leap_year(year: int) -> bool:
367
+ return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
368
+
369
+
318
370
  # ═══════════════════════════════════════════
319
371
  # Secret Redaction
320
372
  # ═══════════════════════════════════════════
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-first-data
3
- Version: 0.13.0
3
+ Version: 0.13.2
4
4
  Summary: A naming convention that lets AI agents understand your data without being told what it means.
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/agentfirstkit/agent-first-data
@@ -29,7 +29,7 @@ print(output_json(value))
29
29
  print(output_plain(value))
30
30
  ```
31
31
 
32
- Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `cli_parse_output`, `cli_output`, and `build_cli_error`.
32
+ Useful names use Python casing: `output_json`, `output_yaml`, `output_plain`, `output_json_with_options`, `redacted_value`, `redact_secrets_in_place`, `redact_url_secrets`, `parse_size`, `normalize_utc_offset`, `is_valid_rfc3339_date`, `is_valid_rfc3339_time`, `cli_parse_output`, `cli_output`, `build_cli_error`, `build_cli_version`, and `cli_handle_version_or_continue`.
33
33
 
34
34
  Logging is available through `init_logging_json`, `init_logging_plain`, `init_logging_yaml`, `span`, and `get_logger`.
35
35
 
@@ -46,6 +46,7 @@ init_logging_json("INFO", secret_names=("authorization",))
46
46
  - YAML/plain quote and escape keys as well as values, sort by UTF-16 code unit order, and render nested objects in arrays as canonical JSON.
47
47
  - Logging records use `code: "log"` plus a separate `level` field, so error-level logs are not terminal protocol errors.
48
48
  - `build_cli_error(message, hint?)` returns `{code:"error", error: message, hint?}` only.
49
+ - Use `cli_handle_version_or_continue()` before argument parsing so bare `--version` stays conventional and `--version --output json|yaml|plain` stays structured.
49
50
 
50
51
  ## Reference
51
52
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-first-data"
3
- version = "0.13.0"
3
+ version = "0.13.2"
4
4
  description = "A naming convention that lets AI agents understand your data without being told what it means."
5
5
  license = "MIT"
6
6
  readme = "README.md"
@@ -9,6 +9,9 @@ from agent_first_data import (
9
9
  cli_output,
10
10
  cli_output_with_options,
11
11
  build_cli_error,
12
+ build_cli_version,
13
+ cli_render_version,
14
+ cli_handle_version_or_continue,
12
15
  output_json,
13
16
  )
14
17
 
@@ -120,3 +123,51 @@ def test_cli_output_with_options_dispatches_raw_yaml():
120
123
  )
121
124
  assert "size_bytes: 1024" in out
122
125
  assert "size:" not in out
126
+
127
+
128
+ # ── version helpers ───────────────────────────────────────────────────────────
129
+
130
+ def test_build_cli_version_standard_shape():
131
+ v = build_cli_version("1.2.3")
132
+ assert v["code"] == "version"
133
+ assert v["version"] == "1.2.3"
134
+ assert "trace" not in v
135
+
136
+
137
+ def test_cli_render_version_is_conventional_by_default():
138
+ assert cli_render_version("agent-cli", "1.2.3") == "agent-cli 1.2.3\n"
139
+
140
+
141
+ def test_cli_render_version_can_render_json():
142
+ out = cli_render_version("agent-cli", "1.2.3", OutputFormat.JSON)
143
+ assert out.endswith("\n")
144
+ assert '"code":"version"' in out
145
+ assert '"version":"1.2.3"' in out
146
+
147
+
148
+ def test_cli_handle_version_is_conventional_by_default():
149
+ assert cli_handle_version_or_continue(["--version"], "agent-cli", "1.2.3") == "agent-cli 1.2.3\n"
150
+
151
+
152
+ def test_cli_handle_version_honors_output_flag():
153
+ out = cli_handle_version_or_continue(
154
+ ["--version", "--output", "plain"],
155
+ "agent-cli",
156
+ "1.2.3",
157
+ )
158
+ assert out is not None
159
+ assert "code=version" in out
160
+ assert "version=1.2.3" in out
161
+
162
+
163
+ def test_cli_handle_version_returns_none_without_version():
164
+ assert cli_handle_version_or_continue(["ping"], "agent-cli", "1.2.3") is None
165
+
166
+
167
+ def test_cli_handle_version_rejects_invalid_output():
168
+ with pytest.raises(ValueError, match="xml"):
169
+ cli_handle_version_or_continue(
170
+ ["--version", "--output", "xml"],
171
+ "agent-cli",
172
+ "1.2.3",
173
+ )
@@ -25,6 +25,8 @@ from agent_first_data import (
25
25
  output_plain,
26
26
  output_plain_with_options,
27
27
  normalize_utc_offset,
28
+ is_valid_rfc3339_date,
29
+ is_valid_rfc3339_time,
28
30
  )
29
31
  from agent_first_data.format import (
30
32
  _format_bytes_human,
@@ -149,6 +151,12 @@ def test_helper_fixtures():
149
151
  elif name == "normalize_utc_offset":
150
152
  got = normalize_utc_offset(inp)
151
153
  assert got == expected, f"[helpers/{name}({inp!r})] got {got!r}"
154
+ elif name == "is_valid_rfc3339_date":
155
+ got = is_valid_rfc3339_date(inp)
156
+ assert got == expected, f"[helpers/{name}({inp!r})] got {got!r}"
157
+ elif name == "is_valid_rfc3339_time":
158
+ got = is_valid_rfc3339_time(inp)
159
+ assert got == expected, f"[helpers/{name}({inp!r})] got {got!r}"
152
160
 
153
161
 
154
162
  def test_output_format_fixtures():