axor-cli 0.2.0__tar.gz → 0.3.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.
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 — 2026-04-29
4
+
5
+ ### Added
6
+ - `ConfigCorruptError` raised when `~/.axor/config.toml` exists but cannot
7
+ be parsed. The previous behaviour silently returned `{}` and let the
8
+ next save **overwrite the broken file**, dropping any other adapter's
9
+ saved key. Refusing to write preserves user data; the user is told to
10
+ fix or delete the file.
11
+
12
+ ### Fixed
13
+ - TOML escaping made spec-compliant. The old version only escaped `\` and
14
+ `"`; pasting an API key that contained a newline (or any control byte)
15
+ produced a file that crashed the next `tomllib.load()`. Now handles
16
+ `\n`, `\r`, `\t`, `\b`, `\f`, plus a `\uXXXX` fallback for any other
17
+ control char.
18
+
19
+ ### Changed
20
+ - Model registry refreshed: `claude-sonnet-4-6`, `claude-opus-4-7`,
21
+ `claude-haiku-4-5`. Default is `claude-sonnet-4-6`.
22
+
23
+ ### Constraints
24
+ - Pin bump: `axor-core>=0.4.0,<0.5` (was `>=0.3.0`).
25
+
26
+ ## 0.2.0 — 2026-04-24
27
+
28
+ ### Added
29
+ - `axor-telemetry` integration. New `/telemetry` slash command:
30
+ `status` / `on [--remote]` / `off` / `preview` / `consent`.
31
+ - One-time stderr opt-in banner. Marker file
32
+ `~/.axor/.telemetry_notice_shown` suppresses subsequent prints;
33
+ `AXOR_NO_BANNER=1` suppresses on-demand.
34
+ - `build_session` wires a `TelemetryPipeline` into `GovernedSession`.
35
+ - Optional `[telemetry]` extra: `pip install axor-cli[telemetry]`.
36
+ - 11 new bridge tests + smoke-test fix (44 total).
37
+
38
+ ## 0.1.0 — 2026-04-14
39
+
40
+ Initial release of the `axor` CLI shell.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axor-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: CLI for axor-core governance kernel — governed agent sessions in your terminal
5
5
  Project-URL: Bug Tracker, https://github.com/Bucha11/axor-cli/issues
6
6
  Project-URL: Changelog, https://github.com/Bucha11/axor-cli/releases
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Topic :: Software Development :: Libraries
18
18
  Classifier: Topic :: Terminals
19
19
  Requires-Python: >=3.11
20
- Requires-Dist: axor-core>=0.3.0
20
+ Requires-Dist: axor-core<0.5,>=0.4.0
21
21
  Provides-Extra: all
22
22
  Requires-Dist: axor-claude>=0.1.0; extra == 'all'
23
23
  Requires-Dist: axor-openai>=0.1.0; extra == 'all'
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -17,8 +17,8 @@ _REGISTRY: dict[str, dict[str, Any]] = {
17
17
  "module": "axor_claude",
18
18
  "install": "pip install axor-claude",
19
19
  "env_var": "ANTHROPIC_API_KEY",
20
- "models": ["claude-sonnet-4-5", "claude-opus-4-5", "claude-haiku-4-5"],
21
- "default_model": "claude-sonnet-4-5",
20
+ "models": ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"],
21
+ "default_model": "claude-sonnet-4-6",
22
22
  },
