maybeai-sheet-cli 0.6.0__tar.gz → 0.8.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 (97) hide show
  1. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/PKG-INFO +9 -2
  2. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/README.md +8 -1
  3. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/pyproject.toml +9 -1
  4. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/__init__.py +1 -1
  5. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/cli.py +1 -0
  6. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/excel_worksheet.py +50 -1
  7. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/range.py +42 -0
  8. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/workbook.py +24 -0
  9. maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/build_fixtures.py +213 -0
  10. maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/flat-records.xlsx +0 -0
  11. maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/mixed-engine.xlsx +0 -0
  12. maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/multi-table-layout.xlsx +0 -0
  13. maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/rows.json +12 -0
  14. maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/simple-grid.xlsx +0 -0
  15. maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/simple-values.json +17 -0
  16. maybeai_sheet_cli-0.8.0/tests/test_benchmark_runner.py +154 -0
  17. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/tests/test_cli.py +59 -0
  18. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/tests/test_phase2_cli.py +28 -0
  19. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/tests/test_two_engine_cli.py +24 -0
  20. maybeai_sheet_cli-0.6.0/.claude/settings.local.json +0 -9
  21. maybeai_sheet_cli-0.6.0/CLAUDE.md +0 -46
  22. maybeai_sheet_cli-0.6.0/cli-packaging-plan.md +0 -279
  23. maybeai_sheet_cli-0.6.0/cli-plan.md +0 -389
  24. maybeai_sheet_cli-0.6.0/docs/agent-friendly-command-tree.md +0 -1424
  25. maybeai_sheet_cli-0.6.0/docs/operation-plan-cli-no-dsl-plan.md +0 -459
  26. maybeai_sheet_cli-0.6.0/docs/superpowers/plans/2026-07-01-mbs-agent-friendly-command-tree.md +0 -1270
  27. maybeai_sheet_cli-0.6.0/docs/superpowers/plans/2026-07-01-mbs-agent-friendly-phase-2-routes.md +0 -57
  28. maybeai_sheet_cli-0.6.0/docs/superpowers/plans/2026-07-01-mbs-agent-friendly-phase-2.md +0 -708
  29. maybeai_sheet_cli-0.6.0/docs/superpowers/plans/2026-07-01-mbs-be-new-backend-contracts-plan.md +0 -928
  30. maybeai_sheet_cli-0.6.0/docs/superpowers/plans/2026-07-01-mbs-deferred-table-routing-plan.md +0 -713
  31. maybeai_sheet_cli-0.6.0/docs/superpowers/plans/2026-07-01-mbs-excel-worksheet-multi-table-plan.md +0 -683
  32. maybeai_sheet_cli-0.6.0/docs/superpowers/plans/2026-07-01-mbs-two-engine-command-refactor.md +0 -139
  33. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/2026-07-01-mbs-deferred-routing-e2e-report.html +0 -239
  34. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/2026-07-01-phase1-test-review.html +0 -326
  35. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/2026-07-01-phase2-test-review.html +0 -309
  36. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/20260701-130317-excel-table-empty-range-e2e.html +0 -99
  37. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/20260701-130447-excel-table-empty-range-e2e.html +0 -23
  38. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/20260701-130556-excel-table-empty-range-e2e.html +0 -204
  39. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/20260701-230803-local-cli-endpoint-verification.html +0 -52
  40. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/20260701-232511-local7011-cli-table-metadata-verification.html +0 -78
  41. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/e2e-artifacts/e2e-payloads-20260701-130317.json +0 -79
  42. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/e2e-artifacts/e2e-payloads-20260701-130447.json +0 -82
  43. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/e2e-artifacts/e2e-payloads-20260701-130556.json +0 -414
  44. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/e2e-artifacts/multi-table-empty-range-20260701-130317.xlsx +0 -0
  45. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/e2e-artifacts/openpyxl-multi-table-empty-range-20260701-130447.xlsx +0 -0
  46. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/import_engine_local_report.html +0 -74
  47. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/llm_cost_import_auto.json +0 -479
  48. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/llm_cost_import_mixed_forced.json +0 -0
  49. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/llm_cost_list_worksheets.json +0 -355
  50. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/llm_cost_read_detail_A1_T5.json +0 -549
  51. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/summary.json +0 -79
  52. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/yunling_import_auto.json +0 -602
  53. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/yunling_list_worksheets.json +0 -598
  54. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-local-20260702-150800/yunling_read_T00_A1_F5.json +0 -343
  55. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-merged-20260702-153018/import_auto.json +0 -587
  56. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-merged-20260702-153018/list_worksheets.json +0 -112
  57. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-merged-20260702-153018/merged_two_tables_report.html +0 -12
  58. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-merged-20260702-153018/read_merged_1_A1_T5.json +0 -373
  59. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-merged-20260702-153018/read_merged_2_A1_T5.json +0 -373
  60. maybeai_sheet_cli-0.6.0/docs/superpowers/reports/import-engine-merged-20260702-153018/summary.json +0 -145
  61. maybeai_sheet_cli-0.6.0/docs/verification-matrix-fallback.md +0 -35
  62. maybeai_sheet_cli-0.6.0/uv.lock +0 -415
  63. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/.gitignore +0 -0
  64. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/client.py +0 -0
  65. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/__init__.py +0 -0
  66. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/_phase2.py +0 -0
  67. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/cell.py +0 -0
  68. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/column.py +0 -0
  69. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/db_table.py +0 -0
  70. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/excel_table.py +0 -0
  71. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/file.py +0 -0
  72. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/formula.py +0 -0
  73. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/history.py +0 -0
  74. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/raw.py +0 -0
  75. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/row.py +0 -0
  76. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/share.py +0 -0
  77. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/sheet.py +0 -0
  78. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/style.py +0 -0
  79. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/table.py +0 -0
  80. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/worksheet.py +0 -0
  81. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/config.py +0 -0
  82. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/endpoints.py +0 -0
  83. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/errors.py +0 -0
  84. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/formatters.py +0 -0
  85. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/__init__.py +0 -0
  86. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/share.py +0 -0
  87. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/sheet.py +0 -0
  88. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/workbook.py +0 -0
  89. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/options.py +0 -0
  90. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/protocol.py +0 -0
  91. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/resolver.py +0 -0
  92. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/table_resolver.py +0 -0
  93. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/update.py +0 -0
  94. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/tests/test_endpoints.py +0 -0
  95. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/tests/test_formatters.py +0 -0
  96. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/tests/test_resolver.py +0 -0
  97. {maybeai_sheet_cli-0.6.0 → maybeai_sheet_cli-0.8.0}/tests/test_update.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maybeai-sheet-cli
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: CLI for common MaybeAI spreadsheet operations
5
5
  Project-URL: Homepage, https://github.com/OmniMCP-AI/maybeai-sheet-cli
