maybeai-sheet-cli 0.26__tar.gz → 0.26.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/PKG-INFO +19 -16
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/README.md +18 -15
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/pyproject.toml +1 -1
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/_phase2.py +20 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/column.py +6 -2
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/db_table.py +66 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/style.py +59 -24
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/workbook.py +75 -2
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/endpoints.py +10 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_cli.py +84 -1
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_endpoints.py +8 -1
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_phase2_cli.py +24 -11
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_two_engine_cli.py +57 -1
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/.gitignore +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/__init__.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/chart_config.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/chart_style_summary.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/cli.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/client.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/__init__.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/base_table.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/cell.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/chart.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/dashboard.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/excel_table.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/excel_worksheet.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/file.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/formula.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/history.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/image.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/media.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/pivot.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/range.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/raw.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/row.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/share.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/sheet.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/sql.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/table.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/commands/worksheet.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/config.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/dashboard_interactions.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/dashboard_plan.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/dashboard_template.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/errors.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/formatters.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/importing/__init__.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/importing/create.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/importing/models.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/importing/replace.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/models/__init__.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/models/share.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/models/sheet.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/models/workbook.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/options.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/protocol.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resolver.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/__init__.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/__init__.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_cost_benefit.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_decision_matrix.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_finance_report.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_inventory.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_marketing_calendar.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_minimal.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_project.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_risk_register.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_sales_pipeline.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/resources/style_templates/canva_timesheet.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/runtime/dashboard_render_probe.mjs +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/table_resolver.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/src/maybeai_sheet/update.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/fixtures/benchmarks/build_fixtures.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/fixtures/benchmarks/flat-records.xlsx +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/fixtures/benchmarks/mixed-engine.xlsx +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/fixtures/benchmarks/multi-table-layout.xlsx +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/fixtures/benchmarks/rows.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/fixtures/benchmarks/simple-grid.xlsx +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/fixtures/benchmarks/simple-values.json +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_benchmark_runner.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_dashboard_plan.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_dashboard_template.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_formatters.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_import_to_worksheet_benchmark.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_import_to_worksheet_live_benchmark.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_importing.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_resolver.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_update.py +0 -0
- {maybeai_sheet_cli-0.26 → maybeai_sheet_cli-0.26.2}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: maybeai-sheet-cli
|
|
3
|
-
Version: 0.26
|
|
3
|
+
Version: 0.26.2
|
|
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
|
|
@@ -103,8 +103,14 @@ List user-visible workbooks:
|
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
105
|
mbs workbook list-user-workbooks --limit 20
|
|
106
|
+
mbs workbook list-user-workbooks --order-by filename:asc --limit 20
|
|
107
|
+
mbs workbook delete --doc-id abc123 --yes
|
|
106
108
|
```
|
|
107
109
|
|
|
110
|
+
The default ordering is `updated_at DESC`. Use `--order-by FIELD[:DIRECTION]`
|
|
111
|
+
with `created_at`, `updated_at`, `filename`, `file_size`, or `version`; the
|
|
112
|
+
direction can be `asc` or `desc` and defaults to `asc` for an explicit field.
|
|
113
|
+
|
|
108
114
|
Read a worksheet:
|
|
109
115
|
|
|
110
116
|
```bash
|
|
@@ -476,7 +482,8 @@ worksheet metadata, then routes by engine: Sheet worksheets get a frozen
|
|
|
476
482
|
header row, persisted auto-filter, header styling, and semantic column styles
|
|
477
483
|
for date, currency, percent, integer, status, and text columns; Base
|
|
478
484
|
worksheets get equivalent field formatter/style metadata updates through the
|
|
479
|
-
batched
|
|
485
|
+
batched field-style route. Explicit Base column-width updates use the native
|
|
486
|
+
`/table/field/update` route and persist as `field.property.style.width`. The semantic classifier uses
|
|
480
487
|
weighted Chinese/English header keywords, common business
|
|
481
488
|
abbreviations such as AOV/GMV/ARR/MRR, Base field display metadata, and Excel
|
|
482
489
|
sample values. Use `--table-id` to beautify one Excel table within a multi-table
|
|
@@ -486,18 +493,12 @@ inventory, fulfillment status, 毛利率, 广告花费, 退款率, and 履约状
|
|
|
486
493
|
`--dry-run --output json` to inspect each column's category, format, confidence,
|
|
487
494
|
and reasons before applying styles.
|
|
488
495
|
|
|
489
|
-
For explicit Base
|
|
490
|
-
`
|
|
491
|
-
|
|
492
|
-
```json
|
|
493
|
-
[
|
|
494
|
-
{"name": "revenue", "logical_type": "number", "property": {"formatter": "$#,##0.00"}},
|
|
495
|
-
{"name": "margin_rate", "logical_type": "number", "property": {"formatter": "0.00%"}}
|
|
496
|
-
]
|
|
497
|
-
```
|
|
496
|
+
For explicit Base field updates, resolve fields with `base-table field list`,
|
|
497
|
+
then use native `base-table field update`:
|
|
498
498
|
|
|
499
499
|
```bash
|
|
500
|
-
mbs
|
|
500
|
+
mbs base-table field list --doc-id abc123 --table-id tbl_orders
|
|
501
|
+
mbs base-table field update --doc-id abc123 --table-id tbl_orders --field-id fld_revenue --formatter '$#,##0.00' --width 144 --verify
|
|
501
502
|
```
|
|
502
503
|
|
|
503
504
|
For chart specs authored as `type: "json"`, keep metadata `title` when the chart
|
|
@@ -538,6 +539,7 @@ mbs
|
|
|
538
539
|
│ ├── list-user-workbooks List the caller's workbooks with a bounded result
|
|
539
540
|
│ ├── list-worksheets List worksheets and their engines in one workbook
|
|
540
541
|
│ ├── search Search the caller's workbooks by query
|
|
542
|
+
│ ├── delete Soft-delete a workbook with explicit confirmation
|
|
541
543
|
│ └── metadata Inspect workbook routing and worksheet metadata
|
|
542
544
|
│
|
|
543
545
|
├── worksheet
|
|
@@ -703,6 +705,7 @@ mbs workbook copy --doc-id abc123 --title "Budget Copy"
|
|
|
703
705
|
mbs workbook export --doc-id abc123 --out workbook.xlsx
|
|
704
706
|
mbs workbook calculate --doc-id abc123
|
|
705
707
|
mbs workbook list-user-workbooks --limit 20
|
|
708
|
+
mbs workbook list-user-workbooks --order-by updated_at:desc --limit 20
|
|
706
709
|
mbs workbook list-worksheets --doc-id abc123
|
|
707
710
|
mbs workbook search --query "Budget" --limit 20
|
|
708
711
|
mbs workbook metadata --doc-id abc123
|
|
@@ -756,7 +759,7 @@ mbs base-table record create --doc-id abc123 --table-id tbl_orders --records rec
|
|
|
756
759
|
mbs base-table record replace --doc-id abc123 --table-id tbl_orders --records records.json --expected-revision 7
|
|
757
760
|
mbs base-table record upsert --doc-id abc123 --table-id tbl_orders --key-field-id fld_sku --records records.json
|
|
758
761
|
mbs base-table field list --doc-id abc123 --table-id tbl_orders
|
|
759
|
-
mbs base-table field
|
|
762
|
+
mbs base-table field list --doc-id abc123 --table-id tbl_orders
|
|
760
763
|
mbs base-table field update --doc-id abc123 --table-id tbl_orders --field-id fld_amount --formatter "#,##0.00" --expected-revision 7 --verify
|
|
761
764
|
mbs base-table field update --doc-id abc123 --name Orders --field amount --formatter "#,##0.00" --verify # resolves native table and field IDs
|
|
762
765
|
mbs base-table formula compile --doc-id abc123 --table-id tbl_orders --field-id fld_total --expression '{fld_qty} * {fld_price}'
|
|
@@ -765,7 +768,7 @@ mbs base-table formula recalculate --doc-id abc123 --table-id tbl_orders --works
|
|
|
765
768
|
|
|
766
769
|
# db-table
|
|
767
770
|
mbs db-table read --doc-id abc123 --name orders-large --limit 100 --offset 200
|
|
768
|
-
mbs db-table field batch-update --doc-id abc123 --name ActualsTable --updates field-updates.json
|
|
771
|
+
mbs db-table field batch-update --doc-id abc123 --name ActualsTable --updates field-updates.json # compatibility only
|
|
769
772
|
|
|
770
773
|
# excel-worksheet row
|
|
771
774
|
mbs excel-worksheet row insert --doc-id abc123 --worksheet-name Actuals --row 10 --count 2
|
|
@@ -930,8 +933,8 @@ Relationship guide:
|
|
|
930
933
|
worksheet style planning/apply. `mbs worksheet style ...` and `mbs style ...`
|
|
931
934
|
expose the same style commands. For Base worksheets, `style format`,
|
|
932
935
|
`style columns-width`, and `style cell batch-set` automatically update field
|
|
933
|
-
metadata (`formatter`, text color, background color, and width)
|
|
934
|
-
|
|
936
|
+
metadata (`formatter`, text color, background color, and width) through the
|
|
937
|
+
native field API. Base width is stored at `field.property.style.width`.
|
|
935
938
|
- Use `excel-worksheet chart` for list/get/create/update/delete of individual
|
|
936
939
|
source-backed visualizations.
|
|
937
940
|
- Use `excel-worksheet dashboard` for local validation and worksheet-level chart
|
|
@@ -79,8 +79,14 @@ List user-visible workbooks:
|
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
mbs workbook list-user-workbooks --limit 20
|
|
82
|
+
mbs workbook list-user-workbooks --order-by filename:asc --limit 20
|
|
83
|
+
mbs workbook delete --doc-id abc123 --yes
|
|
82
84
|
```
|
|
83
85
|
|
|
86
|
+
The default ordering is `updated_at DESC`. Use `--order-by FIELD[:DIRECTION]`
|
|
87
|
+
with `created_at`, `updated_at`, `filename`, `file_size`, or `version`; the
|
|
88
|
+
direction can be `asc` or `desc` and defaults to `asc` for an explicit field.
|
|
89
|
+
|
|
84
90
|
Read a worksheet:
|
|
85
91
|
|
|
86
92
|
```bash
|
|
@@ -452,7 +458,8 @@ worksheet metadata, then routes by engine: Sheet worksheets get a frozen
|
|
|
452
458
|
header row, persisted auto-filter, header styling, and semantic column styles
|
|
453
459
|
for date, currency, percent, integer, status, and text columns; Base
|
|
454
460
|
worksheets get equivalent field formatter/style metadata updates through the
|
|
455
|
-
batched
|
|
461
|
+
batched field-style route. Explicit Base column-width updates use the native
|
|
462
|
+
`/table/field/update` route and persist as `field.property.style.width`. The semantic classifier uses
|
|
456
463
|
weighted Chinese/English header keywords, common business
|
|
457
464
|
abbreviations such as AOV/GMV/ARR/MRR, Base field display metadata, and Excel
|
|
458
465
|
sample values. Use `--table-id` to beautify one Excel table within a multi-table
|
|
@@ -462,18 +469,12 @@ inventory, fulfillment status, 毛利率, 广告花费, 退款率, and 履约状
|
|
|
462
469
|
`--dry-run --output json` to inspect each column's category, format, confidence,
|
|
463
470
|
and reasons before applying styles.
|
|
464
471
|
|
|
465
|
-
For explicit Base
|
|
466
|
-
`
|
|
467
|
-
|
|
468
|
-
```json
|
|
469
|
-
[
|
|
470
|
-
{"name": "revenue", "logical_type": "number", "property": {"formatter": "$#,##0.00"}},
|
|
471
|
-
{"name": "margin_rate", "logical_type": "number", "property": {"formatter": "0.00%"}}
|
|
472
|
-
]
|
|
473
|
-
```
|
|
472
|
+
For explicit Base field updates, resolve fields with `base-table field list`,
|
|
473
|
+
then use native `base-table field update`:
|
|
474
474
|
|
|
475
475
|
```bash
|
|
476
|
-
mbs
|
|
476
|
+
mbs base-table field list --doc-id abc123 --table-id tbl_orders
|
|
477
|
+
mbs base-table field update --doc-id abc123 --table-id tbl_orders --field-id fld_revenue --formatter '$#,##0.00' --width 144 --verify
|
|
477
478
|
```
|
|
478
479
|
|
|
479
480
|
For chart specs authored as `type: "json"`, keep metadata `title` when the chart
|
|
@@ -514,6 +515,7 @@ mbs
|
|
|
514
515
|
│ ├── list-user-workbooks List the caller's workbooks with a bounded result
|
|
515
516
|
│ ├── list-worksheets List worksheets and their engines in one workbook
|
|
516
517
|
│ ├── search Search the caller's workbooks by query
|
|
518
|
+
│ ├── delete Soft-delete a workbook with explicit confirmation
|
|
517
519
|
│ └── metadata Inspect workbook routing and worksheet metadata
|
|
518
520
|
│
|
|
519
521
|
├── worksheet
|
|
@@ -679,6 +681,7 @@ mbs workbook copy --doc-id abc123 --title "Budget Copy"
|
|
|
679
681
|
mbs workbook export --doc-id abc123 --out workbook.xlsx
|
|
680
682
|
mbs workbook calculate --doc-id abc123
|
|
681
683
|
mbs workbook list-user-workbooks --limit 20
|
|
684
|
+
mbs workbook list-user-workbooks --order-by updated_at:desc --limit 20
|
|
682
685
|
mbs workbook list-worksheets --doc-id abc123
|
|
683
686
|
mbs workbook search --query "Budget" --limit 20
|
|
684
687
|
mbs workbook metadata --doc-id abc123
|
|
@@ -732,7 +735,7 @@ mbs base-table record create --doc-id abc123 --table-id tbl_orders --records rec
|
|
|
732
735
|
mbs base-table record replace --doc-id abc123 --table-id tbl_orders --records records.json --expected-revision 7
|
|
733
736
|
mbs base-table record upsert --doc-id abc123 --table-id tbl_orders --key-field-id fld_sku --records records.json
|
|
734
737
|
mbs base-table field list --doc-id abc123 --table-id tbl_orders
|
|
735
|
-
mbs base-table field
|
|
738
|
+
mbs base-table field list --doc-id abc123 --table-id tbl_orders
|
|
736
739
|
mbs base-table field update --doc-id abc123 --table-id tbl_orders --field-id fld_amount --formatter "#,##0.00" --expected-revision 7 --verify
|
|
737
740
|
mbs base-table field update --doc-id abc123 --name Orders --field amount --formatter "#,##0.00" --verify # resolves native table and field IDs
|
|
738
741
|
mbs base-table formula compile --doc-id abc123 --table-id tbl_orders --field-id fld_total --expression '{fld_qty} * {fld_price}'
|
|
@@ -741,7 +744,7 @@ mbs base-table formula recalculate --doc-id abc123 --table-id tbl_orders --works
|
|
|
741
744
|
|
|
742
745
|
# db-table
|
|
743
746
|
mbs db-table read --doc-id abc123 --name orders-large --limit 100 --offset 200
|
|
744
|
-
mbs db-table field batch-update --doc-id abc123 --name ActualsTable --updates field-updates.json
|
|
747
|
+
mbs db-table field batch-update --doc-id abc123 --name ActualsTable --updates field-updates.json # compatibility only
|
|
745
748
|
|
|
746
749
|
# excel-worksheet row
|
|
747
750
|
mbs excel-worksheet row insert --doc-id abc123 --worksheet-name Actuals --row 10 --count 2
|
|
@@ -906,8 +909,8 @@ Relationship guide:
|
|
|
906
909
|
worksheet style planning/apply. `mbs worksheet style ...` and `mbs style ...`
|
|
907
910
|
expose the same style commands. For Base worksheets, `style format`,
|
|
908
911
|
`style columns-width`, and `style cell batch-set` automatically update field
|
|
909
|
-
metadata (`formatter`, text color, background color, and width)
|
|
910
|
-
|
|
912
|
+
metadata (`formatter`, text color, background color, and width) through the
|
|
913
|
+
native field API. Base width is stored at `field.property.style.width`.
|
|
911
914
|
- Use `excel-worksheet chart` for list/get/create/update/delete of individual
|
|
912
915
|
source-backed visualizations.
|
|
913
916
|
- Use `excel-worksheet dashboard` for local validation and worksheet-level chart
|
|
@@ -19,6 +19,26 @@ _A1_RANGE_RE = re.compile(r"^([A-Za-z]+[1-9][0-9]*):([A-Za-z]+[1-9][0-9]*)$")
|
|
|
19
19
|
_TRUE_VALUES = frozenset({"1", "true", "t", "yes", "y", "on"})
|
|
20
20
|
_FALSE_VALUES = frozenset({"0", "false", "f", "no", "n", "off"})
|
|
21
21
|
_LINEAGE_MAX_RANGE_CELLS = 100
|
|
22
|
+
_PIXEL_DIMENSION_RE = re.compile(r"^(?P<value>\d+(?:\.\d+)?)px$", re.IGNORECASE)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_pixel_dimension(raw: str, *, name: str, example: str = "140px") -> tuple[float, str | None]:
|
|
26
|
+
"""Parse new px syntax while preserving legacy numeric CLI input."""
|
|
27
|
+
value = raw.strip()
|
|
28
|
+
match = _PIXEL_DIMENSION_RE.fullmatch(value)
|
|
29
|
+
if match:
|
|
30
|
+
return float(match.group("value")), "px"
|
|
31
|
+
try:
|
|
32
|
+
numeric = float(value)
|
|
33
|
+
except ValueError as exc:
|
|
34
|
+
raise UsageError(f"{name} must be a number or a pixel value such as {example}.") from exc
|
|
35
|
+
if numeric <= 0:
|
|
36
|
+
raise UsageError(f"{name} must be greater than 0.")
|
|
37
|
+
typer.echo(
|
|
38
|
+
f"Warning: numeric {name} uses legacy units; use {name} with an explicit px suffix (for example {example}).",
|
|
39
|
+
err=True,
|
|
40
|
+
)
|
|
41
|
+
return numeric, None
|
|
22
42
|
|
|
23
43
|
|
|
24
44
|
def run_post_command(
|
|
@@ -14,6 +14,7 @@ from ._phase2 import (
|
|
|
14
14
|
common_options,
|
|
15
15
|
require_worksheet,
|
|
16
16
|
require_yes_or_dry_run,
|
|
17
|
+
parse_pixel_dimension,
|
|
17
18
|
run_post_command,
|
|
18
19
|
)
|
|
19
20
|
|
|
@@ -186,7 +187,7 @@ def width(
|
|
|
186
187
|
worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Worksheet name."),
|
|
187
188
|
start_column: str = typer.Option(..., "--start-column", help="A1-style first column."),
|
|
188
189
|
end_column: str = typer.Option(..., "--end-column", help="A1-style last column."),
|
|
189
|
-
width:
|
|
190
|
+
width: str = typer.Option(..., "--width", help="Column width, for example 140px. Bare numbers remain legacy-compatible."),
|
|
190
191
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show the request without sending it."),
|
|
191
192
|
output: Optional[str] = output_option(),
|
|
192
193
|
verbose: Optional[bool] = verbose_option(),
|
|
@@ -203,11 +204,14 @@ def width(
|
|
|
203
204
|
verbose=verbose,
|
|
204
205
|
timeout=timeout,
|
|
205
206
|
)
|
|
207
|
+
width_value, width_unit = parse_pixel_dimension(width, name="--width")
|
|
206
208
|
payload = base_payload(target) | {
|
|
207
209
|
"start_column": start_column,
|
|
208
210
|
"end_column": end_column,
|
|
209
|
-
"width":
|
|
211
|
+
"width": width_value,
|
|
210
212
|
}
|
|
213
|
+
if width_unit:
|
|
214
|
+
payload["width_unit"] = width_unit
|
|
211
215
|
run_post_command(
|
|
212
216
|
state=state,
|
|
213
217
|
endpoint="/api/v1/excel/set_columns_width",
|
|
@@ -603,12 +603,75 @@ def _first_tabular_value(value: Any) -> Any:
|
|
|
603
603
|
return None
|
|
604
604
|
|
|
605
605
|
|
|
606
|
+
def _logical_type_from_pg_type(pg_type: str) -> str:
|
|
607
|
+
normalized = pg_type.strip().lower()
|
|
608
|
+
if normalized.startswith(("smallint", "integer", "bigint", "numeric", "real", "double precision")):
|
|
609
|
+
return "number"
|
|
610
|
+
if normalized == "boolean":
|
|
611
|
+
return "boolean"
|
|
612
|
+
if normalized.startswith(("date", "timestamp")):
|
|
613
|
+
return "datetime"
|
|
614
|
+
return "text"
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _typed_create_columns(response: dict[str, Any], headers: list[str]) -> list[dict[str, Any]] | None:
|
|
618
|
+
schema = response.get("columns")
|
|
619
|
+
if schema is None:
|
|
620
|
+
return None
|
|
621
|
+
if not isinstance(schema, list):
|
|
622
|
+
raise UsageError("SQL query response columns must be an array.")
|
|
623
|
+
if schema and all(isinstance(column, str) for column in schema):
|
|
624
|
+
return None
|
|
625
|
+
if not all(isinstance(column, dict) for column in schema):
|
|
626
|
+
raise UsageError("SQL query response columns must contain typed column objects.")
|
|
627
|
+
|
|
628
|
+
typed_columns: list[dict[str, Any]] = []
|
|
629
|
+
schema_names: list[str] = []
|
|
630
|
+
for index, column in enumerate(schema):
|
|
631
|
+
name = column.get("name")
|
|
632
|
+
pg_type = column.get("pg_type")
|
|
633
|
+
if not isinstance(name, str) or not name.strip():
|
|
634
|
+
raise UsageError("SQL query typed column schema must include non-empty names.")
|
|
635
|
+
if not isinstance(pg_type, str) or not pg_type.strip():
|
|
636
|
+
raise UsageError("SQL query typed column schema must include pg_type for every column.")
|
|
637
|
+
name = name.strip()
|
|
638
|
+
schema_names.append(name)
|
|
639
|
+
typed_columns.append(
|
|
640
|
+
{
|
|
641
|
+
"name": name,
|
|
642
|
+
"logical_type": _logical_type_from_pg_type(pg_type),
|
|
643
|
+
"pg_type": pg_type,
|
|
644
|
+
"index": index,
|
|
645
|
+
}
|
|
646
|
+
)
|
|
647
|
+
if len(set(schema_names)) != len(schema_names):
|
|
648
|
+
raise UsageError("SQL query typed column schema contains duplicate column names.")
|
|
649
|
+
if schema_names != headers:
|
|
650
|
+
raise UsageError("SQL query typed column schema does not match the result header row.")
|
|
651
|
+
return typed_columns
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _row_headers(rows: list[dict[str, Any]]) -> list[str]:
|
|
655
|
+
headers: list[str] = []
|
|
656
|
+
seen: set[str] = set()
|
|
657
|
+
for row in rows:
|
|
658
|
+
for key in row:
|
|
659
|
+
name = str(key).strip()
|
|
660
|
+
if name and name not in seen:
|
|
661
|
+
seen.add(name)
|
|
662
|
+
headers.append(name)
|
|
663
|
+
return headers
|
|
664
|
+
|
|
665
|
+
|
|
606
666
|
def _rows_from_tabular_response(response: dict[str, Any]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
607
667
|
table = _first_tabular_value(response)
|
|
608
668
|
if table is None:
|
|
609
669
|
raise UsageError("SQL query response did not contain tabular values.")
|
|
610
670
|
if isinstance(table, list) and all(isinstance(row, dict) for row in table):
|
|
611
671
|
rows = [dict(row) for row in table]
|
|
672
|
+
typed_columns = _typed_create_columns(response, _row_headers(rows))
|
|
673
|
+
if typed_columns is not None:
|
|
674
|
+
return rows, typed_columns
|
|
612
675
|
columns = _infer_create_columns(rows)
|
|
613
676
|
return rows, columns
|
|
614
677
|
if not isinstance(table, list) or not all(isinstance(row, list) for row in table):
|
|
@@ -627,6 +690,9 @@ def _rows_from_tabular_response(response: dict[str, Any]) -> tuple[list[dict[str
|
|
|
627
690
|
for idx, header in enumerate(headers)
|
|
628
691
|
}
|
|
629
692
|
rows.append(row)
|
|
693
|
+
typed_columns = _typed_create_columns(response, headers)
|
|
694
|
+
if typed_columns is not None:
|
|
695
|
+
return rows, typed_columns
|
|
630
696
|
columns = [
|
|
631
697
|
{
|
|
632
698
|
"name": header,
|
|
@@ -29,6 +29,7 @@ from ._phase2 import (
|
|
|
29
29
|
load_json_object_argument,
|
|
30
30
|
parse_bool_text,
|
|
31
31
|
parse_optional_bool_text,
|
|
32
|
+
parse_pixel_dimension,
|
|
32
33
|
require_worksheet,
|
|
33
34
|
run_post_command,
|
|
34
35
|
)
|
|
@@ -71,6 +72,7 @@ _BEAUTIFY_COLUMN_POLICY: dict[str, object] = {
|
|
|
71
72
|
}
|
|
72
73
|
_COLUMN_BACKGROUND_STYLE_KEYS = frozenset({"bg_color", "backgroundColor", "fillColor", "background_color", "fill_color"})
|
|
73
74
|
_COLUMN_FONT_COLOR_STYLE_KEYS = frozenset({"font_color", "color", "fontColor", "text_color", "textColor"})
|
|
75
|
+
_COLUMN_FORMAT_STYLE_KEYS = frozenset({"format_code", "formatCode"})
|
|
74
76
|
_BEAUTIFY_CONFIG_KEYS = frozenset(
|
|
75
77
|
{
|
|
76
78
|
"defaults",
|
|
@@ -1510,12 +1512,15 @@ def _column_style_for_policy(style: dict[str, object], style_policy: dict[str, o
|
|
|
1510
1512
|
column_policy = _column_policy_from_style_policy(style_policy)
|
|
1511
1513
|
allow_font_color = bool(column_policy.get("allowFontColorBySemanticType"))
|
|
1512
1514
|
allow_background = bool(column_policy.get("backgroundBySemanticType"))
|
|
1515
|
+
allow_format = bool(column_policy.get("allowFormatBySemanticType", True))
|
|
1513
1516
|
cleaned: dict[str, object] = {}
|
|
1514
1517
|
for key, value in style.items():
|
|
1515
1518
|
if key in _COLUMN_BACKGROUND_STYLE_KEYS and not allow_background:
|
|
1516
1519
|
continue
|
|
1517
1520
|
if key in _COLUMN_FONT_COLOR_STYLE_KEYS and not allow_font_color:
|
|
1518
1521
|
continue
|
|
1522
|
+
if key in _COLUMN_FORMAT_STYLE_KEYS and not allow_format:
|
|
1523
|
+
continue
|
|
1519
1524
|
cleaned[key] = value
|
|
1520
1525
|
return cleaned
|
|
1521
1526
|
|
|
@@ -2188,14 +2193,13 @@ def _base_beautify_field_update(
|
|
|
2188
2193
|
style = _column_style_for_policy(classification.style, style_policy)
|
|
2189
2194
|
logical_type = _field_logical_type(field, None)
|
|
2190
2195
|
# Base validates number fields against numeric formatter patterns. A name
|
|
2191
|
-
# such as "period" can suggest a date even when
|
|
2192
|
-
#
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
"
|
|
2196
|
-
"
|
|
2197
|
-
"
|
|
2198
|
-
"decimal",
|
|
2196
|
+
# such as "period" can suggest a date even when its metadata omits the
|
|
2197
|
+
# type or it is stored as a number, so date formatting needs an explicit
|
|
2198
|
+
# date/time logical type rather than header-based inference alone.
|
|
2199
|
+
if classification.category == "date" and (logical_type or "").lower() not in {
|
|
2200
|
+
"date",
|
|
2201
|
+
"datetime",
|
|
2202
|
+
"timestamp",
|
|
2199
2203
|
}:
|
|
2200
2204
|
style.pop("format_code", None)
|
|
2201
2205
|
existing_property = field.get("property")
|
|
@@ -2462,6 +2466,33 @@ def _base_field_update_payload(
|
|
|
2462
2466
|
return payload
|
|
2463
2467
|
|
|
2464
2468
|
|
|
2469
|
+
def _base_native_field_width_payload(
|
|
2470
|
+
*,
|
|
2471
|
+
target,
|
|
2472
|
+
gid: int,
|
|
2473
|
+
metadata_response: dict[str, object],
|
|
2474
|
+
field_index: int,
|
|
2475
|
+
width: int,
|
|
2476
|
+
) -> dict[str, object]:
|
|
2477
|
+
field_metadata, _ = _resolve_field(
|
|
2478
|
+
metadata_response,
|
|
2479
|
+
_resolved_base_target(target, gid),
|
|
2480
|
+
field=None,
|
|
2481
|
+
field_index=field_index,
|
|
2482
|
+
)
|
|
2483
|
+
field_id = field_metadata.get("field_id")
|
|
2484
|
+
if not isinstance(field_id, str) or not field_id.strip():
|
|
2485
|
+
raise UsageError(
|
|
2486
|
+
"Base field metadata did not include field_id; native width update cannot continue."
|
|
2487
|
+
)
|
|
2488
|
+
return {
|
|
2489
|
+
"document_id": target.document_id,
|
|
2490
|
+
"gid": gid,
|
|
2491
|
+
"field_id": field_id,
|
|
2492
|
+
"property": {"style": {"width": width}},
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
|
|
2465
2496
|
def _render_base_field_update_result(
|
|
2466
2497
|
*,
|
|
2467
2498
|
state,
|
|
@@ -2993,16 +3024,19 @@ def format_field(
|
|
|
2993
3024
|
handle_cli_error(error)
|
|
2994
3025
|
|
|
2995
3026
|
|
|
2996
|
-
@app.command(
|
|
3027
|
+
@app.command(
|
|
3028
|
+
"columns-width",
|
|
3029
|
+
help="Set Sheet column widths, or persist Base widths on field.property.style.width.",
|
|
3030
|
+
)
|
|
2997
3031
|
def columns_width(
|
|
2998
3032
|
ctx: typer.Context,
|
|
2999
3033
|
doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
|
|
3000
3034
|
url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
|
|
3001
3035
|
uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
|
|
3002
3036
|
worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Worksheet name."),
|
|
3003
|
-
start_column: str = typer.Option(..., "--start-column", help="A1-style first column."),
|
|
3004
|
-
end_column: str = typer.Option(..., "--end-column", help="A1-style last column."),
|
|
3005
|
-
width:
|
|
3037
|
+
start_column: str = typer.Option(..., "--start-column", help="A1-style first column (resolved to a Base field in Base mode)."),
|
|
3038
|
+
end_column: str = typer.Option(..., "--end-column", help="A1-style last column (resolved to a Base field in Base mode)."),
|
|
3039
|
+
width: str = typer.Option(..., "--width", help="Column width in pixels, for example 140px. Bare numbers remain legacy-compatible."),
|
|
3006
3040
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show the request without sending it."),
|
|
3007
3041
|
output: Optional[str] = output_option(),
|
|
3008
3042
|
verbose: Optional[bool] = verbose_option(),
|
|
@@ -3019,11 +3053,14 @@ def columns_width(
|
|
|
3019
3053
|
verbose=verbose,
|
|
3020
3054
|
timeout=timeout,
|
|
3021
3055
|
)
|
|
3056
|
+
width_value, width_unit = parse_pixel_dimension(width, name="--width")
|
|
3022
3057
|
payload = base_payload(target) | {
|
|
3023
3058
|
"start_column": start_column,
|
|
3024
3059
|
"end_column": end_column,
|
|
3025
|
-
"width":
|
|
3060
|
+
"width": width_value,
|
|
3026
3061
|
}
|
|
3062
|
+
if width_unit:
|
|
3063
|
+
payload["width_unit"] = width_unit
|
|
3027
3064
|
if not dry_run:
|
|
3028
3065
|
client = MaybeAIClient(state)
|
|
3029
3066
|
engine, gid, _worksheet = _resolve_worksheet_engine(client=client, target=target)
|
|
@@ -3037,22 +3074,17 @@ def columns_width(
|
|
|
3037
3074
|
last_index = max(start_index, end_index)
|
|
3038
3075
|
updates: list[dict[str, object]] = []
|
|
3039
3076
|
for field_index in range(first_index, last_index + 1):
|
|
3040
|
-
update_payload =
|
|
3077
|
+
update_payload = _base_native_field_width_payload(
|
|
3041
3078
|
target=target,
|
|
3042
3079
|
gid=gid,
|
|
3043
3080
|
metadata_response=metadata_response,
|
|
3044
|
-
field=None,
|
|
3045
3081
|
field_index=field_index,
|
|
3046
|
-
|
|
3047
|
-
formatter=None,
|
|
3048
|
-
color=None,
|
|
3049
|
-
background_color=None,
|
|
3050
|
-
width=width,
|
|
3082
|
+
width=int(round(width_value)),
|
|
3051
3083
|
)
|
|
3052
|
-
updates.append(_normalize_base_mode_output(client.post_json("/api/v1/excel/
|
|
3084
|
+
updates.append(_normalize_base_mode_output(client.post_json("/api/v1/excel/table/field/update", update_payload)))
|
|
3053
3085
|
render_output(
|
|
3054
3086
|
build_command_output(
|
|
3055
|
-
endpoint="/api/v1/excel/
|
|
3087
|
+
endpoint="/api/v1/excel/table/field/update",
|
|
3056
3088
|
target=target,
|
|
3057
3089
|
result={
|
|
3058
3090
|
"success": all(bool(update.get("success", True)) for update in updates),
|
|
@@ -3093,7 +3125,7 @@ def rows_height(
|
|
|
3093
3125
|
worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Worksheet name."),
|
|
3094
3126
|
start_row: int = typer.Option(..., "--start-row", min=1, help="1-based first row."),
|
|
3095
3127
|
end_row: int = typer.Option(..., "--end-row", min=1, help="1-based last row."),
|
|
3096
|
-
height:
|
|
3128
|
+
height: str = typer.Option(..., "--height", help="Row height in pixels, for example 32px. Bare numbers remain legacy-compatible."),
|
|
3097
3129
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show the request without sending it."),
|
|
3098
3130
|
output: Optional[str] = output_option(),
|
|
3099
3131
|
verbose: Optional[bool] = verbose_option(),
|
|
@@ -3110,11 +3142,14 @@ def rows_height(
|
|
|
3110
3142
|
verbose=verbose,
|
|
3111
3143
|
timeout=timeout,
|
|
3112
3144
|
)
|
|
3145
|
+
height_value, height_unit = parse_pixel_dimension(height, name="--height", example="32px")
|
|
3113
3146
|
payload = base_payload(target) | {
|
|
3114
3147
|
"start_row": start_row,
|
|
3115
3148
|
"end_row": end_row,
|
|
3116
|
-
"height":
|
|
3149
|
+
"height": height_value,
|
|
3117
3150
|
}
|
|
3151
|
+
if height_unit:
|
|
3152
|
+
payload["height_unit"] = height_unit
|
|
3118
3153
|
run_post_command(
|
|
3119
3154
|
state=state,
|
|
3120
3155
|
endpoint="/api/v1/excel/set_rows_height",
|
|
@@ -16,7 +16,7 @@ from ..formatters import build_command_output, handle_cli_error, load_json_file,
|
|
|
16
16
|
from ..models.workbook import CreateWorkbookRequest
|
|
17
17
|
from ..options import output_option, resolve_command_state, timeout_option, verbose_option
|
|
18
18
|
from ..resolver import ResolvedTarget, build_document_url, parse_document_target, resolve_target
|
|
19
|
-
from ._phase2 import bounded_limit
|
|
19
|
+
from ._phase2 import bounded_limit, require_yes_or_dry_run, run_post_command
|
|
20
20
|
from .db_table import (
|
|
21
21
|
_adopt_existing_create_response,
|
|
22
22
|
_created_table_target,
|
|
@@ -42,6 +42,8 @@ _WORKSHEET_DIMENSIONS_ENDPOINT = "/api/v1/excel/worksheet/dimensions"
|
|
|
42
42
|
_COPY_WORKSHEET_ENDPOINT = "/api/v1/excel/copy_worksheet"
|
|
43
43
|
_GOOGLE_SHEET_HOST = "docs.google.com"
|
|
44
44
|
_GOOGLE_SHEET_PATH_MARKER = "/spreadsheets/d/"
|
|
45
|
+
_WORKBOOK_ORDER_FIELDS = ("created_at", "file_size", "filename", "updated_at", "version")
|
|
46
|
+
_WORKBOOK_ORDER_DIRECTIONS = ("asc", "desc")
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
class _EmptySourceWorksheetError(UsageError):
|
|
@@ -153,6 +155,23 @@ def _normalize_engine_token(value: str) -> str:
|
|
|
153
155
|
)
|
|
154
156
|
|
|
155
157
|
|
|
158
|
+
def _normalize_workbook_order(value: str | None) -> tuple[str, str]:
|
|
159
|
+
if value is None or not value.strip():
|
|
160
|
+
return "updated_at", "desc"
|
|
161
|
+
parts = [part.strip().lower() for part in value.split(":")]
|
|
162
|
+
if len(parts) > 2 or not parts[0]:
|
|
163
|
+
raise UsageError("--order-by must use FIELD[:DIRECTION]")
|
|
164
|
+
field = parts[0]
|
|
165
|
+
if field not in _WORKBOOK_ORDER_FIELDS:
|
|
166
|
+
raise UsageError(
|
|
167
|
+
"--order-by field must be one of: " + ", ".join(_WORKBOOK_ORDER_FIELDS)
|
|
168
|
+
)
|
|
169
|
+
direction = parts[1] if len(parts) == 2 and parts[1] else "asc"
|
|
170
|
+
if direction not in _WORKBOOK_ORDER_DIRECTIONS:
|
|
171
|
+
raise UsageError("--order-by direction must be one of: asc, desc")
|
|
172
|
+
return field, direction
|
|
173
|
+
|
|
174
|
+
|
|
156
175
|
def _normalize_import_source_engine(engine: str | None) -> str:
|
|
157
176
|
if engine is None:
|
|
158
177
|
return "sheet"
|
|
@@ -2251,6 +2270,11 @@ def calculate(
|
|
|
2251
2270
|
def list_workbooks(
|
|
2252
2271
|
ctx: typer.Context,
|
|
2253
2272
|
limit: int = typer.Option(20, "--limit", min=1, help="Maximum workbooks to return."),
|
|
2273
|
+
order_by: Optional[str] = typer.Option(
|
|
2274
|
+
None,
|
|
2275
|
+
"--order-by",
|
|
2276
|
+
help="Sort workbooks by FIELD[:DIRECTION]. Default: updated_at:desc.",
|
|
2277
|
+
),
|
|
2254
2278
|
output: Optional[str] = output_option(),
|
|
2255
2279
|
verbose: Optional[bool] = verbose_option(),
|
|
2256
2280
|
timeout: Optional[float] = timeout_option(),
|
|
@@ -2263,7 +2287,12 @@ def list_workbooks(
|
|
|
2263
2287
|
timeout=timeout,
|
|
2264
2288
|
)
|
|
2265
2289
|
endpoint = "/api/v1/excel/list_files"
|
|
2266
|
-
|
|
2290
|
+
sort_field, sort_direction = _normalize_workbook_order(order_by)
|
|
2291
|
+
payload = {
|
|
2292
|
+
"limit": bounded_limit(limit, option="--limit"),
|
|
2293
|
+
"order_by": sort_field,
|
|
2294
|
+
"order_direction": sort_direction,
|
|
2295
|
+
}
|
|
2267
2296
|
response = MaybeAIClient(state).post_json(endpoint, payload)
|
|
2268
2297
|
render_output(
|
|
2269
2298
|
build_command_output(
|
|
@@ -2277,10 +2306,53 @@ def list_workbooks(
|
|
|
2277
2306
|
handle_cli_error(error)
|
|
2278
2307
|
|
|
2279
2308
|
|
|
2309
|
+
@app.command("delete")
|
|
2310
|
+
def delete_workbook(
|
|
2311
|
+
ctx: typer.Context,
|
|
2312
|
+
doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
|
|
2313
|
+
url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
|
|
2314
|
+
uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
|
|
2315
|
+
yes: bool = typer.Option(False, "--yes", help="Confirm destructive workbook deletion."),
|
|
2316
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show the request without sending it."),
|
|
2317
|
+
output: Optional[str] = output_option(),
|
|
2318
|
+
verbose: Optional[bool] = verbose_option(),
|
|
2319
|
+
timeout: Optional[float] = timeout_option(),
|
|
2320
|
+
) -> None:
|
|
2321
|
+
"""Soft-delete a workbook."""
|
|
2322
|
+
try:
|
|
2323
|
+
require_yes_or_dry_run(yes=yes, dry_run=dry_run)
|
|
2324
|
+
state: CLIContext = resolve_command_state(
|
|
2325
|
+
ctx,
|
|
2326
|
+
doc_id=doc_id,
|
|
2327
|
+
url=url,
|
|
2328
|
+
uri=uri,
|
|
2329
|
+
output=output,
|
|
2330
|
+
verbose=verbose,
|
|
2331
|
+
timeout=timeout,
|
|
2332
|
+
)
|
|
2333
|
+
target = resolve_target(state)
|
|
2334
|
+
endpoint = "/api/v1/excel/delete_file"
|
|
2335
|
+
run_post_command(
|
|
2336
|
+
state=state,
|
|
2337
|
+
endpoint=endpoint,
|
|
2338
|
+
payload={"uri": target.uri},
|
|
2339
|
+
target=target,
|
|
2340
|
+
dry_run=dry_run,
|
|
2341
|
+
command_id="workbook.delete",
|
|
2342
|
+
)
|
|
2343
|
+
except Exception as error: # pragma: no cover - CLI boundary
|
|
2344
|
+
handle_cli_error(error)
|
|
2345
|
+
|
|
2346
|
+
|
|
2280
2347
|
@app.command("list-user-workbooks")
|
|
2281
2348
|
def list_user_workbooks(
|
|
2282
2349
|
ctx: typer.Context,
|
|
2283
2350
|
limit: int = typer.Option(20, "--limit", min=1, help="Maximum workbooks to return."),
|
|
2351
|
+
order_by: Optional[str] = typer.Option(
|
|
2352
|
+
None,
|
|
2353
|
+
"--order-by",
|
|
2354
|
+
help="Sort workbooks by FIELD[:DIRECTION]. Default: updated_at:desc.",
|
|
2355
|
+
),
|
|
2284
2356
|
output: Optional[str] = output_option(),
|
|
2285
2357
|
verbose: Optional[bool] = verbose_option(),
|
|
2286
2358
|
timeout: Optional[float] = timeout_option(),
|
|
@@ -2288,6 +2360,7 @@ def list_user_workbooks(
|
|
|
2288
2360
|
list_workbooks(
|
|
2289
2361
|
ctx,
|
|
2290
2362
|
limit=limit,
|
|
2363
|
+
order_by=order_by,
|
|
2291
2364
|
output=output,
|
|
2292
2365
|
verbose=verbose,
|
|
2293
2366
|
timeout=timeout,
|