@cteno/sdk-linux-x64 0.0.0-stage → 2.10.14

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 (116) hide show
  1. package/LICENSE.daemon +31 -0
  2. package/README.md +4 -2
  3. package/agents/flow-analyzer/AGENT.md +71 -0
  4. package/bin/cteno-host-cli +4 -0
  5. package/bin/cteno-wiki +0 -0
  6. package/bin/rg +0 -0
  7. package/bin/secret-exec +0 -0
  8. package/manifest.json +12 -0
  9. package/package.json +33 -4
  10. package/plugins/agent-secrets/.cteno-plugin/plugin.json +133 -0
  11. package/plugins/agent-secrets/bin/linux-x64/fnox +4 -0
  12. package/plugins/agent-secrets/bin/linux-x64/secret-exec +0 -0
  13. package/plugins/agent-secrets/skills/secret/SKILL.md +201 -0
  14. package/plugins/cteno.browser/.cteno-plugin/plugin.json +65 -0
  15. package/plugins/cteno.browser/skills/browser/SKILL.md +28 -0
  16. package/plugins/cteno.inbox/.cteno-plugin/plugin.json +132 -0
  17. package/plugins/cteno.inbox/bin/all/cteno-connector-credential +31 -0
  18. package/plugins/cteno.inbox/bin/all/cteno-telegram-connector +33 -0
  19. package/plugins/cteno.inbox/bin/linux-x64/cteno-inbox +0 -0
  20. package/plugins/cteno.inbox/skills/cteno-inbox/SKILL.md +374 -0
  21. package/plugins/cteno.inbox/tools/inbox_focus/TOOL.md +44 -0
  22. package/plugins/cteno.memory/.cteno-plugin/plugin.json +176 -0
  23. package/plugins/cteno.memory/agents/llm-wiki/AGENT.md +96 -0
  24. package/plugins/cteno.memory/bin/linux-x64/cteno-wiki +0 -0
  25. package/plugins/cteno.memory/skills/codebase-wiki/SKILL.md +52 -0
  26. package/plugins/cteno.memory/skills/cteno.memory.native/SKILL.md +110 -0
  27. package/plugins/cteno.memory/tools/memory/TOOL.md +492 -0
  28. package/plugins/cteno.memory/ui/index.html +822 -0
  29. package/plugins/cteno.models/.cteno-plugin/plugin.json +191 -0
  30. package/plugins/cteno.websearch/.cteno-plugin/plugin.json +45 -0
  31. package/plugins/jev/.cteno-plugin/plugin.json +309 -0
  32. package/plugins/jev/skills/jev-browser/SKILL.md +66 -0
  33. package/plugins/jev/skills/jev-browser/jev_browser.py +335 -0
  34. package/skills/agent-create/SKILL.md +86 -0
  35. package/skills/cteno-host-cli/SKILL.md +256 -0
  36. package/skills/docx/LICENSE +23 -0
  37. package/skills/docx/NOTICE.md +8 -0
  38. package/skills/docx/SKILL.md +69 -0
  39. package/skills/docx/references/cjk_typography.md +357 -0
  40. package/skills/docx/references/design_principles.md +819 -0
  41. package/skills/docx/references/revisions-and-comments.md +88 -0
  42. package/skills/docx/references/track_changes_guide.md +200 -0
  43. package/skills/docx/references/typography_guide.md +294 -0
  44. package/skills/docx/scripts/cjk_fonts.py +244 -0
  45. package/skills/docx/scripts/docx_comments.py +289 -0
  46. package/skills/docx/scripts/docx_common.py +92 -0
  47. package/skills/docx/scripts/docx_create.py +259 -0
  48. package/skills/docx/scripts/docx_edit.py +250 -0
  49. package/skills/docx/scripts/docx_read.py +149 -0
  50. package/skills/docx/scripts/docx_revisions.py +146 -0
  51. package/skills/docx/scripts/docx_template.py +70 -0
  52. package/skills/docx/scripts/docx_validate.py +156 -0
  53. package/skills/docx/scripts/render.py +250 -0
  54. package/skills/docx/tests/test_docx_skill.py +525 -0
  55. package/skills/home-devices/SKILL.md +121 -0
  56. package/skills/mcp/SKILL.md +72 -0
  57. package/skills/mcp/scripts/mcp_setup.py +439 -0
  58. package/skills/pdf/LICENSE +23 -0
  59. package/skills/pdf/NOTICE.md +8 -0
  60. package/skills/pdf/SKILL.md +63 -0
  61. package/skills/pdf/references/design-tokens.md +381 -0
  62. package/skills/pdf/references/forms.md +99 -0
  63. package/skills/pdf/references/nano-pdf-editing.md +40 -0
  64. package/skills/pdf/references/ocr-extraction.md +165 -0
  65. package/skills/pdf/scripts/_raster.py +76 -0
  66. package/skills/pdf/scripts/extract_marker.py +88 -0
  67. package/skills/pdf/scripts/extract_pymupdf.py +101 -0
  68. package/skills/pdf/scripts/pdf_create.py +191 -0
  69. package/skills/pdf/scripts/pdf_fill_form.py +97 -0
  70. package/skills/pdf/scripts/pdf_form_layout.py +168 -0
  71. package/skills/pdf/scripts/pdf_make_form.py +145 -0
  72. package/skills/pdf/scripts/pdf_merge.py +50 -0
  73. package/skills/pdf/scripts/pdf_meta.py +115 -0
  74. package/skills/pdf/scripts/pdf_page_image.py +99 -0
  75. package/skills/pdf/scripts/pdf_read.py +153 -0
  76. package/skills/pdf/scripts/pdf_secure.py +71 -0
  77. package/skills/pdf/scripts/pdf_split.py +84 -0
  78. package/skills/pdf/scripts/pdf_stamp.py +143 -0
  79. package/skills/pdf/scripts/pdf_watermark.py +51 -0
  80. package/skills/pdf/scripts/render.py +250 -0
  81. package/skills/pdf/tests/test_pdf_skill.py +415 -0
  82. package/skills/pptx/LICENSE +23 -0
  83. package/skills/pptx/NOTICE.md +8 -0
  84. package/skills/pptx/SKILL.md +62 -0
  85. package/skills/pptx/references/design-system.md +392 -0
  86. package/skills/pptx/references/pptxgenjs.md +420 -0
  87. package/skills/pptx/references/slide-types.md +413 -0
  88. package/skills/pptx/scripts/cjk_fonts.py +244 -0
  89. package/skills/pptx/scripts/pptx_create.py +240 -0
  90. package/skills/pptx/scripts/pptx_edit.py +436 -0
  91. package/skills/pptx/scripts/pptx_from_template.py +88 -0
  92. package/skills/pptx/scripts/pptx_read.py +131 -0
  93. package/skills/pptx/scripts/pptx_render.py +96 -0
  94. package/skills/pptx/scripts/render.py +250 -0
  95. package/skills/pptx/tests/test_powerpoint_skill.py +477 -0
  96. package/skills/skill-create/SKILL.md +114 -0
  97. package/skills/video-generation/SKILL.md +27 -0
  98. package/skills/xlsx/LICENSE +23 -0
  99. package/skills/xlsx/NOTICE.md +8 -0
  100. package/skills/xlsx/SKILL.md +63 -0
  101. package/skills/xlsx/references/format.md +71 -0
  102. package/skills/xlsx/references/restructuring.md +71 -0
  103. package/skills/xlsx/scripts/csv_to_xlsx.py +110 -0
  104. package/skills/xlsx/scripts/formula_check.py +428 -0
  105. package/skills/xlsx/scripts/render.py +250 -0
  106. package/skills/xlsx/scripts/xlsx_create.py +265 -0
  107. package/skills/xlsx/scripts/xlsx_edit.py +269 -0
  108. package/skills/xlsx/scripts/xlsx_read.py +166 -0
  109. package/skills/xlsx/scripts/xlsx_recalc.py +119 -0
  110. package/skills/xlsx/scripts/xlsx_restructure.py +343 -0
  111. package/skills/xlsx/scripts/xlsx_to_csv.py +75 -0
  112. package/skills/xlsx/tests/test_xlsx_skill.py +544 -0
  113. package/tools/create_goal/TOOL.md +36 -0
  114. package/tools/image_generation/TOOL.md +81 -0
  115. package/tools/skill/TOOL.md +117 -0
  116. package/tools/update_profile_notes/TOOL.md +39 -0
