# 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`)页面。当天实况用正则从 `
` 里取城市、日期、气温、天气、湿度、风向、紫外线、空气质量、 PM、日出日落;7 / 15 / 30 天预报再多请求一次页面使用的 `tianqidata` 数据接口。 `city` 必须是天气网用的城市拼音(合肥是 `hefei`,不是 `hefei shi`),只接受小写字母和数字。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `city` | 是 | 城市拼音,如 `hefei`、`beijing`、`shanghai` | | `days` | 否 | `today`、`7`、`15`、`30`,默认 `today` | ### query_express `query_express.py` — 按快递网(`kuaidi.com`)网页的调用方式查物流:先访问首页拿到 cookie, 再用单号去 `index-ajaxselectinfo` 识别快递公司;不指定 `company` 时按识别结果依次尝试前 3 家,命中就返回状态和轨迹。 返回的 `status` 是数字码翻译过来的(在途、揽件、已签收、派件……)。全都没查到时 `ok` 为 `false`,同时在 `candidates` 里给出识别到的公司列表,方便指定 `company` 重试。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `number` | 是 | 快递单号,只含字母、数字和短横线,5–40 位 | | `company` | 否 | 快递公司代码,如 `jtexpress`;不填则自动识别 | ### query_ip `query_ip.py` — 请求 `ipv4.jsonip.com` 返回当前公网 IPv4,入参为空。无参数。 ### query_location `query_location.py` — 按公网 IP 请求 `ip9.com.cn`,返回省份和城市。默认查当前网络, 也可以用 `ip` 参数查询指定的 IPv4 地址(走 `https://ip9.com.cn/get?ip=`)。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `ip` | 否 | 要查询的 IPv4 地址,如 `223.5.5.5`;不填则查当前网络 | > 这两个脚本没有第三方依赖,只用标准库。macOS 下会优先用系统自带的 > `/etc/ssl/cert.pem` 做 HTTPS 证书校验。 ### current_time `current_time.py` — 获取当前时间,**优先从网络取,网络不可用时降级为本机时间**。 不依赖任何 API 密钥:依次请求几个常见站点,读 HTTP 响应的 `Date` 头(HTTP 的 Date 一律是 GMT),若都失败才退回本机时钟。无参数。 返回里带有 `time`(本地时区 YYYY-MM-DD HH:MM:SS)、`iso`(含时区的 ISO 字符串)、 `date`、`weekday`(中文星期)、`timezone`、`utc`、`timestamp`,以及 `source`(`network` / `local`)和本机时钟相对网络时间的偏差 `clock_offset_seconds`(本地时钟被改过年代时,这个值会很明显)。 ### list_dir `list_dir.py` — 列出目录内容,跨平台。默认排除隐藏项,可按名称 / 修改时间 / 大小排序,可限制返回条数。每个条目给出 `name`、`path`、`type`(file / dir / symlink / other)、`size`、`mtime`。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 否 | 目录路径,默认用户主目录;支持 `~` 开头 | | `sort_by` | 否 | `name`、`time`、`size`,默认 `name`;time / size 按降序(最近 / 最大在前) | | `limit` | 否 | 最多返回多少条,默认 100,上限 1000 | | `hidden` | 否 | 是否包含隐藏项(`true` 时也列出以 `.` 开头的),默认 `false` | 单条条目取不到元数据(权限、已删除)时会尽力返回而不中断整体列举。 ### find_files `find_files.py` — 在目录树里递归查找文件,按**最近修改优先**排序。`pattern` 含 `*` / `?` / `[` 时按 glob 匹配**文件名**,否则按文件名子串(忽略大小写)。 自动跳过 `.git`、`node_modules`、`__pycache__`、`.cache` 等常见大目录,并有深度 上限,避免反应迟缓或扫到系统盘。可以指定只找 `file` / `dir` / `symlink`。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 否 | 根目录,默认用户主目录;支持 `~` 开头 | | `pattern` | 是 | 匹配规则:含 glob 通配符时按 glob,否则为文件名子串(如 `*.xlsx`、`报表`) | | `limit` | 否 | 最多返回多少条,默认 50,上限 200 | | `type` | 否 | 只匹配 `file` / `dir` / `symlink` | | `depth` | 否 | 向下最多几层,默认 6,越大越慢 | | `skip_hidden` | 否 | 是否跳过隐藏目录,默认 `true` | 返回里 `matched` 是匹配总数(可能超过 `limit`),`entries` 是实际返回的条目。 遇到无权限的目录会静默跳过,不影响其它结果。 ### file_info `file_info.py` — 获取文件 / 目录 / 符号链接的元信息,跨平台:类型、大小与可读大小 (`3.1KB` 这种)、修改 / 创建 / 访问时间、权限(`-rw-r--r--` 与八进制)、属主属组、 是否可读可写、是否为符号链接(含目标)。Unix 下属主属组解析成用户名,其它平台回退 为数字 ID 而不报错。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 目标路径,支持 `~` 开头 | 对符号链接用 `lstat`,不跟随——得到的是链接本身的信息,同时用 `realpath` 给出 实际指向的路径。`mode_octal` 带 `0o` 前缀,解析时可去掉。 ### delete_file `delete_file.py` — 删除工作空间 `~/.xla/workspace` 里的单个文件。**安全约束**: - `path` 只接受工作空间内的相对路径,绝对路径、`~` 开头的路径一律拒绝; - 解析后必须仍落在工作空间内,`..` 穿越、指向外部的符号链接会拒绝; - 一次只删一个普通文件:目录拒绝(不做递归删除),不展开通配符, 文件名含 `* ? [` 直接报错。 删除成功返回被删文件的名称、完整路径和大小。删除不可恢复,调用前建议先用 `find_files` / `list_dir` / `file_info` 确认目标。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 工作空间内的相对路径(如 `报告/9月.docx`) | ### read_text_file `read_text_file.py` — 读取 log、json、txt、md、go、js、css、html 等**任意文本类** 文件的一段内容。自动探测编码(`utf-8-sig` → `utf-8` → `gb18030`),检测到空字节 会明确报"可能是二进制文件"。支持按行窗口读取:默认从头读,`tail` 为 `true` 时读 文件末尾一段,适合看日志尾部;可给每行加行号。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 目标文件路径,支持 `~` 开头 | | `start_line` | 否 | 从第几行开始(1 起),默认 1 | | `max_lines` | 否 | 最多返回多少行,默认 1000,上限 5000 | | `tail` | 否 | 为 `true` 时改为读文件末尾 `max_lines` 行 | | `max_chars` | 否 | 输出字数上限,默认 12000,上限 16000 | | `line_numbers` | 否 | 是否在每行前加行号,默认 `false` | 返回 `total_lines`、`start_line` / `end_line`(1 起的闭合区间)、`returned_lines`、 `content`、`truncated` 和探测出的 `encoding`。超过 8MB 的大文件只读我们关心的片段 (tail 读末尾,否则读开头),不会整份载入,也不会因此臆造内容。 ### lunar_date `lunar_date.py` — 把**公历日期转成农历**,纯标准库,内嵌 1900–2100 的农历数据表。 不填参数时取今天,也可用 `date` 指定某一天。 返回农历年(数字 `lunar_year`、干支 `lunar_ganzhi`、生肖 `lunar_shengxiao`)、 农历月日数值键 `lunar_month` / `lunar_day` / `lunar_leap`(是否闰月)、月份与日期的 中文文本,以及拼好的展示 `text`(如 `甲辰年八月十五`)。**数值键是主程序做「农历 定时任务」匹配用的。** 无第三方依赖。 ### draw_chart `draw_chart.py` — 把数据画成**彩色图片**(折线图 / 柱状图 / 饼图),返回 base64 PNG。 纯标准库实现(自绘光栅化 + 2x 超采样抗锯齿),不依赖 matplotlib / Pillow。 中文用**微软雅黑**渲染:首次调用时自动从 `http://oss.mtit.net/msyh.ttc` 下载到 `~/.xla/fonts/msyh.ttc` 并长期复用(字体轮廓自行解析 TrueType glyf / OTF CFF 并光栅化);无网络或下载失败时退回系统中文字体 (Windows 微软雅黑/宋体、macOS PingFang SC、Linux Noto/文泉驿); 都没有时才用内嵌的 24x24 中文字库位图兜底(GB2312 一级汉字)。 可用环境变量 `XLA_CHART_FONT` 指定字体文件、`XLA_CHART_BITMAP=1` 强制走内嵌位图。 - `type`:`line`(趋势)、`bar`(对比)、`pie`(占比); - `labels`:类目名;`series`:line/bar 的数据序列(可多组,自动配色 + 图例); `values`:pie 的数值; - `title` / `width` / `height` 可选。 图片按「images 约定」返回(见 `rules.md` 第四节):助手端直接把图渲染给用户, 模型只拿到一句 `summary`。适合用户给出一组数据、要求画图、对比或看占比时主动调用。 ### run_python `run_python.py` — 在本机跑一个 Python 脚本(`python3 -X utf8 -u`,工作目录为脚本 所在目录),返回退出码、stdout、stderr。适合大表分段处理、批量改写、多步计算这类 对话里来回搬运低效的任务。脚本可用 `pydeps/` 里的第三方库(如 openpyxl)。 **受限运行**:脚本装着 `xla_guard.py` 的写入围栏(审计钩子实现)—— - 任何位置都可**读**; - **写 / 删 / 移动 / 建目录**只允许工作空间 `~/.xla/workspace` 与系统临时目录 (`tempfile.gettempdir()`,POSIX 上含 `/tmp`、`/var/tmp`); - 脚本内**禁止再启动子进程**(subprocess、os.system 等会报 PermissionError); - 被拦截时脚本报 `PermissionError`,信息里写明允许的目录; - 环境变量 `XLA_RUNPY_UNRESTRICTED=1` 可整体解除(预留给沙箱化运行)。 | 参数 | 必填 | 说明 | | --- | --- | --- | | `path` | 是 | 脚本路径,绝对路径或 `~` 开头(任意扩展名均可) | | `args` | 否 | 传给脚本的命令行参数列表 | | `timeout` | 否 | 超时秒数,默认 55(主程序单次调用上限 60 秒) | > **关于最终成果**:脚本生成要交给用户的成品文件时,直接写到工作空间即可,脚本侧 > 不再需要任何产物标记。是否在对话里显示可打开的文件卡片,由大模型在最终回答里用 > `绝对路径` 标记决定,前端据此渲染。 本地调试: ```sh echo '{"path": "样例.py", "args": []}' | python3 -X utf8 tools/run_python.py ``` ## 新增一个工具 1. 在 `tools/` 下写好脚本,从 stdin 读 JSON、往 stdout 打带 `ok` 的 JSON; **先读一遍 [`rules.md`](rules.md)**,那是工具开发的完整规范; 2. 在 `tools.json` 的 `tools` 数组里加一项,`script` 指向该文件 (省略时默认取 `.py`); 3. 本地跑一遍确认,然后提交并**打上新的 tag**,客户端才会同步到。 清单里 `parameters` 的 `type` 支持 `string`、`integer`、`number`、`boolean`、`array`、 `object`,会直接转成模型的 function schema。脚本路径必须位于 `tools/` 目录内, 工具名不能重复,声明的脚本必须存在——否则主程序会拒绝加载整个清单,并沿用上一版。 ## 本地调试 单个脚本可以直接喂 JSON 跑: ```bash echo '{"ip": "1.1.1.1"}' | python3 -X utf8 tools/query_ip.py echo '{"query": "重疾险 等待期", "limit": 3}' | python3 -X utf8 tools/web_search.py ``` 需要用到第三方库的脚本,把 `pydeps` 加进 `PYTHONPATH`: ```bash echo '{"path": "~/报表.xlsx", "limit": 20}' \ | PYTHONPATH=tools/pydeps python3 -X utf8 tools/read_spreadsheet.py ``` 让助手加载本地 `tools/` 而不是远程版本: ```bash XLA_DEV=1 ./dist/XLAs ```