atlas:AI Agent 工具实践指南
2026-10-03
2026-10-06 0
工作中遇到相关需求时,hass-axi值得先读说明,因为它主要用于适用于家庭助理的符合人体工程学的 CLI (AXI) - 实体、传感器、能源统计、历史记录、注册表; TOON 输出。实际做软件开发时,经常会碰到依赖、接口和异常处理往往比主路径更影响采用,所以功能列表并不能代替验证。落地前可以在隔离分支完成一个可回滚的小任务,用安装步骤、接口契约、测试结果和错误信息判断它是否真的省事。整体来看,它适合需要可检查开发流程而非单次演示的工程师拿来做对照测试,最终决定仍应回到真实结果和维护状态。

哈斯阿克西
用于家庭助理的代理 eXperience 接口 (AXI) CLI。
从
ha-axi重命名。 截至 0.7.1,此工具发布为ha-axi。一个不相关的家 Assistant CLI 也以该名称发布,并且两者安装相同的二进制文件,因此这一个 现在是hass-axi。请参阅 从ha-axi移动到 — 旧变量保留 工作,hass-axi setup hooks修复安装的旧名称的挂钩。
家庭助理的 REST API 会很高兴地告诉您灯已关闭,甚至它显示的名称。它 不会告诉你这个名字从何而来,灯在哪个房间,或者那个房间是否来自 来自实体或其背后的设备——并且它无法改变其中任何一个。 实体, 区域和设备注册表只能通过 WebSocket API 访问,因此管理 从脚本安装意味着手动启动 WebSocket 客户端,或单击 UI 相反。
这是 hass-axi 存在的两个作业中的第一个。第二个是拨打服务电话正确
— 在发送前进行检查,并在 Home Assistant 拒绝发送时进行解释,并提供状态代码和否
身体。同样的判断也适用于记录仪所持有的内容:读数及其
history 通过测量发现
以及它们在哪里,并总结了它们显示的数据质量问题
数量。该工具所做的其他一切 - 状态、模板、任意 REST 路径、任意
WebSocket 命令 — 是 管道,这两个需要,并且是赌注
任何地方。
注册表 REST 不公开
一个命令可以重命名一个实体并将其移动到一个房间,通过 WebSocket API,通过以下方式解析该区域:
名称或 ID。如果没有 --write,它会显示它将更改的内容并且不发送任何内容;与它:
$ hass-axi entity update light.example_lamp --name 'Reading Lamp' --area 'Example Room' --write
entity: light.example_lamp
updated[2]: area_id,name
name: Reading Lamp
area: Example Room
area_id: example_room
area_source: entity
答案是生成的注册表行,而不是请求的回显:updated 命名字段
实际上发生了变化,area_source 表示该行中的区域来自哪里。最后那个场
是调用者无法自行解决的问题,因此它很重要,因为:
$ hass-axi entity get sensor.example_bridge_dawn
entity:
entity_id: sensor.example_bridge_dawn
name: Example Bridge Next dawn
area: Example Hall
area_id: example_hall
platform: sun
domain: sensor
device_id: 8465672c0d0394de228e85c53dcc041b
original_name: Next dawn
disabled: false
hidden: false
entity_category: diagnostic
unique_id: 01M0QEH70H9SQQBCX9E2GF8RKM-next_dawn
icon: ""
area_source: device
该实体自己的注册表行包含 area_id: null 和 name: null。家庭助理仍然有位置
它在示例大厅中,因为一个没有自己区域的实体继承了其设备的,并且它
仍然将其显示为示例桥下黎明,因为实体的名称由两个组成
注册表:某人设置的名称完全获胜,否则设备的显示名称将被加入
到该实体自己的一半。单独读取实体行的客户端将该实体报告为
未分配且无名。两者都错了,而且都默默地错了。
hass-axi 在命名实体的每个视图中应用这两个规则,因此过滤器符合
用户看到:
$ hass-axi entity list --area 'Example Hall' --limit 3 --fields entity_id,name,area,platform
count: 3 of 9 matched (23 total)
entities[3]{entity_id,name,area,platform}:
sensor.example_bridge_dawn,Example Bridge Next dawn,Example Hall,sun
binary_sensor.example_bridge_rising,Example Bridge Solar rising,Example Hall,sun
sensor.example_bridge_dusk,Example Bridge Next dusk,Example Hall,sun
help[3]:
Run `hass-axi entity get ` for one entry in full
Run `hass-axi entity list --area 'Example Hall' --limit 9` to see all 9
Run `hass-axi entity update --name "" --area ` to preview a change, and add --write to send it
$ hass-axi entity list --search 'Example Bridge' --limit 2 --fields entity_id,name,original_name
count: 2 of 9 matched (23 total)
entities[2]{entity_id,name,original_name}:
sensor.example_bridge_dawn,Example Bridge Next dawn,Next dawn
binary_sensor.example_bridge_rising,Example Bridge Solar rising,Solar rising
help[3]:
Run `hass-axi entity get ` for one entry in full
Run `hass-axi entity list --search 'Example Bridge' --limit 9` to see all 9
Run `hass-axi entity update --name "" --area ` to preview a change, and add --write to send it
--search 与组合名称匹配,这就是为什么搜索用户读取的名称会找到
实体,即使没有注册表行包含该字符串。 original_name 作为字段保持可用
当你想要实体自己的一半时。
同样的后备使算术保持诚实。 area list 将实体计入其所在区域
确实在,每个区域的计数加上 unassigned_entities 总和就是注册表的大小:
$ hass-axi area list
count: 3 areas
unassigned_entities: 12
areas[3]{area_id,name,entities,devices}:
example_hall,Example Hall,9,1
example_room,Example Room,2,0
example_study,Example Study,0,0
help[3]:
Run `hass-axi entity list --area ` to see what one area holds
Run `hass-axi area update --name ''` to preview a rename, and add --write to send it
Run `hass-axi entity list --area none` to find entities with no area
entity_id 是不稳定的身份,这里没有任何内容鼓励这样对待它:过滤
区域、按名称搜索或列出一台设备提供的 entity list --device —
从不透明设备 id 到其实体的唯一路由,因为该 id 不可搜索,并且
不应该。区域在任何地方接受名称或 id 出现,并且一个不明确的名称是
错误而不是猜测。名称按其键入方式进行比较:大小写和印刷
撇号(’,手机将其写入自己的名称)并不重要,因此 --search "example's"
找到它。与任何内容都不匹配的名称将使用现有的最接近的名称来回答 - did you mean
——而不是主动提出创造错误的内容。 entity get 和 area get 完整显示一个条目; area get 还
报告 area list 默认情况下省略的图标、楼层和别名 — area list --fields area_id,name,floor_id 将楼层添加到列表中。
键入的写入表面为 entity update — --name、--area 和 --icon,每个表面都有一个匹配的
--clear-* 回退到集成提供的内容,加上 --new-id 来重命名
entity_id 本身 — 连同 area create 和 area update 以及 device get 和
它们背后的注册表上有 device update。删除区域是故意不指定类型的
命令;如果您是认真的,hass-axi ws area.delete --write 就在那里,禁用或禁用也是如此。
删除设备。
这些写入中的每一项都是预览,直到 --write。 entity update、area create、
area update 和 device update 读取注册表,解析命名内容,并打印字段
除了请求的值之外,它们还会更改现在存储的值,并且不发送任何内容。添加
--write 发送的正是这一点。对已存储内容的请求会回答 already matches
方式,因为没有什么可发送的。写入可以解决的问题,预览也可以解决:一个区域
不存在的内容会失败,并且 --floor 无楼层答案也会失败,Home Assistant
本身会毫无怨言地存储。
设备通常是名称或区域错误所在的级别,这就是为什么 device update
存在而不是实体上的循环:没有自己区域的实体继承其设备,
一个没有自己名字的实体是根据它的设备来命名的,所以这里的一个更正会移动每个
设备提供的实体并且没有留下任何东西,仍然在说旧话。这需要一个
device_id 或显示的名称,以及 --name/--area 与匹配的 --clear-* 标志,相同
形状 entity update 有。一个不对称性是家庭助理的而不是这个工具的:一个设备
有两个名字,只有name_by_user可写。 --name 设置它,--clear-name 删除它
并回退到集成自己的 name,并且 device get 与
name_source 表示正在显示哪一个。
已检查但未转发的服务呼叫
Home Assistant 将接受无法执行任何操作的服务呼叫,并使用以下内容回答 200
空列表。三种不同的结果到达网络时看起来相同,并且一个客户
转接呼叫并打印回复报告这三项均已成功。
除非您这么说,否则不会发送任何内容。 service call 没有 --write 是预览:它读取
发布服务,解析目标,运行功能预检查并打印请求
将制作、将到达的实体以及检查发现的内容 - 并且不发送任何内容。它失败了,因为
对于不存在的服务或未达到任何目标的目标,该调用将,因此每个
下面的拒绝是带有或不带有标志的相同答案。 --write 发送。同一面旗帜,
相同的含义,门控类型化注册表写入、写入方法 hass-axi api 请求和写入
hass-axi ws 命令:如果没有它,此工具中的任何内容都不会改变 Home Assistant。
当一个无法做到这一点的实体穿过一个区域或到达时,它会被默默地丢弃
一个设备。 hass-axi 读取服务发布的能力并在发送前说明:
$ hass-axi service call cover.set_cover_position --target-area example_room --data position=50 --write
error: "cover.set_cover_position needs a supported_features bitmask containing any of 4, and no entity the target matched has one: cover.example_blind reports 3"
code: UNSUPPORTED_CAPABILITY
help[4]:
Run `hass-axi state list --area example_room --domain cover` to see what is there
Run `hass-axi area list` to see each area's id and how much it holds
Run `hass-axi service get cover.set_cover_position` to see what this service targets
Run the same command with --no-check to send it anyway
发布的要求是一个替代方案列表,并且被解读为一个,因为那是主页
Assistant自己的规则:media_player.volume_up命名为VOLUME_SET和VOLUME_STEP,因为核心
支持不能迈步的球员和可以设置的球员。只有第一个的扬声器没有门控,
因为它有效。该要求也仅针对服务自己的域读取 - reolink.ptz_move
目标 button 实体并命名 camera 功能,并根据相机的位检查按钮
会拒绝每一个电话。 --no-check 存在是因为已发布的需求是集成的
对自己的主张,错误的主张不能成为一堵墙。
与任何内容都不匹配的目标退出 1 并这样说,而不是报告成功的空操作:
$ hass-axi service call light.turn_on --target-area example_study --write
error: "area example_study matched 0 entities light.turn_on can act on, so the call did nothing"
code: NO_ENTITIES_TARGETED
help[3]:
Run `hass-axi state list --area example_study --domain light` to see what is there
Run `hass-axi area list` to see each area's id and how much it holds
Run `hass-axi service get light.turn_on` to see what this service targets
真正无关的调用是成功的,并且表明:
$ hass-axi service call light.turn_off --target-entity light.example_lamp --write
service: light.turn_off
changed: light.turn_off accepted with 0 states changed
target: entity light.example_lamp matched 1 entity; which reported no state change
help[2]:
Run `hass-axi state get ` to see an entity's current state
Run `hass-axi service get light.turn_off` to see what this service targets
Home Assistant 返回实际更改的状态,因此 [] 意味着“一切都已经发生”
正如所要求的”和“根本没有达到任何目标”,但它从未说明是哪一个。 hass-axi 解析目标
当且仅当更改集为空并且给出目标时:没有达到任何目标时退出 1,
达到某个值时,计数为 0,任何实体为 unavailable,因此
跳过了。服务可以到达哪些域是从其发布的 target 中读取的,从未猜测过
它的名字。
拒绝完全没有理由。 Home Assistant 将拒绝的服务呼叫视为空的
400 — 状态行,没有正文 — 因此是未知服务、未声明字段和缺失
所需的一个在电线上是无法区分的。 (两个拒绝更糟糕:一个命名实体
缺乏功能,并且 --response 调用不匹配任何内容,以纯文本 500 形式到达
并道歉。)hass-axi 获取此时的服务模型并从中回答:
$ hass-axi service call light.turn_on --target-entity light.example_lamp --data brightnes=180 --write
error: light.turn_on does not accept field brightnes
code: UNKNOWN_SERVICE_FIELD
help[2]:
fields for light.turn_on: transition, rgb_color, color_temp_kelvin, brightness_pct, brightness_step_pct, effect, rgbw_color, rgbww_color, color_name, hs_color, xy_color, brightness, brightness_step, white, profile, flash
Run `hass-axi service get light.turn_on` for their types and which are required
成功的调用不会支付任何费用:解释只是失败路径。 (预览 在发送任何内容之前命名相同的字段,作为警告而不是拒绝,因为 发布的字段列表是集成对其自身的声明。)该模型是 从未缓存 - 添加或删除的集成会重写它,并且没有任何信号表明何时。
service get 根据安装本身的需求渲染相同的模型,因此它永远不会过时
并且从不描述您没有的集成:
$ hass-axi service get cover.set_cover_position
service: cover.set_cover_position
name: set_cover_position
description: ""
response: none
target: entity domain cover; supported_features matching any of 4
fields[1]{field,required,type,description}:
position,true,number,""
help[2]:
Run `hass-axi service call cover.set_cover_position --target-entity --data position=` to preview the call, and add --write to send it
Run `hass-axi state get ` and compare its supported_features attribute
hass-axi 故意不做的是将模型转换为命令。 Home Assistant
发布足够的元数据来为每个服务生成键入的命令,生成它们意味着
大约 77 个名词和 327 个子命令包装了已经到达所有这些的一个命令 -
每次向一个设备开火,而不询问它是否可以做这件事。使用模型来
验证、解释和恢复需要一个共享依赖项,而这些都不是。 --data key=value
永远覆盖每项服务的每一个领域,没有元数据会过时。
安装
pip install hass-axi
# or run it without installing
uvx hass-axi entity list --area 'Example Room'
从结账处:
scripts/dev-setup.sh # creates .venv and installs this checkout into it
scripts/install-hooks.sh # point core.hooksPath at .githooks
hass-axi 通常作为隔离的用户级工具安装,在 PATH 上有自己的启动器,并且
任何解释器的可编辑安装都会覆盖该启动器并
将其指向结帐处 - 因此删除结帐部分会使读者自己的安装失效。
dev-setup.sh 改为构建 .venv,环境 .github/workflows/ci.yml 已使用,并且
每个开发命令都会用完它:.venv/bin/pytest、.venv/bin/ruff、.venv/bin/hass-axi。
配置
关于任何特定安装的任何内容均未包含在内:基础 URL 和令牌来自 环境,实体表是在运行时获取的。两个变量,没有别的——第三个, 可选的一项使会话 成为只读:
export HA_URL=https://homeassistant.example.com # or HASS_SERVER
export HA_TOKEN= # or HASS_TOKEN
在您的个人资料页面上的 Home Assistant 中的安全 → 长期访问权限下创建令牌 代币。
HA_URL 可以命名几个基本 URLs,以逗号分隔 — 通常首先是本地地址,然后是
远程一秒:
export HA_URL=https://homeassistant.example.com,https://remote.example.net
它们按顺序进行尝试,第一个接受连接(对于 https,是 TLS 握手)
整个运行过程中两种运输方式均使用; hass-axi ping、doctor 和主视图表示
后来就用了一个。只有传输失败才会转移到下一个 URL。 HTTP 答案 — 401、
404、500 — 是相同的安装应答,尝试其他方式只会重复
拒绝。使用 URL 不会探测到任何内容,也不会发生任何变化。
故意没有 --token 标志,也没有凭证文件。 命令行上的令牌泄漏
进入 shell 历史记录和进程表;文件中的令牌泄漏到提交中。环境是
唯一的渠道。任何令牌形状的内容在到达 stdout 或 stderr 之前都会被编辑,因此
凭证也无法通过错误消息或调试行转义。还有两个不变量成立
URL 周围:更改方案或主机的重定向被 拒绝 而不是遵循,因为
urllib 会将 Authorization 标头复制到其上,并且 HA_URL 中的任何 user:password@ 都是
在打印基本 URL 之前,将其剥离并注册为秘密。裸主机默认为
https://,从来没有http://。
立即检查两种传输:
$ hass-axi doctor
healthy: true
checks[4]{check,status,detail}:
read_only,ok,"HASS_AXI_READ_ONLY is not set: writes are allowed"
environment,ok,HA_URL and HA_TOKEN are set
rest,ok,API running. (version 2026.8.3)
websocket,ok,"authenticated, 23 registry entries in 3 areas"
version: 2026.8.3
当任何分支失败时,doctor 以非零值退出,因此它可以用作脚本或钩子中的门。
阅读材料、历史和能量,总结及其注意事项
关于房子的问题通常是关于读数的问题——厨房有多少电量 画画,一夜之间消耗了多少能量,大厅变得多么寒冷。从原始状态回答它 意味着连接两个传输并手动对桶进行求和,如果出错,就会产生 看似合理但不真实的数字。
hass-axi sensor list --device-class power --area 'Example Room'
hass-axi statistics get sensor.example_legacy_meter --start 7d
hass-axi history get binary_sensor.example_doorway --start 24h
hass-axi logbook get --entity light.example_lamp --start 2h
sensor list 通过测量内容和位置查找读数 — --device-class,
--unit、--area、--search — 并回答其值和单位; --fields 添加其面积,
阅读的年龄和其他。该地区来了
来自注册表,从设备继承,除非实体设置自己的,所以每次运行都会读取
两种运输方式。设置诊断和配置传感器(电池电压、信号强度)
除了注册表自己的 entity_category 以外,绝不是名称模式; --all 将它们放回原处。
不匹配任何内容的过滤器会列出本应匹配的设备类别和单元。statistics get 读取记录器的长期统计数据并以以下形式回答
统计有。 仪表(总统计:能源、水、天然气)报告其总和
窗户;读数(平均统计数据:功率、温度)报告其平均值及其最小值
和最大值;轴承报告圆形平均值。那种来自记录者自己的元数据,
永远不会来自设备类或名称 - 请求错误的类型并不是上游的错误,
水桶回来时是空的。--start 7d 在每日存储桶中
是八个桶,每月的桶是整个月——所以将桶相加就是计算之前的时间
--start。因此,总计、平均值、最小值和最大值是从开始的每小时行中读取的
窗户里面; --period 仅决定桶的计数方式以及警告的位置
看看。过去 400 天的数据桶会在到达时进行汇总,并附上警告说明其涵盖的内容。caveats 行在它旁边这样写着:桶丢失了
窗户,一个向后移动的仪表,一个大约每天重置一次的仪表(所以它的实时状态
是自重置后的读数,而不是运行总数),一个桶可容纳一半以上
一切,是中位数的二十倍——一个传感器,简单地将一生的数据报告为
每天,或者是真正沉重的一天;桶不能说是哪个。该数字始终是录音机所保存的数字。
每次检查都是关于桶形状的规则,而不是关于任何特定的集成。history get 总结了一个时间线 — 读数的范围,或者其他任何内容的长度
在每个状态中花费的时间——计算窗口打开时实体已经处于的状态。logbook get 说明 Home Assistant 记录时导致每次变化的原因,折叠
将六个 context_* 组合到一个 cause 中:按名称命名的自动化、服务、实体。每个窗口以--start和--end为年龄(30m、24h、7d)或ISO 8601时间;一次
没有偏移量的是UTC,结束默认为现在。结尾总是发送,因为首页
助理的历史记录和日志视图会结束一个在启动后一天就没有任何窗口的窗口 - 因此
长达一周的请求,没有它默默地回答一天。
主视图(hass-axi,无参数)还列出了需要注意的内容:实体不
报告、电池电量低于 20%,以及集成的传感器在 24 小时内没有报告任何内容。
hass-axi ping 乘以一个经过身份验证的请求,以获得比 doctor 更便宜的活性门。
只读会话
第三个变量使会话无法更改任何内容:
export HASS_AXI_READ_ONLY=1
每次写入都会被拒绝在发送之前,并且拒绝并不关心哪个路由 写了。键入的命令:
$ hass-axi entity update light.example_lamp --name 'Something Else' --write
error: "`hass-axi entity update` is a write, and this session is read-only"
code: READ_ONLY
class: usage
help[3]:
This session is read-only; the command was refused before anything changed
Reads still work, e.g. `hass-axi state list`, `hass-axi entity list`, `hass-axi area list`
Unset HASS_AXI_READ_ONLY (or HA_AXI_READ_ONLY, its deprecated spelling) to allow writes; it is a switch, so any non-empty value enables it
原始的 WebSocket 逃生舱口,这是注册表实际写入的位置:
$ hass-axi ws --raw config/area_registry/create --param name='Bypass Attempt' --write
error: "`hass-axi ws` is a write, and this session is read-only"
code: READ_ONLY
以及原始的 REST 逃生舱口:
$ hass-axi api POST /services/light/turn_on --field entity_id=light.example_lamp --write
error: "`hass-axi api` is a write, and this session is read-only"
code: READ_ONLY
最后两行都打印与第一行相同的三行 help ;仅输出头部
在此转载。三者全部退出2,之后区域注册表不变:拒绝
在打开任一传输之前,甚至在读取令牌之前就到达了。没有
--write 这三个都是预览版,不发送任何内容,因此仍然允许:
只读会话可以看到请求是什么,并被告知写入将被拒绝。
它有四件事是经过深思熟虑的,前两点就是它值得拥有的原因。
service call 通过看起来像任何其他 POST 的表面进行变异,并且 WebSocket 命令集
根本不遵循 REST 约定。无人分类的命令被拒绝,因此失败
遗忘模式是一种拒绝,而不是一种不加防范的突变——并且测试列举了这两种情况
命令表并在第一个没有任何声明的情况下失败。0 和 false。
解析该值是当操作员认为防护装置处于打开状态时防护装置关闭的原因;取消设置
该变量是允许写入的唯一方法。--help 和命令表中,
因为无法看到所需命令的特工无法弄清楚为什么其计划是不可能的。执行机构位于三个点:调度员、RestClient.request 和 WsClient.send_command
读取未受影响,包括 template render — 呈现服务器端并进行更改的 POST
没有什么。 hass-axi doctor 将模式报告为第一个检查,并且打印无参数视图
设置后为 read_only: on,因此会话在计划任何事情之前就知道它是什么。
它没有涵盖什么。 hass-axi api 提供了一条直接通往安装的不透明路径,因此有
该方法是唯一可用的事实,并且规则错误关闭:GET、HEAD 和 OPTIONS 通过,
其他一切都被拒绝,包括 POST 碰巧没有改变任何东西。任何家庭
在 GET 上突变的助理端点将通过该检查 - 没有一个能够通过,并且相同
假设是每个只读 HTTP 代理所做的假设,但它是一个假设而不是一个假设
保证。 hass-axi ws --raw 通过它命名的 API 类型来判断:一个已经声明的命令
读取通过时命名,并且拒绝未声明的类型。
它达到的其他一切
这些都是任何家庭助理客户的赌注。他们在这里是因为上面的两个部分
需要它们,因为拥有该工具的特工不应该放弃它——而不是因为它们是
hass-axi 的用途是什么。
state list / state get — REST 的运行时视图:实体现在正在做什么以及
其属性,包括 --domain、--state、--search、--fields 和 --limit,以及 --stale — 至少在那么长时间内未报告的任何域的实体,家庭的通用形式
视图的陈旧传感器计数。service list / service get — 了解可以要求安装执行哪些操作。 服务 list --domain --fields service,response,target 添加每个服务是否用
有效负载以及是否需要目标。template render — 在服务器端渲染 Jinja 模板,来自 --template、--template-file
或标准输入。它可以看到 Home Assistant 知道的每个实体。api — 任何经过身份验证的 REST 路径,包含 --field、--body 和 --query。逃生舱口
对于没有键入命令的任何内容。预览除 GET 和 HEAD 之外的任何内容,直到 --write
已通过。长响应被缩短——每个列表到前 25 个项目,每个字符串到一个
预览 - 报告完整尺寸,--full 打印所有内容。ws — 任何 WebSocket 命令。 ws --list 打印声明的名称以及每个是否读取
或写入,ws --param k=v 发送读取并预览写入,直到 --write 通过,
并且 ws --raw 达到了一个还没有声明名称的类型——这算作一个
写。结果像api一样被缩短,其中--full为一的整体。添加一个
声明的命令是 src/hass_axi/ws.py 中 REGISTRY 中的一项;身份验证握手,id
相关性和错误翻译是共享的。ping — 一个经过身份验证的请求,定时:比 doctor 便宜的活性门。doctor — 两种传输的环境和连接检查。setup — 安装、检查或删除此计算机上的代理集成(如下)。context — 会话挂钩打印的环境文档。读取环境、命令
仅表和本地会话记录,因此它什么也没有达到,并且什么也没有退出 0
配置(如下)。整个命令面和传输层的每一半运行在:
| 命令 | 交通 | 它的作用 |
|---|---|---|
| `hass-axi 实体列表\ | 得到\ | 更新` | WebSocket | 实体注册表:名称、区域、平台、实体 ID |
| `hass-axi 区域列表\ | 得到\ | 创建\ | 更新` | WebSocket | 地区登记处 |
| `hass-axi 设备列表\ | 得到\ | 更新` | WebSocket | 设备注册表:实体继承的设备名称和区域 |
| `hass-axi 服务列表\ | 得到\ | 打电话` | REST | 发现服务、阅读字段、预览呼叫并发送 |
| `hass-axi 状态列表\ | 得到` | REST | 目前的实体状态和属性 |
hass-axi sensor list |
两者 | 按设备类别、单位、区域或名称划分的传感器,以及值和单位 |
hass-axi history get |
REST | 州时间表,每个州的每个读数的范围或时间 |
hass-axi logbook get |
REST | 日志条目及其原因 |
| `hass-axi 统计列表\ | 得到` | WebSocket | 记录仪统计:一米总计、读数平均值、最小值和最大值 |
hass-axi template render |
REST | 在服务器端渲染 Jinja 模板 |
hass-axi ws |
WebSocket | 任何 WebSocket 命令,声明的或原始的 |
hass-axi api |
REST | 任何经过身份验证的 REST 路径 |
hass-axi ping |
REST | 一个经过身份验证的请求,定时 |
hass-axi doctor |
两者 | 环境和连接检查 |
hass-axi setup |
— | 安装、检查或删除代理集成 |
hass-axi context |
— | 会话挂钩打印的环境文档 |
任何命令上的 --help 都是权威参考:它列出了每个子命令的每个标志,其中
默认值和两个或三个工作示例。这里没有任何东西可以重复它,并且它无需任何操作即可工作
配置存在。
state 和 entity 是不同的视图,两者都需要
state 是 REST 的运行时视图 — 实体正在做什么以及它显示的名称。 entity
是 WebSocket 的注册表视图 — 实体的稳定身份:用户设置的名称、它所在的区域
属于提供它的集成。所有这些都只存在于注册表中;状态直播
仅超过 REST。
--area 在 state list、entity list 和 device list 上的工作方式相同。在 state list 上花费
一次额外的注册表往返,因为区域位于 WebSocket 之上,并且仅当
标志已通过:
$ hass-axi state list --area 'Example Room'
count: 2 of 2 matched (23 total)
states[2]{entity_id,name,state}:
light.example_lamp,Reading Lamp,off
cover.example_blind,Example Blind,closed
help[2]:
Run `hass-axi state get ` for one entity's full attributes
Run `hass-axi state list --domain light` to narrow by domain
该标志存在是因为在 entity list 上学习到 --area 的代理将在
state list。过滤器与将注册表列导入运行时视图不同,后者
这就是为什么 area 仍然不是 state list --fields 选择的原因。
库: hass_axi.toolkit
此工具应用的规则可由 CLI 以外的程序导入。 hass_axi.toolkit 是
支持的库表面:纯值输入、纯值输出以及失败的查找或答案
错误形状报告为数据,而不是作为 CLI 错误引发。其中没有任何内容导入
命令模块、参数解析器、输出边界或传输,这取决于
单独的标准库,它生成的任何文本都不会命名要运行的命令。它是新的,并且可能会增长。
from hass_axi.toolkit import names, recorder, shapes
found = names.resolve(
"example's den", areas, ident=lambda a: a["area_id"], name=lambda a: a["name"]
)
found.match # the one area that was named, or None
found.ties # every area sharing the folded name, when more than one does
found.near # the nearest areas, when nothing matched
shapes.health_fault(answer) # None, or why this is not Home Assistant's API root
recorder.summarize(meta, buckets, start, "day", end=end, hourly=hourly_rows)
names — fold 比较人们键入名称的方式:大小写、印刷引号、
破折号、省略号、重音符号和间距并不重要。 resolve 通过标识符查找条目并
然后通过折叠的名字,报告平局而不是打破它:两个折叠相似的名字出现
作为候选人回来,而不是作为选秀权。 nearest 返回未遂事件。shapes — 解码后的答案是否是家庭助理的 API 给出的(health_fault,
shape_fault),正文是否为文本(is_text),值是否可以是实体id。recorder — 来自其元数据的统计类型,内部的总计、平均值、最小值和最大值
一个窗口(summarize),以及其存储桶支持的警告:丢失存储桶、丢失的仪表
向后或重置,一个水桶使其他水桶相形见绌,水桶伸到窗外。CLI自己的命令是建立在它之上的,所以两者不能不一致。
输出格式
默认情况下,标准输出上的结构化 TOON 大约便宜 40% 比等价的 JSON 的代币:
$ hass-axi state list --domain light --domain cover
count: 2 of 2 matched (23 total)
states[2]{entity_id,name,state}:
light.example_lamp,Reading Lamp,off
cover.example_blind,Example Blind,closed
help[1]:
Run `hass-axi state get ` for one entity's full attributes
--human 为人员呈现对齐的表格。--json 发出原始 JSON。-v、-V 和 --version 分别打印裸版本 - 数字而不是其他内容 - 并退出 0,
每种输出模式。在加载工具的其余部分之前,会回答裸标志,因此请探测它
成本与启动 Python 的成本相当。--debug),代理不会读取这些信息。0 成功 — 包括幂等无操作 — 1 错误、2 使用错误。只读
拒绝是2:在不触及安装的情况下得出结论,并且没有争论
相同的命令会改变它。 401 是 1,因为只有服务器可以说出它。$ hass-axi state list --domian light
error: unknown flag --domian for `state list`
code: UNKNOWN_FLAG
help[2]:
valid flags for `state list`: --area, --domain, --state, --search, --stale, --limit, --fields (--help always allowed)
Run `hass-axi state --help` for the full reference
一个记录的偏差:help[N]: 块每行呈现一个建议,而不是作为
分隔符连接的 TOON 数组。建议是通常包含逗号的命令行,这
是 AXI 标准和同级 AXI CLIs 使用的形状。每个数据结构都是严格的TOON,
而“strict”是一个测试结果而不是一个声明:所有179个规范本身的一致性
套件中的固定装置均已出售,每一项都必须通过。
错误代码
每个故障都带有一个 code 来命名出错的一件事,以及一个 class 来命名出错的类型
事情确实如此。类是要打开的内容:它是重试、更改
参数,并获取不同的令牌。
$ hass-axi state list
error: "could not reach Home Assistant: [Errno 111] Connection refused"
code: UNREACHABLE
class: transport
help[2]:
Check HA_URL points at a reachable Home Assistant instance
Run `hass-axi doctor` to test the connection
| 类 | 发生了什么事 | 接下来做什么 |
|---|---|---|
usage |
调用错误;没有发送任何内容 | 更改命令行 |
config |
该机器未设置为与 Home Assistant 对话 | 改变环境 |
transport |
未联系到家庭助理,或未提供服务 | 不做任何更改并重试 |
auth |
凭证被拒绝 | 创建一个新的长期访问令牌 |
permission |
证书被接受;呼叫者不被允许 | 使用不同的帐户,或解除实例上的阻止 |
not_found |
指定的主题无法解析为此处存在的一件事 | 查一下然后再问 |
refused |
主题存在并且此请求被拒绝 | 改变参数 |
internal |
hass-axi 中的一个错误 | 举报 |
其中三个是过去无法区分的,它们需要相反的反应。
无法从命令中区分拒绝令牌的代理此版本不会重试
永远的令牌;无法区分任一者和无法访问的主机的用户报告称,Home Assistant 正在
当它不是时就下来。 permission 是第四个:家庭助理回答“您的凭证没问题,您
两种传输方式均不允许”——超过 REST 的禁止地址,该帐户不是
WebSocket 的管理员 — 而新的令牌则不能解决这两个问题。
整个词汇表是封闭的:
usage — UNKNOWN_COMMAND, UNKNOWN_SUBCOMMAND, MISSING_SUBCOMMAND, UNKNOWN_FLAG,
MISSING_VALUE, MISSING_ARGUMENT, UNEXPECTED_ARGUMENT, CONFLICTING_FLAGS, UNKNOWN_FIELD,
BAD_LIMIT, BAD_TIMEOUT, BAD_TIME, BAD_WINDOW, BAD_PERIOD, BAD_KIND, BAD_JSON,
BAD_ICON,
BAD_PAIR, BAD_SERVICE, MISSING_PATH, MISSING_NAME, MISSING_TEMPLATE, MISSING_COMMAND, MISSING_PARAM, NO_CHANGES, NO_SUCH_COMMAND,
UNREADABLE, UNREADABLE_FILE, UNWRITABLE, READ_ONLYconfig — NOT_CONFIGURED, BAD_URL, BAD_TOKEN, MISSING_DEPENDENCY, REDIRECT_REFUSED,
NOT_HOME_ASSISTANTtransport — UNREACHABLE, TIMEOUT, TLS_ERROR, CONNECTION_DROPPED, UNAVAILABLE,
WS_HANDSHAKE, WS_CLOSED, WS_PROTOCOLauth — UNAUTHORIZEDpermission — FORBIDDENnot_found — NOT_FOUND, NO_SUCH_ENTITY, NO_SUCH_AREA, AMBIGUOUS_AREA, NO_SUCH_FLOOR,
AMBIGUOUS_FLOOR, NO_SUCH_DEVICE,
AMBIGUOUS_DEVICE, NO_SUCH_DOMAIN, NO_SUCH_SERVICE, NO_SUCH_STATISTIC,
NO_ENTITIES_TARGETED, NO_SUCH_WS_COMMAND, NO_WEBSOCKET_APIrefused — BAD_REQUEST, METHOD_NOT_ALLOWED, SERVER_ERROR, API_ERROR, INVALID_FORMAT,
NOT_ALLOWED, NOT_SUPPORTED, HOME_ASSISTANT_ERROR, SERVICE_VALIDATION_ERROR,
TEMPLATE_ERROR, UNKNOWN_SERVICE_FIELD, MISSING_SERVICE_FIELD, MISSING_TARGET,
UNSUPPORTED_CAPABILITY,
RESPONSE_REQUIRED, RESPONSE_NOT_SUPPORTEDinternal — INTERNAL_ERROR, ID_REUSE整个表都有两个属性,并且两个属性都是由套件强制执行的,而不是承诺的
在这里。 词汇表是封闭的:代码总是在提出时写出,永远不会
根据状态号或服务器发送的字符串构建,因此上面的集合是整个集合和一个
呼叫者可以彻底切换它。 分类发生在运输边界 —
RestClient.request 和 WsClient.send_command — 并且从未在命令正文中,因此添加了一个命令
无论其作者是否知道分类法的存在,后来都会被分类。 tests/test_error_codes.py
针对两个传输上的每个故障运行每个子命令,并在第一个无法传输的故障上失败
说出它遇到了哪个班级。
覆盖范围是有限的,值得直白地说:课程与家庭助理所提供的一样好 电线。它没有任何理由地提出拒绝——有几个是——按其分类 状态仅此而已。
代理整合
有两种方法可以使其被发现。 你只需要一个。
会话挂钩 — 每个会话中的环境上下文,对于支持挂钩的代理:
hass-axi setup hooks
为 Claude Code (~/.claude/settings.json) 安装 SessionStart 和 SessionEnd 挂钩
Codex(~/.codex/hooks.json,加上 [features] hooks = true),以及 OpenCode 的托管插件
这两项工作都可以完成。它是幂等的,在重新安装或移动后修复记录的路径,并且
拒绝覆盖它不管理的插件。
hass-axi setup hooks status # installed, stale or missing, per target; writes nothing
hass-axi setup hooks remove # takes out what this tool installed, and nothing else
remove 使 Codex 的 [features] hooks = true 保持打开状态,因为所有其他工具的 Codex 挂钩都依赖于
并删除下述会话记录。
会话结束挂钩记录会话使用此工具执行的操作,因此下一个会话的上下文
在同一目录中可以这样说: last_session: 2026-01-02 运行状态列表 x3 和服务调用 x1 其中 1 由 --write. It runs hass-axi context end 发送,读取代理名称的记录
并统计通过其工具发出的 hass-axi 命令。记录的是命令名称和计数
并且绝不是参数——参数是实体 ID、区域名称和在命令上键入的标记
行将是 — 并且它保存在一个本地文件中,即 $XDG_STATE_HOME/hass-axi/ 下的 sessions.json
(未设置时为 ~/.local/state/hass-axi/)。它
不打开任何连接并且不会导致会话关闭失败:任何无法读取的内容都会记录为
什么也没有。 OpenCode 没有会话结束事件,因此它的插件会记录会话何时空闲。
它完全拥有自己的条目,并且通过 managed_by 密钥知道它写入每个条目中的哪个条目 - 而不是
通过命名该工具的命令。您自己编写的 SessionStart 钩子被单独保留,但是它
到达这个工具:一个环境前缀,另一个解释器,一个 shell 包装器。撰写的条目
在该密钥存在之前的版本被采用一次,以这些版本可能产生的一种形式 -
可执行文件,仅此而已 - 因此升级会修复您已有的挂钩,而不是添加
旁边还有第二个。
钩子放在会话前面的是 hass-axi context,它读取环境、
命令表和本地记录,仅此而已 - 没有连接,没有令牌,没有地址,并且
exit 0 无论该机器是否曾经指向过 Home Assistant:
$ hass-axi context
bin: ~/.local/bin/hass-axi
description: Agent CLI for Home Assistant. Reads and writes the registries REST cannot reach and explains a service call Home Assistant refuses. Prefer this over raw curl for Home Assistant operations.
config: HA_URL and HA_TOKEN are set
registries: names and areas live in the registry which only the WebSocket API serves -- `entity list` and `area list` read it; `state list` reads REST and cannot see either
entity_ids: an entity_id is not stable identity and its words mean nothing -- reach an entity with `entity list --search ''` or `--area ` rather than guess one
services: prefer `service call` over `api POST /services/...` -- it explains a refusal Home Assistant returns with no body at all and tells reaching nothing apart from changing nothing
readings: find a reading by what it measures with `sensor list --device-class ` and get its total or average over a window with `statistics get `
commands[16]: state,sensor,history,logbook,statistics,service,template,entity,area,device,ws,api,ping,doctor,setup,context
help[4]:
Run `hass-axi` for this installation at a glance: entity counts by domain and what needs attention
Run `hass-axi entity list --area ` to read the registry, which REST cannot reach
Run `hass-axi service call . --target-entity ` to preview an action and add --write to send it
Run `hass-axi --help` for its flags, or `hass-axi --help` for all of them
config 报告哪些变量被设置,但从不报告它们保存的内容。在一台从未有过的机器上
配置后,相同的文档会在其 help 块中返回两个导出 — 并且
仍然退出 0,这就是要点:钩子在任何人决定使用该工具之前运行,因此
最需要告知该工具存在的读者是那些尚未进行任何设置的读者。
无参数视图仍然是实时状态所在的位置,一旦配置完成就值得运行 到位:
$ hass-axi
bin: ~/.local/bin/hass-axi
description: Agent CLI for Home Assistant. Reads and writes the registries REST cannot reach and explains a service call Home Assistant refuses. Prefer this over raw curl for Home Assistant operations.
url: https://homeassistant.example.com
entities: 21 in 10 domains
unavailable: 0
unknown: 7
domains[8]{domain,entities}:
sensor,12
conversation,1
event,1
input_boolean,1
light,1
person,1
sun,1
todo,1
not_reporting[4]{entity_id,name,state,for}:
sensor.example_next_backup,Example Next Backup,unknown,21s
sensor.example_last_backup,Example Last Backup,unknown,21s
sensor.example_backup_attempt,Example Backup Attempt,unknown,21s
person.example_person,Example Person,unknown,21s
low_battery[1]{entity_id,name,battery}:
sensor.example_remote_battery,Example Remote Battery,14%
help[6]:
Run `hass-axi state list --domain ` to list entity states
Run `hass-axi state list` for all 21 entities across 10 domains
Run `hass-axi entity list --area ` to read the registry, which REST cannot reach
Run `hass-axi area list` to see the areas defined here
Run `hass-axi sensor list --device-class ` to find a reading by what it measures
Run `hass-axi service call . --target-entity ` to preview an action and add --write to send it
它打开一个连接并打印安装地址,这就是为什么它不是一个钩子
运行:作为环境上下文,它将在每次会话开始时进行往返并将地址放入
代理的上下文。在未进行任何配置或安装没有响应的情况下,
仍然退出 0:它打印 live_state: not available、code 和 class 中的内容
方式、命令名称和设置行。 hass-axi ping 和 hass-axi doctor 是命令
其退出代码报告安装是否可访问。的
url 行在此处读取为文档占位符,因为此文件中的每个示例都已运行
反对一个一次性的家庭助理,这就是读者自己的价值的一行。这个
块已重新运行以针对新块进行重命名,因此其计数是其自己的计数而不是
安装上面的注册表示例进行了描述。关注列表——not_reporting,
low_battery,以及陈旧的传感器(如果有的话)——仅当它们里面有东西时才会出现。
unavailable 和 unknown 被分开计算,因为它们是不同的事实: unknown 表示
可达且尚未报告,这是两者中最常见的,并将它们总结为
一个人的名字会与 state list --state unavailable 完全矛盾。
代理技能 — 按需加载,无每会话令牌成本,适用于任何支持的代理 技能:
npx skills add dmealing/hass-axi --skill hass-axi
skills/hass-axi/SKILL.md是由hass-axi setup skill从CLI自己的命令表生成的,并且
scripts/ci-local.sh 运行 hass-axi setup skill --check,因此它永远不会偏离它记录的命令。
该存储库是公共的,并且保持通用
该工具与家庭自动化安装进行对话,因此重要的故障不是错误 - 而是 描述或授予访问某人房屋的提交。这是由扫描仪强制执行的,而不是 通过约定:
scripts/leakcheck.py # scan every tracked file
scripts/leakcheck.py --staged # scan what a commit would actually record
scripts/leakcheck.py --commit-msg # scan a commit message
scripts/leakcheck.py --pull-request # scan a pull request's title and body
scripts/leakcheck.py --rules # list the rules and what each one catches
scripts/leakcheck.py --demo # self-test: prove every rule still fires
它捕获了什么。 运行 --rules 获取实时列表;今天是 JWTs(多么一个家庭助理令牌
看起来像),RFC1918 和 CGNAT 地址,IPv4/IPv6 链路本地和唯一本地地址,.local
/.lan/.localdomain 主机名、*.ui.nabu.casa 远程访问主机名、地理坐标
(/api/config 返回房屋的)、MAC 和 Zigbee IEEE 地址、绝对主目录、
保留文档域之外的电子邮件以及文字承载凭证。
每个文件都会扫描两次:一次每行,一次压缩,包含空格、引号、
反斜杠和 + 从整个文件中删除。跨行分割或组装的凭证
连接对于线路传递来说是不可见的,而将令牌拆分到片段正是如何实现的
隐藏——有意或无意。压缩传递仅运行令牌规则,因为加入任意
线路可以将不相关的数字融合成一个可信的地址,并且喊“狼来了”的守卫会被绕过。
它没有捕捉到什么, 明确说明,这样覆盖范围就不会被误认为是: 通用公共主机名或 IP 恰好是某人的实例,这两者都不是秘密 JWT- 形状或不记名前缀,二进制或图像内的任何内容,任何形状没有规则 描述。扫描仪缩小了泄漏可能发生的范围;它不会使审查变得不必要。
必须合法保留这些形状之一的行带有 leakcheck: allow=。的
豁免是每条规则的目的 - 毯子标记将关闭该行上的每条规则,
其中包括一个没有人想到的,这就是实时凭证隐藏在压制的凭证后面的方式
棉绒。
它在四个地方运行:
scripts/install-hooks.sh # points core.hooksPath at .githooks
.githooks/pre-commit 本地阻止提交,包括 a 中的第一个提交
存储库,没有 HEAD 可供比较。.githooks/commit-msg 扫描消息,该消息是与文件内容分开的通道
就像公开的一样。--demo - 证明扫描仪仍然检测到它声称的内容 - 然后扫描
整棵树。该演示自己的输出也已发布,因此它报告的结果没有
价值观。绕过本地钩子只会延迟失败。pytest 标头带有
rootdir: 线保存绝对路径。当无法读取拉取请求时,检查失败
而不是报告一个干净的它不能支持的,它报告一个领域,线路和规则
匹配 - 加上结果通过读取所写文本时的偏移量 - 不打印
匹配:CI 日志比其来源页面更公开。对于同样的
拉取请求无法携带 allow= 标记的原因 - 在提交标记的文件中并且
经过审查,在主体中,它是一个关闭开关,任何人都可以在每次检查运行后添加。检查并扫描提交消息。 release-please 构建变更日志和版本
来自提交消息的碰撞,并且当它的解析器无法读取它在调试级别上所说的消息时,会删除
提交并 exits 0 — 从未发布的合并修复,并在其上运行绿色版本。所以
.githooks/commit-msg 还运行 scripts/commitcheck.py,发布工作流程会重新检查每个
自最后一个标签以来提交。丰富的提交主体是这段历史的重点,但这里什么都没有
限制他们;需要知道的一个形状是身体线条不能以“直线”“开始”
进入未闭合或嵌套的括号 - 行开头的“ Decimal(repr(v)) ”被拒绝,并且
同一短语再多加一个字就可以了。该存储库从未丢失过版本,
tests/fixtures/commit-messages/ 记录了有多窄。运行 scripts/commitcheck.py --rules
语法规则及其引用。
装置、测试、文档和示例始终使用发明的标识符:
light.example_lamp,区域Example Room,https://homeassistant.example.com。测试夹具搭建
凭证在运行时形成而不是嵌入文字,因此证明扫描器的套件
作品本身不会绊倒它。
测试
scripts/dev-setup.sh # creates .venv; see Install for why it is not a bare install
.venv/bin/pytest
.venv/bin/ruff check . && .venv/bin/ruff format --check .
无需实时安装,也不需要实时令牌,这就是重点。 该套件运行于
环回上的真实本地服务器:REST 的 http.server,以及 websockets 的真实 websockets 服务器
执行与 auth_required / auth / auth_ok 握手 Home Assistant 相同的操作。一秒钟
套件确实与真正的家庭助理交谈,并且可以选择加入:请参阅下面的“实时套件”。
测试涵盖:
toon-format/spec 并在每个 pytest 上运行。案例
count 也被断言,因此停止收集的固定装置使套件失败,而不是
悄悄降低分数;--help(且从未被盗)
来自标志值),以及 --help 对于每个命令,无需任何配置;tests/test_exit_codes.py):退出 2 是一个使用错误,并且是
无需安装即可决定,因此每个静态故障都运行可达、不可达和
未配置,并且在所有三个中都必须有相同的错误 - 扫过每个取值标志
调度表中的每个子命令,因此会保留稍后添加的标志;tests/test_toon_roundtrip.py):
规范的装置,以及每个命令的 TOON 与其自己的 --json,通过
toon-format。没有任何东西可以将工具与其本身进行比较。tests/test_properties.py):任何 JSON 值往返,以及
任何参数向量以退出 0、1 或 2 结尾,并带有类为 usage 的可解析文档
恰好当出口为 2 时;tests/test_snapshots.py):--help 对于每个
命令、每种错误文档之一以及 context 文档。该工具写入的文本
仅来自其自己的声明——绝不是实时数据;tests/test_registry_writes.py):一个区域、一个楼层、一个
实体和设备针对拒绝的替身创建、重命名、碰撞、移动和删除
Home Assistant 自己的处理程序的方式 — 重复的名称、另一个域的 ID、删除
那里不存在什么——并且每次键入的写入都作为预览,不发送任何内容;git commit 端到端;只有现场安装才能确认 - 真正的家庭助理接受确切的请求
此处构建的机构、return_response 行为、非常大的注册表在实践中的行为方式,以及
已发布的 supported_features 要求是否与强制执行的要求一致 - 是
下面是Live Suite。双打执行书面协议并执行
其中已知的部分: REST 双拒绝嵌套服务调用 target 回家的方式
助手这样做,拒绝具有相同空 400 且没有主体的未知服务,并丢弃一个
unavailable 或同样沉默的无能实体。所以他们根据该客户验证
规范而不是针对特定的服务器构建。
双精度数无法发明的是普通数据的形状。他们的固定装置带有
真实安装的发行版 — 完全由其设备命名的条目、按其名称命名的条目
只有他们自己的一半,每个设置的 has_entity_name,一个根本没有状态的禁用条目,一个
unknown 状态以及 unavailable 状态、不发表散文的服务和领域 — 以及
tests/test_double_fidelity.py 断言这些形状中的每一个仍然存在。他们每个人都是
缺席一次,每次缺席都会造成绿色套件无法看到的缺陷。
现场套房
tests/live/ 针对真实的家庭助理运行此构建,并将每个答案与
该工具未生成的内容:由线束本身读取的原始 REST 和 WebSocket APIs
独立的TOON解码器,以及自带的录音机行。 选择加入两次 — pyproject.toml
取消选择 live 标记,因此 pytest、scripts/ci-local.sh 和门永远不会收集它,并且
如果没有 HASS_AXI_LIVE=1,其中的每个测试都会跳过 - 并且它是手动运行的:
scripts/live-test.sh --house # reads, previews and loopback faults
scripts/live-test.sh --house-writes # the same, plus writes that undo themselves
scripts/live-test.sh --lab # rejected credentials and destructive writes, in a container
scripts/live-test.sh --house --env-file # HA_URL and HA_TOKEN from a file, never printed
| 层 | 什么运行 | 哪里 |
|---|---|---|
| 一个 | 读取、扫描每个域和区域并对照原始 API 进行检查 | 安装在HA_URL |
| 乙 | 每个写入形状的预览,以及之前和之后的注册表指纹 | 一样的 |
| C | 自行反转并读回的写入:创建和取消通知、设置为自身的存储值、创建和删除一个暂存区域 | 相同,仅与--house-writes |
| D | 故障 — 关闭的端口、错误的证书、重定向、来自非 Home Assistant 的 200 — 来自带有合成令牌的环回存根 | 这台机器 |
| 乙 | 凭证被拒绝,实体、区域、楼层和设备确实被重命名、移动和删除 | 一次性容器,随后取出 |
三个规则可以保证指向房屋的安全。 目标是在运行时根据形状选择的 — “一盏灯 是开还是关”、“其区域来自其设备的实体”——因此该套件没有实体 ID, 区域名称或地址以及安装缺少的形状会跳过而不是失败。 层级中没有任何内容 A 到 D 向安装发送无效凭据,调用真实设备上的服务,或者 重命名或移动任何真实的东西;被拒绝的凭据会发出通知并可以禁止地址 在真实服务器上,因此这些案例仅在容器中运行。并且令牌永远不会保留:它是 从每个结果中清除,打印它或其任何片段的调用失败,并且日志 记录命令、退出和计时,但不记录输出,因为真实安装的输出是 该安装的数据。
实验室层需要 docker。它从固定图像创建一个容器
(HASS_AXI_LAB_IMAGE 选择另一个版本),载入它,铸造一个仅存在于
该过程,并删除容器,无论测试是否通过。套房里没有什么能让
LLM 调用或使用付费 API,并且没有安排任何内容:没有 cron 条目、计时器或工作流程
按设计运行它。
持续集成
GitHub 操作在此存储库上被禁用,因此这些工作流程今天都不会运行。办理入住手续
ci.yml 通过 scripts/ci-local.sh 在本地运行,无错误门在每次更改时运行
(.no-mistakes.yaml); scripts/ci-local.sh --matrix 在 3.9–3.12 上添加了 pytest。
| 工作流程 | 跑步者 | 触发器 | 运行什么 |
|---|---|---|---|
ci.yml |
自托管 | 推送至 main,每晚,手动 |
scripts/ci-local.sh 的每个部分:泄漏扫描、提交审核、lint、3.9–3.12 上的 pytest、生成技能检查 |
hygiene.yml |
ubuntu-latest |
pull_request,包括edited |
树的泄漏扫描,以及读取拉取请求自己的标题和正文的两个检查 |
release.yml |
ubuntu-latest |
推送至 main,手动 |
release-please,并在发布 PR 合并时发布 OIDC |
启用操作后,拉取请求会显示一个托管检查,这是故意的。泄漏扫描是
在人类读取差异之前必须运行的门;所有较重的东西都由维护者自己运行
机器,其中完整的矩阵是免费的并且不会在任何人后面排队。 edited 就在那
触发列表,因为拉取请求的标题和正文在写入时立即发布,
存在于无检出中,在无钩子下通过,并且可以在运行其他所有检查后重写。 ci.yml从不
在 pull_request 上触发,并且不能启动 - 该存储库是公共的,并且运行器是
个人工作站,因此拉取请求触发器将使任何贡献者代码在其上执行。
释放
版本更新和变更日志是由传统提交驱动的
发布-请,准备发布 PR 时
main 承载面向用户的提交。
今天手动发布版本。 GitHub 操作在此存储库上被禁用,因此
release.yml 不运行:0.8.0 和 0.9.0 已构建、冒烟测试并从
维护者工作站,使用 PyPI API 令牌进行身份验证。该令牌持有于此
工作站,并且此存储库中没有文件。
release.yml 描述了另一条路由,该路由未使用:启用操作后,合并
发布 PR 构建发行版,对构建的轮子进行冒烟测试,并通过发布
可信发布 — 一个 OIDC 交换,不需要
存储的令牌。它需要存储库所有者(项目
hass-axi,所有者 dmealing,工作流程 release.yml,环境 pypi);值得信赖的出版商
注册为 ha-axi 的不结转。过渡包下的ha-axi
legacy/ha-axi/ 不是由工作流程构建的 - 请参阅从 ha-axi 移动的 [。
从 ha-axi 转移
该工具在0.7.1之前发布为ha-axi,并重命名为hass-axi,因为一个不相关的
Home Assistant CLI 以旧名称发布并安装相同的二进制文件。
| 之前 | 现在 |
|---|---|
pip install ha-axi |
pip install hass-axi |
ha-axi … |
hass-axi … |
import ha_axi |
import hass_axi |
HA_AXI_READ_ONLY |
HASS_AXI_READ_ONLY |
HA_AXI_DEBUG |
HASS_AXI_DEBUG |
skills/ha-axi/SKILL.md |
skills/hass-axi/SKILL.md |
HA_URL、HA_TOKEN 及其 HASS_SERVER/HASS_TOKEN 别名保持不变。旧的
HA_AXI_* 名称仍然有效,并在 stderr 上打印一行弃用通知;设置的会话
HA_AXI_READ_ONLY 仍然是只读的,因为升级绝不能成为切换守卫的原因
关闭。升级后运行一次 hass-axi setup hooks:它重写钩子条目并删除
OpenCode 插件以旧名称安装。它通过该工具编写的确切命令来识别它们,因此
属于另一个 ha-axi 的钩子被保留。
最终的 ha-axi 版本是一个过渡包。 此存储库中的 legacy/ha-axi/ 版本
ha-axi 0.8.0,不包含自己的代码:它依赖于 hass-axi,所以 pip install -U ha-axi brings the renamed tool in, and its ha-axi command forwards to hass-axi 之后
stderr 上的弃用通知,因此现有脚本和挂钩将继续工作,直到它们被移动。它是
手动发布一次,hass-axi本身在PyPI上,并且在旧的下没有发布任何内容
其后的名字。卸载它(pip uninstall ha-axi)会删除转发命令并释放
其他工具的名称。