6
6
  Project-URL: Repository, https://github.com/OmniMCP-AI/maybeai-sheet-cli
@@ -167,6 +167,8 @@ Search, style, inspect history, and export:
167
167
  ```bash
168
168
  mbs excel-worksheet range search --doc-id abc123 --worksheet-name Sheet1 --query revenue --max-results 20
169
169
  mbs excel-worksheet style freeze-panes --doc-id abc123 --worksheet-name Sheet1 --cell B2
170
+ mbs workbook calculate --doc-id abc123
171
+ mbs excel-worksheet calculate --doc-id abc123 --worksheet-name Sheet1
170
172
  mbs excel-worksheet range calculate --doc-id abc123 --worksheet-name Sheet1 --cell E2 --formula "=SUM(B2:D2)"
171
173
  mbs version list --doc-id abc123 --limit 10
172
174
  mbs workbook export --doc-id abc123 --out workbook.xlsx
@@ -216,6 +218,7 @@ mbs
216
218
  │ ├── list-user-workbooks API List user workbooks with bounded output.
217
219
  | list-worksheets API List worksheets, including engine.
218
220
  │ ├── search NOW Search user workbooks with bounded output.
221
+ │ ├── calculate NOW Recalculate formulas across the workbook.
219
222
  │ ├── export API Export workbook bytes.
220
223
  │ └── copy API Copy a workbook.
221
224
  │ ├── upload API Upload a file through workbook import flow.
@@ -226,7 +229,8 @@ mbs
226
229
 
227
230
  ├── excel_worksheet
228
231
  │ ├── read API Read the full range of the worksheet.
229
- │ ├── list-table API List worksheets in a workbook.
232
+ │ ├── calculate NOW Recalculate formulas in one worksheet.
233
+ │ ├── list-table API Detect content-backed tables in a worksheet.
230
234
  │ ├── create API Create a worksheet, optionally with starter values.
231
235
  │ ├── update API Update worksheet metadata.
232
236
  │ ├── sample PLAN Return bounded worksheet/range sample rows.
@@ -351,6 +355,7 @@ mbs excel-worksheet read --doc-id abc123 --worksheet-name Actuals --range A1:D20
351
355
  mbs excel-worksheet create --doc-id abc123 --name Actuals # API
352
356
  mbs excel-worksheet update --doc-id abc123 --worksheet-name Actuals --title "Actuals FY26" # API
353
357
  mbs excel-worksheet metadata --doc-id abc123 --worksheet-name Actuals # API
358
+ mbs excel-worksheet list-table --doc-id abc123 --gid 3 # API
354
359
  mbs excel-worksheet sample --doc-id abc123 --worksheet-name Actuals --range A1:Z5000 --limit 50 # PLAN
355
360
 
356
361
  # excel-worksheet range
@@ -393,6 +398,8 @@ mbs excel-worksheet column append --doc-id abc123 --worksheet-name Actuals --val
393
398
  mbs excel-worksheet range lineage --doc-id abc123 --worksheet-name Model --cell E2 --format tree # API
394
399
 
395
400
  # formula
401
+ mbs workbook calculate --doc-id abc123 # API
402
+ mbs excel-worksheet calculate --doc-id abc123 --worksheet-name Model # API
396
403
  mbs excel-worksheet range calculate --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
397
404
  mbs excel-worksheet range set-formula --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
398
405
 
@@ -143,6 +143,8 @@ Search, style, inspect history, and export:
143
143
  ```bash
