zalos-sheetops 0.4.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 (136) hide show
  1. zalos_sheetops-0.4.2/.github/workflows/ci.yml +54 -0
  2. zalos_sheetops-0.4.2/.gitignore +10 -0
  3. zalos_sheetops-0.4.2/AGENTS.md +64 -0
  4. zalos_sheetops-0.4.2/LICENSE +21 -0
  5. zalos_sheetops-0.4.2/PKG-INFO +141 -0
  6. zalos_sheetops-0.4.2/README.md +126 -0
  7. zalos_sheetops-0.4.2/docs/architecture-and-references.md +218 -0
  8. zalos_sheetops-0.4.2/pyproject.toml +43 -0
  9. zalos_sheetops-0.4.2/scripts/setup-libreoffice +40 -0
  10. zalos_sheetops-0.4.2/skills/claude/SKILL.md +1 -0
  11. zalos_sheetops-0.4.2/skills/codex/SKILL.md +80 -0
  12. zalos_sheetops-0.4.2/src/zalos_sheetops/__init__.py +5 -0
  13. zalos_sheetops-0.4.2/src/zalos_sheetops/benchmarks/__init__.py +1 -0
  14. zalos_sheetops-0.4.2/src/zalos_sheetops/benchmarks/large_xlsx.py +361 -0
  15. zalos_sheetops-0.4.2/src/zalos_sheetops/cli.py +1591 -0
  16. zalos_sheetops-0.4.2/src/zalos_sheetops/command_registry.py +227 -0
  17. zalos_sheetops-0.4.2/src/zalos_sheetops/compare/__init__.py +1 -0
  18. zalos_sheetops-0.4.2/src/zalos_sheetops/compare/workbooks.py +265 -0
  19. zalos_sheetops-0.4.2/src/zalos_sheetops/contracts.py +32 -0
  20. zalos_sheetops-0.4.2/src/zalos_sheetops/diff/__init__.py +1 -0
  21. zalos_sheetops-0.4.2/src/zalos_sheetops/diff/tabular.py +204 -0
  22. zalos_sheetops-0.4.2/src/zalos_sheetops/display/__init__.py +1 -0
  23. zalos_sheetops-0.4.2/src/zalos_sheetops/display/scan.py +309 -0
  24. zalos_sheetops-0.4.2/src/zalos_sheetops/display/widths.py +125 -0
  25. zalos_sheetops-0.4.2/src/zalos_sheetops/display/xlsx_metadata.py +127 -0
  26. zalos_sheetops-0.4.2/src/zalos_sheetops/errors.py +11 -0
  27. zalos_sheetops-0.4.2/src/zalos_sheetops/formulas/__init__.py +1 -0
  28. zalos_sheetops-0.4.2/src/zalos_sheetops/formulas/scan.py +333 -0
  29. zalos_sheetops-0.4.2/src/zalos_sheetops/formulas/xml_scan.py +324 -0
  30. zalos_sheetops-0.4.2/src/zalos_sheetops/hyperlinks/__init__.py +1 -0
  31. zalos_sheetops-0.4.2/src/zalos_sheetops/hyperlinks/assertions.py +390 -0
  32. zalos_sheetops-0.4.2/src/zalos_sheetops/io/__init__.py +1 -0
  33. zalos_sheetops-0.4.2/src/zalos_sheetops/io/cellref.py +141 -0
  34. zalos_sheetops-0.4.2/src/zalos_sheetops/io/headers.py +27 -0
  35. zalos_sheetops-0.4.2/src/zalos_sheetops/io/inspect.py +98 -0
  36. zalos_sheetops-0.4.2/src/zalos_sheetops/io/read_cells.py +231 -0
  37. zalos_sheetops-0.4.2/src/zalos_sheetops/io/read_csv_table.py +45 -0
  38. zalos_sheetops-0.4.2/src/zalos_sheetops/io/read_table.py +240 -0
  39. zalos_sheetops-0.4.2/src/zalos_sheetops/io/search_cells.py +532 -0
  40. zalos_sheetops-0.4.2/src/zalos_sheetops/io/xlsx_xml.py +1654 -0
  41. zalos_sheetops-0.4.2/src/zalos_sheetops/normalize/__init__.py +1 -0
  42. zalos_sheetops-0.4.2/src/zalos_sheetops/normalize/amounts.py +51 -0
  43. zalos_sheetops-0.4.2/src/zalos_sheetops/normalize/dates.py +25 -0
  44. zalos_sheetops-0.4.2/src/zalos_sheetops/normalize/preview.py +148 -0
  45. zalos_sheetops-0.4.2/src/zalos_sheetops/normalize/text.py +22 -0
  46. zalos_sheetops-0.4.2/src/zalos_sheetops/paths.py +28 -0
  47. zalos_sheetops-0.4.2/src/zalos_sheetops/recalc/__init__.py +1 -0
  48. zalos_sheetops-0.4.2/src/zalos_sheetops/recalc/libreoffice.py +119 -0
  49. zalos_sheetops-0.4.2/src/zalos_sheetops/reconcile/__init__.py +1 -0
  50. zalos_sheetops-0.4.2/src/zalos_sheetops/reconcile/exact.py +183 -0
  51. zalos_sheetops-0.4.2/src/zalos_sheetops/runtime/__init__.py +1 -0
  52. zalos_sheetops-0.4.2/src/zalos_sheetops/runtime/libreoffice.py +130 -0
  53. zalos_sheetops-0.4.2/src/zalos_sheetops/styles/__init__.py +1 -0
  54. zalos_sheetops-0.4.2/src/zalos_sheetops/styles/assertions.py +253 -0
  55. zalos_sheetops-0.4.2/src/zalos_sheetops/styles/read.py +398 -0
  56. zalos_sheetops-0.4.2/src/zalos_sheetops/styles/xml_assertions.py +372 -0
  57. zalos_sheetops-0.4.2/src/zalos_sheetops/testing.py +43 -0
  58. zalos_sheetops-0.4.2/src/zalos_sheetops/validation/__init__.py +1 -0
  59. zalos_sheetops-0.4.2/src/zalos_sheetops/validation/workbook.py +451 -0
  60. zalos_sheetops-0.4.2/tests/fixtures/README.md +9 -0
  61. zalos_sheetops-0.4.2/tests/fixtures/manifest.json +232 -0
  62. zalos_sheetops-0.4.2/tests/fixtures/upstream/calamine/LICENSE +23 -0
  63. zalos_sheetops-0.4.2/tests/fixtures/upstream/calamine/formula.issue.xlsx +0 -0
  64. zalos_sheetops-0.4.2/tests/fixtures/upstream/calamine/shared_formula_bench.xlsx +0 -0
  65. zalos_sheetops-0.4.2/tests/fixtures/upstream/calamine/shared_formula_simple.xlsx +0 -0
  66. zalos_sheetops-0.4.2/tests/fixtures/upstream/fastexcel/LICENSE +21 -0
  67. zalos_sheetops-0.4.2/tests/fixtures/upstream/fastexcel/div0.xlsx +0 -0
  68. zalos_sheetops-0.4.2/tests/fixtures/upstream/fastexcel/fixture-changing-header-location.xlsx +0 -0
  69. zalos_sheetops-0.4.2/tests/fixtures/upstream/fastexcel/fixture-sheets-different-visibilities.xlsx +0 -0
  70. zalos_sheetops-0.4.2/tests/fixtures/upstream/fastexcel/fixture-single-sheet-duplicated-columns.xlsx +0 -0
  71. zalos_sheetops-0.4.2/tests/fixtures/upstream/fastexcel/sheet-and-table-with-offset.xlsx +0 -0
  72. zalos_sheetops-0.4.2/tests/fixtures/upstream/fastexcel/sheet-and-table-with-whitespace.xlsx +0 -0
  73. zalos_sheetops-0.4.2/tests/fixtures/upstream/openpyxl/LICENSE +23 -0
  74. zalos_sheetops-0.4.2/tests/fixtures/upstream/openpyxl/complex-styles.xlsx +0 -0
  75. zalos_sheetops-0.4.2/tests/fixtures/upstream/openpyxl/empty-with-styles.xlsx +0 -0
  76. zalos_sheetops-0.4.2/tests/fixtures/upstream/openpyxl/hidden_sheets.xlsx +0 -0
  77. zalos_sheetops-0.4.2/tests/fixtures/upstream/openpyxl/sample.xlsx +0 -0
  78. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/LICENSE +19 -0
  79. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/datetime.csv +1 -0
  80. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/datetime.xlsx +0 -0
  81. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/empty_row.csv +3 -0
  82. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/empty_row.xlsx +0 -0
  83. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/last-column-empty.xlsx +0 -0
  84. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/utf8.csv +5 -0
  85. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsx2csv/utf8.xlsx +0 -0
  86. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsxwriter/LICENSE +25 -0
  87. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsxwriter/autofit01.xlsx +0 -0
  88. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsxwriter/formula_results01.xlsx +0 -0
  89. zalos_sheetops-0.4.2/tests/fixtures/upstream/xlsxwriter/hyperlink01.xlsx +0 -0
  90. zalos_sheetops-0.4.2/tests/test_cli_cells_search.py +85 -0
  91. zalos_sheetops-0.4.2/tests/test_cli_compare.py +141 -0
  92. zalos_sheetops-0.4.2/tests/test_cli_contracts.py +456 -0
  93. zalos_sheetops-0.4.2/tests/test_cli_diff.py +370 -0
  94. zalos_sheetops-0.4.2/tests/test_cli_display_scan.py +136 -0
  95. zalos_sheetops-0.4.2/tests/test_cli_doctor.py +100 -0
  96. zalos_sheetops-0.4.2/tests/test_cli_formulas_scan.py +370 -0
  97. zalos_sheetops-0.4.2/tests/test_cli_help.py +41 -0
  98. zalos_sheetops-0.4.2/tests/test_cli_hyperlinks_assert.py +190 -0
  99. zalos_sheetops-0.4.2/tests/test_cli_normalize_preview.py +90 -0
  100. zalos_sheetops-0.4.2/tests/test_cli_output_format.py +32 -0
  101. zalos_sheetops-0.4.2/tests/test_cli_read_cells.py +158 -0
  102. zalos_sheetops-0.4.2/tests/test_cli_read_table.py +513 -0
  103. zalos_sheetops-0.4.2/tests/test_cli_recalc.py +298 -0
  104. zalos_sheetops-0.4.2/tests/test_cli_reconcile.py +215 -0
  105. zalos_sheetops-0.4.2/tests/test_cli_startup.py +99 -0
  106. zalos_sheetops-0.4.2/tests/test_cli_styles_assert.py +303 -0
  107. zalos_sheetops-0.4.2/tests/test_cli_styles_read.py +109 -0
  108. zalos_sheetops-0.4.2/tests/test_cli_validate.py +320 -0
  109. zalos_sheetops-0.4.2/tests/test_compare_workbooks.py +218 -0
  110. zalos_sheetops-0.4.2/tests/test_diff_tabular.py +130 -0
  111. zalos_sheetops-0.4.2/tests/test_display_scan.py +135 -0
  112. zalos_sheetops-0.4.2/tests/test_display_widths.py +25 -0
  113. zalos_sheetops-0.4.2/tests/test_fixture_manifest.py +35 -0
  114. zalos_sheetops-0.4.2/tests/test_formulas_scan.py +420 -0
  115. zalos_sheetops-0.4.2/tests/test_headers.py +72 -0
  116. zalos_sheetops-0.4.2/tests/test_hyperlinks_assertions.py +138 -0
  117. zalos_sheetops-0.4.2/tests/test_inspect.py +624 -0
  118. zalos_sheetops-0.4.2/tests/test_large_xlsx_benchmark.py +143 -0
  119. zalos_sheetops-0.4.2/tests/test_libreoffice_runtime.py +91 -0
  120. zalos_sheetops-0.4.2/tests/test_normalize_amounts.py +32 -0
  121. zalos_sheetops-0.4.2/tests/test_normalize_dates.py +33 -0
  122. zalos_sheetops-0.4.2/tests/test_normalize_preview.py +83 -0
  123. zalos_sheetops-0.4.2/tests/test_package_version.py +7 -0
  124. zalos_sheetops-0.4.2/tests/test_read_cells.py +225 -0
  125. zalos_sheetops-0.4.2/tests/test_read_csv_table.py +72 -0
  126. zalos_sheetops-0.4.2/tests/test_read_table.py +350 -0
  127. zalos_sheetops-0.4.2/tests/test_recalc.py +271 -0
  128. zalos_sheetops-0.4.2/tests/test_reconcile_exact.py +122 -0
  129. zalos_sheetops-0.4.2/tests/test_release_docs.py +20 -0
  130. zalos_sheetops-0.4.2/tests/test_repo_docs.py +43 -0
  131. zalos_sheetops-0.4.2/tests/test_search_cells.py +188 -0
  132. zalos_sheetops-0.4.2/tests/test_skill_docs.py +60 -0
  133. zalos_sheetops-0.4.2/tests/test_styles_assertions.py +201 -0
  134. zalos_sheetops-0.4.2/tests/test_styles_read.py +202 -0
  135. zalos_sheetops-0.4.2/tests/test_validate_workbook.py +441 -0
  136. zalos_sheetops-0.4.2/uv.lock +132 -0
