maybeai-sheet-cli 0.7.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.
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/PKG-INFO +7 -1
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/README.md +6 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/pyproject.toml +1 -1
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/__init__.py +1 -1
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/excel_worksheet.py +44 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/range.py +42 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/workbook.py +24 -0
- maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/build_fixtures.py +213 -0
- maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/flat-records.xlsx +0 -0
- maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/mixed-engine.xlsx +0 -0
- maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/multi-table-layout.xlsx +0 -0
- maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/rows.json +12 -0
- maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/simple-grid.xlsx +0 -0
- maybeai_sheet_cli-0.8.0/tests/fixtures/benchmarks/simple-values.json +17 -0
- maybeai_sheet_cli-0.8.0/tests/test_benchmark_runner.py +154 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/tests/test_cli.py +35 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/tests/test_phase2_cli.py +28 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/.gitignore +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/cli.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/client.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/__init__.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/_phase2.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/cell.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/column.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/db_table.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/excel_table.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/file.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/formula.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/history.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/raw.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/row.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/share.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/sheet.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/style.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/table.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/worksheet.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/config.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/endpoints.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/errors.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/formatters.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/__init__.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/share.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/sheet.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/models/workbook.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/options.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/protocol.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/resolver.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/table_resolver.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/update.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/tests/test_endpoints.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/tests/test_formatters.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/tests/test_resolver.py +0 -0
- {maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/tests/test_two_engine_cli.py +0 -0
- {maybeai_sheet_cli-0.7.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.
|
|
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,6 +229,7 @@ mbs
|
|
|
226
229
|
│
|
|
227
230
|
├── excel_worksheet
|
|
228
231
|
│ ├── read API Read the full range of the worksheet.
|
|
232
|
+
│ ├── calculate NOW Recalculate formulas in one worksheet.
|
|
229
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.
|
|
@@ -394,6 +398,8 @@ mbs excel-worksheet column append --doc-id abc123 --worksheet-name Actuals --val
|
|
|
394
398
|
mbs excel-worksheet range lineage --doc-id abc123 --worksheet-name Model --cell E2 --format tree # API
|
|
395
399
|
|
|
396
400
|
# formula
|
|
401
|
+
mbs workbook calculate --doc-id abc123 # API
|
|
402
|
+
mbs excel-worksheet calculate --doc-id abc123 --worksheet-name Model # API
|
|
397
403
|
mbs excel-worksheet range calculate --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
|
|
398
404
|
mbs excel-worksheet range set-formula --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
|
|
399
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,6 +205,7 @@ mbs
|
|
|
202
205
|
│
|
|
203
206
|
├── excel_worksheet
|
|
204
207
|
│ ├── read API Read the full range of the worksheet.
|
|
208
|
+
│ ├── calculate NOW Recalculate formulas in one worksheet.
|
|
205
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.
|
|
@@ -370,6 +374,8 @@ mbs excel-worksheet column append --doc-id abc123 --worksheet-name Actuals --val
|
|
|
370
374
|
mbs excel-worksheet range lineage --doc-id abc123 --worksheet-name Model --cell E2 --format tree # API
|
|
371
375
|
|
|
372
376
|
# formula
|
|
377
|
+
mbs workbook calculate --doc-id abc123 # API
|
|
378
|
+
mbs excel-worksheet calculate --doc-id abc123 --worksheet-name Model # API
|
|
373
379
|
mbs excel-worksheet range calculate --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
|
|
374
380
|
mbs excel-worksheet range set-formula --doc-id abc123 --worksheet-name Model --cell E2 --formula "=SUM(B2:D2)" # API
|
|
375
381
|
|
{maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/excel_worksheet.py
RENAMED
|
@@ -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")
|
|
@@ -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("&", "&")
|
|
31
|
+
.replace("<", "<")
|
|
32
|
+
.replace(">", ">")
|
|
33
|
+
.replace('"', """)
|
|
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())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from scripts import benchmark_mbs_cli
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BenchmarkRunnerTests(unittest.TestCase):
|
|
13
|
+
def test_case_catalog_has_exactly_50_ranked_cases(self) -> None:
|
|
14
|
+
cases = benchmark_mbs_cli.load_cases(benchmark_mbs_cli.DEFAULT_CASES)
|
|
15
|
+
|
|
16
|
+
self.assertEqual(len(cases), 50)
|
|
17
|
+
self.assertEqual([case["rank"] for case in cases], list(range(1, 51)))
|
|
18
|
+
self.assertEqual(cases[0]["case_id"], "001")
|
|
19
|
+
self.assertEqual(cases[-1]["case_id"], "050")
|
|
20
|
+
|
|
21
|
+
def test_dry_run_writes_summary_html_and_case_artifacts(self) -> None:
|
|
22
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
23
|
+
report_root = Path(tmp)
|
|
24
|
+
result = subprocess.run(
|
|
25
|
+
[
|
|
26
|
+
"./.venv/bin/python",
|
|
27
|
+
"scripts/benchmark_mbs_cli.py",
|
|
28
|
+
"--dry-run",
|
|
29
|
+
"--no-publish-results",
|
|
30
|
+
"--skip-fixture-build",
|
|
31
|
+
"--run-id",
|
|
32
|
+
"unit",
|
|
33
|
+
"--rank-max",
|
|
34
|
+
"2",
|
|
35
|
+
"--report-root",
|
|
36
|
+
str(report_root),
|
|
37
|
+
],
|
|
38
|
+
text=True,
|
|
39
|
+
capture_output=True,
|
|
40
|
+
check=False,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
44
|
+
run_dir = report_root / "mbs-cli-benchmark-unit"
|
|
45
|
+
self.assertTrue((run_dir / "summary.json").exists())
|
|
46
|
+
self.assertTrue((run_dir / "report.html").exists())
|
|
47
|
+
self.assertTrue((run_dir / "maybesheet_publish.json").exists())
|
|
48
|
+
self.assertTrue((run_dir / "cases" / "001-workbook-list-worksheets" / "verdict.json").exists())
|
|
49
|
+
|
|
50
|
+
summary = json.loads((run_dir / "summary.json").read_text(encoding="utf-8"))
|
|
51
|
+
self.assertEqual(summary["total"], 2)
|
|
52
|
+
self.assertEqual(summary["status"], "PASS")
|
|
53
|
+
self.assertEqual(summary["maybesheet"]["status"], "skipped")
|
|
54
|
+
|
|
55
|
+
def test_maybesheet_result_sheets_are_summary_details_and_html(self) -> None:
|
|
56
|
+
cases = benchmark_mbs_cli.load_cases(benchmark_mbs_cli.DEFAULT_CASES)[:2]
|
|
57
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
58
|
+
run_dir = Path(tmp) / "run"
|
|
59
|
+
run_dir.mkdir()
|
|
60
|
+
run = benchmark_mbs_cli.BenchmarkRun(
|
|
61
|
+
run_id="unit",
|
|
62
|
+
started_at="2026-07-02T00:00:00+00:00",
|
|
63
|
+
run_dir=run_dir,
|
|
64
|
+
mbs="./.venv/bin/mbs",
|
|
65
|
+
base_url=None,
|
|
66
|
+
context={"READ_DOC_ID": "doc1", "READ_TYPICAL_GID": "19"},
|
|
67
|
+
)
|
|
68
|
+
for case in cases:
|
|
69
|
+
case_dir = run_dir / "cases" / benchmark_mbs_cli.case_slug(case)
|
|
70
|
+
case_dir.mkdir(parents=True)
|
|
71
|
+
run.results.append(
|
|
72
|
+
benchmark_mbs_cli.CaseResult(
|
|
73
|
+
case=case,
|
|
74
|
+
case_dir=case_dir,
|
|
75
|
+
command=["mbs", *case["args"]],
|
|
76
|
+
status="success",
|
|
77
|
+
exit_code=0,
|
|
78
|
+
duration_ms=10,
|
|
79
|
+
response={"success": True},
|
|
80
|
+
key_response={"success": True, "document_id": "doc1"},
|
|
81
|
+
checks=[{"name": "exit_code", "pass": True}],
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
summary = benchmark_mbs_cli.build_summary(run, 0, "0.7.0")
|
|
85
|
+
benchmark_mbs_cli.generate_html_report(run, summary)
|
|
86
|
+
|
|
87
|
+
sheets = benchmark_mbs_cli.build_result_sheets(run, summary)
|
|
88
|
+
|
|
89
|
+
self.assertEqual(list(sheets), ["Summary", "Details", "HTMLReport"])
|
|
90
|
+
self.assertEqual(len(sheets["Details"]), 3)
|
|
91
|
+
self.assertEqual(sheets["Details"][0][0], "rank")
|
|
92
|
+
self.assertEqual(sheets["HTMLReport"][0], ["section", "item", "value"])
|
|
93
|
+
|
|
94
|
+
def test_stdout_contains_check_and_key_response_capture(self) -> None:
|
|
95
|
+
result = benchmark_mbs_cli.CommandResult(
|
|
96
|
+
command=["mbs", "update", "--dry-run"],
|
|
97
|
+
exit_code=0,
|
|
98
|
+
stdout="python -m pip install --upgrade maybeai-sheet-cli==0.7.0\n",
|
|
99
|
+
stderr="",
|
|
100
|
+
duration_ms=5,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
checks = benchmark_mbs_cli.evaluate_case_checks(
|
|
104
|
+
{
|
|
105
|
+
"expected_exit_code": 0,
|
|
106
|
+
"expected_json": False,
|
|
107
|
+
"expected_success": None,
|
|
108
|
+
"checks": [{"type": "stdout_contains", "value": "maybeai-sheet-cli==0.7.0"}],
|
|
109
|
+
},
|
|
110
|
+
result,
|
|
111
|
+
None,
|
|
112
|
+
{},
|
|
113
|
+
{},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
self.assertTrue(all(check["pass"] for check in checks))
|
|
117
|
+
|
|
118
|
+
def test_case_env_unset_removes_proxy_for_command(self) -> None:
|
|
119
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
120
|
+
script = Path(tmp) / "env_probe.py"
|
|
121
|
+
script.write_text(
|
|
122
|
+
"import os, sys\n"
|
|
123
|
+
"sys.stdout.write('missing' if 'ALL_PROXY' not in os.environ else os.environ['ALL_PROXY'])\n",
|
|
124
|
+
encoding="utf-8",
|
|
125
|
+
)
|
|
126
|
+
case = {
|
|
127
|
+
"rank": 1,
|
|
128
|
+
"case_id": "env",
|
|
129
|
+
"name": "env probe",
|
|
130
|
+
"category": "unit",
|
|
131
|
+
"args": [str(script)],
|
|
132
|
+
"expected_json": False,
|
|
133
|
+
"expected_success": None,
|
|
134
|
+
"env_unset": ["ALL_PROXY"],
|
|
135
|
+
"checks": [{"type": "stdout_contains", "value": "missing"}],
|
|
136
|
+
}
|
|
137
|
+
run_dir = Path(tmp) / "run"
|
|
138
|
+
run = benchmark_mbs_cli.BenchmarkRun(
|
|
139
|
+
run_id="unit",
|
|
140
|
+
started_at="2026-07-02T00:00:00+00:00",
|
|
141
|
+
run_dir=run_dir,
|
|
142
|
+
mbs="./.venv/bin/python",
|
|
143
|
+
base_url=None,
|
|
144
|
+
context={"RUN_DIR": str(run_dir)},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
result = benchmark_mbs_cli.run_case(case, run, {"ALL_PROXY": "socks5://127.0.0.1:9"}, False)
|
|
148
|
+
|
|
149
|
+
self.assertEqual(result.status, "success")
|
|
150
|
+
self.assertEqual(result.stdout, "missing")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
unittest.main()
|
|
@@ -1126,6 +1126,41 @@ class CLITests(unittest.TestCase):
|
|
|
1126
1126
|
self.assertEqual(endpoint, "/api/v1/excel/recalculate_formulas")
|
|
1127
1127
|
self.assertEqual(payload["worksheet_name"], "Model")
|
|
1128
1128
|
|
|
1129
|
+
def test_workbook_calculate_recalculates_whole_workbook(self) -> None:
|
|
1130
|
+
with patch("maybeai_sheet.commands.sheet.MaybeAIClient.post_json") as post_json:
|
|
1131
|
+
post_json.return_value = {"success": True}
|
|
1132
|
+
result = self.runner.invoke(
|
|
1133
|
+
app,
|
|
1134
|
+
["workbook", "calculate", "--doc-id", "abc123"],
|
|
1135
|
+
env=self.env,
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
self.assertEqual(result.exit_code, 0, result.output)
|
|
1139
|
+
endpoint, payload = post_json.call_args.args
|
|
1140
|
+
self.assertEqual(endpoint, "/api/v1/excel/recalculate_formulas")
|
|
1141
|
+
self.assertNotIn("worksheet_name", payload)
|
|
1142
|
+
|
|
1143
|
+
def test_excel_worksheet_calculate_recalculates_worksheet(self) -> None:
|
|
1144
|
+
with patch("maybeai_sheet.commands.sheet.MaybeAIClient.post_json") as post_json:
|
|
1145
|
+
post_json.return_value = {"success": True}
|
|
1146
|
+
result = self.runner.invoke(
|
|
1147
|
+
app,
|
|
1148
|
+
[
|
|
1149
|
+
"excel-worksheet",
|
|
1150
|
+
"calculate",
|
|
1151
|
+
"--doc-id",
|
|
1152
|
+
"abc123",
|
|
1153
|
+
"--worksheet-name",
|
|
1154
|
+
"Model",
|
|
1155
|
+
],
|
|
1156
|
+
env=self.env,
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
self.assertEqual(result.exit_code, 0, result.output)
|
|
1160
|
+
endpoint, payload = post_json.call_args.args
|
|
1161
|
+
self.assertEqual(endpoint, "/api/v1/excel/recalculate_formulas")
|
|
1162
|
+
self.assertEqual(payload["worksheet_name"], "Model")
|
|
1163
|
+
|
|
1129
1164
|
def test_workbook_import_alias_uses_import_endpoint(self) -> None:
|
|
1130
1165
|
with tempfile.NamedTemporaryFile(suffix=".xlsx") as temp_file:
|
|
1131
1166
|
with patch("maybeai_sheet.commands.workbook.MaybeAIClient.upload_file") as upload_file:
|
|
@@ -540,6 +540,34 @@ class Phase2CLITests(unittest.TestCase):
|
|
|
540
540
|
self.assertEqual(lineage_payload["targets"], [{"worksheet_name": "Model", "cell": "E2"}])
|
|
541
541
|
self.assertNotIn("compile", help_result.output)
|
|
542
542
|
|
|
543
|
+
def test_excel_worksheet_range_calculate_uses_formula_calculate_endpoint(self) -> None:
|
|
544
|
+
with patch("maybeai_sheet.commands.range.MaybeAIClient.post_json") as post_json:
|
|
545
|
+
post_json.return_value = {"success": True}
|
|
546
|
+
result = self.runner.invoke(
|
|
547
|
+
app,
|
|
548
|
+
[
|
|
549
|
+
"excel-worksheet",
|
|
550
|
+
"range",
|
|
551
|
+
"calculate",
|
|
552
|
+
"--doc-id",
|
|
553
|
+
"abc123",
|
|
554
|
+
"--worksheet-name",
|
|
555
|
+
"Model",
|
|
556
|
+
"--cell",
|
|
557
|
+
"E2",
|
|
558
|
+
"--formula",
|
|
559
|
+
"=SUM(B2:D2)",
|
|
560
|
+
],
|
|
561
|
+
env=self.env,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
self.assertEqual(result.exit_code, 0, result.output)
|
|
565
|
+
endpoint, payload = post_json.call_args.args
|
|
566
|
+
self.assertEqual(endpoint, "/api/v1/excel/calc-formula")
|
|
567
|
+
self.assertEqual(payload["cellAddress"], "E2")
|
|
568
|
+
self.assertEqual(payload["formula"], "=SUM(B2:D2)")
|
|
569
|
+
self.assertEqual(payload["worksheet_name"], "Model")
|
|
570
|
+
|
|
543
571
|
def test_cell_lineage_uses_verified_endpoint(self) -> None:
|
|
544
572
|
with patch("maybeai_sheet.commands.cell.MaybeAIClient.post_json") as post_json:
|
|
545
573
|
post_json.return_value = {"success": True}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{maybeai_sheet_cli-0.7.0 → maybeai_sheet_cli-0.8.0}/src/maybeai_sheet/commands/excel_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|