144
144
  mbs excel-worksheet range search --doc-id abc123 --worksheet-name Sheet1 --query revenue --max-results 20
145
145
  mbs excel-worksheet style freeze-panes --doc-id abc123 --worksheet-name Sheet1 --cell B2
146
+ mbs workbook calculate --doc-id abc123
147
+ mbs excel-worksheet calculate --doc-id abc123 --worksheet-name Sheet1
146
148
  mbs excel-worksheet range calculate --doc-id abc123 --worksheet-name Sheet1 --cell E2 --formula "=SUM(B2:D2)"
147
149
  mbs version list --doc-id abc123 --limit 10
148
150
  mbs workbook export --doc-id abc123 --out workbook.xlsx
@@ -192,6 +194,7 @@ mbs
192
194
  │ ├── list-user-workbooks API List user workbooks with bounded output.
193
195
  | list-worksheets API List worksheets, including engine.
194
196
  │ ├── search NOW Search user workbooks with bounded output.
197
+ │ ├── calculate NOW Recalculate formulas across the workbook.
195
198
  │ ├── export API Export workbook bytes.
196
199
  │ └── copy API Copy a workbook.
197
200
  │ ├── upload API Upload a file through workbook import flow.
@@ -202,7 +205,8 @@ mbs
202
205
 
203
206
  ├── excel_worksheet
204
207
  │ ├── read API Read the full range of the worksheet.
205
- │ ├── list-table API List worksheets in a workbook.
208
+ │ ├── calculate NOW Recalculate formulas in one worksheet.
209
+ │ ├── list-table API Detect content-backed tables in a worksheet.
206
210
  │ ├── create API Create a worksheet, optionally with starter values.
207
211
  │ ├── update API Update worksheet metadata.
208
212
  │ ├── sample PLAN Return bounded worksheet/range sample rows.
@@ -327,6 +331,7 @@ mbs excel-worksheet read --doc-id abc123 --worksheet-name Actuals --range A1:D20
327
331
  mbs excel-worksheet create --doc-id abc123 --name Actuals # API
328
332
  mbs excel-worksheet update --doc-id abc123 --worksheet-name Actuals --title "Actuals FY26" # API
329
333
  mbs excel-worksheet metadata --doc-id abc123 --worksheet-name Actuals # API
334
+ mbs excel-worksheet list-table --doc-id abc123 --gid 3 # API
330
335
  mbs excel-worksheet sample --doc-id abc123 --worksheet-name Actuals --range A1:Z5000 --limit 50 # PLAN
331
336
 
332
337
  # excel-worksheet range
