agent-first-data 0.35.0__tar.gz → 0.35.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/PKG-INFO +1 -1
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/format.py +74 -14
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data.egg-info/PKG-INFO +1 -1
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/pyproject.toml +1 -1
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/README.md +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/__init__.py +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/assets/cli-help-v2.schema.json +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/assets/cli-spec-v1.schema.json +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/assets/protocol-v1.schema.json +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/assets/registry.json +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/cli.py +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data.egg-info/SOURCES.txt +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data.egg-info/dependency_links.txt +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data.egg-info/top_level.txt +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/setup.cfg +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/tests/test_cli.py +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/tests/test_format.py +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/tests/test_no_stderr_policy.py +0 -0
- {agent_first_data-0.35.0 → agent_first_data-0.35.1}/tests/test_requires_python_floor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.1
|
|
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 Markdown structure 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
|
|
@@ -1117,7 +1117,7 @@ def _redact_url_value(value: Any, context: _RedactionContext, depth: int) -> Any
|
|
|
1117
1117
|
# ═══════════════════════════════════════════
|
|
1118
1118
|
|
|
1119
1119
|
|
|
1120
|
-
def _redact_url_in_str(s: str, context: _RedactionContext) -> str | None:
|
|
1120
|
+
def _redact_url_in_str(s: str, context: _RedactionContext, depth: int = 0) -> str | None:
|
|
1121
1121
|
"""Redact secret components of a single URL string.
|
|
1122
1122
|
|
|
1123
1123
|
Returns the redacted string when ``s`` is a processable URL, or None when it
|
|
@@ -1127,6 +1127,9 @@ def _redact_url_in_str(s: str, context: _RedactionContext) -> str | None:
|
|
|
1127
1127
|
# Fast path + precondition: a single, whitespace-free, scheme-prefixed URL.
|
|
1128
1128
|
if "://" not in s or not _is_single_url(s):
|
|
1129
1129
|
return None
|
|
1130
|
+
# Bound recursive redirect inspection, failing closed before stack growth.
|
|
1131
|
+
if depth >= 16:
|
|
1132
|
+
return REDACTED_MARKER
|
|
1130
1133
|
scheme_sep = s.find("://")
|
|
1131
1134
|
scheme = s[:scheme_sep]
|
|
1132
1135
|
rest = s[scheme_sep + 3 :]
|
|
@@ -1137,9 +1140,29 @@ def _redact_url_in_str(s: str, context: _RedactionContext) -> str | None:
|
|
|
1137
1140
|
if c in "/?#":
|
|
1138
1141
|
auth_end = i
|
|
1139
1142
|
break
|
|
1143
|
+
# An unencoded '?' or '#' inside a password ends that authority early and
|
|
1144
|
+
# leaves the rest of the password in the query or fragment. When a ':'
|
|
1145
|
+
# precedes that early end and an '@' follows it before the first '/', the
|
|
1146
|
+
# userinfo runs to that '@' — how libpq reads such a connection string.
|
|
1147
|
+
first_slash = rest.find("/")
|
|
1148
|
+
if first_slash == -1:
|
|
1149
|
+
first_slash = len(rest)
|
|
1150
|
+
at = rest.rfind("@", 0, first_slash)
|
|
1151
|
+
if ":" in rest[:auth_end] and at > auth_end:
|
|
1152
|
+
auth_end = len(rest)
|
|
1153
|
+
for i in range(at, len(rest)):
|
|
1154
|
+
if rest[i] in "/?#":
|
|
1155
|
+
auth_end = i
|
|
1156
|
+
break
|
|
1140
1157
|
authority = rest[:auth_end]
|
|
1141
1158
|
remainder = rest[auth_end:]
|
|
1142
1159
|
|
|
1160
|
+
# An unencoded '/' inside a password ends the authority at a "port" that
|
|
1161
|
+
# is not a number, with the '@' somewhere after it. Where the password
|
|
1162
|
+
# ends cannot be known, so the whole value is replaced.
|
|
1163
|
+
if "@" not in authority and _has_non_numeric_port(authority) and "@" in remainder:
|
|
1164
|
+
return REDACTED_MARKER
|
|
1165
|
+
|
|
1143
1166
|
new_authority = _redact_userinfo_password(authority)
|
|
1144
1167
|
|
|
1145
1168
|
# `remainder` is `path[?query][#fragment]`; '#' ends the query, so split the
|
|
@@ -1147,7 +1170,7 @@ def _redact_url_in_str(s: str, context: _RedactionContext) -> str | None:
|
|
|
1147
1170
|
before_fragment, hash_sep, fragment = remainder.partition("#")
|
|
1148
1171
|
path, question_sep, query = before_fragment.partition("?")
|
|
1149
1172
|
if question_sep:
|
|
1150
|
-
new_before_fragment = f"{path}?{_redact_query(query, context)}"
|
|
1173
|
+
new_before_fragment = f"{path}?{_redact_query(query, context, depth)}"
|
|
1151
1174
|
else:
|
|
1152
1175
|
new_before_fragment = before_fragment
|
|
1153
1176
|
# A fragment gets the same treatment as the query: `k=v&k=v` after the '#'
|
|
@@ -1155,7 +1178,19 @@ def _redact_url_in_str(s: str, context: _RedactionContext) -> str | None:
|
|
|
1155
1178
|
# secret-named fragment parameter must not survive where the identically
|
|
1156
1179
|
# named query parameter would not. A fragment that is not in that shape has
|
|
1157
1180
|
# no '=' in its segments and passes through byte-for-byte.
|
|
1158
|
-
|
|
1181
|
+
# A hash route (`#/cb?k=v`) carries its parameters after its first '?',
|
|
1182
|
+
# while a form reader keeps that '?' inside a value; redact both readings:
|
|
1183
|
+
# the fragment as parameters first, so a secret value absorbs its '?',
|
|
1184
|
+
# then whatever follows the first '?' that survives.
|
|
1185
|
+
if not hash_sep:
|
|
1186
|
+
new_fragment = ""
|
|
1187
|
+
else:
|
|
1188
|
+
params = _redact_query(fragment, context, depth)
|
|
1189
|
+
route, route_sep, route_query = params.partition("?")
|
|
1190
|
+
if route_sep:
|
|
1191
|
+
new_fragment = f"#{route}?{_redact_query(route_query, context, depth)}"
|
|
1192
|
+
else:
|
|
1193
|
+
new_fragment = f"#{params}"
|
|
1159
1194
|
|
|
1160
1195
|
return f"{scheme}://{new_authority}{new_before_fragment}{new_fragment}"
|
|
1161
1196
|
|
|
@@ -1306,7 +1341,7 @@ def _redact_userinfo_password(authority: str) -> str:
|
|
|
1306
1341
|
return f"{authority[:colon]}:{REDACTED_MARKER}{authority[at:]}"
|
|
1307
1342
|
|
|
1308
1343
|
|
|
1309
|
-
def _redact_query(query: str, context: _RedactionContext) -> str:
|
|
1344
|
+
def _redact_query(query: str, context: _RedactionContext, depth: int) -> str:
|
|
1310
1345
|
"""Redact the values of secret-named query parameters.
|
|
1311
1346
|
|
|
1312
1347
|
Preserves the raw bytes of every other segment (keys, benign values,
|
|
@@ -1324,11 +1359,33 @@ def _redact_query(query: str, context: _RedactionContext) -> str:
|
|
|
1324
1359
|
name = unquote_plus(raw_key)
|
|
1325
1360
|
if context.is_secret_key(name):
|
|
1326
1361
|
segments.append(f"{raw_key}={REDACTED_MARKER}")
|
|
1362
|
+
continue
|
|
1363
|
+
# A redirect target carries its own parameters: redact a nested URL in
|
|
1364
|
+
# place, or the whole value when it arrived encoded.
|
|
1365
|
+
raw_value = segment[eq + 1 :]
|
|
1366
|
+
value = unquote_plus(raw_value)
|
|
1367
|
+
redacted = _redact_url_in_str(value, context, depth + 1)
|
|
1368
|
+
if redacted is not None and redacted != value:
|
|
1369
|
+
if value == raw_value:
|
|
1370
|
+
segments.append(f"{raw_key}={redacted}")
|
|
1371
|
+
else:
|
|
1372
|
+
segments.append(f"{raw_key}={REDACTED_MARKER}")
|
|
1327
1373
|
else:
|
|
1328
1374
|
segments.append(segment)
|
|
1329
1375
|
return "&".join(segments)
|
|
1330
1376
|
|
|
1331
1377
|
|
|
1378
|
+
def _has_non_numeric_port(authority: str) -> bool:
|
|
1379
|
+
"""True when the authority's text after its last ':' is not a port number.
|
|
1380
|
+
|
|
1381
|
+
A bracketed IPv6 literal without a port ends in ']' and has none.
|
|
1382
|
+
"""
|
|
1383
|
+
colon = authority.rfind(":")
|
|
1384
|
+
if colon == -1 or authority.endswith("]"):
|
|
1385
|
+
return False
|
|
1386
|
+
return not all("0" <= c <= "9" for c in authority[colon + 1 :])
|
|
1387
|
+
|
|
1388
|
+
|
|
1332
1389
|
def _is_single_url(s: str) -> bool:
|
|
1333
1390
|
"""True when ``s`` is a single bare URL, not a URL embedded in prose.
|
|
1334
1391
|
|
|
@@ -1425,20 +1482,23 @@ def _as_int(value: Any) -> int | None:
|
|
|
1425
1482
|
return None
|
|
1426
1483
|
|
|
1427
1484
|
|
|
1428
|
-
def
|
|
1429
|
-
|
|
1430
|
-
|
|
1485
|
+
def _raw_as_int(value: Any) -> int | None:
|
|
1486
|
+
if isinstance(value, _RawNumber):
|
|
1487
|
+
if re.fullmatch(r"-?\d+", value.literal):
|
|
1488
|
+
return int(value.literal)
|
|
1489
|
+
return _as_int(float(value.literal))
|
|
1490
|
+
return _as_int(value)
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
def _as_u64(value: Any) -> int | None:
|
|
1494
|
+
n = _raw_as_int(value)
|
|
1495
|
+
if n is not None and 0 <= n <= 18_446_744_073_709_551_615:
|
|
1431
1496
|
return n
|
|
1432
1497
|
return None
|
|
1433
1498
|
|
|
1434
1499
|
|
|
1435
1500
|
def _as_i64(value: Any) -> int | None:
|
|
1436
|
-
|
|
1437
|
-
if not re.fullmatch(r"-?\d+", value.literal):
|
|
1438
|
-
return None
|
|
1439
|
-
n = int(value.literal)
|
|
1440
|
-
else:
|
|
1441
|
-
n = _as_int(value)
|
|
1501
|
+
n = _raw_as_int(value)
|
|
1442
1502
|
if n is not None and -9_223_372_036_854_775_808 <= n <= 9_223_372_036_854_775_807:
|
|
1443
1503
|
return n
|
|
1444
1504
|
return None
|
|
@@ -1577,7 +1637,7 @@ def _try_process_field(key: str, value: Any) -> tuple[str, str] | None:
|
|
|
1577
1637
|
return None
|
|
1578
1638
|
stripped = _strip_suffix_ci(key, "_bytes")
|
|
1579
1639
|
if stripped is not None:
|
|
1580
|
-
n =
|
|
1640
|
+
n = _as_u64(value)
|
|
1581
1641
|
if n is not None:
|
|
1582
1642
|
return stripped, _format_bytes_human(n)
|
|
1583
1643
|
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-first-data
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.1
|
|
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 Markdown structure 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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agent-first-data"
|
|
3
|
-
version = "0.35.
|
|
3
|
+
version = "0.35.1"
|
|
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 Markdown structure and safely editing structured JSON, TOML, YAML, dotenv, and INI documents."
|
|
5
5
|
license = "MIT"
|
|
6
6
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
{agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/assets/cli-help-v2.schema.json
RENAMED
|
File without changes
|
{agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/assets/cli-spec-v1.schema.json
RENAMED
|
File without changes
|
{agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data/assets/protocol-v1.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_first_data-0.35.0 → agent_first_data-0.35.1}/agent_first_data.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|