maybeai-sheet-cli 0.5.0__tar.gz → 0.7.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 (74) hide show
  1. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/PKG-INFO +28 -6
  2. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/README.md +27 -5
  3. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/pyproject.toml +9 -1
  4. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/__init__.py +1 -1
  5. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/cli.py +1 -0
  6. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/db_table.py +156 -5
  7. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/excel_worksheet.py +6 -1
  8. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/workbook.py +56 -7
  9. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/endpoints.py +3 -0
  10. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/tests/test_cli.py +99 -0
  11. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/tests/test_endpoints.py +3 -0
  12. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/tests/test_two_engine_cli.py +133 -4
  13. maybeai_sheet_cli-0.5.0/.claude/settings.local.json +0 -9
  14. maybeai_sheet_cli-0.5.0/CLAUDE.md +0 -46
  15. maybeai_sheet_cli-0.5.0/cli-packaging-plan.md +0 -279
  16. maybeai_sheet_cli-0.5.0/cli-plan.md +0 -389
  17. maybeai_sheet_cli-0.5.0/docs/agent-friendly-command-tree.md +0 -1424
  18. maybeai_sheet_cli-0.5.0/docs/operation-plan-cli-no-dsl-plan.md +0 -459
  19. maybeai_sheet_cli-0.5.0/docs/superpowers/plans/2026-07-01-mbs-agent-friendly-command-tree.md +0 -1270
  20. maybeai_sheet_cli-0.5.0/docs/superpowers/plans/2026-07-01-mbs-agent-friendly-phase-2-routes.md +0 -57
  21. maybeai_sheet_cli-0.5.0/docs/superpowers/plans/2026-07-01-mbs-agent-friendly-phase-2.md +0 -708
  22. maybeai_sheet_cli-0.5.0/docs/superpowers/plans/2026-07-01-mbs-be-new-backend-contracts-plan.md +0 -928
  23. maybeai_sheet_cli-0.5.0/docs/superpowers/plans/2026-07-01-mbs-deferred-table-routing-plan.md +0 -713
  24. maybeai_sheet_cli-0.5.0/docs/superpowers/plans/2026-07-01-mbs-excel-worksheet-multi-table-plan.md +0 -683
  25. maybeai_sheet_cli-0.5.0/docs/superpowers/plans/2026-07-01-mbs-two-engine-command-refactor.md +0 -139
  26. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/2026-07-01-mbs-deferred-routing-e2e-report.html +0 -239
  27. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/2026-07-01-phase1-test-review.html +0 -326
  28. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/2026-07-01-phase2-test-review.html +0 -309
  29. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/20260701-130317-excel-table-empty-range-e2e.html +0 -99
  30. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/20260701-130447-excel-table-empty-range-e2e.html +0 -23
  31. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/20260701-130556-excel-table-empty-range-e2e.html +0 -204
  32. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/20260701-230803-local-cli-endpoint-verification.html +0 -52
  33. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/20260701-232511-local7011-cli-table-metadata-verification.html +0 -78
  34. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/e2e-artifacts/e2e-payloads-20260701-130317.json +0 -79
  35. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/e2e-artifacts/e2e-payloads-20260701-130447.json +0 -82
  36. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/e2e-artifacts/e2e-payloads-20260701-130556.json +0 -414
  37. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/e2e-artifacts/multi-table-empty-range-20260701-130317.xlsx +0 -0
  38. maybeai_sheet_cli-0.5.0/docs/superpowers/reports/e2e-artifacts/openpyxl-multi-table-empty-range-20260701-130447.xlsx +0 -0
  39. maybeai_sheet_cli-0.5.0/docs/verification-matrix-fallback.md +0 -35
  40. maybeai_sheet_cli-0.5.0/uv.lock +0 -415
  41. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/.gitignore +0 -0
  42. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/client.py +0 -0
  43. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/__init__.py +0 -0
  44. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/_phase2.py +0 -0
  45. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/cell.py +0 -0
  46. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/column.py +0 -0
  47. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/excel_table.py +0 -0
  48. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/file.py +0 -0
  49. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/formula.py +0 -0
  50. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/history.py +0 -0
  51. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/range.py +0 -0
  52. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/raw.py +0 -0
  53. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/row.py +0 -0
  54. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/share.py +0 -0
  55. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/sheet.py +0 -0
  56. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/style.py +0 -0
  57. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/table.py +0 -0
  58. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/commands/worksheet.py +0 -0
  59. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/config.py +0 -0
  60. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/errors.py +0 -0
  61. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/formatters.py +0 -0
  62. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/models/__init__.py +0 -0
  63. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/models/share.py +0 -0
  64. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/models/sheet.py +0 -0
  65. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/models/workbook.py +0 -0
  66. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/options.py +0 -0
  67. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/protocol.py +0 -0
  68. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/resolver.py +0 -0
  69. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/table_resolver.py +0 -0
  70. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/src/maybeai_sheet/update.py +0 -0
  71. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/tests/test_formatters.py +0 -0
  72. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/tests/test_phase2_cli.py +0 -0
  73. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.0}/tests/test_resolver.py +0 -0
  74. {maybeai_sheet_cli-0.5.0 → maybeai_sheet_cli-0.7.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.5.0
3
+ Version: 0.7.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
@@ -135,6 +135,13 @@ Import a large table-like `.xlsx` into SheetTable/PG:
135
135
  mbs workbook import ./large-table.xlsx --engine postgres
136
136
  ```
137
137
 
138
+ Import a mixed workbook and let the backend select engines per worksheet:
139
+
140
+ ```bash
141
+ mbs workbook import ./mixed-workbook.xlsx --engine auto
142
+ mbs workbook import ./mixed-workbook.xlsx --engine "postgres,excel,excel,postgres"
143
+ ```
144
+
138
145
  Append rows and verify:
139
146
 
140
147
  ```bash
@@ -219,7 +226,7 @@ mbs
219
226
 
220
227
  ├── excel_worksheet
221
228
  │ ├── read API Read the full range of the worksheet.
222
- │ ├── list-table API List worksheets in a workbook.
229
+ │ ├── list-table API Detect content-backed tables in a worksheet.
223
230
  │ ├── create API Create a worksheet, optionally with starter values.
224
231
  │ ├── update API Update worksheet metadata.
225
232
  │ ├── sample PLAN Return bounded worksheet/range sample rows.
@@ -252,9 +259,9 @@ mbs
252
259
  | |- lineage API Trace cell/range lineage.
253
260
 
254
261
  ├── db_worksheet(AKA db_table)
255
- │ ├── create PG Create a PG-backed table.
256
- │ ├── update PG Update PG-backed table metadata.
257
- │ ├── delete PG Delete a PG-backed table; requires --yes unless --dry-run.
262
+ │ ├── create PLAN Create a PG-backed table from JSON row data.
263
+ │ ├── update PLAN Update PG-backed rows from JSON row data.
264
+ │ ├── delete PLAN Delete PG-backed rows/table; requires --yes unless --dry-run.
258
265
  │ ├── schema API Infer table schema from a bounded PG read.
259
266
  │ ├── sample API Return bounded table sample rows.
260
267
  │ ├── read API Read PG-backed table rows.
@@ -344,6 +351,7 @@ mbs excel-worksheet read --doc-id abc123 --worksheet-name Actuals --range A1:D20
344
351
  mbs excel-worksheet create --doc-id abc123 --name Actuals # API
345
352
  mbs excel-worksheet update --doc-id abc123 --worksheet-name Actuals --title "Actuals FY26" # API
346
353
  mbs excel-worksheet metadata --doc-id abc123 --worksheet-name Actuals # API
354
+ mbs excel-worksheet list-table --doc-id abc123 --gid 3 # API
347
355
  mbs excel-worksheet sample --doc-id abc123 --worksheet-name Actuals --range A1:Z5000 --limit 50 # PLAN
348
356
 
349
357
  # excel-worksheet range
@@ -363,8 +371,17 @@ mbs excel-table style columns-width --doc-id abc123 --worksheet-name Orders --ta
363
371
  mbs db-table metadata --doc-id abc123 --name orders-large # API
364
372
  mbs db-table schema --doc-id abc123 --name orders-large # API
365
373
  mbs db-table sample --doc-id abc123 --name orders-large --limit 50 # API
366
- mbs db-table read --doc-id abc123 --name orders-large --limit 50 # API
374
+ mbs db-table read --doc-id abc123 --name orders-large --limit 100 --offset 200 # API
367
375
  mbs db-table insert --doc-id abc123 --name orders-large --rows rows.json # API
376
+ mbs db-table create --doc-id abc123 --name Orders --rows orders.json # PLAN
377
+ mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json # PLAN
378
+ mbs db-table update --doc-id abc123 --name Orders --row-index 42 --rows row_patch.json # PLAN
379
+ mbs db-table update --doc-id abc123 --name Orders --where '{"status":"open"}' --rows status_patch.json # PLAN
380
+ mbs db-table update --doc-id abc123 --name Orders --column-index 3 --column-name status # PLAN
381
+ mbs db-table delete --doc-id abc123 --name Orders --where '{"order_id":"A1"}' --yes # PLAN
382
+ mbs db-table delete --doc-id abc123 --name Orders --row-index 42 --count 10 --yes # PLAN
383
+ mbs db-table delete --doc-id abc123 --name Orders --column-index 3 --yes # PLAN
384
+ mbs db-table delete --doc-id abc123 --name Orders --column-name status --yes # PLAN
368
385
 
369
386
  # row
370
387
  mbs excel-worksheet row insert --doc-id abc123 --worksheet-name Actuals --row 10 --count 2 # API
@@ -404,6 +421,11 @@ mbs version restore --doc-id abc123 --version-id v42 --dry-run # PLAN
404
421
  mbs share create --doc-id abc123 --email teammate@example.com --role viewer # API
405
422
  ```
406
423
 
424
+ `db-table read`, `schema`, and `sample` keep reads bounded. The default read
425
+ limit is 100 rows, and `--limit` / `--offset` are capped at 10,000 to avoid
426
+ accidental large PG scans. Use a keyset-style backend contract for deeper
427
+ pagination when it becomes available.
428
+
407
429
  Refactor planning is tracked separately from this README:
408
430
  `docs/superpowers/plans/2026-07-01-mbs-two-engine-command-refactor.md`.
409
431
 
@@ -111,6 +111,13 @@ Import a large table-like `.xlsx` into SheetTable/PG:
111
111
  mbs workbook import ./large-table.xlsx --engine postgres
112
112
  ```
113
113
 
114
+ Import a mixed workbook and let the backend select engines per worksheet:
115
+
116
+ ```bash
117
+ mbs workbook import ./mixed-workbook.xlsx --engine auto
118
+ mbs workbook import ./mixed-workbook.xlsx --engine "postgres,excel,excel,postgres"
119
+ ```
120
+
114
121
  Append rows and verify:
115
122
 
116
123
  ```bash
@@ -195,7 +202,7 @@ mbs
195
202
 
196
203
  ├── excel_worksheet
197
204
  │ ├── read API Read the full range of the worksheet.
198
- │ ├── list-table API List worksheets in a workbook.
205
+ │ ├── list-table API Detect content-backed tables in a worksheet.
199
206
  │ ├── create API Create a worksheet, optionally with starter values.
200
207
  │ ├── update API Update worksheet metadata.
201
208
  │ ├── sample PLAN Return bounded worksheet/range sample rows.
@@ -228,9 +235,9 @@ mbs
228
235
  | |- lineage API Trace cell/range lineage.
229
236
 
230
237
  ├── db_worksheet(AKA db_table)
231
- │ ├── create PG Create a PG-backed table.
232
- │ ├── update PG Update PG-backed table metadata.
233
- │ ├── delete PG Delete a PG-backed table; requires --yes unless --dry-run.
238
+ │ ├── create PLAN Create a PG-backed table from JSON row data.
239
+ │ ├── update PLAN Update PG-backed rows from JSON row data.
240
+ │ ├── delete PLAN Delete PG-backed rows/table; requires --yes unless --dry-run.
234
241
  │ ├── schema API Infer table schema from a bounded PG read.
235
242
  │ ├── sample API Return bounded table sample rows.
236
243
  │ ├── read API Read PG-backed table rows.
@@ -320,6 +327,7 @@ mbs excel-worksheet read --doc-id abc123 --worksheet-name Actuals --range A1:D20
320
327
  mbs excel-worksheet create --doc-id abc123 --name Actuals # API
321
328
  mbs excel-worksheet update --doc-id abc123 --worksheet-name Actuals --title "Actuals FY26" # API
322
329
  mbs excel-worksheet metadata --doc-id abc123 --worksheet-name Actuals # API
330
+ mbs excel-worksheet list-table --doc-id abc123 --gid 3 # API
323
331
  mbs excel-worksheet sample --doc-id abc123 --worksheet-name Actuals --range A1:Z5000 --limit 50 # PLAN
324
332
 
325
333
  # excel-worksheet range
@@ -339,8 +347,17 @@ mbs excel-table style columns-width --doc-id abc123 --worksheet-name Orders --ta
339
347
  mbs db-table metadata --doc-id abc123 --name orders-large # API
340
348
  mbs db-table schema --doc-id abc123 --name orders-large # API
341
349
  mbs db-table sample --doc-id abc123 --name orders-large --limit 50 # API
342
- mbs db-table read --doc-id abc123 --name orders-large --limit 50 # API
350
+ mbs db-table read --doc-id abc123 --name orders-large --limit 100 --offset 200 # API
343
351
  mbs db-table insert --doc-id abc123 --name orders-large --rows rows.json # API
352
+ mbs db-table create --doc-id abc123 --name Orders --rows orders.json # PLAN
353
+ mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json # PLAN
354
+ mbs db-table update --doc-id abc123 --name Orders --row-index 42 --rows row_patch.json # PLAN
355
+ mbs db-table update --doc-id abc123 --name Orders --where '{"status":"open"}' --rows status_patch.json # PLAN
356
+ mbs db-table update --doc-id abc123 --name Orders --column-index 3 --column-name status # PLAN
357
+ mbs db-table delete --doc-id abc123 --name Orders --where '{"order_id":"A1"}' --yes # PLAN
358
+ mbs db-table delete --doc-id abc123 --name Orders --row-index 42 --count 10 --yes # PLAN
359
+ mbs db-table delete --doc-id abc123 --name Orders --column-index 3 --yes # PLAN
360
+ mbs db-table delete --doc-id abc123 --name Orders --column-name status --yes # PLAN
344
361
 
345
362
  # row
346
363
  mbs excel-worksheet row insert --doc-id abc123 --worksheet-name Actuals --row 10 --count 2 # API
@@ -380,6 +397,11 @@ mbs version restore --doc-id abc123 --version-id v42 --dry-run # PLAN
380
397
  mbs share create --doc-id abc123 --email teammate@example.com --role viewer # API
381
398
  ```
382
399
 
400
+ `db-table read`, `schema`, and `sample` keep reads bounded. The default read
401
+ limit is 100 rows, and `--limit` / `--offset` are capped at 10,000 to avoid
402
+ accidental large PG scans. Use a keyset-style backend contract for deeper
403
+ pagination when it becomes available.
404
+
383
405
  Refactor planning is tracked separately from this README:
384
406
  `docs/superpowers/plans/2026-07-01-mbs-two-engine-command-refactor.md`.
385
407
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "maybeai-sheet-cli"
7
- version = "0.5.0"
7
+ version = "0.7.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.5.0"
5
+ __version__ = "0.7.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(
@@ -22,6 +22,8 @@ from ..table_resolver import (
22
22
  from ._phase2 import bounded_limit
23
23
 
24
24
  app = typer.Typer(help="PG/SheetTable-backed table operations.", no_args_is_help=True)
25
+ DB_TABLE_SAFE_MAX_LIMIT = 10_000
26
+ DB_TABLE_SAFE_MAX_OFFSET = 10_000
25
27
 
26
28
 
27
29
  def _state(
@@ -79,13 +81,27 @@ def _read_payload(
79
81
  resolved: ResolvedTableTarget,
80
82
  *,
81
83
  limit: int,
84
+ offset: int = 0,
82
85
  ) -> dict[str, Any]:
83
- return {
86
+ payload: dict[str, Any] = {
84
87
  "uri": target.uri,
85
88
  "worksheet_name": resolved.worksheet_name,
86
- "range_address": bounded_a1_range(resolved.range_address, limit),
87
89
  "value_render_option": "FORMATTED_VALUE",
90
+ "limit": limit,
88
91
  }
92
+ if offset:
93
+ payload["offset"] = offset
94
+ if resolved.range_address:
95
+ payload["range_address"] = bounded_a1_range(resolved.range_address, limit)
96
+ return payload
97
+
98
+
99
+ def _bounded_offset(value: int) -> int:
100
+ if value < 0:
101
+ raise UsageError("--offset must be at least 0.")
102
+ if value > DB_TABLE_SAFE_MAX_OFFSET:
103
+ raise UsageError(f"--offset must be {DB_TABLE_SAFE_MAX_OFFSET} or less.")
104
+ return value
89
105
 
90
106
 
91
107
  def _load_rows(rows: Path) -> list[dict[str, Any]]:
@@ -168,6 +184,7 @@ def schema(
168
184
  verbose=verbose,
169
185
  timeout=timeout,
170
186
  )
187
+ bounded = bounded_limit(limit, option="--limit", maximum=DB_TABLE_SAFE_MAX_LIMIT)
171
188
  workbook_target = resolve_target(state)
172
189
  client = MaybeAIClient(state)
173
190
  resolved, target = _resolve_db_table(
@@ -179,7 +196,11 @@ def schema(
179
196
  endpoint = "/api/v1/excel/read_sheet"
180
197
  response = client.post_json(
181
198
  endpoint,
182
- _read_payload(target, resolved, limit=bounded_limit(limit, option="--limit")),
199
+ _read_payload(
200
+ target,
201
+ resolved,
202
+ limit=bounded,
203
+ ),
183
204
  )
184
205
  result = {
185
206
  **schema_from_read_payload(response),
@@ -224,6 +245,7 @@ def sample(
224
245
  verbose=verbose,
225
246
  timeout=timeout,
226
247
  )
248
+ bounded = bounded_limit(limit, option="--limit", maximum=DB_TABLE_SAFE_MAX_LIMIT)
227
249
  workbook_target = resolve_target(state)
228
250
  client = MaybeAIClient(state)
229
251
  resolved, target = _resolve_db_table(
@@ -232,7 +254,6 @@ def sample(
232
254
  name=name,
233
255
  backend_id=backend_id,
234
256
  )
235
- bounded = bounded_limit(limit, option="--limit")
236
257
  endpoint = "/api/v1/excel/read_sheet"
237
258
  response = client.post_json(endpoint, _read_payload(target, resolved, limit=bounded))
238
259
  result = {
@@ -264,6 +285,7 @@ def read(
264
285
  name: Optional[str] = typer.Option(None, "--name", help="PG-backed table name."),
265
286
  backend_id: Optional[str] = typer.Option(None, "--backend-id", help="Advanced backend table id."),
266
287
  limit: int = typer.Option(100, "--limit", min=1, help="Maximum rows to return."),
288
+ offset: int = typer.Option(0, "--offset", min=0, help="Rows to skip before reading."),
267
289
  output: Optional[str] = output_option(),
268
290
  verbose: Optional[bool] = verbose_option(),
269
291
  timeout: Optional[float] = timeout_option(),
@@ -278,6 +300,8 @@ def read(
278
300
  verbose=verbose,
279
301
  timeout=timeout,
280
302
  )
303
+ bounded = bounded_limit(limit, option="--limit", maximum=DB_TABLE_SAFE_MAX_LIMIT)
304
+ bounded_offset = _bounded_offset(offset)
281
305
  workbook_target = resolve_target(state)
282
306
  client = MaybeAIClient(state)
283
307
  resolved, target = _resolve_db_table(
@@ -289,7 +313,12 @@ def read(
289
313
  endpoint = "/api/v1/excel/read_sheet"
290
314
  response = client.post_json(
291
315
  endpoint,
292
- _read_payload(target, resolved, limit=bounded_limit(limit, option="--limit")),
316
+ _read_payload(
317
+ target,
318
+ resolved,
319
+ limit=bounded,
320
+ offset=bounded_offset,
321
+ ),
293
322
  )
294
323
  render_output(
295
324
  build_command_output(
@@ -304,6 +333,128 @@ def read(
304
333
  handle_cli_error(error)
305
334
 
306
335
 
336
+ DB_TABLE_CREATE_EXAMPLE = """Example:
337
+
338
+ mbs db-table create --doc-id <doc_id> --name Orders --rows orders.json
339
+
340
+ `orders.json` should contain a JSON array of row objects, similar to workbook
341
+ import data:
342
+
343
+ [
344
+ {"order_id": "A1", "amount": 120.5},
345
+ {"order_id": "A2", "amount": 88.0}
346
+ ]
347
+ """
348
+
349
+
350
+ DB_TABLE_UPDATE_EXAMPLE = """Example:
351
+
352
+ mbs db-table update --doc-id <doc_id> --name Orders --key order_id --rows orders_patch.json
353
+ mbs db-table update --doc-id <doc_id> --name Orders --row-index 42 --rows row_patch.json
354
+ mbs db-table update --doc-id <doc_id> --name Orders --where '{"status":"open"}' --rows status_patch.json
355
+ mbs db-table update --doc-id <doc_id> --name Orders --column-index 3 --column-name status
356
+
357
+ `orders_patch.json` should contain row objects keyed by --key. Positional
358
+ updates use 1-based --row-index and --column-index when the backend exposes a
359
+ stable SheetTable index contract.
360
+ """
361
+
362
+
363
+ DB_TABLE_DELETE_EXAMPLE = """Example:
364
+
365
+ mbs db-table delete --doc-id <doc_id> --name Orders --where '{"order_id":"A1"}' --yes
366
+ mbs db-table delete --doc-id <doc_id> --name Orders --row-index 42 --yes
367
+ mbs db-table delete --doc-id <doc_id> --name Orders --row-index 42 --count 10 --yes
368
+ mbs db-table delete --doc-id <doc_id> --name Orders --column-index 3 --yes
369
+ mbs db-table delete --doc-id <doc_id> --name Orders --column-name status --yes
370
+
371
+ Use --dry-run first once the backend route is available.
372
+ """
373
+
374
+
375
+ def _planned_db_table_error(command: str, example: str) -> None:
376
+ raise UsageError(
377
+ f"db-table {command} is planned but the backend route is not implemented yet.\n\n"
378
+ f"{example.strip()}"
379
+ )
380
+
381
+
382
+ @app.command(
383
+ "create",
384
+ help="Planned: create a PG/SheetTable-backed table from JSON row data.",
385
+ epilog=DB_TABLE_CREATE_EXAMPLE,
386
+ )
387
+ def create(
388
+ doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
389
+ name: Optional[str] = typer.Option(None, "--name", help="New PG-backed table name."),
390
+ rows: Optional[Path] = typer.Option(
391
+ None,
392
+ "--rows",
393
+ exists=True,
394
+ readable=True,
395
+ resolve_path=True,
396
+ help="Path to JSON rows payload.",
397
+ ),
398
+ ) -> None:
399
+ _ = (doc_id, name, rows)
400
+ try:
401
+ _planned_db_table_error("create", DB_TABLE_CREATE_EXAMPLE)
402
+ except Exception as error: # pragma: no cover - CLI boundary
403
+ handle_cli_error(error)
404
+
405
+
406
+ @app.command(
407
+ "update",
408
+ help="Planned: update PG/SheetTable-backed rows from JSON row data.",
409
+ epilog=DB_TABLE_UPDATE_EXAMPLE,
410
+ )
411
+ def update(
412
+ doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
413
+ name: Optional[str] = typer.Option(None, "--name", help="PG-backed table name."),
414
+ key: Optional[str] = typer.Option(None, "--key", help="Unique key column for matching rows."),
415
+ row_index: Optional[int] = typer.Option(None, "--row-index", min=1, help="1-based SheetTable row index to update."),
416
+ column_index: Optional[int] = typer.Option(None, "--column-index", min=1, help="1-based SheetTable column index to update."),
417
+ column_name: Optional[str] = typer.Option(None, "--column-name", help="Column name to update."),
418
+ where: Optional[str] = typer.Option(None, "--where", help="JSON object filter for rows to update."),
419
+ rows: Optional[Path] = typer.Option(
420
+ None,
421
+ "--rows",
422
+ exists=True,
423
+ readable=True,
424
+ resolve_path=True,
425
+ help="Path to JSON rows payload.",
426
+ ),
427
+ ) -> None:
428
+ _ = (doc_id, name, key, row_index, column_index, column_name, where, rows)
429
+ try:
430
+ _planned_db_table_error("update", DB_TABLE_UPDATE_EXAMPLE)
431
+ except Exception as error: # pragma: no cover - CLI boundary
432
+ handle_cli_error(error)
433
+
434
+
435
+ @app.command(
436
+ "delete",
437
+ help="Planned: delete PG/SheetTable-backed rows or a table.",
438
+ epilog=DB_TABLE_DELETE_EXAMPLE,
439
+ )
440
+ def delete(
441
+ doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
442
+ name: Optional[str] = typer.Option(None, "--name", help="PG-backed table name."),
443
+ where: Optional[str] = typer.Option(None, "--where", help="JSON object filter for rows to delete."),
444
+ row_index: Optional[int] = typer.Option(None, "--row-index", min=1, help="1-based SheetTable row index to delete."),
445
+ column_index: Optional[int] = typer.Option(None, "--column-index", min=1, help="1-based SheetTable column index to delete."),
446
+ column_name: Optional[str] = typer.Option(None, "--column-name", help="Column name to delete."),
447
+ count: int = typer.Option(1, "--count", min=1, help="Number of rows to delete from --row-index."),
448
+ yes: bool = typer.Option(False, "--yes", help="Confirm destructive delete."),
449
+ dry_run: bool = typer.Option(False, "--dry-run", help="Preview delete when backend support exists."),
450
+ ) -> None:
451
+ _ = (doc_id, name, where, row_index, column_index, column_name, count, yes, dry_run)
452
+ try:
453
+ _planned_db_table_error("delete", DB_TABLE_DELETE_EXAMPLE)
454
+ except Exception as error: # pragma: no cover - CLI boundary
455
+ handle_cli_error(error)
456
+
457
+
307
458
  @app.command("insert")
308
459
  def insert(
309
460
  ctx: typer.Context,
@@ -288,6 +288,7 @@ def list_table(
288
288
  doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
289
289
  url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
290
290
  uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
291
+ gid: Optional[int] = typer.Option(None, "--gid", help="Worksheet gid."),
291
292
  worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Optional worksheet name filter."),
292
293
  output: Optional[str] = output_option(),
293
294
  verbose: Optional[bool] = verbose_option(),
@@ -299,12 +300,16 @@ def list_table(
299
300
  doc_id=doc_id,
300
301
  url=url,
301
302
  uri=uri,
303
+ gid=gid,
302
304
  worksheet_name=worksheet_name,
303
305
  output=output,
304
306
  verbose=verbose,
305
307
  timeout=timeout,
306
308
  )
307
- target = resolve_target(state)
309
+ target = resolve_target(
310
+ state,
311
+ force_gid_uri=bool(state.gid is not None and not state.worksheet_name),
312
+ )
308
313
  if target.worksheet_name or target.gid is not None:
309
314
  endpoint = "/api/v1/excel/table/metadata"
310
315
  payload: dict[str, object] = {
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
- from typing import Optional
4
+ from typing import Any, Optional
5
5
 
6
6
  import typer
7
7
 
@@ -18,15 +18,63 @@ from ._phase2 import bounded_limit
18
18
  app = typer.Typer(help="Workbook-level operations.", no_args_is_help=True)
19
19
 
20
20
 
21
+ _WORKSHEET_NAME_ALIASES = ("title", "name", "sheet_name")
22
+
23
+
24
+ def _simplify_list_worksheets_response(response: dict[str, Any]) -> dict[str, Any]:
25
+ simplified = dict(response)
26
+ worksheets = simplified.get("worksheets")
27
+ if not isinstance(worksheets, list):
28
+ return simplified
29
+ simplified["worksheets"] = [
30
+ _simplify_worksheet_item(item) if isinstance(item, dict) else item
31
+ for item in worksheets
32
+ ]
33
+ return simplified
34
+
35
+
36
+ def _simplify_worksheet_item(item: dict[str, Any]) -> dict[str, Any]:
37
+ simplified = dict(item)
38
+ canonical = simplified.get("worksheet_name")
39
+ if not canonical:
40
+ for alias in _WORKSHEET_NAME_ALIASES:
41
+ value = simplified.get(alias)
42
+ if value:
43
+ canonical = value
44
+ simplified["worksheet_name"] = value
45
+ break
46
+ for alias in _WORKSHEET_NAME_ALIASES:
47
+ if simplified.get(alias) == canonical:
48
+ simplified.pop(alias, None)
49
+ return simplified
50
+
51
+
21
52
  def _normalize_create_from_file_engine(engine: str | None) -> str | None:
22
53
  if engine is None:
23
54
  return None
24
55
  normalized = engine.strip().lower()
25
- if normalized in {"", "excelize"}:
56
+ if normalized == "":
26
57
  return None
27
- if normalized in {"pg", "postgres", "postgresql"}:
58
+ if "," in normalized:
59
+ return ",".join(_normalize_engine_token(part) for part in normalized.split(","))
60
+ token = _normalize_engine_token(normalized)
61
+ if token == "excelize":
62
+ return None
63
+ return token
64
+
65
+
66
+ def _normalize_engine_token(value: str) -> str:
67
+ normalized = value.strip().lower()
68
+ if normalized in {"auto"}:
69
+ return "auto"
70
+ if normalized in {"excel", "excelize", "xlsx"}:
71
+ return "excelize"
72
+ if normalized in {"pg", "postgres", "postgresql", "sheettable", "sheet_table"}:
28
73
  return "postgres"
29
- raise UsageError("Unsupported engine. Use 'excelize', 'postgres', or 'pg'.")
74
+ raise UsageError(
75
+ "Unsupported engine. Use 'auto', 'excelize', 'postgres', 'pg', "
76
+ "or a comma-separated per-worksheet list such as 'postgres,excel,excel'."
77
+ )
30
78
 
31
79
 
32
80
  @app.callback()
@@ -130,7 +178,7 @@ def create_from_file(
130
178
  engine: Optional[str] = typer.Option(
131
179
  None,
132
180
  "--engine",
133
- help="Creation engine: excelize (default) or postgres/pg for SheetTable.",
181
+ help="Creation engine: excelize (default), postgres/pg, auto, or comma-separated per-worksheet engines.",
134
182
  ),
135
183
  output: Optional[str] = output_option(),
136
184
  verbose: Optional[bool] = verbose_option(),
@@ -187,7 +235,7 @@ def import_workbook(
187
235
  engine: Optional[str] = typer.Option(
188
236
  None,
189
237
  "--engine",
190
- help="Creation engine: excelize (default) or postgres/pg for SheetTable.",
238
+ help="Creation engine: excelize (default), postgres/pg, auto, or comma-separated per-worksheet engines.",
191
239
  ),
192
240
  output: Optional[str] = output_option(),
193
241
  verbose: Optional[bool] = verbose_option(),
@@ -399,11 +447,12 @@ def list_worksheets(
399
447
  target = resolve_target(state)
400
448
  endpoint = "/api/v1/excel/list_worksheets"
401
449
  response = MaybeAIClient(state).post_json(endpoint, {"uri": target.uri})
450
+ result = _simplify_list_worksheets_response(response) if isinstance(response, dict) else response
402
451
  render_output(
403
452
  build_command_output(
404
453
  endpoint=endpoint,
405
454
  target=target,
406
- result=response,
455
+ result=result,
407
456
  metadata=get_endpoint_metadata("workbook.list-worksheets") if state.verbose else None,
408
457
  ),
409
458
  state.output,
@@ -65,6 +65,9 @@ COMMAND_ENDPOINTS: dict[str, EndpointMetadata] = {
65
65
  "db-table.sample": _meta("db-table.sample", "API", "POST", "/api/v1/excel/read_sheet", notes="CLI-composed sample from bounded PG read."),
66
66
  "db-table.read": _meta("db-table.read", "API", "POST", "/api/v1/excel/read_sheet", large_output=True, default_limit=100),
67
67
  "db-table.insert": _meta("db-table.insert", "API", "POST", "/api/v1/excel/append_rows", verify_supported=True),
68
+ "db-table.create": _meta("db-table.create", "PLAN", None, None, notes="Planned native db-table create route; CLI help documents the target rows-file shape."),
69
+ "db-table.update": _meta("db-table.update", "PLAN", None, None, notes="Planned native db-table update route; CLI help documents keyed JSON row patches."),
70
+ "db-table.delete": _meta("db-table.delete", "PLAN", None, None, destructive=True, confirmation_required=True, notes="Planned native db-table delete route."),
68
71
  "worksheet.list": _meta("worksheet.list", "NOW", "POST", "/api/v1/excel/list_worksheets"),
69
72
  "worksheet.create": _meta("worksheet.create", "NOW", "POST", "/api/v1/excel/write_new_worksheet"),
70
73
  "range.read": _meta("range.read", "NOW", "POST", "/api/v1/excel/read_sheet"),