datoon 1.9.1__tar.gz → 3.0.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 (140) hide show
  1. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/package-installer-smoke.yml +2 -2
  2. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/pages.yml +1 -1
  3. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/pre-commit.yml +1 -1
  4. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/publish-mcp.yml +1 -1
  5. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/publish.yml +1 -1
  6. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/release.yml +3 -3
  7. datoon-3.0.0/.github/workflows/tests.yml +24 -0
  8. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/validate-artifacts.yml +2 -2
  9. {datoon-1.9.1 → datoon-3.0.0}/.github/workflows/workflow-lint.yml +2 -2
  10. {datoon-1.9.1 → datoon-3.0.0}/.gitignore +1 -0
  11. {datoon-1.9.1 → datoon-3.0.0}/CHANGELOG.md +39 -0
  12. {datoon-1.9.1 → datoon-3.0.0}/CLAUDE.md +8 -5
  13. {datoon-1.9.1 → datoon-3.0.0}/CONTRIBUTING.md +2 -8
  14. {datoon-1.9.1 → datoon-3.0.0}/INSTALL.md +24 -17
  15. {datoon-1.9.1 → datoon-3.0.0}/PKG-INFO +35 -12
  16. {datoon-1.9.1 → datoon-3.0.0}/README.md +28 -10
  17. {datoon-1.9.1/plugins/datoon/skills/datoon → datoon-3.0.0}/SKILL.md +1 -1
  18. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/generate_payloads.py +3 -6
  19. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/run.py +17 -5
  20. {datoon-1.9.1 → datoon-3.0.0/datoon}/SKILL.md +1 -1
  21. datoon-3.0.0/datoon.skill +0 -0
  22. {datoon-1.9.1 → datoon-3.0.0}/docs/index.html +2 -3
  23. {datoon-1.9.1 → datoon-3.0.0/plugins/datoon}/skills/datoon/SKILL.md +1 -1
  24. {datoon-1.9.1 → datoon-3.0.0}/pyproject.toml +6 -5
  25. {datoon-1.9.1 → datoon-3.0.0}/scripts/ab_skill_benchmark.py +3 -4
  26. {datoon-1.9.1 → datoon-3.0.0}/scripts/install.py +3 -4
  27. {datoon-1.9.1 → datoon-3.0.0}/scripts/summarize_agent_skill_eval.py +0 -1
  28. {datoon-1.9.1 → datoon-3.0.0}/scripts/validate_plugin_metadata.py +1 -2
  29. {datoon-1.9.1 → datoon-3.0.0}/scripts/validate_skill_sync.py +1 -3
  30. {datoon-1.9.1 → datoon-3.0.0}/setup.sh +5 -15
  31. {datoon-1.9.1 → datoon-3.0.0}/skills/datoon/README.md +1 -2
  32. {datoon-1.9.1 → datoon-3.0.0/skills}/datoon/SKILL.md +1 -1
  33. {datoon-1.9.1 → datoon-3.0.0}/smithery.yaml +1 -2
  34. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/__init__.py +9 -5
  35. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/_version.py +3 -3
  36. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/cli.py +13 -13
  37. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/converter.py +26 -55
  38. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/mcp_server.py +40 -10
  39. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/models.py +9 -10
  40. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/columnar.py +1 -1
  41. datoon-3.0.0/src/datoon/tokenizers.py +298 -0
  42. {datoon-1.9.1 → datoon-3.0.0}/src/datoon.egg-info/PKG-INFO +35 -12
  43. {datoon-1.9.1 → datoon-3.0.0}/src/datoon.egg-info/SOURCES.txt +4 -1
  44. {datoon-1.9.1 → datoon-3.0.0}/src/datoon.egg-info/requires.txt +8 -1
  45. {datoon-1.9.1 → datoon-3.0.0}/src/datoon.egg-info/scm_file_list.json +100 -97
  46. datoon-3.0.0/src/datoon.egg-info/scm_version.json +8 -0
  47. datoon-3.0.0/tests/conftest.py +11 -0
  48. datoon-3.0.0/tests/integration/test_toon_roundtrip.py +56 -0
  49. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_cli.py +52 -27
  50. datoon-3.0.0/tests/unit/test_converter_policy.py +272 -0
  51. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_errors.py +1 -1
  52. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_mcp_server.py +58 -18
  53. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_models.py +15 -7
  54. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_readers.py +2 -10
  55. datoon-3.0.0/tests/unit/test_tokenizers.py +366 -0
  56. datoon-3.0.0/tests/unit/test_tokens.py +75 -0
  57. {datoon-1.9.1 → datoon-3.0.0}/uv.lock +537 -231
  58. datoon-1.9.1/.github/workflows/tests.yml +0 -43
  59. datoon-1.9.1/datoon.skill +0 -0
  60. datoon-1.9.1/src/datoon.egg-info/scm_version.json +0 -8
  61. datoon-1.9.1/tests/integration/test_toon_cli_integration.py +0 -68
  62. datoon-1.9.1/tests/unit/test_converter_policy.py +0 -191
  63. datoon-1.9.1/tests/unit/test_tokens.py +0 -43
  64. {datoon-1.9.1 → datoon-3.0.0}/.agents/plugins/marketplace.json +0 -0
  65. {datoon-1.9.1 → datoon-3.0.0}/.claude-plugin/marketplace.json +0 -0
  66. {datoon-1.9.1 → datoon-3.0.0}/.claude-plugin/plugin.json +0 -0
  67. {datoon-1.9.1 → datoon-3.0.0}/.editorconfig +0 -0
  68. {datoon-1.9.1 → datoon-3.0.0}/.gitattributes +0 -0
  69. {datoon-1.9.1 → datoon-3.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  70. {datoon-1.9.1 → datoon-3.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  71. {datoon-1.9.1 → datoon-3.0.0}/.github/dependabot.yml +0 -0
  72. {datoon-1.9.1 → datoon-3.0.0}/.pre-commit-config.yaml +0 -0
  73. {datoon-1.9.1 → datoon-3.0.0}/.releaserc.yaml +0 -0
  74. {datoon-1.9.1 → datoon-3.0.0}/LICENSE +0 -0
  75. {datoon-1.9.1 → datoon-3.0.0}/MARKETPLACES.md +0 -0
  76. {datoon-1.9.1 → datoon-3.0.0}/SECURITY.md +0 -0
  77. {datoon-1.9.1 → datoon-3.0.0}/assets/datoon-banner.svg +0 -0
  78. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/REPORT.md +0 -0
  79. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/agent_results.json +0 -0
  80. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/expected_answers.json +0 -0
  81. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/1_large.json +0 -0
  82. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/1_medium.json +0 -0
  83. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/1_small.json +0 -0
  84. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/2_large.json +0 -0
  85. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/2_medium.json +0 -0
  86. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/2_small.json +0 -0
  87. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/3_large.json +0 -0
  88. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/3_medium.json +0 -0
  89. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/payloads/3_small.json +0 -0
  90. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/1_large.report.json +0 -0
  91. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/1_medium.report.json +0 -0
  92. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/1_small.report.json +0 -0
  93. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/2_large.report.json +0 -0
  94. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/2_medium.report.json +0 -0
  95. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/2_small.report.json +0 -0
  96. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/3_large.report.json +0 -0
  97. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/3_medium.report.json +0 -0
  98. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/reports/3_small.report.json +0 -0
  99. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/scored_agent_results.json +0 -0
  100. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/1_large.toon +0 -0
  101. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/1_medium.toon +0 -0
  102. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/1_small.toon +0 -0
  103. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/2_large.toon +0 -0
  104. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/2_medium.toon +0 -0
  105. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/2_small.toon +0 -0
  106. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/3_large.toon +0 -0
  107. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/3_medium.toon +0 -0
  108. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/agent_skill_eval/toon/3_small.toon +0 -0
  109. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/payloads.json +0 -0
  110. {datoon-1.9.1 → datoon-3.0.0}/benchmarks/results/.gitkeep +0 -0
  111. {datoon-1.9.1 → datoon-3.0.0}/docs/.nojekyll +0 -0
  112. {datoon-1.9.1 → datoon-3.0.0}/docs/app.js +0 -0
  113. {datoon-1.9.1 → datoon-3.0.0}/docs/assets/datoon-banner.svg +0 -0
  114. {datoon-1.9.1 → datoon-3.0.0}/docs/styles.css +0 -0
  115. {datoon-1.9.1 → datoon-3.0.0}/glama.json +0 -0
  116. {datoon-1.9.1 → datoon-3.0.0}/install.sh +0 -0
  117. {datoon-1.9.1 → datoon-3.0.0}/plugins/datoon/.codex-plugin/plugin.json +0 -0
  118. {datoon-1.9.1 → datoon-3.0.0}/server.json +0 -0
  119. {datoon-1.9.1 → datoon-3.0.0}/setup.cfg +0 -0
  120. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/analyzer.py +0 -0
  121. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/errors.py +0 -0
  122. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/py.typed +0 -0
  123. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/__init__.py +0 -0
  124. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/_coerce.py +0 -0
  125. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/_tabular.py +0 -0
  126. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/csv.py +0 -0
  127. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/excel.py +0 -0
  128. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/jsonl.py +0 -0
  129. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/numbers.py +0 -0
  130. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/xml.py +0 -0
  131. {datoon-1.9.1 → datoon-3.0.0}/src/datoon/readers/yaml.py +0 -0
  132. {datoon-1.9.1 → datoon-3.0.0}/src/datoon.egg-info/dependency_links.txt +0 -0
  133. {datoon-1.9.1 → datoon-3.0.0}/src/datoon.egg-info/entry_points.txt +0 -0
  134. {datoon-1.9.1 → datoon-3.0.0}/src/datoon.egg-info/top_level.txt +0 -0
  135. {datoon-1.9.1 → datoon-3.0.0}/tests/fixtures/users.expected.toon +0 -0
  136. {datoon-1.9.1 → datoon-3.0.0}/tests/fixtures/users.json +0 -0
  137. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_analyzer.py +0 -0
  138. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_benchmark_readme.py +0 -0
  139. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_installer.py +0 -0
  140. {datoon-1.9.1 → datoon-3.0.0}/tests/unit/test_tabular_rows.py +0 -0
@@ -15,10 +15,10 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v7
18
+ uses: actions/checkout@v7.0.1
19
19
 
20
20
  - name: Setup Python
21
- uses: actions/setup-python@v6
21
+ uses: actions/setup-python@v7
22
22
  with:
23
23
  python-version: "3.12"
24
24
 
@@ -26,7 +26,7 @@ jobs:
26
26
  runs-on: ubuntu-latest
27
27
  steps:
28
28
  - name: Checkout
29
- uses: actions/checkout@v7
29
+ uses: actions/checkout@v7.0.1
30
30
 
31
31
  - name: Setup Pages
32
32
  id: setup_pages
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v7
18
+ uses: actions/checkout@v7.0.1
19
19
 
20
20
  - name: Setup uv
21
21
  uses: astral-sh/setup-uv@v7
@@ -26,7 +26,7 @@ jobs:
26
26
  || github.event.workflow_run.conclusion == 'success'
27
27
  runs-on: ubuntu-latest
28
28
  steps:
29
- - uses: actions/checkout@v7
29
+ - uses: actions/checkout@v7.0.1
30
30
  with:
31
31
  ref: main
32
32
  fetch-depth: 0
@@ -24,7 +24,7 @@ jobs:
24
24
  outputs:
25
25
  version: ${{ steps.ver.outputs.version }}
26
26
  steps:
27
- - uses: actions/checkout@v7
27
+ - uses: actions/checkout@v7.0.1
28
28
  with:
29
29
  ref: main
30
30
  fetch-depth: 0
@@ -15,17 +15,17 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v7
18
+ uses: actions/checkout@v7.0.1
19
19
  with:
20
20
  fetch-depth: 0
21
21
 
22
22
  - name: Setup Node.js
23
- uses: actions/setup-node@v6
23
+ uses: actions/setup-node@v7
24
24
  with:
25
25
  node-version: "22"
26
26
 
27
27
  - name: Setup Python
28
- uses: actions/setup-python@v6
28
+ uses: actions/setup-python@v7
29
29
  with:
30
30
  python-version: "3.12"
31
31
 
@@ -0,0 +1,24 @@
1
+ name: Tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ concurrency:
10
+ group: tests-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ unit-tests:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v7.0.1
19
+
20
+ - name: Setup uv
21
+ uses: astral-sh/setup-uv@v7
22
+
23
+ - name: Run tests
24
+ run: uv run --with ".[dev]" pytest
@@ -18,10 +18,10 @@ jobs:
18
18
  runs-on: ubuntu-latest
19
19
  steps:
20
20
  - name: Checkout
21
- uses: actions/checkout@v7
21
+ uses: actions/checkout@v7.0.1
22
22
 
23
23
  - name: Setup Python
24
- uses: actions/setup-python@v6
24
+ uses: actions/setup-python@v7
25
25
  with:
26
26
  python-version: "3.12"
27
27
 
@@ -22,7 +22,7 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  steps:
24
24
  - name: Checkout
25
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
25
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
26
26
  with:
27
27
  persist-credentials: false
28
28
 
@@ -35,7 +35,7 @@ jobs:
35
35
  runs-on: ubuntu-latest
36
36
  steps:
37
37
  - name: Checkout
38
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
38
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
39
39
  with:
40
40
  persist-credentials: false
41
41
 
@@ -40,6 +40,7 @@ benchmarks/results/**/*.json
40
40
  # Local Codex/Claude artifacts
41
41
  .agents/skills/
42
42
  .claude/
43
+ .omc/
43
44
  skills-lock.json
44
45
 
45
46
  # Generated by setuptools-scm during local builds/tests
@@ -1,3 +1,42 @@
1
+ # [3.0.0](https://github.com/andrii-su/datoon/compare/v2.0.0...v3.0.0) (2026-11-11)
2
+
3
+
4
+ * feat!: selectable token counters (auto, chars, tiktoken, claude, hf, callable) ([cab2bfb](https://github.com/andrii-su/datoon/commit/cab2bfb9f1106f2a4cf40122588249acb0371f94))
5
+
6
+
7
+ ### Bug Fixes
8
+
9
+ * **mcp:** accept only local token counters as a tool argument ([1f2e54d](https://github.com/andrii-su/datoon/commit/1f2e54d4f83ea266590933f0762019d2d37f7270))
10
+
11
+
12
+ ### BREAKING CHANGES
13
+
14
+ * `ConversionConfig.token_encoding` is replaced by
15
+ `tokenizer`; the CLI flag `--encoding` and the MCP `encoding` argument are
16
+ replaced by `--tokenizer` / `tokenizer` (default from `DATOON_TOKENIZER`,
17
+ else `auto`). `ConversionReport` gains a `tokenizer` field. Every counter
18
+ returns at least 1, including for empty text.
19
+
20
+ # [2.0.0](https://github.com/andrii-su/datoon/compare/v1.9.1...v2.0.0) (2026-11-11)
21
+
22
+
23
+ * feat!: encode TOON in pure Python, drop the Node.js CLI ([1bffef7](https://github.com/andrii-su/datoon/commit/1bffef7c068cd3f2a73df80a9348f0af28dd3040))
24
+ * feat!: encode TOON in pure Python, drop the Node.js CLI ([#69](https://github.com/andrii-su/datoon/issues/69)) ([7e28513](https://github.com/andrii-su/datoon/commit/7e28513aa1fb0cf0be315d8e09586eedb0ce6800))
25
+
26
+
27
+ ### BREAKING CHANGES
28
+
29
+ * `toon-format` is a required dependency; the
30
+ `ConversionConfig.toon_cli_timeout` field, the `--timeout` CLI flag and
31
+ the `timeout` argument of the `convert_json` / `convert_text` MCP tools
32
+ are removed. The skip reason for a failed conversion is now
33
+ "TOON encoding failed; falling back to JSON."
34
+ * `toon-format` is a required dependency; the
35
+ `ConversionConfig.toon_cli_timeout` field, the `--timeout` CLI flag and
36
+ the `timeout` argument of the `convert_json` / `convert_text` MCP tools
37
+ are removed. The skip reason for a failed conversion is now
38
+ "TOON encoding failed; falling back to JSON."
39
+
1
40
  ## [1.9.1](https://github.com/andrii-su/datoon/compare/v1.9.0...v1.9.1) (2026-07-07)
2
41
 
3
42
 
@@ -30,8 +30,9 @@ datoon/
30
30
  │
31
31
  ├── src/datoon/ # Python package source
32
32
  │ ├── analyzer.py # Payload structure suitability checks
33
- │ ├── converter.py # JSON normalization, TOON CLI call, policy gating
33
+ │ ├── converter.py # JSON normalization, TOON encoder call, policy gating
34
34
  │ ├── models.py # Config/report/outcome dataclasses
35
+ │ ├── tokenizers.py # Token counter specs and estimates
35
36
  │ ├── cli.py # CLI entrypoint
36
37
  │ └── mcp_server.py # MCP server tools
37
38
  │
@@ -68,6 +69,7 @@ Edit these files directly:
68
69
  | Conversion policy | `src/datoon/converter.py` |
69
70
  | Payload analysis | `src/datoon/analyzer.py` |
70
71
  | Public data contracts | `src/datoon/models.py` |
72
+ | Token counters | `src/datoon/tokenizers.py` |
71
73
  | CLI behavior | `src/datoon/cli.py` |
72
74
  | MCP behavior | `src/datoon/mcp_server.py` |
73
75
  | Skill behavior | `skills/datoon/SKILL.md` |
@@ -105,11 +107,12 @@ It runs on pull requests and pushes to `main`. The old auto-sync-on-main flow wa
105
107
 
106
108
  - Never change semantic values or key names during conversion.
107
109
  - Invalid JSON must fail loudly.
108
- - Missing `npx` must not break auto mode; fallback to JSON unless `force=True`.
110
+ - An encoder failure must not break auto mode; fallback to JSON unless `force=True`.
109
111
  - `--force` is for controlled experiments and should bypass gating, not hide conversion errors.
110
112
  - Auto mode skips before conversion when analysis rejects the payload, and skips after conversion when estimated savings are below `min_savings_ratio`.
111
113
  - Keep skip reasons user-facing, specific, and stable enough for CLI/MCP troubleshooting.
112
- - Token counts are estimates. Be precise about whether a number is payload-token estimate, visible output estimate, or real model usage.
114
+ - Token counts are estimates, whichever tokenizer produced them (`claude:` counts included). Be precise about whether a number is payload-token estimate, visible output estimate, or real model usage, and name the tokenizer it came from.
115
+ - Only the `auto` tokenizer falls back silently; an explicit spec with a missing package or credentials must raise `DatoonError`, in auto and force mode alike.
113
116
  - Benchmarks in README must come from committed scripts/artifacts.
114
117
  - Keep generated caches out of commits: `.venv/`, `.pytest_cache/`, `.ruff_cache/`, `__pycache__/`, local benchmark result JSON under `benchmarks/results/`.
115
118
 
@@ -122,7 +125,7 @@ python scripts/validate_plugin_metadata.py
122
125
  uvx pre-commit run --all-files
123
126
  ```
124
127
 
125
- Integration tests require Node.js and `npx`.
128
+ Conversion is pure Python (`toon-format`); no Node.js is needed for tests.
126
129
 
127
130
  ## Benchmark Commands
128
131
 
@@ -168,7 +171,7 @@ Config writes must:
168
171
  - remove only `datoon`-owned entries on uninstall;
169
172
  - keep dry-run side-effect free.
170
173
 
171
- Security and privacy docs should state that the installer does not read or upload user payloads, but selected targets may call external tooling: Claude Code through `claude plugin ...`, Python package resolution through `uvx`, and TOON conversion through `npx --yes @toon-format/cli@2` when conversion actually runs.
174
+ Security and privacy docs should state that the installer does not read or upload user payloads, but selected targets may call external tooling: Claude Code through `claude plugin ...`, Python package resolution through `uvx`; TOON conversion runs in-process through the `toon-format` package and calls no external tooling.
172
175
 
173
176
  ## Known Gaps
174
177
 
@@ -15,7 +15,7 @@ python scripts/validate_skill_sync.py
15
15
  python scripts/validate_plugin_metadata.py
16
16
  ```
17
17
 
18
- Integration tests need Node.js and `npx`, because conversion uses the official TOON CLI.
18
+ Conversion is pure Python (the `toon-format` package); no Node.js is required for tests.
19
19
 
20
20
  ## What To Edit
21
21
 
@@ -67,12 +67,6 @@ Run the full suite:
67
67
  uv run pytest
68
68
  ```
69
69
 
70
- Run unit tests only:
71
-
72
- ```bash
73
- uv run pytest -m "not integration"
74
- ```
75
-
76
70
  Run pre-commit locally:
77
71
 
78
72
  ```bash
@@ -112,7 +106,7 @@ Use Conventional Commit subjects:
112
106
 
113
107
  ```text
114
108
  feat: add MCP analyze_json tool
115
- fix: preserve JSON fallback on missing npx
109
+ fix: preserve JSON fallback on encoder failure
116
110
  docs: clarify Claude Code plugin install
117
111
  test: cover non-uniform arrays
118
112
  ```
@@ -16,7 +16,6 @@ The setup script:
16
16
 
17
17
  - checks for Python 3.12+;
18
18
  - installs `uv` if it is missing;
19
- - warns if Node.js is missing;
20
19
  - runs `uv sync --extra dev`;
21
20
  - installs the editable `datoon` CLI globally through `uv tool install`.
22
21
 
@@ -43,9 +42,29 @@ Optional extras:
43
42
 
44
43
  ```bash
45
44
  pip install "datoon[tokens]" # tiktoken token estimates
45
+ pip install "datoon[claude]" # Claude token counts via the Anthropic API
46
+ pip install "datoon[hf]" # Hugging Face tokenizers
46
47
  pip install "datoon[mcp]" # MCP server dependencies
47
48
  ```
48
49
 
50
+ ### Choosing a tokenizer
51
+
52
+ Select the token counter with `--tokenizer <spec>` (CLI), the `tokenizer` argument (MCP tools), or `DATOON_TOKENIZER`; the default is `auto`. The MCP tools accept only local counters (`auto`, `chars`, `tiktoken`) per call; `claude:` and `hf:` reach the network, so only the server operator can enable them through `DATOON_TOKENIZER`.
53
+
54
+ | Spec | Counts with | Needs |
55
+ |---|---|---|
56
+ | `auto` | `tiktoken:o200k_base` when tiktoken is installed, else `chars:3` | — |
57
+ | `chars` / `chars:<N>` | `ceil(len(text) / N)`, default `N = 3` | — |
58
+ | `tiktoken` / `tiktoken:<encoding>` | tiktoken, default `o200k_base` | `datoon[tokens]` |
59
+ | `claude:<model-id>` | Anthropic token counting API | `datoon[claude]`, Anthropic credentials (e.g. `ANTHROPIC_API_KEY`) |
60
+ | `hf:<repo-id>` | Hugging Face `tokenizers` tokenizer | `datoon[hf]` |
61
+
62
+ Local counters suit runtime gating; `claude:` and `hf:` suit benchmarks and evals where counts for the target model matter. `claude:` makes network calls, and Anthropic documents its counts as estimates. `hf:` downloads the tokenizer from the Hugging Face Hub on first use.
63
+
64
+ ```bash
65
+ datoon input.json --tokenizer claude:claude-opus-5-5 --report-stdout
66
+ ```
67
+
49
68
  ## CLI
50
69
 
51
70
  Read from stdin:
@@ -69,21 +88,16 @@ datoon input.json --force --report-stdout
69
88
 
70
89
  ## TOON Dependency
71
90
 
72
- `datoon` calls the official TOON CLI through `npx`.
73
-
74
- Required for conversion:
91
+ `datoon` converts to TOON in pure Python using the official [`toon-format`](https://pypi.org/project/toon-format/) package, which is installed automatically as a dependency. No Node.js is needed.
75
92
 
76
- - Node.js
77
- - `npx` in `PATH`
78
-
79
- Without Node.js, structure analysis still works, but conversion falls back to JSON in auto mode. Forced conversion fails loudly.
93
+ If the encoder fails, auto mode falls back to JSON. Forced conversion fails loudly.
80
94
 
81
95
  In auto mode, `datoon` skips conversion and returns compact JSON when:
82
96
 
83
97
  - the payload has no uniform object array with at least the configured row count;
84
98
  - the payload exceeds the configured maximum nesting depth;
85
99
  - estimated TOON savings are below `--min-savings`;
86
- - Node.js or `npx` is unavailable, or the TOON CLI fails.
100
+ - the TOON encoder fails.
87
101
 
88
102
  Invalid JSON never falls back silently. It raises an error.
89
103
 
@@ -232,12 +246,7 @@ echo '{"users":[{"id":1,"name":"Ada"},{"id":2,"name":"Lin"},{"id":3,"name":"Grac
232
246
  | datoon --report-stdout
233
247
  ```
234
248
 
235
- If the reason mentions the TOON dependency, install Node.js and confirm `npx` is available:
236
-
237
- ```bash
238
- node --version
239
- npx --version
240
- ```
249
+ If the reason says TOON encoding failed, rerun with `--force` to see the underlying encoder error.
241
250
 
242
251
  If the reason mentions structure or savings, the skip is expected auto-mode behavior. Use `--force` only for controlled experiments where a failed TOON conversion should fail the command.
243
252
 
@@ -264,8 +273,6 @@ python scripts/validate_skill_sync.py
264
273
  PYTHONPATH=src python benchmarks/run.py --dry-run
265
274
  ```
266
275
 
267
- Full integration tests require Node.js and `npx`.
268
-
269
276
  ## Uninstall
270
277
 
271
278
  For the editable CLI installed by `setup.sh`:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datoon
3
- Version: 1.9.1
3
+ Version: 3.0.0
4
4
  Summary: Smart structured-data-to-TOON gateway with pragmatic auto-gating for LLM prompts.
5
5
  Author-email: Andrii Suruhov <andrii.suruhov@gmail.com>
6
6
  License: MIT License
@@ -37,12 +37,17 @@ Classifier: Operating System :: OS Independent
37
37
  Requires-Python: >=3.12
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
+ Requires-Dist: toon-format>=1.2.0
40
41
  Provides-Extra: dev
41
42
  Requires-Dist: pytest>=9.0.3; extra == "dev"
42
43
  Requires-Dist: pre-commit>=4.6.0; extra == "dev"
43
44
  Requires-Dist: pyyaml>=6.0; extra == "dev"
44
45
  Provides-Extra: tokens
45
46
  Requires-Dist: tiktoken>=0.13.0; extra == "tokens"
47
+ Provides-Extra: claude
48
+ Requires-Dist: anthropic>=2.0.0; extra == "claude"
49
+ Provides-Extra: hf
50
+ Requires-Dist: tokenizers>=0.23.0; extra == "hf"
46
51
  Provides-Extra: mcp
47
52
  Requires-Dist: mcp>=1.28.0; extra == "mcp"
48
53
  Provides-Extra: yaml
@@ -55,7 +60,7 @@ Requires-Dist: fastavro>=1.9; extra == "columnar"
55
60
  Provides-Extra: numbers
56
61
  Requires-Dist: numbers-parser>=4.4; extra == "numbers"
57
62
  Provides-Extra: all
58
- Requires-Dist: datoon[columnar,excel,mcp,numbers,tokens,yaml]; extra == "all"
63
+ Requires-Dist: datoon[claude,columnar,excel,hf,mcp,numbers,tokens,yaml]; extra == "all"
59
64
  Dynamic: license-file
60
65
 
61
66
  <p align="center">
@@ -169,7 +174,7 @@ Same result. Zero JSON serialization in your code.
169
174
  {"decision":"skip","reason":"No uniform object arrays found with at least 3 rows."}
170
175
  ```
171
176
 
172
- No Node.js call. No silent corruption.
177
+ No silent corruption.
173
178
 
174
179
  </td>
175
180
  </tr>
@@ -211,6 +216,10 @@ pip install "datoon[numbers]"
211
216
  # with tiktoken-based token counting
212
217
  pip install "datoon[tokens]"
213
218
 
219
+ # with Claude token counting (Anthropic API) / Hugging Face tokenizers
220
+ pip install "datoon[claude]"
221
+ pip install "datoon[hf]"
222
+
214
223
  # with MCP server
215
224
  pip install "datoon[mcp]"
216
225
 
@@ -218,7 +227,7 @@ pip install "datoon[mcp]"
218
227
  pip install "datoon[all]"
219
228
  ```
220
229
 
221
- Requires Python 3.12+. TOON conversion requires Node.js with `npx` in PATH — analysis and format reading work without it.
230
+ Requires Python 3.12+. TOON conversion is pure Python (official [`toon-format`](https://pypi.org/project/toon-format/) encoder, installed automatically) — no Node.js needed.
222
231
 
223
232
  For Claude Code plugin, Codex, and MCP config → [**INSTALL.md**](./INSTALL.md).
224
233
 
@@ -252,11 +261,26 @@ For Claude Code plugin, Codex, and MCP config → [**INSTALL.md**](./INSTALL.md)
252
261
  1. **Detect format** — from `--format` flag, file extension, or default to JSON for stdin
253
262
  1. **Read + normalize** — parse source into list of row dicts; serialize to compact JSON
254
263
  1. **Analyze structure** — uniform object arrays? acceptable depth? minimum rows?
255
- 1. **Gate early** — non-candidates skip before any CLI call; no Node.js overhead
256
- 1. **Convert + estimate** — TOON CLI runs, token savings calculated
264
+ 1. **Gate early** — non-candidates skip before any TOON encoding
265
+ 1. **Convert + estimate** — TOON encoder runs in-process, token savings calculated
257
266
  1. **Gate savings** — below threshold → return JSON; above → return TOON with report
258
267
 
259
- Every path returns a `ConversionReport` with `decision`, `reason`, and token estimates. Pipelines never get silent surprises.
268
+ Every path returns a `ConversionReport` with `decision`, `reason`, token estimates, and the `tokenizer` that produced them. Pipelines never get silent surprises.
269
+
270
+ ## Choosing a tokenizer
271
+
272
+ Token estimates drive the savings gate, so pick the counter that fits the job. Pass one spec with `--tokenizer`, the `tokenizer` MCP argument, `ConversionConfig(tokenizer=...)`, or the `DATOON_TOKENIZER` environment variable; in Python any callable `str -> int` works too. The MCP tools accept only local counters (`auto`, `chars`, `tiktoken`) per call; `claude:` and `hf:` reach the network, so only the server operator can enable them through `DATOON_TOKENIZER`.
273
+
274
+ | Spec | Counts with | Needs |
275
+ |---|---|---|
276
+ | `auto` (default) | `tiktoken:o200k_base` when tiktoken is installed, else `chars:3` | — |
277
+ | `chars` / `chars:<N>` | `ceil(len(text) / N)`, default `N = 3` | — |
278
+ | `tiktoken` / `tiktoken:<encoding>` | tiktoken, default `o200k_base` | `datoon[tokens]` |
279
+ | `claude:<model-id>` | Anthropic token counting API for that model | `datoon[claude]`, Anthropic credentials (e.g. `ANTHROPIC_API_KEY`) |
280
+ | `hf:<repo-id>` | Hugging Face `tokenizers` tokenizer from the Hub | `datoon[hf]` |
281
+ | a callable (Python) | your own function, reported as `custom` | — |
282
+
283
+ Use the local counters (`auto`, `chars`, `tiktoken`) to gate payloads at runtime: they are fast and run locally (tiktoken fetches its encoding file once on first use). Use `claude:` or `hf:` in benchmarks and evals where counts for the target model matter. `claude:` makes network calls, and Anthropic documents its counts as estimates. Only `auto` falls back silently: an explicit spec with a missing package fails with an error that names the extra to install, and `claude:` without Anthropic credentials fails too. Every report records the resolved name in `tokenizer`.
260
284
 
261
285
  ______________________________________________________________________
262
286
 
@@ -344,7 +368,7 @@ outcome = convert_json_for_llm(json_text, ConversionConfig())
344
368
  send_to_model(outcome.payload_text)
345
369
  ```
346
370
 
347
- **Structure-only analysis (no Node.js required):**
371
+ **Structure-only analysis:**
348
372
 
349
373
  ```python
350
374
  from datoon.analyzer import analyze_payload
@@ -366,7 +390,7 @@ ______________________________________________________________________
366
390
  |---|---|
367
391
  | `convert_json` | Full JSON conversion with policy gating |
368
392
  | `convert_text` | Converts CSV, YAML, XML, or JSONL text with policy gating |
369
- | `analyze_json` | Structure analysis only — no Node.js needed |
393
+ | `analyze_json` | Structure analysis only — no conversion |
370
394
 
371
395
  **Claude Desktop / Cursor / Windsurf config:**
372
396
 
@@ -419,7 +443,7 @@ ______________________________________________________________________
419
443
  | `--min-savings` | `0.15` | Minimum relative token savings required |
420
444
  | `--max-depth` | `6` | Maximum nesting depth for auto-conversion |
421
445
  | `--min-uniform-rows` | `3` | Minimum rows in uniform object arrays |
422
- | `--timeout` | `30` | Seconds before TOON CLI call is aborted |
446
+ | `--tokenizer <spec>` | `$DATOON_TOKENIZER`, else `auto` | Token counter for estimates — see [Choosing a tokenizer](#choosing-a-tokenizer) |
423
447
  | `--report <path>` | — | Write JSON conversion report to file |
424
448
  | `--report-stdout` | — | Print JSON conversion report to stderr |
425
449
  | `-o <path>` | stdout | Output file path |
@@ -514,8 +538,7 @@ uvx pre-commit install
514
538
  **Tests:**
515
539
 
516
540
  ```bash
517
- pytest -m "not integration" # unit only (102 tests)
518
- pytest # with integration (requires Node.js + npx)
541
+ pytest
519
542
  ```
520
543
 
521
544
  **Skill sync + plugin metadata:**
@@ -109,7 +109,7 @@ Same result. Zero JSON serialization in your code.
109
109
  {"decision":"skip","reason":"No uniform object arrays found with at least 3 rows."}
110
110
  ```
111
111
 
112
- No Node.js call. No silent corruption.
112
+ No silent corruption.
113
113
 
114
114
  </td>
115
115
  </tr>
@@ -151,6 +151,10 @@ pip install "datoon[numbers]"
151
151
  # with tiktoken-based token counting
152
152
  pip install "datoon[tokens]"
153
153
 
154
+ # with Claude token counting (Anthropic API) / Hugging Face tokenizers
155
+ pip install "datoon[claude]"
156
+ pip install "datoon[hf]"
157
+
154
158
  # with MCP server
155
159
  pip install "datoon[mcp]"
156
160
 
@@ -158,7 +162,7 @@ pip install "datoon[mcp]"
158
162
  pip install "datoon[all]"
159
163
  ```
160
164
 
161
- Requires Python 3.12+. TOON conversion requires Node.js with `npx` in PATH — analysis and format reading work without it.
165
+ Requires Python 3.12+. TOON conversion is pure Python (official [`toon-format`](https://pypi.org/project/toon-format/) encoder, installed automatically) — no Node.js needed.
162
166
 
163
167
  For Claude Code plugin, Codex, and MCP config → [**INSTALL.md**](./INSTALL.md).
164
168
 
@@ -192,11 +196,26 @@ For Claude Code plugin, Codex, and MCP config → [**INSTALL.md**](./INSTALL.md)
192
196
  1. **Detect format** — from `--format` flag, file extension, or default to JSON for stdin
193
197
  1. **Read + normalize** — parse source into list of row dicts; serialize to compact JSON
194
198
  1. **Analyze structure** — uniform object arrays? acceptable depth? minimum rows?
195
- 1. **Gate early** — non-candidates skip before any CLI call; no Node.js overhead
196
- 1. **Convert + estimate** — TOON CLI runs, token savings calculated
199
+ 1. **Gate early** — non-candidates skip before any TOON encoding
200
+ 1. **Convert + estimate** — TOON encoder runs in-process, token savings calculated
197
201
  1. **Gate savings** — below threshold → return JSON; above → return TOON with report
198
202
 
199
- Every path returns a `ConversionReport` with `decision`, `reason`, and token estimates. Pipelines never get silent surprises.
203
+ Every path returns a `ConversionReport` with `decision`, `reason`, token estimates, and the `tokenizer` that produced them. Pipelines never get silent surprises.
204
+
205
+ ## Choosing a tokenizer
206
+
207
+ Token estimates drive the savings gate, so pick the counter that fits the job. Pass one spec with `--tokenizer`, the `tokenizer` MCP argument, `ConversionConfig(tokenizer=...)`, or the `DATOON_TOKENIZER` environment variable; in Python any callable `str -> int` works too. The MCP tools accept only local counters (`auto`, `chars`, `tiktoken`) per call; `claude:` and `hf:` reach the network, so only the server operator can enable them through `DATOON_TOKENIZER`.
208
+
209
+ | Spec | Counts with | Needs |
210
+ |---|---|---|
211
+ | `auto` (default) | `tiktoken:o200k_base` when tiktoken is installed, else `chars:3` | — |
212
+ | `chars` / `chars:<N>` | `ceil(len(text) / N)`, default `N = 3` | — |
213
+ | `tiktoken` / `tiktoken:<encoding>` | tiktoken, default `o200k_base` | `datoon[tokens]` |
214
+ | `claude:<model-id>` | Anthropic token counting API for that model | `datoon[claude]`, Anthropic credentials (e.g. `ANTHROPIC_API_KEY`) |
215
+ | `hf:<repo-id>` | Hugging Face `tokenizers` tokenizer from the Hub | `datoon[hf]` |
216
+ | a callable (Python) | your own function, reported as `custom` | — |
217
+
218
+ Use the local counters (`auto`, `chars`, `tiktoken`) to gate payloads at runtime: they are fast and run locally (tiktoken fetches its encoding file once on first use). Use `claude:` or `hf:` in benchmarks and evals where counts for the target model matter. `claude:` makes network calls, and Anthropic documents its counts as estimates. Only `auto` falls back silently: an explicit spec with a missing package fails with an error that names the extra to install, and `claude:` without Anthropic credentials fails too. Every report records the resolved name in `tokenizer`.
200
219
 
201
220
  ______________________________________________________________________
202
221
 
@@ -284,7 +303,7 @@ outcome = convert_json_for_llm(json_text, ConversionConfig())
284
303
  send_to_model(outcome.payload_text)
285
304
  ```
286
305
 
287
- **Structure-only analysis (no Node.js required):**
306
+ **Structure-only analysis:**
288
307
 
289
308
  ```python
290
309
  from datoon.analyzer import analyze_payload
@@ -306,7 +325,7 @@ ______________________________________________________________________
306
325
  |---|---|
307
326
  | `convert_json` | Full JSON conversion with policy gating |
308
327
  | `convert_text` | Converts CSV, YAML, XML, or JSONL text with policy gating |
309
- | `analyze_json` | Structure analysis only — no Node.js needed |
328
+ | `analyze_json` | Structure analysis only — no conversion |
310
329
 
311
330
  **Claude Desktop / Cursor / Windsurf config:**
312
331
 
@@ -359,7 +378,7 @@ ______________________________________________________________________
359
378
  | `--min-savings` | `0.15` | Minimum relative token savings required |
360
379
  | `--max-depth` | `6` | Maximum nesting depth for auto-conversion |
361
380
  | `--min-uniform-rows` | `3` | Minimum rows in uniform object arrays |
362
- | `--timeout` | `30` | Seconds before TOON CLI call is aborted |
381
+ | `--tokenizer <spec>` | `$DATOON_TOKENIZER`, else `auto` | Token counter for estimates — see [Choosing a tokenizer](#choosing-a-tokenizer) |
363
382
  | `--report <path>` | — | Write JSON conversion report to file |
364
383
  | `--report-stdout` | — | Print JSON conversion report to stderr |
365
384
  | `-o <path>` | stdout | Output file path |
@@ -454,8 +473,7 @@ uvx pre-commit install
454
473
  **Tests:**
455
474
 
456
475
  ```bash
457
- pytest -m "not integration" # unit only (102 tests)
458
- pytest # with integration (requires Node.js + npx)
476
+ pytest
459
477
  ```
460
478
 
461
479
  **Skill sync + plugin metadata:**
@@ -50,7 +50,7 @@ Skip conversion when any condition fails:
50
50
 
51
51
  - Never change semantic values or key names.
52
52
  - Fail loudly for invalid JSON.
53
- - If TOON CLI dependency is unavailable, keep JSON and report fallback reason.
53
+ - If TOON encoding fails, keep JSON and report fallback reason.
54
54
  - Use `--force` only for controlled experiments.
55
55
 
56
56
  ## Boundaries
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env python3
2
1
  """Generate deterministic JSON payloads for agent skill evaluation."""
3
2
 
4
3
  from __future__ import annotations
@@ -67,12 +66,10 @@ def expected_for(payload: dict[str, object]) -> dict[str, object]:
67
66
  region_totals[region] = region_totals.get(region, 0) + revenue
68
67
  category_totals[category] = category_totals.get(category, 0) + revenue
69
68
 
70
- top_region = sorted(region_totals.items(), key=lambda item: (-item[1], item[0]))[0][
69
+ top_region = min(region_totals.items(), key=lambda item: (-item[1], item[0]))[0][0]
70
+ top_category = min(category_totals.items(), key=lambda item: (-item[1], item[0]))[
71
71
  0
72
- ]
73
- top_category = sorted(
74
- category_totals.items(), key=lambda item: (-item[1], item[0])
75
- )[0][0]
72
+ ][0]
76
73
 
77
74
  return {
78
75
  "scenario": payload["scenario"],