@@ -0,0 +1,54 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+ tags: ["v*"]
8
+ workflow_dispatch:
9
+ inputs:
10
+ publish:
11
+ description: Publish the verified package to PyPI
12
+ type: boolean
13
+ default: false
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ verify:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
23
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
24
+ - run: uv sync --all-groups --locked
25
+ - name: Install rtk shim
26
+ run: |
27
+ mkdir -p "$HOME/.local/bin"
28
+ printf '#!/usr/bin/env bash\nexec "$@"\n' > "$HOME/.local/bin/rtk"
29
+ chmod +x "$HOME/.local/bin/rtk"
30
+ echo "$HOME/.local/bin" >> "$GITHUB_PATH"
31
+ - run: rtk uv run pytest
32
+ - run: rtk uv run ruff check .
33
+ - run: rtk uv run ruff format --check .
34
+ - run: uv build --no-sources
35
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
36
+ with:
37
+ name: distributions
38
+ path: dist/*
39
+ if-no-files-found: error
40
+
41
+ publish:
42
+ if: github.event_name == 'workflow_dispatch' && inputs.publish && github.ref == 'refs/heads/main'
43
+ needs: verify
44
+ runs-on: ubuntu-latest
45
+ environment: pypi
46
+ permissions:
47
+ id-token: write
48
+ steps:
49
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
50
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
51
+ with:
52
+ name: distributions
53
+ path: dist
54
+ - run: uv publish --trusted-publishing always dist/*
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ .pytest_cache/
3
+ .ruff_cache/
4
+ __pycache__/
5
+ *.py[cod]
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .superpowers/
10
+ .benchmarks/
@@ -0,0 +1,64 @@
1
+ # Zalos SheetOps Agent Instructions
2
+
3
+ This repo builds spreadsheet tools that Codex, Claude Code, and other AI agents
4
+ can trust for precise Excel and CSV work.
5
+
6
+ ## Non-Negotiable Rules
7
+
8
+ - Use test-driven development for every behavior change.
9
+ - Write the failing test first and verify it fails for the expected reason.
10
+ - Add return type annotations to every function.
11
+ - Use built-in collection types such as `list` and `dict`.
12
+ - Do not add a CLI command without JSON contract tests.
13
+ - Do not silently guess workbook structure.
14
+ - Fail closed with a stable `errorCode` and compact JSON details.
15
+ - Keep output deterministic so agents can parse it safely.
16
+ - Record provenance for copied or adapted upstream tests.
17
+ - Keep `skills/claude/SKILL.md` as a symlink to `skills/codex/SKILL.md`.
18
+
19
+ ## Command Contract
20
+
21
+ Every command returns a JSON object with this successful shape:
22
+
23
+ ```json
24
+ {
25
+ "ok": true,
26
+ "command": "inspect",
27
+ "data": {},
28
+ "warnings": [],
29
+ "artifacts": []
30
+ }
31
+ ```
32
+
33
+ Every failure returns this shape:
34
+
35
+ ```json
36
+ {
37
+ "ok": false,
38
+ "command": "inspect",
39
+ "errorCode": "file_not_found",
40
+ "message": "Workbook file does not exist",
41
+ "details": {},
42
+ "warnings": [],
43
+ "artifacts": []
44
+ }
45
+ ```
46
+
47
+ ## Required Verification
48
+
49
+ Before completing a change, run:
50
+
51
+ ```bash
52
+ rtk uv run pytest
53
+ rtk uv run ruff check .
54
+ rtk uv run ruff format --check .
55
+ ```
56
+
57
+ ## Contribution Flow
58
+
59
+ 1. Pick one public behavior.
60
+ 2. Add or port the failing test first.
61
+ 3. Run the focused test and confirm the red state.
62
+ 4. Implement the smallest code that passes.
63
+ 5. Run the focused tests and relevant suite.
64
+ 6. Update examples or skills only after behavior is stable.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zalos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.5
2
+ Name: zalos-sheetops
3
+ Version: 0.4.2
4
+ Summary: Agent-native Excel and CSV operations toolkit
5
+ Project-URL: Repository, https://github.com/Zalos-io/zalos-sheetops
6
+ Project-URL: Issues, https://github.com/Zalos-io/zalos-sheetops/issues
7
+ Author: Zalos
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.11
13
+ Requires-Dist: openpyxl<4,>=3.1.0
14
+ Description-Content-Type: text/markdown
15
+
16
+ # Zalos SheetOps
17
+
18
+ Agent-native Excel and CSV operations toolkit.
19
+
20
+ The project prioritizes precise, deterministic spreadsheet operations for AI
21
+ agents. Features are added test-first and exposed through machine-readable CLI
22
+ contracts before agents are encouraged to rely on them.
23
+
24
+ ## Agent Examples
25
+
26
+ Every command prints one line of compact JSON by default. Pass `--pretty` when
27
+ a human needs to read the output; `--raw-output` remains accepted as a
28
+ deprecated alias for the default.
29
+
30
+ ```bash
31
+ sheetops doctor libreoffice
32
+ sheetops inspect --json '{"path":"output.xlsx","engine":"auto","maxFormulaCells":20}'
33
+ sheetops read-table --json '{"path":"output.xlsx","sheet":"Data","headerStrategy":"row","headerRow":1,"maxRows":50}'
34
+ sheetops cells read --json '{"path":"output.xlsx","sheet":"Summary","range":"A1:D20","include":["values","formulas","cachedValues","numberFormats"]}'
35
+ sheetops cells search --json '{"path":"output.xlsx","query":"#4BACC6","fields":["fillColors"],"sheet":"Summary","range":"A1:Z200","limit":100,"offset":0}'
36
+ sheetops hyperlinks assert --json '{"path":"output.xlsx","sheet":"Summary","checks":[{"cell":"B2","target":"https://example.com/customer","display":"Customer portal"}]}'
37
+ sheetops styles assert --json '{"path":"output.xlsx","sheet":"Merchant","checks":[{"cell":"E2","fill":"#D9EAF7"},{"cell":"H2","commentContains":"Method of Payment Code"},{"cell":"G3","fill":"#D9EAF7","formula":true}]}'
38
+ sheetops formulas scan --json '{"path":"output.xlsx","sheet":"Summary","range":"A1:D50","include":["errors"],"failFast":true,"engine":"auto"}'
39
+ sheetops normalize preview --json '{"path":"input.xlsx","sheet":"Data","headerStrategy":"detect","columns":[{"source":"Amount","kind":"amount","name":"amount"}]}'
40
+ sheetops recalc --json '{"path":"output.xlsx","outputDir":"outputs/recalc"}'
41
+ sheetops diff --json '{"left":{"path":"actual.csv"},"right":{"path":"expected.csv"},"keyColumns":["reference"],"compareColumns":["amount"],"amountTolerance":"0.00"}'
42
+ sheetops reconcile --json '{"left":{"path":"bank.csv"},"right":{"path":"processor.csv"},"leftKeyColumns":["reference"],"rightKeyColumns":["reference"],"leftAmountColumn":"amount","rightAmountColumn":"amount","amountTolerance":"0.01"}'
43
+ ```
44
+
45
+ Run `scripts/setup-libreoffice` when `sheetops doctor libreoffice`
46
+ reports that LibreOffice is missing or broken. The script prints the appropriate
47
+ macOS or Linux install command and never runs `sudo` automatically.
48
+
49
+ `inspect` defaults to the XML engine so large workbook metadata checks do not
50
+ materialize every worksheet through OpenPyXL. Keep OpenPyXL read-only usage to
51
+ bounded cell or range commands where the requested area is explicit.
52
+ Inspection reports exact sheet visibility, defined names, bounded merged ranges,
53
+ and chart/image anchors. `cells search` stops at `maxCells` and reports
54
+ `stoppedEarly` rather than silently claiming a complete result.
55
+
56
+ Keep responses token-bounded: `maxRows` on table reads, `maxFormulaCells` on
57
+ inspect (default 20), `maxResults` on scans, and explicit `include` lists on
58
+ cell reads. `read-table` returns `headers` plus positional `rows` arrays and
59
+ stops scanning at `maxRows` under `headerStrategy` `row`, reporting
60
+ `"rowCount": null` with `"truncated": true` when the total is unknown.
61
+ `cells read` omits blank cells and null formulas; compare `returnedCellCount`
62
+ with `cellCount` to see how many cells were blank.
63
+
64
+ ## Verification
65
+
66
+ Run these before claiming a change is ready:
67
+
68
+ ```bash
69
+ rtk uv run pytest
70
+ rtk uv run ruff check .
71
+ rtk uv run ruff format --check .
72
+ ```
73
+
74
+ ## Releases
75
+
76
+ `v0.4.2` is the first public release, with MIT licensing and public package metadata.
77
+ It preserves the `v0.4.1` behavior baseline. It fixes worksheet bounds inspection
78
+ when dimension metadata is missing or invalid, including implicit row and cell
79
+ coordinates. This prevents non-empty worksheets from being reported as `A1`.
80
+
81
+ The `v0.4.0` release added deterministic, bounded
82
+ `cells search` across values, formulas, cached values, number formats, font
83
+ colors, and fill colors, with regex, pagination, hidden-sheet control, and
84
+ explicit scan limits. XML inspection now reports exact sheet states, defined
85
+ names, merged-range metadata, and chart/image anchors while failing closed
86
+ with `invalid_workbook` for malformed drawing relationships. Metadata output
87
+ is independently truncated without hiding total counts.
88
+
89
+ It builds on the `v0.3.1` performance and agent-output release, including the
90
+ packaging guard that keeps `zalos_sheetops.__version__` aligned with the
91
+ distribution version. The CLI dropped typer/click/rich for a zero-dependency
92
+ dispatcher, so cold start halved again and `--help` renders plain text.
93
+ `read-table` and `normalize preview` stop streaming at `maxRows` under
94
+ explicit header rows (about 80x faster on 100k-row workbooks), `validate`
95
+ runs about twice as fast, and `formulas scan` streams through the shared
96
+ expat engine instead of ElementTree. Output is compact JSON by default with
97
+ `--pretty` opt-in, `read-table` rows are positional arrays under `headers`,
98
+ `cells read` omits blank cells and null formulas and reports
99
+ `returnedCellCount`, `styles read` drops redundant row/column fields, and
100
+ `inspect` caps `formulaCells` at 20 by default (`formulaCellsTruncated`
101
+ signals more). The XML engine now expands shared formulas natively using
102
+ openpyxl's translator, so `cells read`, `formulas scan`, and
103
+ `compare labeled-cells` report the real per-cell formula instead of "=" for
104
+ shared-formula followers, and formula scans on shared-formula workbooks no
105
+ longer fall back to the slow openpyxl load. Two precision fixes validated
106
+ against 100MB+ production workbooks: `inspect` tag counting no longer drops
107
+ tags that straddle its internal 1MB scan-chunk boundaries, and `maxResults`
108
+ on `formulas scan` caps returned items across the whole workbook on both
109
+ engines instead of per sheet. The envelope contract
110
+ (`ok`/`command`/`data`/`warnings`/`artifacts`) is unchanged. Consumers upgrading from `v0.2.11` must adapt to
111
+ the new `data` payload shapes for `read-table`, `cells read`, and
112
+ `styles read`, and to the compact default output.
113
+
114
+ It includes the `v0.1.0` `inspect`, `read-table`, `validate`, and
115
+ `formulas scan` baseline, plus fixture-backed `cells read`, scoped formula
116
+ scanning, validation rules, LibreOffice doctor/recalc support, normalization,
117
+ diff, reconciliation, style/comment assertion commands, display overflow
118
+ scanning, italic style assertions, style extraction for bounded cell ranges,
119
+ and a fast XML-backed style assertion path for targeted formula, fill, and
120
+ comment checks. Formula scanning defaults to the faster XML engine, supports
121
+ formula/error category selection, supports fail-fast formula error checks,
122
+ and falls back to `openpyxl` for unsupported workbook features. Hyperlink
123
+ assertion supports cell-targeted XML validation for external or internal
124
+ targets, display text, and tooltips. Workbook inspection defaults to the XML
125
+ engine for full-workbook metadata scans, and CLI help explains command
126
+ intent, JSON payloads, and bounded range guidance.
127
+
128
+ ## Install
129
+
130
+ ```bash
131
+ python -m pip install zalos-sheetops==0.4.2
132
+ ```
133
+
134
+ The public repository is https://github.com/Zalos-io/zalos-sheetops.
135
+ Until the initial PyPI release is available, install from its reviewed release tag.
136
+ The internal repository and its history are not part of this public distribution.
137
+
138
+ ## License
139
+
140
+ MIT. Borrowed test fixtures retain their upstream licenses and attribution in
141
+ `tests/fixtures/manifest.json` and the adjacent upstream license files.
@@ -0,0 +1,126 @@
1
+ # Zalos SheetOps
2
+
3
+ Agent-native Excel and CSV operations toolkit.
4
+
5
+ The project prioritizes precise, deterministic spreadsheet operations for AI
6
+ agents. Features are added test-first and exposed through machine-readable CLI
7
+ contracts before agents are encouraged to rely on them.
8
+
9
+ ## Agent Examples
10
+
11
+ Every command prints one line of compact JSON by default. Pass `--pretty` when
12
+ a human needs to read the output; `--raw-output` remains accepted as a
13
+ deprecated alias for the default.
14
+
15
+ ```bash
16
+ sheetops doctor libreoffice
17
+ sheetops inspect --json '{"path":"output.xlsx","engine":"auto","maxFormulaCells":20}'
18
+ sheetops read-table --json '{"path":"output.xlsx","sheet":"Data","headerStrategy":"row","headerRow":1,"maxRows":50}'
19
+ sheetops cells read --json '{"path":"output.xlsx","sheet":"Summary","range":"A1:D20","include":["values","formulas","cachedValues","numberFormats"]}'
20
+ sheetops cells search --json '{"path":"output.xlsx","query":"#4BACC6","fields":["fillColors"],"sheet":"Summary","range":"A1:Z200","limit":100,"offset":0}'
21
+ sheetops hyperlinks assert --json '{"path":"output.xlsx","sheet":"Summary","checks":[{"cell":"B2","target":"https://example.com/customer","display":"Customer portal"}]}'
22
+ sheetops styles assert --json '{"path":"output.xlsx","sheet":"Merchant","checks":[{"cell":"E2","fill":"#D9EAF7"},{"cell":"H2","commentContains":"Method of Payment Code"},{"cell":"G3","fill":"#D9EAF7","formula":true}]}'
23
+ sheetops formulas scan --json '{"path":"output.xlsx","sheet":"Summary","range":"A1:D50","include":["errors"],"failFast":true,"engine":"auto"}'
24
+ sheetops normalize preview --json '{"path":"input.xlsx","sheet":"Data","headerStrategy":"detect","columns":[{"source":"Amount","kind":"amount","name":"amount"}]}'
25
+ sheetops recalc --json '{"path":"output.xlsx","outputDir":"outputs/recalc"}'
26
+ sheetops diff --json '{"left":{"path":"actual.csv"},"right":{"path":"expected.csv"},"keyColumns":["reference"],"compareColumns":["amount"],"amountTolerance":"0.00"}'
27
+ sheetops reconcile --json '{"left":{"path":"bank.csv"},"right":{"path":"processor.csv"},"leftKeyColumns":["reference"],"rightKeyColumns":["reference"],"leftAmountColumn":"amount","rightAmountColumn":"amount","amountTolerance":"0.01"}'
28
+ ```
29
+
30
+ Run `scripts/setup-libreoffice` when `sheetops doctor libreoffice`
31
+ reports that LibreOffice is missing or broken. The script prints the appropriate
32
+ macOS or Linux install command and never runs `sudo` automatically.
33
+
34
+ `inspect` defaults to the XML engine so large workbook metadata checks do not
35
+ materialize every worksheet through OpenPyXL. Keep OpenPyXL read-only usage to
36
+ bounded cell or range commands where the requested area is explicit.
37
+ Inspection reports exact sheet visibility, defined names, bounded merged ranges,
38
+ and chart/image anchors. `cells search` stops at `maxCells` and reports
39
+ `stoppedEarly` rather than silently claiming a complete result.
40
+
41
+ Keep responses token-bounded: `maxRows` on table reads, `maxFormulaCells` on
42
+ inspect (default 20), `maxResults` on scans, and explicit `include` lists on
43
+ cell reads. `read-table` returns `headers` plus positional `rows` arrays and
44
+ stops scanning at `maxRows` under `headerStrategy` `row`, reporting
45
+ `"rowCount": null` with `"truncated": true` when the total is unknown.
46
+ `cells read` omits blank cells and null formulas; compare `returnedCellCount`
47
+ with `cellCount` to see how many cells were blank.
48
+
49
+ ## Verification
50
+
51
+ Run these before claiming a change is ready:
52
+
53
+ ```bash
54
+ rtk uv run pytest
55
+ rtk uv run ruff check .
56
+ rtk uv run ruff format --check .
57
+ ```
58
+
59
+ ## Releases
60
+
61
+ `v0.4.2` is the first public release, with MIT licensing and public package metadata.
62
+ It preserves the `v0.4.1` behavior baseline. It fixes worksheet bounds inspection
63
+ when dimension metadata is missing or invalid, including implicit row and cell
64
+ coordinates. This prevents non-empty worksheets from being reported as `A1`.
65
+
66
+ The `v0.4.0` release added deterministic, bounded
67
+ `cells search` across values, formulas, cached values, number formats, font
68
+ colors, and fill colors, with regex, pagination, hidden-sheet control, and
69
+ explicit scan limits. XML inspection now reports exact sheet states, defined
70
+ names, merged-range metadata, and chart/image anchors while failing closed
71
+ with `invalid_workbook` for malformed drawing relationships. Metadata output
72
+ is independently truncated without hiding total counts.
73
+
74
+ It builds on the `v0.3.1` performance and agent-output release, including the
75
+ packaging guard that keeps `zalos_sheetops.__version__` aligned with the
76
+ distribution version. The CLI dropped typer/click/rich for a zero-dependency
77
+ dispatcher, so cold start halved again and `--help` renders plain text.
78
+ `read-table` and `normalize preview` stop streaming at `maxRows` under
79
+ explicit header rows (about 80x faster on 100k-row workbooks), `validate`
80
+ runs about twice as fast, and `formulas scan` streams through the shared
81
+ expat engine instead of ElementTree. Output is compact JSON by default with
82
+ `--pretty` opt-in, `read-table` rows are positional arrays under `headers`,
83
+ `cells read` omits blank cells and null formulas and reports
84
+ `returnedCellCount`, `styles read` drops redundant row/column fields, and
85
+ `inspect` caps `formulaCells` at 20 by default (`formulaCellsTruncated`
86
+ signals more). The XML engine now expands shared formulas natively using
87
+ openpyxl's translator, so `cells read`, `formulas scan`, and
88
+ `compare labeled-cells` report the real per-cell formula instead of "=" for
89
+ shared-formula followers, and formula scans on shared-formula workbooks no
90
+ longer fall back to the slow openpyxl load. Two precision fixes validated
91
+ against 100MB+ production workbooks: `inspect` tag counting no longer drops
92
+ tags that straddle its internal 1MB scan-chunk boundaries, and `maxResults`
93
+ on `formulas scan` caps returned items across the whole workbook on both
94
+ engines instead of per sheet. The envelope contract
95
+ (`ok`/`command`/`data`/`warnings`/`artifacts`) is unchanged. Consumers upgrading from `v0.2.11` must adapt to
96
+ the new `data` payload shapes for `read-table`, `cells read`, and
97
+ `styles read`, and to the compact default output.
98
+
99
+ It includes the `v0.1.0` `inspect`, `read-table`, `validate`, and
100
+ `formulas scan` baseline, plus fixture-backed `cells read`, scoped formula
101
+ scanning, validation rules, LibreOffice doctor/recalc support, normalization,
102
+ diff, reconciliation, style/comment assertion commands, display overflow
103
+ scanning, italic style assertions, style extraction for bounded cell ranges,
104
+ and a fast XML-backed style assertion path for targeted formula, fill, and
105
+ comment checks. Formula scanning defaults to the faster XML engine, supports
106
+ formula/error category selection, supports fail-fast formula error checks,
107
+ and falls back to `openpyxl` for unsupported workbook features. Hyperlink
108
+ assertion supports cell-targeted XML validation for external or internal
109
+ targets, display text, and tooltips. Workbook inspection defaults to the XML
110
+ engine for full-workbook metadata scans, and CLI help explains command
111
+ intent, JSON payloads, and bounded range guidance.
112
+
113
+ ## Install
114
+
115
+ ```bash
116
+ python -m pip install zalos-sheetops==0.4.2
117
+ ```
118
+
119
+ The public repository is https://github.com/Zalos-io/zalos-sheetops.
120
+ Until the initial PyPI release is available, install from its reviewed release tag.
121
+ The internal repository and its history are not part of this public distribution.
122
+
123
+ ## License
124
+
125
+ MIT. Borrowed test fixtures retain their upstream licenses and attribution in
126
+ `tests/fixtures/manifest.json` and the adjacent upstream license files.
@@ -0,0 +1,218 @@
1
+ # SheetOps Architecture And Reference Repositories
2
+
3
+ This document is the orientation file for future AI agents contributing to
4
+ `zalos-sheetops`. It explains the package architecture, the trust model, and
5
+ the external repositories we studied or borrowed fixtures from.
6
+
7
+ ## Product Goal
8
+
9
+ `zalos-sheetops` is an agent-native Excel and CSV toolkit for Codex, Claude
10
+ Code, and financial-agent workflows. Its job is to make common spreadsheet
11
+ operations precise, deterministic, and safe enough that agents can call a
12
+ tested command instead of rewriting spreadsheet logic inside every financial
13
+ agent.
14
+
15
+ The package favors a small set of trusted operations over a large set of
16
+ loosely-tested helpers.
17
+
18
+ ## Architecture
19
+
20
+ The public surface is a JSON-first CLI plus small Python library modules behind
21
+ each command.
22
+
23
+ ```text
24
+ src/zalos_sheetops/
25
+ cli.py # JSON CLI dispatcher
26
+ command_registry.py # machine-readable command manifest
27
+ contracts.py # success/error response helpers
28
+ errors.py # stable SheetOpsError
29
+ paths.py # path hardening and local file validation
30
+
31
+ io/
32
+ inspect.py # workbook/sheet/cell metadata
33
+ search_cells.py # bounded, paginated value/formula/style search
34
+ headers.py # header detection and canonicalization
35
+ read_table.py # XLSX table extraction
36
+ read_csv_table.py # planned CSV table extraction
37
+
38
+ validation/
39
+ workbook.py # required sheets/columns and uniqueness checks
40
+
41
+ normalize/
42
+ text.py # header/text canonicalization
43
+ amounts.py # planned amount normalization
44
+ dates.py # planned date normalization
45
+ preview.py # planned normalize-preview command logic
46
+
47
+ formulas/
48
+ scan.py # formula and formula-error discovery
49
+
50
+ recalc/
51
+ libreoffice.py # planned LibreOffice recalculation wrapper
52
+
53
+ diff/
54
+ tabular.py # planned table diff helpers
55
+
56
+ reconcile/
57
+ exact.py # planned exact reconciliation helpers
58
+ ```
59
+
60
+ ## Command Contract
61
+
62
+ Every command returns structured JSON. Agents should parse these fields and
63
+ avoid parsing human prose.
64
+
65
+ Success:
66
+
67
+ ```json
68
+ {
69
+ "ok": true,
70
+ "command": "inspect",
71
+ "data": {},
72
+ "warnings": [],
73
+ "artifacts": []
74
+ }
75
+ ```
76
+
77
+ Failure:
78
+
79
+ ```json
80
+ {
81
+ "ok": false,
82
+ "command": "inspect",
83
+ "errorCode": "file_not_found",
84
+ "message": "Workbook file does not exist",
85
+ "details": {},
86
+ "warnings": [],
87
+ "artifacts": []
88
+ }
89
+ ```
90
+
91
+ ## Trust Model
92
+
93
+ - Public behavior starts with a failing test.
94
+ - Tests must verify exact JSON shapes for CLI behavior.
95
+ - Real workbook behavior should use fixture-backed tests where possible.
96
+ - Fixture provenance is mandatory for borrowed files.
97
+ - Commands fail closed when inputs are ambiguous.
98
+ - Source files are not mutated by default.
99
+ - Recalculation and generated outputs must go to explicit output paths.
100
+
101
+ ## Fixture Strategy
102
+
103
+ Fixtures live under `tests/fixtures/`.
104
+
105
+ ```text
106
+ tests/fixtures/
107
+ README.md
108
+ manifest.json
109
+ upstream/
110
+ calamine/
111
+ fastexcel/
112
+ openpyxl/
113
+ xlsx2csv/
114
+ xlsxwriter/
115
+ ```
116
+
117
+ Every upstream fixture must be registered in
118
+ `tests/fixtures/manifest.json` with:
119
+
120
+ - `fixtureId`
121
+ - `sourceRepo`
122
+ - `sourcePath`
123
+ - `license`
124
+ - `destination`
125
+ - `sha256`
126
+ - `behavior`
127
+
128
+ The manifest hash-locks fixture files so agents cannot unknowingly change test
129
+ ground truth.
130
+
131
+ ## Reference Repositories
132
+
133
+ These repositories were used as design or fixture references.
134
+
135
+ ### Spreadsheet Agent Skill
136
+
137
+ | Repo | Used For |
138
+ | --- | --- |
139
+ | `anthropics/skills` | `xlsx` skill conventions: formula-first workbooks, LibreOffice recalc, formula-error scans, color conventions, and agent-facing spreadsheet workflows |
140
+ | `dust-tt/dust@dc59bd33` (`front/lib/api/sandbox/image/profile/soffice/xlsx_inspect.py`) | MIT-licensed design reference for drawing-aware inspection, defined names, displayed color resolution, and paginated workbook search. No upstream implementation or tests were copied; SheetOps behavior was specified and tested independently. |
141
+
142
+ ### Core Workbook IO And Writing
143
+
144
+ | Repo | License | Used For |
145
+ | --- | --- | --- |
146
+ | `openpyxl` | MIT | Existing workbook inspection, formula text vs cached values, hidden sheets, hyperlinks, comments, defined names, formula token/translation behavior, XLSX fixtures |
147
+ | `XlsxWriter` | BSD-2-Clause | Generated workbook guardrails, formula writing, tables, data validation, conditional formatting, overlap checks, comparison XLSX fixtures |
148
+ | `fastexcel` | MIT | Fast Excel ingestion patterns, `header_row`, `skip_rows`, whitespace, duplicate columns, sheet visibility, table-offset fixtures |
149
+ | `calamine` | MIT | Cross-format spreadsheet parsing behavior, formula edge cases, shared formulas, `.xls`/`.xlsb`/`.ods` future support, fixtures |
150
+
151
+ ### Data Processing
152
+
153
+ | Repo | Used For |
154
+ | --- | --- |
155
+ | `polars` | DataFrame transform patterns: `select`, `with_columns`, `filter`, `group_by`, `join`, `concat`, lazy scans |
156
+ | `pandas` | Excel/CSV IO option reference: sheet selection, `header`, `dtype`, `parse_dates`, `thousands`, `decimal`, `skipfooter` |
157
+ | `numpy` | Numeric behavior reference only; not a primary SheetOps dependency |
158
+
159
+ ### Validation And Data Contracts
160
+
161
+ | Repo | Used For |
162
+ | --- | --- |
163
+ | `pandera` | Schema/check design: required columns, strict schemas, uniqueness, nullable/coerce behavior, dataframe-level checks |
164
+ | `frictionless-py` | File/table diagnostics: dialect, header rows, missing values, checklist-style validation |
165
+ | `great_expectations` | Expectation-style validation concepts and reporting vocabulary |
166
+
167
+ ### Matching And Reconciliation
168
+
169
+ | Repo | Used For |
170
+ | --- | --- |
171
+ | `RapidFuzz` | Future fuzzy matching, string similarity thresholds, scorer behavior |
172
+
173
+ ### CSV, Diff, And Conversion
174
+
175
+ | Repo | License | Used For |
176
+ | --- | --- | --- |
177
+ | `xlsx2csv` | MIT | XLSX-to-CSV fixtures and expected CSV baselines for datetimes, UTF-8, empty rows, hyperlinks, delimiters |
178
+ | `csvkit` | MIT | CSV dialect and cleanup edge cases: BOM, Latin-1, short/long rows, no-header files, newline behavior |
179
+ | `csv-diff` | Apache-2.0 | Keyed row diff shape: added rows, removed rows, changed rows |
180
+ | `daff` | MIT | Table diff concepts, row/column alignment, patch-style comparison behavior |
181
+
182
+ ### Formula Evaluation And Recalculation
183
+
184
+ | Repo | Used For |
185
+ | --- | --- |
186
+ | `xlcalculator` | Offline formula-evaluation reference and limitations |
187
+ | `formulas` | Formula parser/evaluator reference, supported function landscape, compare-method ideas |
188
+ | LibreOffice | Primary future recalculation path because it recalculates real XLSX workbooks instead of approximating Excel behavior |
189
+
190
+ ## Borrowed Fixture Sources
191
+
192
+ The current fixture manifest borrows files from:
193
+
194
+ - `fastexcel`
195
+ - `calamine`
196
+ - `openpyxl`
197
+ - `xlsx2csv`
198
+ - `XlsxWriter`
199
+
200
+ See `tests/fixtures/manifest.json` for exact source paths, destination paths,
201
+ licenses, SHA256 hashes, and the behavior each fixture protects.
202
+
203
+ ## How To Add A New Reference Or Fixture
204
+
205
+ 1. Prefer a small synthetic fixture if it proves the behavior.
206
+ 2. Borrow an upstream fixture only when real workbook structure matters.
207
+ 3. Copy the file into `tests/fixtures/upstream/<repo>/`.
208
+ 4. Record source repo, source path, license, destination, SHA256, and protected
209
+ behavior in `tests/fixtures/manifest.json`.
210
+ 5. Add or update a test that fails before the implementation change.
211
+ 6. Run:
212
+
213
+ ```bash
214
+ rtk uv run pytest
215
+ rtk uv run ruff check .
216
+ rtk uv run ruff format --check .
217
+ ```
218
+