@@ -369,6 +374,8 @@ mbs excel-worksheet column append --doc-id abc123 --worksheet-name Actuals --val
369
374
  mbs excel-worksheet range lineage --doc-id abc123 --worksheet-name Model --cell E2 --format tree # API
370
375
 
371
376
  # formula
377
+ mbs workbook calculate --doc-id abc123 # API
378
+ mbs excel-worksheet calculate --doc-id abc123 --worksheet-name Model # API
372
379
  mbs excel-worksheet range calculate --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
373
380
  mbs excel-worksheet range set-formula --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
374
381
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "maybeai-sheet-cli"
7
- version = "0.6.0"
7
+ version = "0.8.0"
8
8
  description = "CLI for common MaybeAI spreadsheet operations"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -39,3 +39,11 @@ mbs = "maybeai_sheet.cli:app"
39
39
 
40
40
  [tool.hatch.build.targets.wheel]
41
41
  packages = ["src/maybeai_sheet"]
42
+
43
+ [tool.hatch.build.targets.sdist]
44
+ include = [
45
+ "/src",
46
+ "/tests",
47
+ "/README.md",
48
+ "/pyproject.toml",
49
+ ]
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.6.0"
5
+ __version__ = "0.8.0"
@@ -52,6 +52,7 @@ def main(
52
52
  base_url: str = typer.Option(
53
53
  "https://play-be.omnimcp.ai",
54
54
  "--base-url",
55
+ envvar="MAYBEAI_BASE_URL",
55
56
  help="MaybeAI API base URL.",
56
57
  ),
57
58
  doc_id: Optional[str] = typer.Option(
@@ -16,6 +16,7 @@ from ..table_resolver import filter_content_backed_excel_tables
16
16
  from .column import app as column_app
17
17
  from .range import app as range_app
18
18
  from .row import app as row_app
19
+ from .sheet import recalculate as sheet_recalculate
19
20
  from .style import app as style_app
20
21
  from .worksheet import copy as worksheet_copy
21
22
  from .worksheet import create as worksheet_create
@@ -232,6 +233,49 @@ def move(
232
233
  )
233
234
 
234
235
 
236
+ @app.command("calculate")
237
+ def calculate(
238
+ ctx: typer.Context,
239
+ doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
240
+ url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
241
+ uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
242
+ gid: Optional[int] = typer.Option(None, "--gid", help="Worksheet gid."),
243
+ worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Worksheet name."),
244
+ output: Optional[str] = output_option(),
245
+ verbose: Optional[bool] = verbose_option(),
246
+ timeout: Optional[float] = timeout_option(),
247
+ ) -> None:
248
+ """Recalculate formulas in one worksheet."""
249
+ try:
250
+ if gid is not None and worksheet_name is None:
251
+ state = resolve_command_state(
252
+ ctx,
253
+ doc_id=doc_id,
254
+ url=url,
255
+ uri=uri,
256
+ gid=gid,
257
+ output=output,
258
+ verbose=verbose,
259
+ timeout=timeout,
260
+ )
261
+ target = resolve_target(state, force_gid_uri=True)
262
+ worksheet_name = target.worksheet_name
263
+ uri = target.uri
264
+ doc_id = target.document_id
265
+ sheet_recalculate(
266
+ ctx,
267
+ doc_id=doc_id,
268
+ url=url,
269
+ uri=uri,
270
+ worksheet_name=worksheet_name,
271
+ output=output,
272
+ verbose=verbose,
273
+ timeout=timeout,
274
+ )
275
+ except Exception as error: # pragma: no cover - CLI boundary
276
+ handle_cli_error(error)
277
+
278
+
235
279
  app.add_typer(range_app, name="range")
236
280
  app.add_typer(row_app, name="row")
237
281
  app.add_typer(column_app, name="column")
@@ -288,6 +332,7 @@ def list_table(
288
332
  doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
289
333
  url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
290
334
  uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
335
+ gid: Optional[int] = typer.Option(None, "--gid", help="Worksheet gid."),
291
336
  worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Optional worksheet name filter."),
292
337
  output: Optional[str] = output_option(),
293
338
  verbose: Optional[bool] = verbose_option(),
@@ -299,12 +344,16 @@ def list_table(
299
344
  doc_id=doc_id,
300
345
  url=url,
301
346
  uri=uri,
347
+ gid=gid,
302
348
  worksheet_name=worksheet_name,
303
349
  output=output,
304
350
  verbose=verbose,
305
351
  timeout=timeout,
306
352
  )
307
- target = resolve_target(state)
353
+ target = resolve_target(
354
+ state,
355
+ force_gid_uri=bool(state.gid is not None and not state.worksheet_name),
356
+ )
308
357
  if target.worksheet_name or target.gid is not None:
309
358
  endpoint = "/api/v1/excel/table/metadata"
310
359
  payload: dict[str, object] = {
@@ -132,6 +132,7 @@ def search(
132
132
  verbose: Optional[bool] = verbose_option(),
133
133
  timeout: Optional[float] = timeout_option(),
134
134
  ) -> None:
135
+ """Search worksheet cells for text."""
135
136
  try:
136
137
  state = common_options(
137
138
  ctx,
@@ -161,3 +162,44 @@ def search(
161
162
  )
162
163
  except Exception as error: # pragma: no cover - CLI boundary
163
164
  handle_cli_error(error)
165
+
166
+
167
+ @app.command("calculate")
168
+ def calculate(
169
+ ctx: typer.Context,
170
+ doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
171
+ url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
172
+ uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
173
+ worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Worksheet name."),
174
+ cell: str = typer.Option(..., "--cell", help="A1 cell address to calculate."),
175
+ formula: str = typer.Option(..., "--formula", help="Formula text to calculate."),
176
+ output: Optional[str] = output_option(),
177
+ verbose: Optional[bool] = verbose_option(),
178
+ timeout: Optional[float] = timeout_option(),
179
+ ) -> None:
180
+ """Calculate one formula expression for a worksheet cell."""
181
+ try:
182
+ state = common_options(
183
+ ctx,
184
+ doc_id=doc_id,
185
+ url=url,
186
+ uri=uri,
187
+ gid=None,
188
+ worksheet_name=worksheet_name,
189
+ output=output,
190
+ verbose=verbose,
191
+ timeout=timeout,
192
+ )
193
+ target = command_target(state)
194
+ require_worksheet(target)
195
+ payload = base_payload(target) | {"cellAddress": cell, "formula": formula}
196
+ run_post_command(
197
+ state=state,
198
+ endpoint="/api/v1/excel/calc-formula",
199
+ payload=payload,
200
+ target=target,
201
+ client_class=MaybeAIClient,
202
+ command_id="formula.calculate",
203
+ )
204
+ except Exception as error: # pragma: no cover - CLI boundary
205
+ handle_cli_error(error)
@@ -14,6 +14,7 @@ from ..models.workbook import CreateWorkbookRequest
14
14
  from ..options import output_option, resolve_command_state, timeout_option, verbose_option
15
15
  from ..resolver import ResolvedTarget, parse_document_target, resolve_target
16
16
  from ._phase2 import bounded_limit
17
+ from .sheet import recalculate as sheet_recalculate
17
18
 
18
19
  app = typer.Typer(help="Workbook-level operations.", no_args_is_help=True)
19
20
 
@@ -252,6 +253,29 @@ def import_workbook(
252
253
  )
253
254
 
254
255
 
256
+ @app.command("calculate")
257
+ def calculate(
258
+ ctx: typer.Context,
259
+ doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
260
+ url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
261
+ uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
262
+ output: Optional[str] = output_option(),
263
+ verbose: Optional[bool] = verbose_option(),
264
+ timeout: Optional[float] = timeout_option(),
265
+ ) -> None:
266
+ """Recalculate formulas across the workbook."""
267
+ sheet_recalculate(
268
+ ctx,
269
+ doc_id=doc_id,
270
+ url=url,
271
+ uri=uri,
272
+ worksheet_name=None,
273
+ output=output,
274
+ verbose=verbose,
275
+ timeout=timeout,
276
+ )
277
+
278
+
255
279
  @app.command("list")
256
280
  def list_workbooks(
257
281
  ctx: typer.Context,
@@ -0,0 +1,213 @@
1
+ """Build deterministic XLSX and JSON fixtures for the mbs benchmark suite."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from pathlib import Path
8
+ from zipfile import ZIP_DEFLATED, ZipFile
9
+
10
+
11
+ ROOT = Path(__file__).resolve().parent
12
+
13
+
14
+ def cell_ref(row: int, col: int) -> str:
15
+ letters = ""
16
+ while col:
17
+ col, rem = divmod(col - 1, 26)
18
+ letters = chr(65 + rem) + letters
19
+ return f"{letters}{row}"
20
+
21
+
22
+ def cell_xml(row: int, col: int, value: object) -> str:
23
+ ref = cell_ref(row, col)
24
+ if value is None or value == "":
25
+ return f'<c r="{ref}"/>'
26
+ if isinstance(value, (int, float)):
27
+ return f'<c r="{ref}"><v>{value}</v></c>'
28
+ escaped = (
29
+ str(value)
30
+ .replace("&", "&amp;")
31
+ .replace("<", "&lt;")
32
+ .replace(">", "&gt;")
33
+ .replace('"', "&quot;")
34
+ )
35
+ return f'<c r="{ref}" t="inlineStr"><is><t>{escaped}</t></is></c>'
36
+
37
+
38
+ def sheet_xml(rows: list[list[object]]) -> str:
39
+ row_nodes = []
40
+ for row_idx, row in enumerate(rows, start=1):
41
+ cells = "".join(cell_xml(row_idx, col_idx, value) for col_idx, value in enumerate(row, start=1))
42
+ row_nodes.append(f'<row r="{row_idx}">{cells}</row>')
43
+ return (
44
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
45
+ '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
46
+ f"<sheetData>{''.join(row_nodes)}</sheetData>"
47
+ "</worksheet>"
48
+ )
49
+
50
+
51
+ def workbook_xml(sheet_names: list[str]) -> str:
52
+ sheets = "".join(
53
+ f'<sheet name="{name}" sheetId="{idx}" r:id="rId{idx}"/>'
54
+ for idx, name in enumerate(sheet_names, start=1)
55
+ )
56
+ return (
57
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
58
+ '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
59
+ 'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
60
+ f"<sheets>{sheets}</sheets>"
61
+ "</workbook>"
62
+ )
63
+
64
+
65
+ def workbook_rels_xml(sheet_names: list[str]) -> str:
66
+ rels = "".join(
67
+ '<Relationship '
68
+ f'Id="rId{idx}" '
69
+ 'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" '
70
+ f'Target="worksheets/sheet{idx}.xml"/>'
71
+ for idx, _ in enumerate(sheet_names, start=1)
72
+ )
73
+ return (
74
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
75
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
76
+ f"{rels}</Relationships>"
77
+ )
78
+
79
+
80
+ def root_rels_xml() -> str:
81
+ return (
82
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
83
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
84
+ '<Relationship Id="rId1" '
85
+ 'Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" '
86
+ 'Target="xl/workbook.xml"/>'
87
+ "</Relationships>"
88
+ )
89
+
90
+
91
+ def content_types_xml(sheet_names: list[str]) -> str:
92
+ overrides = "".join(
93
+ '<Override '
94
+ f'PartName="/xl/worksheets/sheet{idx}.xml" '
95
+ 'ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
96
+ for idx, _ in enumerate(sheet_names, start=1)
97
+ )
98
+ return (
99
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
100
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
101
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
102
+ '<Default Extension="xml" ContentType="application/xml"/>'
103
+ '<Override PartName="/xl/workbook.xml" '
104
+ 'ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
105
+ f"{overrides}</Types>"
106
+ )
107
+
108
+
109
+ def write_xlsx(path: Path, sheets: dict[str, list[list[object]]]) -> None:
110
+ sheet_names = list(sheets)
111
+ path.parent.mkdir(parents=True, exist_ok=True)
112
+ with ZipFile(path, "w", ZIP_DEFLATED) as zf:
113
+ zf.writestr("[Content_Types].xml", content_types_xml(sheet_names))
114
+ zf.writestr("_rels/.rels", root_rels_xml())
115
+ zf.writestr("xl/workbook.xml", workbook_xml(sheet_names))
116
+ zf.writestr("xl/_rels/workbook.xml.rels", workbook_rels_xml(sheet_names))
117
+ for idx, name in enumerate(sheet_names, start=1):
118
+ zf.writestr(f"xl/worksheets/sheet{idx}.xml", sheet_xml(sheets[name]))
119
+
120
+
121
+ def write_json(path: Path, data: object) -> None:
122
+ path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
123
+
124
+
125
+ def build(output_dir: Path = ROOT) -> list[Path]:
126
+ output_dir.mkdir(parents=True, exist_ok=True)
127
+ created: list[Path] = []
128
+
129
+ simple_values = [
130
+ ["metric", "Q1", "Q2"],
131
+ ["revenue", 100, 120],
132
+ ["cost", 40, 55],
133
+ ]
134
+ rows = [
135
+ {"metric": "gross_profit", "Q1": 60, "Q2": 65},
136
+ {"metric": "margin", "Q1": "60%", "Q2": "54%"},
137
+ ]
138
+ write_json(output_dir / "simple-values.json", simple_values)
139
+ write_json(output_dir / "rows.json", rows)
140
+ created.extend([output_dir / "simple-values.json", output_dir / "rows.json"])
141
+
142
+ write_xlsx(
143
+ output_dir / "simple-grid.xlsx",
144
+ {
145
+ "BenchData": [
146
+ ["metric", "Q1", "Q2", "Q3"],
147
+ ["revenue", 100, 120, 140],
148
+ ["cost", 40, 55, 60],
149
+ ["profit", 60, 65, 80],
150
+ ]
151
+ },
152
+ )
153
+ created.append(output_dir / "simple-grid.xlsx")
154
+
155
+ write_xlsx(
156
+ output_dir / "flat-records.xlsx",
157
+ {
158
+ "Records": [
159
+ ["id", "customer", "amount", "status"],
160
+ [1, "Acme", 120.5, "paid"],
161
+ [2, "Beta", 340.0, "open"],
162
+ [3, "Core", 88.75, "paid"],
163
+ [4, "Delta", 901.1, "open"],
164
+ ]
165
+ },
166
+ )
167
+ created.append(output_dir / "flat-records.xlsx")
168
+
169
+ write_xlsx(
170
+ output_dir / "mixed-engine.xlsx",
171
+ {
172
+ "RecordsLarge": [
173
+ ["id", "amount", "status"],
174
+ *[[idx, idx * 3, "paid" if idx % 2 else "open"] for idx in range(1, 26)],
175
+ ],
176
+ "Cover": [["title", "Mixed engine fixture"], ["owner", "benchmark"]],
177
+ "Model": [["metric", "value"], ["revenue", 1200], ["cost", 700]],
178
+ "Events": [["event_id", "value"], [1, 20], [2, 22], [3, 25]],
179
+ },
180
+ )
181
+ created.append(output_dir / "mixed-engine.xlsx")
182
+
183
+ multi_table_rows = [
184
+ ["T01 Revenue Summary", "", "", "", ""],
185
+ ["", "", "", "", ""],
186
+ ["metric", "Q1", "Q2", "Q3", "total"],
187
+ ["revenue", 100, 120, 140, 360],
188
+ ["cost", 40, 55, 60, 155],
189
+ ["profit", 60, 65, 80, 205],
190
+ ["", "", "", "", ""],
191
+ ["", "", "", "", ""],
192
+ ["ratio", "Q1", "Q2", "Q3", "trend"],
193
+ ["margin", "60%", "54%", "57%", "up"],
194
+ ["cost_ratio", "40%", "46%", "43%", "down"],
195
+ ]
196
+ write_xlsx(output_dir / "multi-table-layout.xlsx", {"MultiTable": multi_table_rows})
197
+ created.append(output_dir / "multi-table-layout.xlsx")
198
+
199
+ return created
200
+
201
+
202
+ def main() -> int:
203
+ parser = argparse.ArgumentParser()
204
+ parser.add_argument("--output-dir", type=Path, default=ROOT)
205
+ args = parser.parse_args()
206
+ created = build(args.output_dir)
207
+ for path in created:
208
+ print(path)
209
+ return 0
210
+
211
+
212
+ if __name__ == "__main__":
213
+ raise SystemExit(main())
@@ -0,0 +1,12 @@
1
+ [
2
+ {
3
+ "metric": "gross_profit",
4
+ "Q1": 60,
5
+ "Q2": 65
6
+ },
7
+ {
8
+ "metric": "margin",
9
+ "Q1": "60%",
10
+ "Q2": "54%"
11
+ }
12
+ ]
@@ -0,0 +1,17 @@
1
+ [
2
+ [
3
+ "metric",
4
+ "Q1",
5
+ "Q2"
6
+ ],
7
+ [
8
+ "revenue",
9
+ 100,
10
+ 120
11
+ ],
12
+ [
13
+ "cost",
14
+ 40,
15
+ 55
16
+ ]
17
+ ]