@@ -0,0 +1,63 @@
1
+ ---
2
+ id: xlsx
3
+ name: xlsx
4
+ version: "2.0.0"
5
+ description: "Create, read, analyze, edit, clean and restructure Excel workbooks (.xlsx/.xlsm/.csv/.tsv) with live formulas, native tables/charts/validation, recalculation and formula-error checks."
6
+ when_to_use: "Use whenever a spreadsheet is the input or deliverable: 表格, Excel, 报表, 预算, 财务模型, pivot-style summaries, cleaning messy CSV/Excel data, adding columns/rows/formulas, charts in a workbook, converting CSV↔XLSX. Not when the deliverable is a Word report, slides or a chart image."
7
+ license: MIT (see LICENSE; scripts adapted from Nous Research and MiniMax, MIT)
8
+ tags: [xlsx, excel, spreadsheet, csv, 表格, 报表]
9
+ ---
10
+
11
+ # Excel workbooks (.xlsx)
12
+
13
+ | Task | Route |
14
+ |---|---|
15
+ | New workbook | JSON spec → `${SKILL_DIR}/scripts/xlsx_create.py spec.json out.xlsx` (formulas, formats, widths, freeze panes, merges, conditional formats, native tables, charts, validation, notes, protection) |
16
+ | Larger logic | Write an `openpyxl` script; bulk data in/out with `pandas` |
17
+ | Edit existing | `${SKILL_DIR}/scripts/xlsx_edit.py book.xlsx --set B2=… / --append ROWJSON / --insert-rows / --add-table / --define-name … --out new.xlsx` |
18
+ | Move rows/cols keeping references | `${SKILL_DIR}/scripts/xlsx_restructure.py` (read `${SKILL_DIR}/references/restructuring.md`) |
19
+ | Read | `${SKILL_DIR}/scripts/xlsx_read.py book.xlsx --sheets / --json [--data-only] / --formulas / --csv` |
20
+ | CSV ↔ XLSX | `${SKILL_DIR}/scripts/csv_to_xlsx.py`, `${SKILL_DIR}/scripts/xlsx_to_csv.py` |
21
+ | Recalculate | `${SKILL_DIR}/scripts/xlsx_recalc.py book.xlsx` (LibreOffice) |
22
+ | Formula errors | `${SKILL_DIR}/scripts/formula_check.py book.xlsx --summary` |
23
+ | Look at it | `${SKILL_DIR}/scripts/render.py book.xlsx` |
24
+
25
+ Scripts take `--help`, print JSON (formula_check prints a report), exit non-zero on failure. Always call scripts by the absolute paths shown here (not a copy found elsewhere on disk).
26
+ Dependencies: `python3 -m pip install openpyxl pandas lxml`. Recalc/render: LibreOffice.
27
+
28
+ ## Rules for every workbook you produce
29
+
30
+ - **Formulas, not pasted results.** `=SUM(B2:B9)`, not the number Python computed — the sheet must update when inputs change. Python is for cleaning and reshaping data before it goes in.
31
+ - **Recalculate before delivery.** openpyxl writes formulas without cached values, so pandas, previews and `--data-only` reads show empty cells until `xlsx_recalc.py` runs.
32
+ - **Zero formula errors.** `formula_check.py` must report PASS. A clean recalc proves formulas *evaluate*, not that they are *right*: check 2–3 results by hand before filling a whole grid.
33
+ - **Follow the user's spec literally** — exact sheet names, headers, and the formula they described.
34
+ - **Editing someone's file**: match its conventions (fonts, colors, number formats, input-cell markings); write only to input cells; never overwrite their formulas; save under a new name unless told otherwise. `.xlsm`: keep macros (`keep_vba=True`).
35
+ - **Document assumptions** next to the numbers (a note or a labeled cell), with a source when one exists. Unknown inputs are marked, never invented.
36
+
37
+ ## Layout and formatting (details and openpyxl snippets: `${SKILL_DIR}/references/format.md`)
38
+
39
+ - Header row bold, frozen (`freeze_panes: "A2"`), with an autofilter or a native table for lists.
40
+ - Column widths set for the content (CJK characters are about 2× the width of Latin: width ≈ 2 × 字数 + 2).
41
+ - Number formats: thousands separators `#,##0.00`, percentages stored as fractions with `0.0%`, dates as real dates (`yyyy-mm-dd`), negatives in parentheses or red for finance, units in the header (`金额(万元)`).
42
+ - Text left, numbers right; one font throughout (Arial / 微软雅黑 / 等线); totals row bold with a top border.
43
+ - Financial models: blue font for hard-coded inputs, black for formulas, green for cross-sheet links; every assumption in its own cell referenced by formulas (`=B5*(1+$B$6)`, never `=B5*1.05`); guard divisions (`IFERROR`).
44
+
45
+ ## Formula compatibility
46
+
47
+ - Prefer Excel-2007 functions: `SUMIFS`, `COUNTIFS`, `INDEX`/`MATCH`, `IFERROR`, `SUMPRODUCT`, `VLOOKUP`.
48
+ - Post-2007 functions written through openpyxl need the `_xlfn.` prefix (`_xlfn.TEXTJOIN`, `_xlfn.IFS`, `_xlfn.MAXIFS` …) or they become `#NAME?`.
49
+ - Avoid dynamic-array functions (`XLOOKUP`, `FILTER`, `SORT`, `UNIQUE`, `SEQUENCE`) in files you generate: no spill metadata, and LibreOffice/WPS may not compute them. Sort, filter and de-duplicate in Python first.
50
+ - Sheet names with spaces or Chinese need quotes in references: `='销售 数据'!B2`.
51
+
52
+ ## openpyxl gotchas
53
+
54
+ - `data_only=True` gives values without formulas; **saving that workbook destroys every formula**. Read formulas and values with two loads.
55
+ - Merged cells: write only the top-left cell.
56
+ - Workbooks linked to other files lose those links' cached values when re-saved with openpyxl — copy the values first, and tell the user.
57
+ - Very large data (> 100k rows): write with pandas/`write_only` mode; skip per-cell styling.
58
+
59
+ ## Verify before delivery (required)
60
+
61
+ 1. `python3 ${SKILL_DIR}/scripts/xlsx_recalc.py out.xlsx` then `python3 ${SKILL_DIR}/scripts/formula_check.py out.xlsx --summary` → PASS.
62
+ 2. `python3 ${SKILL_DIR}/scripts/xlsx_read.py out.xlsx --json --data-only` → spot-check totals and a few rows against the source.
63
+ 3. For workbooks people will read on screen or print: `python3 ${SKILL_DIR}/scripts/render.py out.xlsx` and look at the pages (truncated `####` columns, wrapped headers, wrong number formats).
@@ -0,0 +1,71 @@
1
+ # Formatting workbooks with openpyxl
2
+
3
+ Apply formatting last, after data and formulas are in place. Work at the cell/column level with openpyxl; never hand-edit `styles.xml`.
4
+
5
+ ## Number formats
6
+
7
+ | Content | Format string | Notes |
8
+ |---|---|---|
9
+ | Money, 2 decimals | `#,##0.00` | thousands separator |
10
+ | Money, finance style | `#,##0;(#,##0);"-"` | negatives in parentheses, zero as dash |
11
+ | RMB | `"¥"#,##0.00` | or put the unit in the header: `金额(元)` |
12
+ | Large units | `#,##0.0,,"M"` / `#,##0.0,"K"` | or divide in a formula and say `(万元)` in the header |
13
+ | Percent | `0.0%` | store 0.15, not 15 |
14
+ | Multiple | `0.0"x"` | valuation multiples |
15
+ | Date | `yyyy-mm-dd` (or `yyyy"年"m"月"d"日"`) | write real `datetime`/`date` values, not strings |
16
+ | Year as label | text `"2026"` | a number would display `2,026` with separators |
17
+
18
+ ```python
19
+ cell.number_format = "#,##0.00"
20
+ ```
21
+
22
+ ## Fonts, fills, borders, alignment
23
+
24
+ ```python
25
+ from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
26
+ hdr_font = Font(name="Arial", bold=True, color="FFFFFF") # for CJK files: name="微软雅黑" or "等线"
27
+ hdr_fill = PatternFill("solid", fgColor="1F4E79")
28
+ thin = Side(style="thin", color="BFBFBF")
29
+ for c in ws[1]:
30
+ c.font, c.fill = hdr_font, hdr_fill
31
+ c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
32
+ c.border = Border(bottom=thin)
33
+ ```
34
+
35
+ - One font family for the whole workbook. Headers bold; totals bold with a top border.
36
+ - Text left-aligned, numbers right-aligned (default), headers centered.
37
+ - Fills sparingly: header row, input cells (light yellow `FFF2CC`), key outputs. No rainbow tables.
38
+ - Build a new `Font`/`Fill` object rather than mutating a shared one (openpyxl styles are immutable proxies).
39
+
40
+ ## Financial-model color code (unless the file has its own)
41
+
42
+ | Meaning | Style |
43
+ |---|---|
44
+ | Hard-coded input / scenario lever | blue font `0000FF` |
45
+ | Formula | black font |
46
+ | Link to another sheet | green font `008000` |
47
+ | Link to another file | red font `FF0000` |
48
+ | Key assumption / cell to fill in | yellow fill `FFFF00` |
49
+
50
+ ## Layout
51
+
52
+ - Column widths: `ws.column_dimensions["A"].width = 18`. Latin text ≈ characters + 2; CJK ≈ 2 × characters + 2. Never leave numbers showing `####`.
53
+ - Freeze the header: `ws.freeze_panes = "A2"` (or `"B2"` to also freeze the label column).
54
+ - Lists of records: make a native table (`xlsx_create.py` `tables`, or `openpyxl.worksheet.table.Table`) — filters, banding and structured references come free.
55
+ - Wrap long text in a few columns only (`wrap_text=True`) and set a row height if needed.
56
+ - Merged cells only for titles; never inside data ranges (they break sorting/filtering).
57
+ - Print setup for sheets people print: `ws.page_setup.orientation = "landscape"`, `ws.page_setup.fitToWidth = 1`, `ws.sheet_properties.pageSetUpPr.fitToPage = True`, `ws.print_title_rows = "1:1"`.
58
+
59
+ ## Conditional formatting
60
+
61
+ Use for signal, not decoration: negative variances red, top values highlighted, a data bar for one key column.
62
+
63
+ ```python
64
+ from openpyxl.formatting.rule import CellIsRule, ColorScaleRule, DataBarRule
65
+ ws.conditional_formatting.add("D2:D100", CellIsRule(operator="lessThan", formula=["0"], font=Font(color="C00000")))
66
+ ws.conditional_formatting.add("E2:E100", DataBarRule(start_type="min", end_type="max", color="5B9BD5"))
67
+ ```
68
+
69
+ ## Charts
70
+
71
+ Native charts (`openpyxl.chart`) stay live with the data. Give them a title and axis titles, one series color per meaning, and anchor them beside the table, not on top of it. Prefer a column/bar chart for comparisons, a line for trends over time, and avoid pies with more than 5 slices.
@@ -0,0 +1,71 @@
1
+ # Reference-aware restructuring (xlsx_restructure.py)
2
+
3
+ `scripts/xlsx_restructure.py` performs one row/column insert or delete
4
+ and rewrites everything that references the moved cells. This document
5
+ gives the exact rewrite rules and honest limits.
6
+
7
+ ## What gets rewritten
8
+
9
+ | Artifact | Scope | Behavior |
10
+ |---|---|---|
11
+ | Formula references | ALL sheets | A1 refs into the edited sheet are shifted; refs into a fully deleted region become `#REF!` |
12
+ | Merged-cell ranges | edited sheet | shifted; expanded when they span the insertion point; dropped (reported `to: null`) when fully deleted |
13
+ | Autofilter ref | edited sheet | shifted/expanded like a range |
14
+ | Freeze panes | edited sheet | anchor cell shifted (never below row/col of the pane's own minimum) |
15
+ | Data validations | edited sheet | each range in the sqref shifted; deleted ranges removed |
16
+ | Conditional formats | edited sheet | applied range (sqref) shifted |
17
+ | Native tables | edited sheet | table `ref` shifted/expanded |
18
+ | Defined names | workbook scope | `attr_text` refs into the edited sheet rewritten |
19
+ | Row heights / column widths | edited sheet | dimension keys re-indexed |
20
+
21
+ ## Reference grammar handled
22
+
23
+ - Relative and absolute coordinates in any mix: `B2`, `$B2`, `B$2`,
24
+ `$B$2` — the `$` flags are preserved through the shift.
25
+ - Ranges `B2:D9`, including partial-absolute endpoints.
26
+ - Cross-sheet refs: `Data!B2`, `'My Sheet'!$A$1:$C$9` (quoted names may
27
+ contain doubled quotes `''`). Only refs whose sheet qualifier matches
28
+ the edited sheet are touched; unqualified refs are interpreted
29
+ relative to the formula's own sheet.
30
+ - String literals inside formulas (`"See B2"`) are never rewritten.
31
+ - Function names that look like cells (`LOG10(...)`) are not touched
32
+ (a reference is never followed by `(`).
33
+ - Whole-row/column refs (`B:B`, `2:2`) pass through unchanged — Excel
34
+ semantics keep them valid across inserts within the span.
35
+
36
+ ## Shift semantics
37
+
38
+ Insert of N at index i: every coordinate >= i moves +N; range endpoints
39
+ move independently, so a range spanning i grows by N.
40
+
41
+ Delete of N at index i: coordinates before i are unchanged; coordinates
42
+ past the deleted block move -N; a single cell inside the block becomes
43
+ `#REF!`; a RANGE partially covering the block is clamped (Excel does the
44
+ same); a range entirely inside the block becomes `#REF!` (formulas) or
45
+ is removed (merges/validations).
46
+
47
+ ## What it CANNOT shift (honest limits)
48
+
49
+ - **Chart anchors and plotted ranges** — openpyxl chart objects are not
50
+ reliably round-tripped; anchors stay where they were. Re-create
51
+ charts after restructuring if their data moved.
52
+ - **Images / drawings** — same reason.
53
+ - **Conditional-format RULE formulas** — the applied range (sqref) is
54
+ shifted, but formulas inside `cell_is`/`expression` rules (e.g.
55
+ `$B1>100`) are left as-is. Review them if they reference moved cells.
56
+ - **Sheet-local defined names** and names using R1C1 or union/
57
+ intersection operators are rewritten only if they parse as plain A1
58
+ refs; anything else passes through untouched.
59
+ - **Structured table references** in formulas (`Table1[Sales]`) don't
60
+ need shifting (they follow the table), and are left alone.
61
+
62
+ Every run prints a JSON report listing exactly which formulas, merges,
63
+ tables, names, and ranges were changed, plus a fixed `not_shifted` list
64
+ of the above limits — inspect it after any structural edit.
65
+
66
+ ## One op per invocation
67
+
68
+ The CLI takes exactly one of `--insert-rows/--delete-rows/
69
+ --insert-cols/--delete-cols` (as `IDX[:N]`; columns accept letters).
70
+ For multiple operations run it repeatedly — ordering compound shifts in
71
+ one pass is where spreadsheet tools historically corrupt references.
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ """Convert a CSV file to a styled .xlsx workbook with type inference.
3
+
4
+ Type inference per cell (disable with --no-infer):
5
+ int, float, bool ("true"/"false", case-insensitive), ISO date
6
+ (YYYY-MM-DD) and ISO datetime; everything else stays a string.
7
+
8
+ Styling applied by default (disable with --plain):
9
+ bold header row with a light fill, frozen top row, autofilter over the
10
+ data range, and column widths sized to the longest cell (capped at 60).
11
+
12
+ Usage:
13
+ csv_to_xlsx.py data.csv out.xlsx
14
+ csv_to_xlsx.py data.csv out.xlsx --sheet-name Import --encoding cp1252
15
+ csv_to_xlsx.py data.csv out.xlsx --delimiter ';' --no-infer
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import csv
21
+ import json
22
+ import sys
23
+ from datetime import date, datetime
24
+
25
+ from openpyxl import Workbook
26
+ from openpyxl.styles import Font, PatternFill
27
+ from openpyxl.utils import get_column_letter
28
+
29
+ MAX_COL_WIDTH = 60
30
+ COL_PADDING = 2
31
+ DEFAULT_COL_WIDTH = 8
32
+
33
+
34
+ def infer(text):
35
+ if text == "":
36
+ return None
37
+ low = text.lower()
38
+ if low in ("true", "false"):
39
+ return low == "true"
40
+ for caster in (int, float):
41
+ try:
42
+ return caster(text)
43
+ except ValueError:
44
+ pass
45
+ for parser in (date.fromisoformat, datetime.fromisoformat):
46
+ try:
47
+ return parser(text)
48
+ except ValueError:
49
+ pass
50
+ return text
51
+
52
+
53
+ def main(argv=None):
54
+ # Text I/O is UTF-8 regardless of the locale (GBK on Chinese Windows).
55
+ for stream in (sys.stdin, sys.stdout, sys.stderr):
56
+ try:
57
+ stream.reconfigure(encoding="utf-8")
58
+ except Exception:
59
+ pass
60
+ ap = argparse.ArgumentParser(description="CSV -> styled .xlsx converter.")
61
+ ap.add_argument("csv_file", help="input CSV path")
62
+ ap.add_argument("output", help="output .xlsx path")
63
+ ap.add_argument("--sheet-name", default="Sheet1")
64
+ ap.add_argument("--encoding", default="utf-8",
65
+ help="CSV file encoding (default utf-8)")
66
+ ap.add_argument("--delimiter", default=",")
67
+ ap.add_argument("--no-infer", action="store_true",
68
+ help="keep every cell as a string")
69
+ ap.add_argument("--plain", action="store_true",
70
+ help="skip header styling / freeze / autofilter")
71
+ args = ap.parse_args(argv)
72
+
73
+ with open(args.csv_file, newline="", encoding=args.encoding) as fh:
74
+ rows = list(csv.reader(fh, delimiter=args.delimiter))
75
+
76
+ wb = Workbook()
77
+ ws = wb.active
78
+ ws.title = args.sheet_name
79
+ for i, row in enumerate(rows):
80
+ if args.no_infer or i == 0:
81
+ ws.append(row)
82
+ else:
83
+ ws.append([infer(cell) for cell in row])
84
+
85
+ if rows and not args.plain:
86
+ header_font = Font(bold=True)
87
+ header_fill = PatternFill("solid", fgColor="DDEBF7")
88
+ for cell in ws[1]:
89
+ cell.font = header_font
90
+ cell.fill = header_fill
91
+ ws.freeze_panes = "A2"
92
+ ws.auto_filter.ref = ws.dimensions
93
+ for col_idx in range(1, ws.max_column + 1):
94
+ longest = max((len(str(r[col_idx - 1])) for r in rows
95
+ if len(r) >= col_idx), default=DEFAULT_COL_WIDTH)
96
+ ws.column_dimensions[get_column_letter(col_idx)].width = \
97
+ min(longest + COL_PADDING, MAX_COL_WIDTH)
98
+
99
+ wb.save(args.output)
100
+ print(json.dumps({"ok": True, "output": args.output,
101
+ "rows": len(rows)}, ensure_ascii=False))
102
+ return 0
103
+
104
+
105
+ if __name__ == "__main__":
106
+ try:
107
+ sys.exit(main())
108
+ except Exception as exc:
109
+ print(json.dumps({"ok": False, "error": str(exc)}), file=sys.stderr)
110
+ sys.exit(1)