23
23
  "openai": {
24
24
  "module": "axor_openai",
@@ -222,21 +222,69 @@ def _read_config_file() -> dict[str, Any]:
222
222
  return tomllib.load(f) # type: ignore
223
223
 
224
224
 
225
+ class ConfigCorruptError(RuntimeError):
226
+ """Raised when an existing config exists but cannot be parsed."""
227
+
228
+
225
229
  def _load_existing_config() -> dict[str, Any]:
226
- """Load existing config or return empty dict if not available."""
230
+ """Load existing config.
231
+
232
+ - Returns an empty dict when the file does not exist (first-time setup).
233
+ - Returns an empty dict when tomllib is unavailable (best effort).
234
+ - Raises `ConfigCorruptError` when the file exists but cannot be parsed.
235
+ The previous behaviour (silently returning {}) caused `save_to_config`
236
+ to overwrite the broken file, dropping any *other* adapter's saved key.
237
+ Refusing to write preserves user data.
238
+ """
227
239
  if not CONFIG_FILE.exists() or tomllib is None:
228
240
  return {}
229
-
241
+
230
242
  try:
231
243
  return _read_config_file()
232
244
  except Exception as e:
233
- logger.warning("Failed to read existing config: %s", e)
234
- return {}
245
+ logger.error(
246
+ "Refusing to overwrite unreadable config %s (%s). "
247
+ "Fix or delete the file before retrying.",
248
+ CONFIG_FILE, e,
249
+ )
250
+ raise ConfigCorruptError(str(e)) from e
235
251
 
236
252
 
237
253
  def _escape_toml_value(val: str) -> str:
238
- """Escape a string for TOML double-quoted value."""
239
- return val.replace("\\", "\\\\").replace('"', '\\"')
254
+ """Escape a string for a TOML basic (double-quoted) string.
255
+
256
+ Per the TOML spec, a basic string may contain `\\b`, `\\t`, `\\n`, `\\f`,
257
+ `\\r`, `\\"`, and `\\\\` literal escapes; any other control character
258
+ (U+0000–U+001F except those listed) must be `\\uXXXX`-escaped. Plain
259
+ `replace("\\\\", "\\\\\\\\").replace('"', '\\\\"')` drops a newline or
260
+ NUL into the TOML output as a literal byte, producing a file that
261
+ crashes the next `tomllib.load()`.
262
+
263
+ Pasted API keys generally do not contain newlines, but if a user pastes
264
+ "sk-...\\n" by accident the previous escape would brick the config.
265
+ """
266
+ out: list[str] = []
267
+ for ch in val:
268
+ code = ord(ch)
269
+ if ch == "\\":
270
+ out.append("\\\\")
271
+ elif ch == '"':
272
+ out.append('\\"')
273
+ elif ch == "\n":
274
+ out.append("\\n")
275
+ elif ch == "\r":
276
+ out.append("\\r")
277
+ elif ch == "\t":
278
+ out.append("\\t")
279
+ elif ch == "\b":
280
+ out.append("\\b")
281
+ elif ch == "\f":
282
+ out.append("\\f")
283
+ elif code < 0x20 or code == 0x7F:
284
+ out.append(f"\\u{code:04X}")
285
+ else:
286
+ out.append(ch)
287
+ return "".join(out)
240
288
 
241
289
 
242
290
  def _serialize_config_to_toml(data: dict[str, Any]) -> str:
@@ -8,7 +8,7 @@ Usage:
8
8
  axor claude "refactor auth module" # single task and exit
9
9
  axor claude --policy readonly # with preset policy
10
10
  axor claude --limit 100000 # with soft token limit
11
- axor claude --model claude-opus-4-5 # specific model
11
+ axor claude --model claude-opus-4-7 # specific model
12
12
  axor --list-adapters # show available adapters
13
13
  """
14
14
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "axor-cli"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "CLI for axor-core governance kernel — governed agent sessions in your terminal"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -22,10 +22,10 @@ classifiers = [
22
22
  ]
23
23
 
24
24
  # axor-core required; adapters are optional — install what you need.
25
- # >=0.3.0 because build_session forwards the `telemetry` kwarg that was
26
- # added to GovernedSession in that release.
25
+ # >=0.4.0 because PolicyComposer federation invariant + keyword_relevance
26
+ # public API are used by the model/adapter wiring.
27
27
  dependencies = [
28
- "axor-core>=0.3.0",
28
+ "axor-core>=0.4.0,<0.5",
29
29
  ]
30
30
 
31
31
  [project.optional-dependencies]
@@ -127,3 +127,40 @@ def test_load_corrupted_file_returns_none(tmp_home):
127
127
  auth_mod.CONFIG_FILE.write_text("not valid [[[ toml")
128
128
  result = auth_mod.load_from_config("claude")
129
129
  assert result is None
130
+
131
+
132
+ # ── escape / corruption ──────────────────────────────────────────────────────
133
+
134
+
135
+ def test_key_with_newline_survives_roundtrip(tmp_home):
136
+ """Regression: previously a `\\n` in the key produced a TOML file that
137
+ parsed as a broken multi-line string, bricking the config until manual fix.
138
+ """
139
+ weird = "sk-line1\nline2\rline3\twith-tab"
140
+ auth_mod.save_to_config("claude", weird)
141
+ assert auth_mod.load_from_config("claude") == weird
142
+
143
+
144
+ def test_key_with_nul_byte_survives_roundtrip(tmp_home):
145
+ """NUL is invalid in TOML basic strings; must be \\u-escaped."""
146
+ weird = "sk-with\x00nul"
147
+ auth_mod.save_to_config("claude", weird)
148
+ assert auth_mod.load_from_config("claude") == weird
149
+
150
+
151
+ def test_key_with_control_chars_survives_roundtrip(tmp_home):
152
+ weird = "sk-\x01\x02\x1ftest"
153
+ auth_mod.save_to_config("claude", weird)
154
+ assert auth_mod.load_from_config("claude") == weird
155
+
156
+
157
+ def test_save_refuses_to_overwrite_unparseable_config(tmp_home):
158
+ """If the existing config is corrupt, refuse to save — overwriting
159
+ silently would drop other adapters' keys.
160
+ """
161
+ auth_mod.CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
162
+ auth_mod.CONFIG_FILE.write_text("[[ this is broken")
163
+ with pytest.raises(auth_mod.ConfigCorruptError):
164
+ auth_mod.save_to_config("claude", "sk-new")
165
+ # File contents preserved.
166
+ assert "[[ this is broken" in auth_mod.CONFIG_FILE.read_text()
@@ -1,17 +0,0 @@
1
- # Changelog
2
-
3
- ## 0.1.0 — 2025-04-12
4
-
5
- Initial release.
6
-
7
- ### Added
8
- - Core governance kernel (axor-core)
9
- - Claude Code adapter (axor-claude)
10
- - CLI with interactive REPL (axor-cli)
11
- - Dynamic policy selection (7-policy matrix)
12
- - ContextManager with 11 waste categories
13
- - ToolResultBus for async tool loop
14
- - Federation via spawn_child with child_executor
15
- - CancelToken cooperative cancellation
16
- - BudgetPolicyEngine (60/80/90/95% thresholds)
17
- - TraceCollector with lineage (17 event kinds)
@@ -1 +0,0 @@
1
- __version__ = "0.2.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes