# XLA-Tools **Repository Path**: empiror/xla-tools ## Basic Information - **Project Name**: XLA-Tools - **Description**: 汐霖个人助手的工具集,用户启动时通过此仓库检查工具更新并同步工具集。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-22 - **Last Updated**: 2026-09-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # xla-tools 希灵智能助手(XLA)的内置工具集。仓库里全是独立的 Python 脚本,每个脚本负责一件事: 读表格、查天气、搜网页、抓正文……助手在对话中需要时,由主程序调用其中一个脚本, 把结果拿回去喂给模型。 这个仓库是公开的,客户端通过 HTTPS 按 **tag** 拉取,不需要 SSH 密钥。 ## 它怎么被使用 主程序(`xiling-assistant`)启动时会: 1. 用 `git ls-remote --tags` 读出本仓库所有形如 `v1.2.0` 的标签,取版本号最大的那个; 2. 把该 tag 浅克隆到 `~/.xla-tools`(已存在则 `fetch` + 切到该 tag),并把版本号写进 `~/.xla-tools/.xla-tag`; 3. 读取目录里的 `tools.json`,按清单加载工具。`pydeps/` 不入库:工具缺库时会自动 `pip install --target` 到脚本目录下的 `pydeps/`(安装包预置的 `pydeps/` 只是 离线预置,可直接复制过去;库里已有的话不会联网)。 所以 **工具更新只依赖 tag**:不推 tag,客户端不会升级。本地开发时设置 `XLA_DEV=1` 或 `XLA_TOOLS_SYNC=0` 可以直接用工程目录里的 `tools/`,不访问远程仓库。 ## 目录结构 ``` tools.json 工具清单:名字、说明、参数、对应脚本 *.py 工具脚本,一个工具一个文件 xla_guard.py run_python 专用:子进程写入围栏(审计钩子),不是工具入口 table_io.py 表格读取 + 列名/条件/日期公共库(多个表格工具共用),不是工具入口 pydeps_boot.py 第三方库自举:缺库时自动 pip install --target 到 pydeps/,不是工具入口 pydeps/ 自动生成的第三方依赖(openpyxl / xlrd / xlwt / pypdf / olefile),不入库 ``` `pydeps/` 被 `.gitignore` 排除,由 `pydeps_boot.py` 在缺库时自主生成:自动执行 `pip install --target pydeps/`(带版本区间、并发锁、50 秒超时),安装失败会给 出可手工执行的 pip3 命令;库里已有该库时不会联网。 ## 脚本约定 主程序用 `python3 -X utf8 -u <脚本>` 执行,工作目录就是脚本所在目录。约定如下: **入参**:从标准输入读一个 JSON 对象,键名与 `tools.json` 里声明的参数一一对应。 未声明的键会被主程序过滤掉。 **出参**:往标准输出打印一个 JSON 对象,且必须带 `ok` 字段。 ```json {"ok": true, "..." : "..."} {"ok": false, "error": "出错原因"} ``` 失败时建议同时以非零码退出;主程序在脚本非零退出时会读 stderr 作为错误信息, stdout 为空时也会退回 stderr。 **其他限制**: - 单个脚本最长执行 60 秒,超时会被终止;同时最多跑 2 个脚本; - 环境变量只传 `PATH`、`HOME`,`PYTHONPATH` 固定指向脚本目录下的 `pydeps/`; - 返回值超过 20000 字会被截断,错误信息超过 2000 字会被截断; - 脚本里除了 `pydeps/` 中的库,只用 Python 标准库。第三方库一律经 `pydeps_boot.ensure("模块名")` 引入,缺库自动安装。 ## 工具一览 | 工具 | 说明 | | --- | --- | | [`read_spreadsheet`](#read_spreadsheet) | 读取 xlsx / xls / csv | | [`write_spreadsheet`](#write_spreadsheet) | 写入 xlsx / xls / csv | | [`calc_table`](#calc_table) | 表格分组聚合统计(分类汇总,支持按日期分组) | | [`probe_table`](#probe_table) | 探测表格结构:表头行 / 列类型 / 空值 | | [`filter_table`](#filter_table) | 按条件筛选行,另存新表 | | [`pivot_table`](#pivot_table) | 透视表 / 交叉表(行 × 列汇总) | | [`lookup_table`](#lookup_table) | 两表关联(VLOOKUP 式)/ 对账 | | [`dedupe_table`](#dedupe_table) | 查重 / 去重 | | [`clean_table`](#clean_table) | 清洗表格(去空格 / 转数字 / 统一日期 / 删空行列) | | [`read_document`](#read_document) | 读取 doc / docx / pdf / html / odt / rtf / md / txt | | [`write_document`](#write_document) | 写入 Word 文档(.docx) | | [`write_text`](#write_text) | 写入文本/代码文件(原样逐字) | | [`web_search`](#web_search) | 百度(360 搜索兜底) | | [`fetch_url`](#fetch_url) | 抓取网页正文 | | [`query_weather`](#query_weather) | 天气网天气与预报 | | [`query_express`](#query_express) | 快递物流查询 | | [`query_ip`](#query_ip) | 查询公网 IPv4 | | [`query_location`](#query_location) | 按公网 IP 定位省市 | | [`current_time`](#current_time) | 当前时间:网络优先,本机兜底 | | [`list_dir`](#list_dir) | 列出目录 | | [`find_files`](#find_files) | 按文件名查找 | | [`file_info`](#file_info) | 文件元信息 | | [`delete_file`](#delete_file) | 删除工作空间里的单个文件 | | [`read_text_file`](#read_text_file) | 读取文本文件片段 | | [`lunar_date`](#lunar_date) | 公历转农历日期 | | [`draw_chart`](#draw_chart) | 折线图 / 柱状图 / 饼图,返回彩色 PNG | | [`run_python`](#run_python) | 运行本地 Python 脚本(受限写入) | ### read_spreadsheet `read_spreadsheet.py` — 读取本地表格,返回工作表名和行数据。大表可用 `offset` 加 `limit` 分段读取:比如读 201–400 行,传 `offset` 200、`limit` 200,同时返回 `total_rows` 和 `offset`,调用方据此知道有没有读完。CSV 依次尝试 `utf-8-sig`、 `utf-8`、`gb18030` 解码。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 表格路径,绝对路径或以 `~` 开头,后缀为 `.xlsx` / `.xls` / `.csv` | | `sheet` | 否 | 工作表名,不填读全部;CSV 忽略 | | `limit` | 否 | 每个工作表最多返回多少行,默认 200,上限 2000 | | `offset` | 否 | 从第几行开始读(0 起),默认 0;配合 `limit` 分段读取大表 | 依赖 `openpyxl`(xlsx)、`xlrd`(xls),缺库时自动安装(`pydeps_boot`)。 ### write_spreadsheet `write_spreadsheet.py` — 把二维数组写进表格。默认 `mode=overwrite`:xlsx 覆盖同名 工作表、保留其他工作表;xls 和 csv 是整文件覆盖。`mode=append` 追加到末尾: xlsx 追加到目标工作表最后一行之后(带样式的空行会跳过),csv 追加到文件末尾 (自动补换行);新文件或不存在的工作表直接整块写入;**老格式 .xls 不支持追加**。 父目录不存在会自动创建。**只给文件名(相对路径)时会保存到 工作空间 `~/.xla/workspace`**,`~` 与绝对路径原样使用。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 目标路径,后缀决定格式 | | `sheet` | 否 | 工作表名,默认 `Sheet1`;CSV 忽略 | | `rows` | 是 | 二维数组,第一行通常是表头 | | `mode` | 否 | `overwrite`(默认)/ `append`(追加到表末尾) | 依赖 `openpyxl`、`xlwt`,缺库时自动安装。合并多个月份文件、往现有台账里加新行用 append。 ### calc_table `calc_table.py` — **分组聚合统计(分类汇总)**:按 `group_by` 列分组,对 `metrics` 列做 sum / avg / max / min / count / distinct_count,`where` 过滤,排序后可用 `top` 取前 N。数字不经过模型,结果准、速度快,大表也不用读进对话。表头行自动探测 (导出文件常见的「前几行是大标题」也能处理),金额列自动清洗(¥、千分位、 百分号、会计括号负数都能算)。 ```json {"path": "9月账单.xlsx", "group_by": ["类别"], "metrics": [{"column": "金额", "func": "sum", "as": "总金额"}], "where": [{"column": "类型", "op": "ne", "value": "退款"}], "sort_by": "总金额", "top": 10} ``` `op` 支持 `eq / ne / contains / gt / gte / lt / lte / not_empty`;`group_by` 不传则 整表汇总一行。返回 `group_count`、`truncated`(分组过多时只给前 100 组)和 `warn`(有单元格不是数字被跳过时提示)。**流程建议:先 `probe_table` 看结构, 再 `calc_table` 出数**。 ### probe_table `probe_table.py` — **探测表格结构**:自动找表头在哪一行、每列的类型 (number / text / date / mixed / empty)、非空与空值数量、示例值,并预览前几行 (`sample` 控制,默认 5 行)。面对陌生表格(尤其是系统导出、表头不在第一行的) 先调它,再决定怎么统计或读取,避免整表盲读。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 表格路径 | | `sheet` | 否 | 工作表名,默认第一个 | | `sample` | 否 | 预览行数,默认 5,最多 20 | ### filter_table `filter_table.py` — **按条件筛选行**:`where` 里的条件全部满足才保留,保留所有列、不做 聚合,把命中的行另存为新表。适合「挑出金额大于 100 的记录」「只要某个门店的行」。 `op` 支持 `eq / ne / contains / gt / gte / lt / lte / not_empty`。默认存到工作空间 「源名_筛选」。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 源表格路径 | | `where` | 是 | 筛选条件数组,如 `[{"column":"金额","op":"gt","value":100}]` | | `columns` | 否 | 只保留这些列(列名或列标);不传保留所有列 | | `out_path` | 否 | 结果路径(.xlsx/.xls/.csv);不传存工作空间「源名_筛选」 | | `out_sheet` | 否 | 结果工作表名,默认「筛选结果」 | | `sheet` | 否 | 源工作表名,默认第一个 | | `header_row` | 否 | 表头行号(从 1 数),不传自动探测 | | `limit` | 否 | 最多写出多少行,默认 100000 | ### pivot_table `pivot_table.py` — **透视表 / 交叉表**:按行维度 × 列维度交叉汇总一个数值(二维分类 汇总)。`func` 支持 sum / avg / max / min / count / distinct_count。行/列维度可按日期 归桶:写 `{"column":"日期","date_bucket":"month"}`,`date_bucket` 取 year / quarter / month / week / day。会把结果连同行列合计另存成一张透视表(默认「源名_透视」)。 维度过多时按合计只保留前 100 行 / 60 列。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 表格路径 | | `row` | 是 | 行维度,如 `{"column":"类别"}` 或 `{"column":"日期","date_bucket":"month"}` | | `col` | 是 | 列维度,写法同 `row` | | `value` | 否 | 被聚合的数值列;`func=count` 时可省略 | | `func` | 否 | 聚合方式,默认 `sum` | | `where` | 否 | 过滤条件,写法同 `filter_table` | | `sheet` | 否 | 工作表名,默认第一个 | | `header_row` | 否 | 表头行号(从 1 数),不传自动探测 | | `out_path` | 否 | 透视结果路径;不传存工作空间「源名_透视」 | ### lookup_table `lookup_table.py` — **按主键关联两张表**,两种模式: - `merge`(默认,VLOOKUP 式):把右表的列带到左表。`bring` 指定带哪些列(不传带右表 除主键外的全部列);右表同一主键有多条时只取第一条,左表没匹配到的行带过来的列留空。 结果存「源名_关联」。 - `reconcile`(对账):找出两表各自多出 / 缺失的主键;再给 `left_value` + `right_value` 两个数值列,会逐键比对差异(如核对两边金额)。结果存「源名_对账」,每行标 仅左表 / 仅右表 / 双方一致 / 双方不一致。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `left_path` / `right_path` | 是 | 左表(主表)/ 右表路径 | | `left_key` / `right_key` | 是 | 两表用来匹配的主键列(列名或列标) | | `mode` | 否 | `merge`(默认)/ `reconcile` | | `bring` | 否 | merge:从右表带哪些列;不传带主键外全部列 | | `left_value` / `right_value` | 否 | reconcile:要逐键比对的数值列,成对使用 | | `left_sheet` / `right_sheet` | 否 | 工作表名,默认第一个 | | `left_header_row` / `right_header_row` | 否 | 表头行号,不传自动探测 | | `out_path` | 否 | 结果路径;不传按模式存「源名_关联」/「源名_对账」 | ### dedupe_table `dedupe_table.py` — **查重 / 去重**:按 `keys` 指定的列(不给则按整行)判定重复。 `mode=report` 只报告有哪些重复、各在原表第几行(1 起);`mode=remove` 每组保留一条 (`keep` 取 first / last)、导出去重后的新表(默认「源名_去重」)。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 表格路径 | | `keys` | 否 | 判重看哪些列(列名或列标);不传按整行 | | `mode` | 否 | `remove`(默认,导出去重表)/ `report`(只报告) | | `keep` | 否 | remove 时保留 `first`(默认)/ `last` | | `out_path` | 否 | 去重结果路径;不传存工作空间「源名_去重」 | | `sheet` | 否 | 工作表名,默认第一个 | | `header_row` | 否 | 表头行号(从 1 数),不传自动探测 | ### clean_table `clean_table.py` — **清洗表格**:去掉单元格首尾空格(含全角空格)、把「看起来就是 数字」的文本转成真数值(去千分位 / 货币符;但身份证、卡号、编号 `007`、带百分号 / 会计括号的保持文本不动)、按需把日期列统一成 `YYYY-MM-DD`、删掉整行皆空的行与整列 皆空的列,另存为一张干净的新表(默认「源名_清洗」)。清洗后再统计更准。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 源表格路径 | | `trim` | 否 | 去首尾空格(含全角),默认 `true` | | `to_number` | 否 | 文本数字保守转数值,默认 `true` | | `date_columns` | 否 | 要统一成 `YYYY-MM-DD` 的日期列;不传不动日期 | | `drop_empty_rows` | 否 | 删整行皆空的行,默认 `true` | | `drop_empty_cols` | 否 | 删表头与数据都空的整列,默认 `false` | | `sheet` | 否 | 源工作表名,默认第一个 | | `header_row` | 否 | 表头行号(从 1 数),不传自动探测 | | `out_path` | 否 | 结果路径;不传存工作空间「源名_清洗」 | | `out_sheet` | 否 | 结果工作表名,默认「清洗结果」 | 这六个表格工具都复用 `table_io.py` 的列名解析 / 条件过滤 / 日期归桶,并调 `write_spreadsheet.py` 的写入实现,保证口径一致。数字不经过模型,结果准确。 ### read_document `read_document.py` — 把文档转成纯文本段落,返回 `paragraphs` 数组和拼好的 `text`。 用这个工具而不是把原始文件直接发给模型。 支持格式: - `.docx` / `.odt` — 按 zip 解出 XML,取 `w:p` / `text:p` 段落 - `.doc` — 解析 OLE 复合文档,走 Word 的 piece table(CLX)取正文,兼容压缩与 UTF-16 两种存储;顺带处理了改名为 `.doc` 的 RTF,加密文档会明确报错 - `.pdf` — 用 `pypdf` 逐页抽取 - `.html` / `.htm` — 跳过 script / style,按块级标签分段 - `.rtf` — 处理 `\uN` 与 `\'xx` 转义后按 `\par` 分段 - `.md` / `.txt` — 按空行分段 依赖 `pypdf`(PDF)、`olefile`(doc),缺库时自动安装。 ### write_document `write_document.py` — 把纯文本段落写成 **Word 文档(.docx)**,空行分隔段落, 段内换行保留。docx 是最小可用的 OOXML(`[Content_Types].xml` + `_rels/.rels` + `word/document.xml` + `word/styles.xml`,不引入第三方库)。 **支持样式**,用 Markdown 子集写在 `text` 里: - 行首 `#` 到 `######` → 对应级别的 Word 标题样式(Heading1-6),带大纲级别, 导航窗格和目录能识别,是真样式而不是字号模拟; - `**文字**` → 加粗。 不成对的 `**` 按字面输出。默认 `mode=overwrite` 覆盖同名文件;`mode=append` 追加到文档末尾(读出旧段落与样式后整体重写,已有标题/加粗保留,文件不存在 则新建)。**内容较长时(如超过 5000 字)务必分多次调用:首次默认写入,之后 每次传 `mode=append` 追加一段**,避免单次参数过长写入失败。 **每次写完都会把文件读回来校验**:解析出段落后归一化比对(加粗在读回时还原 成 `**文字**`,两边口径一致),不一致时明确报错,不会静默截断。txt / md / html / 代码等文本文件请改用 `write_text`。父目录不存在会创建。**只给文件名 (相对路径)时会保存到工作空间 `~/.xla/workspace`**。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 目标路径,必须带 `.docx` 后缀 | | `text` | 是 | 正文,段落之间用空行分隔;`#`~`######` 标题,`**文字**` 加粗 | | `mode` | 否 | `overwrite`(默认)/ `append`(追加到文档末尾) | ### write_text `write_text.py` — 把文本**原样逐字**写入任意文本/代码文件:txt、md、html、 js、css、py、json 等。与 `write_document` 不同,不做任何段落处理——缩进、 空行、特殊符号原样保留,适合写代码与配置文件。 默认 `mode=overwrite` 覆盖;`mode=append` 追加到文件末尾(旧内容末尾没有换行 时自动补一个,避免两段首尾粘连;文件不存在则新建)。长内容分多次调用追加。 每次写完读回逐字校验,不一致明确报错。只支持写文本,检测不到二进制—— 别拿它写图片。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 目标路径,后缀决定文件类型 | | `text` | 是 | 要写入的完整文本内容,原样写入 | | `mode` | 否 | `overwrite`(默认)/ `append`(追加到文件末尾) | ### web_search `web_search.py` — 引擎链按顺序尝试,拿到结果即返回,`engine` 字段标明结果来自 哪个引擎;全部失败时 `error` 里带上各引擎的失败原因。返回条数默认 8,最多 10。 1. **百度**:先请求首页拿 cookie 再搜索;结果块以 `class="result"` 的 div 划界, `mu` 属性是真实地址(`h3 > a` 的 href 是 `baidu.com/link` 跳转链),摘要从 `summary-bottom` 区域抽文本节点; 2. **360 搜索(so.com)**:`HTMLParser` 解析 `res-list` 结果块,跳转链接的真实 地址在锚点的 `data-mdurl` 属性里,直接取用。 需要正文时再对结果里的链接调用 `fetch_url`。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `query` | 是 | 搜索关键词,最长 200 字 | | `limit` | 否 | 返回条数,默认 8,最多 10 | ### fetch_url `fetch_url.py` — 模拟浏览器访问 http / https 地址,去掉 script、style、svg 等, 返回 `title`、`description` 和分段后的正文。会跟随重定向并返回最终 URL; 正文按 `max_chars` 截断。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `url` | 是 | 目标地址,须以 `http://` 或 `https://` 开头 | | `max_chars` | 否 | 正文字数上限,默认 12000,区间 2000–16000 | 返回非网页类型(如图片、压缩包)时不会报错,而是回一句"这个地址返回的不是网页正文"。 ### query_weather `query_weather.py` — 抓天气网(`tianqi.com`)页面。当天实况用正则从 `