manualbeagle 0.1.0__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.
- manualbeagle-0.1.0/.dockerignore +9 -0
- manualbeagle-0.1.0/.editorconfig +26 -0
- manualbeagle-0.1.0/.gitattributes +3 -0
- manualbeagle-0.1.0/.github/CODEOWNERS +2 -0
- manualbeagle-0.1.0/.github/FUNDING.yml +2 -0
- manualbeagle-0.1.0/.github/ISSUE_TEMPLATE/broken_adapter.yml +38 -0
- manualbeagle-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +43 -0
- manualbeagle-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- manualbeagle-0.1.0/.github/ISSUE_TEMPLATE/feature.yml +30 -0
- manualbeagle-0.1.0/.github/ISSUE_TEMPLATE/new_brand.yml +48 -0
- manualbeagle-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +37 -0
- manualbeagle-0.1.0/.github/dependabot.yml +14 -0
- manualbeagle-0.1.0/.github/workflows/ci.yml +34 -0
- manualbeagle-0.1.0/.github/workflows/live-adapters.yml +138 -0
- manualbeagle-0.1.0/.github/workflows/release.yml +193 -0
- manualbeagle-0.1.0/.gitignore +21 -0
- manualbeagle-0.1.0/.pre-commit-config.yaml +7 -0
- manualbeagle-0.1.0/AGENTS.md +6 -0
- manualbeagle-0.1.0/CHANGELOG.md +156 -0
- manualbeagle-0.1.0/CLAUDE.md +99 -0
- manualbeagle-0.1.0/CODE_OF_CONDUCT.md +133 -0
- manualbeagle-0.1.0/CONTRIBUTING.md +382 -0
- manualbeagle-0.1.0/Dockerfile +49 -0
- manualbeagle-0.1.0/LICENSE +202 -0
- manualbeagle-0.1.0/NOTICE +4 -0
- manualbeagle-0.1.0/PKG-INFO +207 -0
- manualbeagle-0.1.0/README.md +150 -0
- manualbeagle-0.1.0/SECURITY.md +64 -0
- manualbeagle-0.1.0/docs/adapters.md +381 -0
- manualbeagle-0.1.0/docs/architecture.md +229 -0
- manualbeagle-0.1.0/docs/brands.md +72 -0
- manualbeagle-0.1.0/docs/docker.md +102 -0
- manualbeagle-0.1.0/docs/integrations/folder.md +77 -0
- manualbeagle-0.1.0/docs/integrations/homeassistant.md +141 -0
- manualbeagle-0.1.0/docs/integrations/homebox.md +231 -0
- manualbeagle-0.1.0/docs/integrations/mcp.md +72 -0
- manualbeagle-0.1.0/docs/integrations/paperless.md +110 -0
- manualbeagle-0.1.0/docs/privacy.md +127 -0
- manualbeagle-0.1.0/docs/releasing.md +94 -0
- manualbeagle-0.1.0/docs/web.md +349 -0
- manualbeagle-0.1.0/pyproject.toml +100 -0
- manualbeagle-0.1.0/src/manualbeagle/__init__.py +3 -0
- manualbeagle-0.1.0/src/manualbeagle/__main__.py +5 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/__init__.py +81 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/base.py +227 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/alorair.yaml +30 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/amcrest.yaml +21 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/apc.yaml +14 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/brother.yaml +20 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/canon.yaml +19 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/chamberlain.yaml +18 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/chargepoint.yaml +18 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/cyberpower.yaml +17 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/dewalt.yaml +42 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/ecoflow.yaml +21 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/epson.yaml +18 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/eufy.yaml +47 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/ge_appliances.yaml +38 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/harbor_freight.yaml +44 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/hatch.yaml +25 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/honda_power.yaml +17 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/klein_tools.yaml +18 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/levoit.yaml +21 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/lg.yaml +29 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/lutron.yaml +22 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/mitsubishi_electric.yaml +25 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/petkit.yaml +37 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/reolink.yaml +25 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/resideo.yaml +24 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/roborock.yaml +18 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/samsung.yaml +26 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/sensorpush.yaml +31 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/solaredge.yaml +18 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/span.yaml +26 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/toyota.yaml +30 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/tplink.yaml +38 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/tripp_lite.yaml +14 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/wen.yaml +21 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/whirlpool.yaml +19 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/defs/whisker.yaml +17 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/__init__.py +1 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/amcrest.py +93 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/brother.py +88 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/chamberlain.py +108 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/ecoflow.py +121 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/epson.py +87 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/hatch.py +121 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/levoit.py +74 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/lg.py +109 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/mitsubishi_electric.py +80 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/reolink.py +95 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/resideo.py +129 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/roborock.py +105 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/samsung.py +119 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/shopify.py +116 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/toyota.py +100 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/whisker.py +91 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/py/zendesk.py +108 -0
- manualbeagle-0.1.0/src/manualbeagle/adapters/yaml_adapter.py +193 -0
- manualbeagle-0.1.0/src/manualbeagle/ask.py +456 -0
- manualbeagle-0.1.0/src/manualbeagle/batch.py +213 -0
- manualbeagle-0.1.0/src/manualbeagle/cli.py +881 -0
- manualbeagle-0.1.0/src/manualbeagle/config.py +286 -0
- manualbeagle-0.1.0/src/manualbeagle/http.py +389 -0
- manualbeagle-0.1.0/src/manualbeagle/library.py +409 -0
- manualbeagle-0.1.0/src/manualbeagle/maintenance.py +1045 -0
- manualbeagle-0.1.0/src/manualbeagle/mcp_server.py +173 -0
- manualbeagle-0.1.0/src/manualbeagle/models.py +157 -0
- manualbeagle-0.1.0/src/manualbeagle/nameplate.py +371 -0
- manualbeagle-0.1.0/src/manualbeagle/newitem.py +242 -0
- manualbeagle-0.1.0/src/manualbeagle/normalize.py +113 -0
- manualbeagle-0.1.0/src/manualbeagle/pipeline.py +118 -0
- manualbeagle-0.1.0/src/manualbeagle/scoreboard.py +190 -0
- manualbeagle-0.1.0/src/manualbeagle/search/__init__.py +38 -0
- manualbeagle-0.1.0/src/manualbeagle/search/searxng.py +68 -0
- manualbeagle-0.1.0/src/manualbeagle/settings.py +194 -0
- manualbeagle-0.1.0/src/manualbeagle/sinks/__init__.py +1 -0
- manualbeagle-0.1.0/src/manualbeagle/sinks/folder.py +84 -0
- manualbeagle-0.1.0/src/manualbeagle/sinks/homebox.py +314 -0
- manualbeagle-0.1.0/src/manualbeagle/sinks/paperless.py +383 -0
- manualbeagle-0.1.0/src/manualbeagle/sinks/seed.py +42 -0
- manualbeagle-0.1.0/src/manualbeagle/sources/__init__.py +1 -0
- manualbeagle-0.1.0/src/manualbeagle/sources/homeassistant.py +432 -0
- manualbeagle-0.1.0/src/manualbeagle/sync.py +322 -0
- manualbeagle-0.1.0/src/manualbeagle/testing.py +192 -0
- manualbeagle-0.1.0/src/manualbeagle/verify.py +386 -0
- manualbeagle-0.1.0/src/manualbeagle/vision.py +208 -0
- manualbeagle-0.1.0/src/manualbeagle/web/__init__.py +6 -0
- manualbeagle-0.1.0/src/manualbeagle/web/app.py +1547 -0
- manualbeagle-0.1.0/src/manualbeagle/web/integrations.py +291 -0
- manualbeagle-0.1.0/src/manualbeagle/web/jobs.py +118 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/app.js +1410 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/icons/icon-180.png +0 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/icons/icon-192.png +0 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/icons/icon-512.png +0 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/icons/icon.svg +1 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/icons/maskable-512.png +0 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/index.html +201 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/manifest.webmanifest +21 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/style.css +245 -0
- manualbeagle-0.1.0/src/manualbeagle/web/static/sw.js +38 -0
- manualbeagle-0.1.0/tests/fixtures/alorair/galaxy-60.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/alorair/sentinel-hd55.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/amcrest/ad410.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/amcrest/ip8m-t2499ew.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/brother/hl-l2395dw.json +23 -0
- manualbeagle-0.1.0/tests/fixtures/brother/hl-l2460dw.json +23 -0
- manualbeagle-0.1.0/tests/fixtures/brother/pt-p750w.json +23 -0
- manualbeagle-0.1.0/tests/fixtures/chamberlain/b970.json +23 -0
- manualbeagle-0.1.0/tests/fixtures/chamberlain/b980.json +23 -0
- manualbeagle-0.1.0/tests/fixtures/dewalt/dcs380p1.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/dewalt/dcs391b.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/dewalt/dwe575sb.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/ecoflow/delta-2.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/ecoflow/river-2.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/ecoflow/trail-plus-300-dc.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/epson/et-2800.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/epson/wf-2850.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/epson/xp-6000.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/eufy/robovac-x8-hybrid.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/eufy/smart-scale-p2-pro.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/ge_appliances/fcm7skww.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/ge_appliances/gtw465asnww.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/ge_appliances/jb645rkss.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/hatch/rest-2nd-gen.json +31 -0
- manualbeagle-0.1.0/tests/fixtures/hatch/rest.json +31 -0
- manualbeagle-0.1.0/tests/fixtures/hatch/restore-2.json +31 -0
- manualbeagle-0.1.0/tests/fixtures/hatch/restore.json +31 -0
- manualbeagle-0.1.0/tests/fixtures/levoit/classic300s.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/levoit/core-300s-p.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/lg/32u889sa-w.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/lg/dlex5500v.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/lg/lw8016er.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/lg/wm5700hva-assevus.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/lg/wm5700hva.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/maintenance/garage_opener.txt +11 -0
- manualbeagle-0.1.0/tests/fixtures/maintenance/robot_vacuum.txt +9 -0
- manualbeagle-0.1.0/tests/fixtures/maintenance/window_ac.txt +15 -0
- manualbeagle-0.1.0/tests/fixtures/mitsubishi_electric/msz-fh12na.json +24 -0
- manualbeagle-0.1.0/tests/fixtures/mitsubishi_electric/msz-gl09na.json +34 -0
- manualbeagle-0.1.0/tests/fixtures/mitsubishi_electric/muz-fh12na.json +44 -0
- manualbeagle-0.1.0/tests/fixtures/mitsubishi_electric/mxz-3c24na2.json +24 -0
- manualbeagle-0.1.0/tests/fixtures/mitsubishi_electric/pac-uswhs002-wf-2.json +24 -0
- manualbeagle-0.1.0/tests/fixtures/nameplate/make_plates.py +51 -0
- manualbeagle-0.1.0/tests/fixtures/nameplate/plate_lg.png +0 -0
- manualbeagle-0.1.0/tests/fixtures/nameplate/plate_whirlpool.jpg +0 -0
- manualbeagle-0.1.0/tests/fixtures/petkit/d4h-2.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/petkit/yumshare-dual-hopper-2.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/reolink/d340w.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/reolink/e1-outdoor-pro.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/reolink/trackflex-floodlight.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/resideo/rcht9610wf.json +31 -0
- manualbeagle-0.1.0/tests/fixtures/resideo/rchtsensor.json +40 -0
- manualbeagle-0.1.0/tests/fixtures/resideo/rth9585wf.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/resideo/th6220wf2006.json +22 -0
- manualbeagle-0.1.0/tests/fixtures/roborock/q-revo.json +58 -0
- manualbeagle-0.1.0/tests/fixtures/roborock/s8-maxv-ultra.json +40 -0
- manualbeagle-0.1.0/tests/fixtures/samsung/hw-q800a-za.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/samsung/qn65qn85aafxza.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/samsung/swa-9500s-za.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/sensorpush/ht-w.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/sensorpush/ht1.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/span/main-32.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/span/main-40.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/toyota/bz-2026.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/toyota/bz4x-2023.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/toyota/camry-2019.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/toyota/rav4-hybrid-2020.json +24 -0
- manualbeagle-0.1.0/tests/fixtures/toyota/sienna-2022.json +24 -0
- manualbeagle-0.1.0/tests/fixtures/tplink/ep25p4.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/tplink/hs300.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/tplink/kp125m.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/wen/3410.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/wen/56235i.json +13 -0
- manualbeagle-0.1.0/tests/fixtures/whisker/litter-robot-4.json +14 -0
- manualbeagle-0.1.0/tests/fixtures/whisker/litter-robot-5-pro.json +23 -0
- manualbeagle-0.1.0/tests/test_adapters.py +29 -0
- manualbeagle-0.1.0/tests/test_ask.py +256 -0
- manualbeagle-0.1.0/tests/test_cli.py +23 -0
- manualbeagle-0.1.0/tests/test_config.py +40 -0
- manualbeagle-0.1.0/tests/test_core.py +279 -0
- manualbeagle-0.1.0/tests/test_homeassistant.py +344 -0
- manualbeagle-0.1.0/tests/test_homebox.py +83 -0
- manualbeagle-0.1.0/tests/test_library.py +134 -0
- manualbeagle-0.1.0/tests/test_maintenance.py +348 -0
- manualbeagle-0.1.0/tests/test_mcp.py +65 -0
- manualbeagle-0.1.0/tests/test_nameplate.py +298 -0
- manualbeagle-0.1.0/tests/test_newitem.py +246 -0
- manualbeagle-0.1.0/tests/test_paperless.py +243 -0
- manualbeagle-0.1.0/tests/test_scoreboard.py +128 -0
- manualbeagle-0.1.0/tests/test_settings.py +157 -0
- manualbeagle-0.1.0/tests/test_sync.py +284 -0
- manualbeagle-0.1.0/tests/test_web.py +375 -0
- manualbeagle-0.1.0/tests/test_web_integrations.py +154 -0
- manualbeagle-0.1.0/tests/test_web_maintenance.py +197 -0
- manualbeagle-0.1.0/tests/test_web_nameplate.py +157 -0
- manualbeagle-0.1.0/tests/test_web_newitem.py +170 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# https://editorconfig.org
|
|
2
|
+
root = true
|
|
3
|
+
|
|
4
|
+
[*]
|
|
5
|
+
charset = utf-8
|
|
6
|
+
end_of_line = lf
|
|
7
|
+
insert_final_newline = true
|
|
8
|
+
trim_trailing_whitespace = true
|
|
9
|
+
indent_style = space
|
|
10
|
+
indent_size = 4
|
|
11
|
+
|
|
12
|
+
[*.py]
|
|
13
|
+
max_line_length = 110
|
|
14
|
+
|
|
15
|
+
[*.{yml,yaml,toml,json,html,css,js}]
|
|
16
|
+
indent_size = 2
|
|
17
|
+
|
|
18
|
+
[*.md]
|
|
19
|
+
indent_size = 2
|
|
20
|
+
# a trailing double space is a line break in Markdown
|
|
21
|
+
trim_trailing_whitespace = false
|
|
22
|
+
|
|
23
|
+
[tests/fixtures/**]
|
|
24
|
+
# recorded responses are written by the test harness; leave them as they are
|
|
25
|
+
trim_trailing_whitespace = false
|
|
26
|
+
insert_final_newline = unset
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Broken adapter
|
|
2
|
+
description: A brand that used to work finds the wrong manual or nothing at all.
|
|
3
|
+
title: "Broken adapter: "
|
|
4
|
+
labels: ["adapter: broken"]
|
|
5
|
+
body:
|
|
6
|
+
- type: input
|
|
7
|
+
id: adapter
|
|
8
|
+
attributes:
|
|
9
|
+
label: Adapter id
|
|
10
|
+
description: From `manualbeagle adapters list` (the first column).
|
|
11
|
+
placeholder: lg
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
- type: input
|
|
15
|
+
id: model
|
|
16
|
+
attributes:
|
|
17
|
+
label: Model
|
|
18
|
+
placeholder: WM5700HVA
|
|
19
|
+
validations:
|
|
20
|
+
required: true
|
|
21
|
+
- type: textarea
|
|
22
|
+
id: output
|
|
23
|
+
attributes:
|
|
24
|
+
label: Output
|
|
25
|
+
description: The output of `manualbeagle find <brand> <model> --why` and, if you have a clone, `manualbeagle adapters test <id> --live -v`.
|
|
26
|
+
render: text
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
- type: textarea
|
|
30
|
+
id: expected
|
|
31
|
+
attributes:
|
|
32
|
+
label: What you expected
|
|
33
|
+
description: The right manual's link, if you found it in a browser.
|
|
34
|
+
- type: input
|
|
35
|
+
id: version
|
|
36
|
+
attributes:
|
|
37
|
+
label: Version or commit
|
|
38
|
+
description: "`manualbeagle --version`, or `git rev-parse --short HEAD`."
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Bug
|
|
2
|
+
description: Something other than a brand adapter doesn't work as documented.
|
|
3
|
+
title: "Bug: "
|
|
4
|
+
labels: ["bug"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
For a security problem, don't open an issue: see [SECURITY.md](https://github.com/manualbeagle/manualbeagle/blob/main/SECURITY.md).
|
|
10
|
+
Please remove API keys, private addresses and anything else private from what you paste.
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: what
|
|
13
|
+
attributes:
|
|
14
|
+
label: What happened
|
|
15
|
+
description: The command you ran and its full output.
|
|
16
|
+
render: text
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: expected
|
|
21
|
+
attributes:
|
|
22
|
+
label: What you expected
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: steps
|
|
27
|
+
attributes:
|
|
28
|
+
label: How to reproduce it
|
|
29
|
+
description: Steps, and the relevant part of your config.toml (without secrets).
|
|
30
|
+
- type: input
|
|
31
|
+
id: version
|
|
32
|
+
attributes:
|
|
33
|
+
label: Version or commit
|
|
34
|
+
description: "`manualbeagle --version`, or `git rev-parse --short HEAD`."
|
|
35
|
+
validations:
|
|
36
|
+
required: true
|
|
37
|
+
- type: input
|
|
38
|
+
id: environment
|
|
39
|
+
attributes:
|
|
40
|
+
label: Operating system and Python version
|
|
41
|
+
placeholder: "Ubuntu 24.04, Python 3.12"
|
|
42
|
+
validations:
|
|
43
|
+
required: true
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Feature
|
|
2
|
+
description: Suggest something new, such as another inventory to file manuals in.
|
|
3
|
+
title: "Feature: "
|
|
4
|
+
labels: ["enhancement"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: problem
|
|
8
|
+
attributes:
|
|
9
|
+
label: What you're trying to do
|
|
10
|
+
description: The problem, not only the solution.
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: idea
|
|
15
|
+
attributes:
|
|
16
|
+
label: What would help
|
|
17
|
+
- type: textarea
|
|
18
|
+
id: alternatives
|
|
19
|
+
attributes:
|
|
20
|
+
label: What you've tried instead
|
|
21
|
+
- type: checkboxes
|
|
22
|
+
id: rules
|
|
23
|
+
attributes:
|
|
24
|
+
label: Project rules
|
|
25
|
+
description: Manual Beagle keeps a few rules whatever the feature (see CONTRIBUTING.md).
|
|
26
|
+
options:
|
|
27
|
+
- label: It works without AI (AI may only add to a rules-only path).
|
|
28
|
+
required: true
|
|
29
|
+
- label: It doesn't host, share or mirror manuals, crawl a site in bulk, or get around bot protection or logins.
|
|
30
|
+
required: true
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: New brand
|
|
2
|
+
description: Ask for (or offer to write) an adapter for a brand Manual Beagle doesn't know yet.
|
|
3
|
+
title: "Brand: "
|
|
4
|
+
labels: ["brand"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: |
|
|
9
|
+
Thanks! Check [docs/brands.md](https://github.com/manualbeagle/manualbeagle/blob/main/docs/brands.md) first: the brand may already be there under
|
|
10
|
+
another name. Writing the adapter yourself is usually about 15 minutes; [CONTRIBUTING.md](https://github.com/manualbeagle/manualbeagle/blob/main/CONTRIBUTING.md) walks through it.
|
|
11
|
+
- type: input
|
|
12
|
+
id: brand
|
|
13
|
+
attributes:
|
|
14
|
+
label: Brand
|
|
15
|
+
description: As it's written on the rating plate, and any other names it goes by.
|
|
16
|
+
placeholder: "AlorAir (also AlorAir Solutions)"
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: input
|
|
20
|
+
id: site
|
|
21
|
+
attributes:
|
|
22
|
+
label: The maker's support or manuals page
|
|
23
|
+
description: Where a person finds manuals on the maker's own site.
|
|
24
|
+
placeholder: "https://www.example.com/support"
|
|
25
|
+
validations:
|
|
26
|
+
required: true
|
|
27
|
+
- type: textarea
|
|
28
|
+
id: models
|
|
29
|
+
attributes:
|
|
30
|
+
label: Example models
|
|
31
|
+
description: Two or more real model numbers, ideally ones you own, so the adapter has test cases.
|
|
32
|
+
placeholder: |
|
|
33
|
+
Galaxy 60
|
|
34
|
+
Sentinel HD55
|
|
35
|
+
validations:
|
|
36
|
+
required: true
|
|
37
|
+
- type: textarea
|
|
38
|
+
id: how
|
|
39
|
+
attributes:
|
|
40
|
+
label: How the site lists documents (if you know)
|
|
41
|
+
description: PDF links in the page's HTML, JSON embedded in the page, or an API the page calls. The browser's developer tools (Network tab) show it.
|
|
42
|
+
- type: checkboxes
|
|
43
|
+
id: rules
|
|
44
|
+
attributes:
|
|
45
|
+
label: Fetching rules
|
|
46
|
+
options:
|
|
47
|
+
- label: As far as I can tell, the site's terms don't forbid automated access, and it doesn't need a login or pass a CAPTCHA to reach the manuals (if it does, say so above; it can still get a link-only adapter).
|
|
48
|
+
- label: I'd like to write this adapter myself.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## What this changes
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. For an adapter: the brand, how it searches, and how many models pass live. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] `ruff check src tests` and `ruff format --check src tests` pass.
|
|
8
|
+
- [ ] `pytest` passes (offline; no test touches the network unless it's marked `live`).
|
|
9
|
+
- [ ] Docs are updated for anything a user or contributor would notice (`README.md`, `CONTRIBUTING.md`,
|
|
10
|
+
`docs/architecture.md`, `docs/adapters.md`, `docs/integrations/`), and public code has docstrings and type
|
|
11
|
+
hints.
|
|
12
|
+
- [ ] Colour in any output is paired with a glyph or a word.
|
|
13
|
+
- [ ] No new dependency, or it's compatible with Apache-2.0 and the description says why it's needed.
|
|
14
|
+
|
|
15
|
+
### For a new or changed adapter
|
|
16
|
+
|
|
17
|
+
- [ ] The adapter starts with its header: the site, how it searches, known gaps, and today's date as
|
|
18
|
+
"Last verified".
|
|
19
|
+
- [ ] At least two real test cases, with fixtures recorded by `manualbeagle adapters test <id> --record -v`.
|
|
20
|
+
- [ ] The fixtures are small and hold no PDF bodies.
|
|
21
|
+
- [ ] `docs/brands.md` regenerated with `manualbeagle adapters scoreboard --write docs/brands.md`.
|
|
22
|
+
- [ ] The output of `manualbeagle adapters test <id> --live -v` is below.
|
|
23
|
+
|
|
24
|
+
### Fetching rules
|
|
25
|
+
|
|
26
|
+
- [ ] Every request goes through `ctx.http` (the polite client): one request at a time per site, the project's
|
|
27
|
+
User-Agent.
|
|
28
|
+
- [ ] Nothing crawls or lists a site in bulk: only what one lookup needs.
|
|
29
|
+
- [ ] Nothing gets around a CAPTCHA, a bot wall or a login (no browser automation, spoofed headers, copied
|
|
30
|
+
cookies or proxies).
|
|
31
|
+
- [ ] The site's terms don't forbid automated access.
|
|
32
|
+
- [ ] Nothing hosts, shares or mirrors PDFs.
|
|
33
|
+
|
|
34
|
+
## Live test output (adapters)
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: pip
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
- package-ecosystem: github-actions
|
|
8
|
+
directory: /
|
|
9
|
+
schedule:
|
|
10
|
+
interval: weekly
|
|
11
|
+
- package-ecosystem: docker # the Dockerfile's base image
|
|
12
|
+
directory: /
|
|
13
|
+
schedule:
|
|
14
|
+
interval: weekly
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths-ignore: ["docs/**", "*.md"]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 10
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
# Every version pyproject.toml's classifiers name.
|
|
24
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
cache: pip
|
|
31
|
+
- run: pip install -e ".[dev]"
|
|
32
|
+
- run: ruff check src tests
|
|
33
|
+
- run: ruff format --check src tests
|
|
34
|
+
- run: pytest -q
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
name: Live adapters
|
|
2
|
+
|
|
3
|
+
# Runs every adapter's test cases against the real sites, which is how we notice when a maker's site changes.
|
|
4
|
+
#
|
|
5
|
+
# - Every night once the repository is public. While it's private, Actions minutes count, so the schedule runs
|
|
6
|
+
# on Mondays only (the second cron line); each job's `if` picks the line that applies.
|
|
7
|
+
# - The run's page shows the live scoreboard (docs/brands.md with a Live column) and a report of what broke; the
|
|
8
|
+
# JSON results, the log and both Markdown files are kept as the run's artifact for 30 days.
|
|
9
|
+
# - A scheduled run keeps one issue labelled `live-adapters` current: opened when something breaks, its text
|
|
10
|
+
# replaced and a comment added each night it stays broken, closed when every case passes again. A manual run
|
|
11
|
+
# touches the issue only when asked to (the `report` input).
|
|
12
|
+
# - Requests follow the project's fetching rules like any lookup: one at a time per site, with a delay, and
|
|
13
|
+
# nothing that crawls. A site that blocks the runner shows as ✗ blocked; nothing tries to get around it.
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
schedule:
|
|
17
|
+
- cron: "17 7 * * *" # nightly, public repository
|
|
18
|
+
- cron: "17 9 * * 1" # Mondays, private repository
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
inputs:
|
|
21
|
+
adapters:
|
|
22
|
+
description: "adapter ids, space-separated (blank: all)"
|
|
23
|
+
required: false
|
|
24
|
+
report:
|
|
25
|
+
description: "open, update or close the live-adapters issue"
|
|
26
|
+
type: boolean
|
|
27
|
+
default: false
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
concurrency:
|
|
33
|
+
group: live-adapters
|
|
34
|
+
cancel-in-progress: false
|
|
35
|
+
|
|
36
|
+
jobs:
|
|
37
|
+
live:
|
|
38
|
+
if: >-
|
|
39
|
+
github.event_name != 'schedule'
|
|
40
|
+
|| (github.event.schedule == '17 7 * * *' && !github.event.repository.private)
|
|
41
|
+
|| (github.event.schedule == '17 9 * * 1' && github.event.repository.private)
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
timeout-minutes: 45
|
|
44
|
+
outputs:
|
|
45
|
+
broken: ${{ steps.report.outputs.broken }}
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
- uses: actions/setup-python@v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.12"
|
|
51
|
+
cache: pip
|
|
52
|
+
- run: pip install -e .
|
|
53
|
+
- name: Run live adapter tests
|
|
54
|
+
# The input goes through an environment variable, never into the script itself, so a value can't inject
|
|
55
|
+
# shell commands. It's split on spaces on purpose: one argument per adapter id.
|
|
56
|
+
env:
|
|
57
|
+
ADAPTERS: ${{ github.event.inputs.adapters }}
|
|
58
|
+
run: |
|
|
59
|
+
# shellcheck disable=SC2086 # split on purpose
|
|
60
|
+
manualbeagle adapters test --live --json live.json $ADAPTERS | tee live.txt
|
|
61
|
+
continue-on-error: true
|
|
62
|
+
- name: Live scoreboard
|
|
63
|
+
run: manualbeagle adapters scoreboard --live live.json --write live-brands.md
|
|
64
|
+
- name: What broke
|
|
65
|
+
id: report
|
|
66
|
+
env:
|
|
67
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
68
|
+
run: |
|
|
69
|
+
set +e
|
|
70
|
+
manualbeagle adapters report live.json --run-url "$RUN_URL" --write report.md
|
|
71
|
+
python -c "import json; r = json.load(open('live.json', encoding='utf-8')); print('broken=' + str(sum(x['status'] in ('fail', 'error', 'blocked') for x in r)))" >> "$GITHUB_OUTPUT"
|
|
72
|
+
exit 0
|
|
73
|
+
- name: Summary
|
|
74
|
+
run: |
|
|
75
|
+
{
|
|
76
|
+
cat report.md
|
|
77
|
+
echo
|
|
78
|
+
echo '## Live scoreboard'
|
|
79
|
+
sed -n '/^| Brand /,$p' live-brands.md
|
|
80
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
81
|
+
- uses: actions/upload-artifact@v4
|
|
82
|
+
if: always()
|
|
83
|
+
with:
|
|
84
|
+
name: live-adapters
|
|
85
|
+
path: |
|
|
86
|
+
live.json
|
|
87
|
+
live.txt
|
|
88
|
+
live-brands.md
|
|
89
|
+
report.md
|
|
90
|
+
retention-days: 30
|
|
91
|
+
if-no-files-found: warn
|
|
92
|
+
- name: Fail the run when a case broke
|
|
93
|
+
env:
|
|
94
|
+
BROKEN: ${{ steps.report.outputs.broken }}
|
|
95
|
+
run: |
|
|
96
|
+
if [ "${BROKEN:-1}" != "0" ]; then echo "✗ $BROKEN broken case(s): see the summary"; exit 1; fi
|
|
97
|
+
echo "✓ every case that ran passed"
|
|
98
|
+
|
|
99
|
+
issue:
|
|
100
|
+
name: Keep the live-adapters issue current
|
|
101
|
+
needs: live
|
|
102
|
+
if: >-
|
|
103
|
+
always() && needs.live.outputs.broken != ''
|
|
104
|
+
&& (github.event_name == 'schedule' || inputs.report == true)
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
timeout-minutes: 5
|
|
107
|
+
permissions:
|
|
108
|
+
contents: read
|
|
109
|
+
issues: write
|
|
110
|
+
steps:
|
|
111
|
+
- uses: actions/download-artifact@v4
|
|
112
|
+
with:
|
|
113
|
+
name: live-adapters
|
|
114
|
+
- name: Open, update or close the issue
|
|
115
|
+
env:
|
|
116
|
+
GH_TOKEN: ${{ github.token }}
|
|
117
|
+
GH_REPO: ${{ github.repository }}
|
|
118
|
+
BROKEN: ${{ needs.live.outputs.broken }}
|
|
119
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
120
|
+
run: |
|
|
121
|
+
label=live-adapters
|
|
122
|
+
gh label create "$label" --color FBCA04 \
|
|
123
|
+
--description "An adapter's test cases fail against the maker's live site" --force
|
|
124
|
+
num=$(gh issue list --label "$label" --state open --json number --jq '.[0].number // empty')
|
|
125
|
+
today=$(date -u +%F)
|
|
126
|
+
if [ "$BROKEN" != "0" ]; then
|
|
127
|
+
if [ -n "$num" ]; then
|
|
128
|
+
gh issue edit "$num" --title "Live adapters: $BROKEN case(s) broken" --body-file report.md
|
|
129
|
+
gh issue comment "$num" --body "✗ $today: $BROKEN case(s) broken ([run]($RUN_URL)). The description has the current list."
|
|
130
|
+
else
|
|
131
|
+
gh issue create --title "Live adapters: $BROKEN case(s) broken" --label "$label" --body-file report.md
|
|
132
|
+
fi
|
|
133
|
+
elif [ -n "$num" ]; then
|
|
134
|
+
gh issue comment "$num" --body "✓ $today: every case that ran passed ([run]($RUN_URL)). Closing."
|
|
135
|
+
gh issue close "$num"
|
|
136
|
+
else
|
|
137
|
+
echo "✓ nothing broken and no open issue"
|
|
138
|
+
fi
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Pushing a version tag (v0.1.0) builds the sdist, the wheel and the container image, checks them, and publishes
|
|
4
|
+
# them only when the repository variable RELEASE_ENABLED is "true" (Settings > Secrets and variables > Actions >
|
|
5
|
+
# Variables). Until then, and on every manual run, it builds and checks everything and publishes nothing. The
|
|
6
|
+
# steps to switch it on, and to make a release, are in docs/releasing.md.
|
|
7
|
+
#
|
|
8
|
+
# - PyPI: trusted publishing (OpenID Connect), so there is no API token to store. It needs a publisher on PyPI
|
|
9
|
+
# for this repository, this workflow file and the environment "pypi", whose required reviewer approves each
|
|
10
|
+
# upload.
|
|
11
|
+
# - Container: ghcr.io/<owner>/<repo>, for linux/amd64 and linux/arm64, tagged with the version; a final version
|
|
12
|
+
# (not a pre-release such as 0.2.0rc1) is also tagged <major>.<minor> and "latest".
|
|
13
|
+
# - GitHub release: made last, from the tag, with the version's CHANGELOG.md section as its notes and the sdist
|
|
14
|
+
# and wheel attached.
|
|
15
|
+
# - Third-party actions are pinned to commit SHAs (the version in a comment), since these jobs can publish;
|
|
16
|
+
# Dependabot keeps them current.
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
tags: ["v[0-9]*"]
|
|
21
|
+
workflow_dispatch: # a dry run: builds and checks, publishes nothing
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
concurrency:
|
|
27
|
+
group: release-${{ github.ref }}
|
|
28
|
+
cancel-in-progress: false
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
build:
|
|
32
|
+
name: Build and check the packages
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
timeout-minutes: 10
|
|
35
|
+
outputs:
|
|
36
|
+
version: ${{ steps.version.outputs.version }}
|
|
37
|
+
prerelease: ${{ steps.version.outputs.prerelease }}
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: actions/setup-python@v5
|
|
41
|
+
with:
|
|
42
|
+
python-version: "3.12"
|
|
43
|
+
- run: pip install build twine
|
|
44
|
+
- run: python -m build
|
|
45
|
+
- run: twine check --strict dist/*
|
|
46
|
+
- name: The version, and whether it's a pre-release
|
|
47
|
+
id: version
|
|
48
|
+
run: |
|
|
49
|
+
version=$(python -c "import glob; print(glob.glob('dist/*.whl')[0].split('-')[1])")
|
|
50
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
51
|
+
if echo "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then pre=false; else pre=true; fi
|
|
52
|
+
echo "prerelease=$pre" >> "$GITHUB_OUTPUT"
|
|
53
|
+
echo "version $version, pre-release: $pre"
|
|
54
|
+
- name: The tag matches the package's version
|
|
55
|
+
if: github.ref_type == 'tag'
|
|
56
|
+
env:
|
|
57
|
+
TAG: ${{ github.ref_name }}
|
|
58
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
59
|
+
run: |
|
|
60
|
+
if [ "v$VERSION" != "$TAG" ]; then
|
|
61
|
+
echo "✗ tag $TAG doesn't match version $VERSION (src/manualbeagle/__init__.py)"; exit 1
|
|
62
|
+
fi
|
|
63
|
+
echo "✓ tag $TAG matches version $VERSION"
|
|
64
|
+
- name: CHANGELOG.md has a section for the version
|
|
65
|
+
if: github.ref_type == 'tag'
|
|
66
|
+
env:
|
|
67
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
68
|
+
run: |
|
|
69
|
+
grep -q "^## \[$VERSION\]" CHANGELOG.md || { echo "✗ no '## [$VERSION]' in CHANGELOG.md"; exit 1; }
|
|
70
|
+
echo "✓ CHANGELOG.md has [$VERSION]"
|
|
71
|
+
- name: The wheel and the sdist each install and run
|
|
72
|
+
run: |
|
|
73
|
+
for pkg in dist/*.whl dist/*.tar.gz; do
|
|
74
|
+
rm -rf /tmp/check && python -m venv /tmp/check
|
|
75
|
+
/tmp/check/bin/pip install --quiet "$pkg"
|
|
76
|
+
/tmp/check/bin/manualbeagle --version
|
|
77
|
+
/tmp/check/bin/manualbeagle adapters list > /dev/null
|
|
78
|
+
echo "✓ $pkg"
|
|
79
|
+
done
|
|
80
|
+
- uses: actions/upload-artifact@v4
|
|
81
|
+
with:
|
|
82
|
+
name: dist
|
|
83
|
+
path: dist/
|
|
84
|
+
if-no-files-found: error
|
|
85
|
+
|
|
86
|
+
docker:
|
|
87
|
+
name: Build, check and (when enabled) publish the image
|
|
88
|
+
needs: build
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
timeout-minutes: 45
|
|
91
|
+
permissions:
|
|
92
|
+
contents: read
|
|
93
|
+
packages: write
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v4
|
|
96
|
+
- name: Build for this runner
|
|
97
|
+
env:
|
|
98
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
99
|
+
run: docker build --build-arg "VERSION=$VERSION" -t manualbeagle:check .
|
|
100
|
+
- name: The image starts and answers
|
|
101
|
+
run: |
|
|
102
|
+
docker run -d --name mb -p 127.0.0.1:8765:8765 -e MANUALBEAGLE_WEB_TOKEN=check manualbeagle:check
|
|
103
|
+
for _ in $(seq 1 30); do curl -fs http://127.0.0.1:8765/healthz && break; sleep 1; done
|
|
104
|
+
curl -fs http://127.0.0.1:8765/healthz
|
|
105
|
+
docker exec mb manualbeagle --version
|
|
106
|
+
docker exec mb python -c "from manualbeagle.library import ocr_available; assert ocr_available(), 'no OCR'"
|
|
107
|
+
test "$(docker exec mb id -u)" = 1000
|
|
108
|
+
echo "✓ the image serves /healthz, has OCR and runs as uid 1000"
|
|
109
|
+
docker image ls manualbeagle:check
|
|
110
|
+
- name: Container log
|
|
111
|
+
if: always()
|
|
112
|
+
run: docker logs mb || true
|
|
113
|
+
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
|
114
|
+
if: github.ref_type == 'tag' && vars.RELEASE_ENABLED == 'true'
|
|
115
|
+
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
|
116
|
+
if: github.ref_type == 'tag' && vars.RELEASE_ENABLED == 'true'
|
|
117
|
+
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
|
118
|
+
if: github.ref_type == 'tag' && vars.RELEASE_ENABLED == 'true'
|
|
119
|
+
with:
|
|
120
|
+
registry: ghcr.io
|
|
121
|
+
username: ${{ github.actor }}
|
|
122
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
123
|
+
- name: Image tags
|
|
124
|
+
id: tags
|
|
125
|
+
if: github.ref_type == 'tag' && vars.RELEASE_ENABLED == 'true'
|
|
126
|
+
env:
|
|
127
|
+
REPO_IMAGE: ghcr.io/${{ github.repository }}
|
|
128
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
129
|
+
PRE: ${{ needs.build.outputs.prerelease }}
|
|
130
|
+
run: |
|
|
131
|
+
image=$(echo "$REPO_IMAGE" | tr '[:upper:]' '[:lower:]')
|
|
132
|
+
tags="$image:$VERSION"
|
|
133
|
+
if [ "$PRE" = false ]; then tags="$tags,$image:${VERSION%.*},$image:latest"; fi
|
|
134
|
+
echo "tags=$tags" >> "$GITHUB_OUTPUT"
|
|
135
|
+
echo "$tags"
|
|
136
|
+
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
|
137
|
+
if: github.ref_type == 'tag' && vars.RELEASE_ENABLED == 'true'
|
|
138
|
+
with:
|
|
139
|
+
context: .
|
|
140
|
+
platforms: linux/amd64,linux/arm64
|
|
141
|
+
push: true
|
|
142
|
+
build-args: VERSION=${{ needs.build.outputs.version }}
|
|
143
|
+
tags: ${{ steps.tags.outputs.tags }}
|
|
144
|
+
|
|
145
|
+
pypi:
|
|
146
|
+
name: Publish to PyPI
|
|
147
|
+
needs: [build, docker]
|
|
148
|
+
if: github.ref_type == 'tag' && vars.RELEASE_ENABLED == 'true'
|
|
149
|
+
runs-on: ubuntu-latest
|
|
150
|
+
timeout-minutes: 10
|
|
151
|
+
environment:
|
|
152
|
+
name: pypi
|
|
153
|
+
url: https://pypi.org/project/manualbeagle/
|
|
154
|
+
permissions:
|
|
155
|
+
id-token: write # trusted publishing
|
|
156
|
+
steps:
|
|
157
|
+
- uses: actions/download-artifact@v4
|
|
158
|
+
with:
|
|
159
|
+
name: dist
|
|
160
|
+
path: dist/
|
|
161
|
+
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
|
|
162
|
+
|
|
163
|
+
github-release:
|
|
164
|
+
name: The GitHub release
|
|
165
|
+
needs: [build, pypi]
|
|
166
|
+
if: github.ref_type == 'tag' && vars.RELEASE_ENABLED == 'true'
|
|
167
|
+
runs-on: ubuntu-latest
|
|
168
|
+
timeout-minutes: 5
|
|
169
|
+
permissions:
|
|
170
|
+
contents: write
|
|
171
|
+
steps:
|
|
172
|
+
- uses: actions/checkout@v4
|
|
173
|
+
- uses: actions/download-artifact@v4
|
|
174
|
+
with:
|
|
175
|
+
name: dist
|
|
176
|
+
path: dist/
|
|
177
|
+
- name: Notes from CHANGELOG.md, then the release
|
|
178
|
+
env:
|
|
179
|
+
GH_TOKEN: ${{ github.token }}
|
|
180
|
+
TAG: ${{ github.ref_name }}
|
|
181
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
182
|
+
PRE: ${{ needs.build.outputs.prerelease }}
|
|
183
|
+
run: |
|
|
184
|
+
awk -v v="$VERSION" '
|
|
185
|
+
index($0, "## [" v "]") == 1 { on = 1; next }
|
|
186
|
+
on && /^## \[/ { exit }
|
|
187
|
+
on && /^\[[^]]+\]: / { exit }
|
|
188
|
+
on { print }
|
|
189
|
+
' CHANGELOG.md > notes.md
|
|
190
|
+
test -s notes.md || { echo "✗ CHANGELOG.md has no notes for $VERSION"; exit 1; }
|
|
191
|
+
flags=""
|
|
192
|
+
if [ "$PRE" = true ]; then flags="--prerelease"; fi
|
|
193
|
+
gh release create "$TAG" dist/* --verify-tag --title "$VERSION" --notes-file notes.md $flags
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov/
|
|
13
|
+
# local library, downloads and secrets
|
|
14
|
+
/library/
|
|
15
|
+
/cache/
|
|
16
|
+
*.sqlite
|
|
17
|
+
*.sqlite-journal
|
|
18
|
+
.env
|
|
19
|
+
|
|
20
|
+
# Claude Code: personal notes for this checkout
|
|
21
|
+
CLAUDE.local.md
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Instructions for AI coding agents working on this repository are in [CLAUDE.md](CLAUDE.md): the layout, how to
|
|
4
|
+
run the tests and lint, how to add a brand adapter, and the project's rules (fetching politely and never around
|
|
5
|
+
bot protection, no AI required, Apache-2.0-compatible dependencies, colour always paired with a glyph or word,
|
|
6
|
+
and documentation as part of every change). Contributors should also read [CONTRIBUTING.md](CONTRIBUTING.md).
|