Mentions
Conversations linking to GitHub, newest first. Times are UTC.
Mentions by source
X
Bluesky
Hacker News
Lobsters
V2EX
Qiita
DEV
Zenn
Mastodon
Latest posts
Networkforge - Create and modify OpenStreetMap road/cycle/path networks and route with it all in QGIS Hi, I've created a QGIS plugin called Networkforge which allows you to add, remove and change your own proposed roads, cycleways and paths to the OpenStreetMap network all in QGIS. This does not upload anything to OSM, it only gives you a local copy. You can then export these files as osm.pbf files and use them to do routing analysis with the network analyst plugin on QGIS routing-earth/network-analyst-qgis-plugin (valhalla) and easy-r5 GISBoost/easy-R5 (R5) for public transport. I created this because there was no easy way to quickly edit the OSM network to add a new housing development and generate some travel time isochrones to see how close the houses in the development would be to nearby shops and services. Now you can use the Networkforge plugin and do all this in a couple of minutes all inside QGIS instead of going into JOSM. I did use AI assistance to create the engine and plugin from code I wrote a couple years back, but I have tested it extensively manually and there are hundreds of tests all for different edge cases as the OSM network has many tags and rules a network needs to abide by to be valid for routers like Valhalla to route on. If you're interested here is the link to the repo - Yibbzz/networkforge-qgis It should also be on the main QGIS plugin list, so just search for 'Networkforge' and you should see it. All the documentation and a couple tutorials are in the repo. Thanks!
Philips VuePACS client crashes often since Chromium / Edge 151 As it took us two months to find out, maybe this information helps others: VuePACS nativebridge.exe depends on a separately installed browser. As far as I know it can be used with any installed Chromium-based browser, but I guess most installations use Edge for that, as Edge is always present in Windows. So this medical product depends on software, which is updated every few weeks by Microsoft (or any other external update tool like google chrome updater). The client "lost the network connection" (= orange warning), even when people worked actively in the program. This happens, when you use more tabs (every tab is similar to a tab in Edge and every tab opens an additional viewer, so this means: each tab = separate mp.exe task), and you work for more than 15 min in tab2 while tab1 is still open in the background. Then you have to kill nativebridge.exe with the Windows Task Manager and restart the program. VuePACS can also be configured to just use 1 tab, then this issue probably won't address you (but still can address you, if you leave VuePACS open and just lock your PC, then come back after more than 15 min). The crashes are a result of a combination of two changes to chromium-based browsers, in my example Edge: 1. WebSockets are disconnected or capped for sleeping tabs since the introduction of the sleeping tabs feature in Microsoft Edge 88 (January 2021). 2. “Disconnect WebSockets on BFCache entry”: New behavior since Edge 151 (July 2026): Pages with active WebSockets may now be included in the Back/Forward Cache. To cache and freeze the page, Edge now actively and intentionally disconnects existing WebSocket connections upon entry into the cache. eur02.safelinks.protection.outlook.com The default timeout for sleeping tabs is 15 min, so if you not use your second/third/forth tab for more than 15 min, it will disconnect the WebSocket, which crashed the complete VuePACS Client. To solve this issue, you have to: 1. Create a GPO to put your vuepacs URL in the list for "keep tabs always active" 2. BackForwardCacheForWebSockets must be disabled again. Create a second GPO to set the following registry value: HKLM\SOFTWARE\Policies\Microsoft\Edge BackForwardCacheForWebSockets = 0 (REG_DWORD) or via ADMX: Computer Configuration └ Administrative Templates └ Microsoft Edge └ Allow Back/Forward Cache for WebSockets -> Disabled See: Microsoft Edge Browser Policy Documentation BackForwardCacheForWebSocketsAllowed | Microsoft Learn This policy is only temporary and will be removed in the future.
e2e 是面向 Web 与移动端的下一代端到端测试框架。 测试可以混合自然语言目标(Agent 驱动)与传统 locator/断言,同一测试中既可用 Agent 步骤也可用确定性检查。Web 引擎基于 Playwright,支持移动端模拟器。 tester-army/e2e 适合希望在自动化测试中引入 Agent 能力、同时保留传统断言可控性的团队。
zed-industries / zed: Code at the speed of thought – Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter. ★92616 zed-industries/zed
让 Codex 去 Steam 查游戏, Moli 负责哪几步? Moli 拿了 10 月 25 日的 Product Hunt 日榜第一。比起这个排名,我更关心一个实际问题:给 Agent 用的浏览器,接到 Codex 里以后,能替我做什么? 下面从使用者的需求看一个查游戏的例子,图片和视频用的是 9 月已有演示,不是上榜后新做的一次实测。 ## 需求很简单:找一款能和朋友一起玩的游戏 比如在《传送门》和《传送门 2》之间挑一款,我会想知道:哪款支持双人合作,多少钱,电脑配置够不够。演示里先让 Codex 调用 Moli 打开 Steam,然后给它这个要求: > 在 Steam 搜索 Portal,比较 Portal 和 Portal 2 的价格、合作模式和系统要求,然后推荐一款适合与朋友一起玩的游戏。附上商品链接。  *原始英文输入,中文转述在上面。对应视频 6—16 秒:只给候选和比较条件,没有把两款商品页全文贴给它。* 这里分工很清楚:Codex 安排步骤、整理比较和推荐;Moli 去网页里输入搜索词、打开商品页、读取内容。Moli 是可独立在本地运行的开源浏览器,这套用法里,它作为 Codex 的浏览器工具被调用。 下面这段能看到搜索、打开两款商品页,再回到 Codex 出结果的过程。 [观看 Steam 操作过程(51.1 秒,无声)]( bilibili.com) *0—16 秒发指令,17—35 秒搜索并查看商品页,35—51 秒看结果。取自 9 月录屏,等待已压缩;网页视角是同一任务的另次录制,片中已标注。视频时长不是任务耗时。* ## 最后拿到了什么 Codex 推荐的是《传送门 2》:一代是单人游戏,二代有独立的双人合作战役,支持在线或本地分屏合作。 结果里还有价格、合作方式、最低配置和商品链接。录屏中两款都是 US$9.99,这是当时的显示价格,买之前要看当前商店。  *对应视频 35—51 秒。比较和推荐由 Codex 完成;截图中的“58s”也不作为完整任务耗时。* 对我来说,这里的方便之处是:不用先自己找好两个商品页、复制资料,再交给 AI 比较。从搜索、进详情到取回内容,都能放进同一个任务。最后按我关心的条件看表格就行。 不过,这份结果还不能直接当购买结论。一代的存储一栏写着“页面未注明”,需要回查;录屏没逐项展示全部配置的读取,也没把要求和我的电脑配置做匹配。任务到推荐就结束了,没有购买、安装或游玩。 ## 我还想用它查什么 比起挑游戏,我更想继续试试比较产品参数、整理几个活动的时间和报名条件。都是先给候选和条件,再让 Agent 去网页里找,回来按同一个标准整理。这些场景还没在这份素材里验证。 翻项目文档时,我注意到两点:Moli 能运行网页里的 JavaScript,读取动态加载后的内容;也能把内容转成 Markdown,把按钮、链接整理成带名称和类型的页面结构。这样 Agent 拿到的不光是资料,还有继续操作页面需要的信息。 它强调的“轻量”也有具体做法:读网页结构和文字时可以跳过布局、绘制,需要截图或坐标操作时再做相应计算。这段 Steam 演示没测内存和速度,所以这里不做性能比较。已有 Playwright 或 Puppeteer 流程可以通过 CDP 接入,但只支持部分接口,需要拿自己的页面验证。 只查两款游戏,手动打开网页也不麻烦。如果现有 Agent 已经能稳定完成这些步骤,也没必要只为这个例子换工具。我更想看它接下来能否做好连续查几页、按条件整理资料的任务。 想试的话,[Moli 开源项目(GitHub)]( lexmount/moli)里有安装和接入说明。想了解云端托管,可以看 [Lexmount 云浏览器]( browser.lexmount.cn);本地运行 Moli 不需要先开通云服务。 你们让 Agent 做这类网页比较任务时,最容易卡在哪一步:打开目标页、读到完整信息,还是最后把结果比对准确?
T3 Code 是一个 Agent 控制面,让你从桌面、Web 或手机统一操控本机已安装的编码 Agent(Claude Code、Codex、Cursor、OpenCode 等)。 使用你自己的订阅,无需额外付费。支持移动端 App、远程访问与后台服务。主语言 TypeScript。 pingdotgg/t3code 适合同时使用多个编码 Agent、希望统一入口与远程控制的开发者。
Keep in mind, works on my machine. JanDeDobbeleer/waku
DiscImageStudio:CD、DVD、Blu-rayディスクの表面に目に見えるアートワークを焼き付けるツール JiaFeiMiao-K-Cat/DiscImageStudio 要約:DiscImageStudioは、画像をCD・DVD・Blu-rayの記録面に目視できる模様として焼き付けるオープンソースツール。Windows版は画像編集・書き込みに対応し、Linux・macOS版は実験的なコマンドラインでイメージ生成が可能。JiaFeiMiao-K-Cat/DiscImageStudio · GitHub
FreeLLMAPI 把多家免费 LLM 额度聚合到一个 OpenAI 兼容端点后。 目前覆盖约 34 家提供方、635 个免费模型端点,合计每月约 74 亿 token。路由器自动选择可用模型、限流时 failover,密钥加密存储。自托管,支持 Docker。 tashfeenahmed/freellmapi 适合想低成本、稳定调用多家免费模型的开发者与 Agent 工作流。
GitHub: zpx01/qlink Blog: zeeshanp.me QwenGPU post: x.com
こんな感じで使えるはずです yutannihilation/tilewarp t.co
عملت تول صغيرة لويندوز بتسهل الـ drag and drop بين البرامج من أرخم الحاجات لما تكون فاتح حاجات كتير مع بعض وعايز تسحب فايل أو نص من ابلكيشن لابلكيشن تاني. والأرخم لما تبقى ماسك الفايل وقاعد تدور على الابلكيشن اللي عايز تنقله فيه وسط كل الحاجات اللى مفتوحة. التول اللى عملتها اسمها "Cairn" علشان تحل المشكلة دي: بتضيف بار على جنب الشاشة، تحط فيه الفايل، ولما توصل للابلكيشن التاني تسحبه منه وفيه مميزات تانية تقدروا تشوفوها على الموقع mahmoudalnkeeb.github.io و ده السورس كود لو حد مهتم mahmoudalnkeeb/cairn
VercelとNeonにデプロイするまでにやったことを記録した VercelとNeonへのデプロイ手順をdocsに分ける github.com/ryo-i/next-nest-started/commit/bb2ba6e…
Things that broke my backup script when it ran from a timer: PIPESTATUS, tar's exit 1, and a missing gzip My Termux (Android) backup script worked fine by hand but failed when run from a scheduled job. Three lessons, in case they save someone else the time: **1. `tar -z` silently depends on PATH.** tar starts gzip itself, and the timer's PATH didn't include it, so tar died with status 127. Fix: run gzip in the pipe yourself, with a fallback path: gzip=$(command -v gzip || echo /system/bin/gzip) tar -cf - -C "$HOME" . 2>"$errlog" | "$gzip" >"$file" **2. Once it's a pipe, `$?` only tells you about gzip.** Copy `PIPESTATUS` straight away, before any other command overwrites it: st=("${PIPESTATUS[@]}") status=${st[0]} [ "${st[1]}" = 0 ] || status=2 **3. tar exits 1 when a file changes while it's being read** (logs, shell history). The archive is still usable, so treating every non-zero exit as failure threw away good backups. Only 2 or higher is a real failure: if [ "$status" -gt 1 ]; then rm -f "$file"; tail -n 3 "$errlog"; exit 1; fi **Bonus:** keeping only the newest N backups without parsing dates: ls -1t "$DEST"/termux-backup-*.tar.gz | tail -n +$((KEEP + 1)) Full script (MIT): github.com/har00069-hue/termux-scripts/blob/main/… Disclosure: I made this with help from an AI assistant and tested it on my own phone. I also sell a separate paid pack of Termux scripts; this one is free and I'm not linking the paid one. Happy to hear how you'd do any of this better.
The skill is free. Drop it in ~/.claude/skills and type /unslop-design. 42 numbered fixes (one accent, quiet badges, no emoji icons, baseline-aligned numbers…) built on Refactoring UI and real before/afters like this one. marcosnovo/unslop-design
How can I set the game to 1920x1080 in version 1.3? github.com/ThirteenAG/WidescreenFixesPack/release… I installed it, but the game won't launch unless I delete the .asi file. After deleting it, when I try to launch the game, I can't find the 1920x1080 resolution option. What do you recommend I do?
Hangi işlerde kullanılır? ▸ CV'nin otomatik elemeden geçip geçmediğini önceden görmek ▸ İlana göre CV'nde eksik kalan anahtar terimleri bulmak ▸ Mülakat dönmeyen başvurularda CV'deki zayıf yeri bulmak alexyc9381/hiring-panel-skill
@addyosmani Step 3 is the one I automated. consilium adds one command, consult, so my coding agent asks a different AI for a second opinion. It advises and never edits files, so the call stays mine. Addy's piece: x.com Lexus2016/consilium
AltGraph 3.0 "Evolution" update: 3D elevation profile, climb viewer and voice copilot for Hammerhead Karoo 2 / 3 Hi everyone! AltGraph, extension for the Hammerhead Karoo 2 and Karoo 3 that turns your route into a live 3D elevation profile, with the real gradient of every block. After a big round of work and a lot of rider feedback, version 3.0 "Evolution" is out. What is it? - 3D Global Route: your route as a 3D profile coloured by gradient, with a rider beacon, hairpins, points of interest and distance left. - 3D Climb Viewer: each climb with length, elevation, average and max gradient, and a recommended gear. - TV Profile 2D: the stage-style profile from race broadcasts. - Altimetry Radar: a compact bar with the gradient of the next kilometres. - Plus VAM target, gradient zones, fatigue grade and a voice copilot (it needs Bluetooth audio, because the Karoo's speaker only plays system beeps). What's new in 3.0 - Metric or imperial units: charts, radar, climb viewer, 2D profile, VAM, the settings panel and the voice announcements all follow your choice (m/km or ft/mi). - Settings templates (Mountain, Flat and gravel, Race, Clean): one tap applies a set of visual options, and the chosen one stays marked. They never touch your FTP, weight, licence, voice or gearing. - White background + Poster style (black body, gold block lines, scale bar), much easier to read in direct sunlight. - Fitted vertical scale (optional) that adapts to the elevations you see and shows the vertical exaggeration (e.g. 14x). - Points of interest filter with the real Hammerhead types (aid station, ATM, bar, coffee, viewpoint, water, winery…). - Next resupply, estimated arrival time and climbing rate (all optional). - Climb viewer: an optional difficulty score, plus a voice summary at the summit with your time, average gradient and VAM. - Radar: respects the Karoo's rounded corners, shades the part you've already ridden, and keeps the % readable when zoomed. - A more reliable accumulated elevation that filters barometer noise. Available in 6 languages: English, Spanish, Portuguese, German, Italian and French. How to install (no root, about 5 minutes) 1. Download app-release.apk from github.com/DAVOE75/AltGraph/releases/latest 2. Send it to your Karoo with the Hammerhead Companion app. 3. Or, with ADB: enable USB debugging, connect, and run adb install -r app-release.apk. How to get started 1. Open Extensions on the Karoo and open AltGraph (the settings panel). 2. In the Style tab, pick your units and a template. 3. Edit a ride profile and add a data field: 3D Global Route, 3D Climb Viewer, TV Profile 2D or Altimetry Radar. 4. Load a route and ride. The profile is built from your route. Good to know - The voice copilot needs a Bluetooth headset or speaker. - The code is published for viewing; all rights are reserved. ELITE features are free for now and are planned to become a paid subscription later. - The newest options (arrival time, climbing rate, next resupply and the summit voice summary) are still being tested on real rides, and the imperial voice announcements are brand new, so please tell me if something sounds off. Links - Download: github.com/DAVOE75/AltGraph/releases/latest - README: DAVOE75/AltGraph - Original thread: support.hammerhead.io - Support: buymeacoffee.com
TradingAgents 是一个基于多 Agent 协作的 LLM 金融交易研究框架。 它通过多个专业化 Agent(分析、策略、风控、执行等角色)协同工作,对市场信息进行推理与决策模拟,目标是探索大模型在量化交易与投研流程中的应用方式。主语言 Python,本周新增超 4600 Star。 项目地址:TauricResearch/TradingAgents 适合对多 Agent 系统、LLM 投研与自动化交易实验感兴趣的开发者与研究者参考。
A Database Restore Does Not Undo Sent Email or Envelopes Restore a database to last night's backup and the rows go back in time. The email your app sent at 2 a.m. does not. Neither does the e-signature envelope it created, the mailbox password it rotated or the message it appended to a Sent folder. The restored app has no memory of any of them, so it can do them again: a second contract envelope to the same signer, a duplicate confirmation email to the same applicant. This post covers a three-part pattern for that problem. First, a registry: every function that calls an outside service, tagged with how a restore recovers it. Second, a test that reads the source code and fails the build when that registry and the code disagree. Third, for an effect nothing reads back and the review does not close, a check after the restore that asks the outside service what it created since the backup. The app here is an onboarding system. It sends mail over SMTP and through Django's mailer, creates and distributes agreements through Documenso (a self-hosted e-signature service), writes to a mailbox host, appends to IMAP folders and calls LLM agents. It also keeps an effect journal (an append-only log of outward actions, the side effects a rollback cannot reach, kept outside the database). After a restore, a post-restore review reads that journal and the providers, then decides whether to lift the restore hold (release the restored system for normal use). What a restore forgets The journal only helps if it covers every effect. Before this work, that coverage was a hand-written claim in a design document, written after a code review. Nothing checked it. A hand-written list of covered effects drifts in one direction. Code adds a new provider call. Nobody updates the list. The list still reads as complete, and the next restore drill passes because the drill never exercises the new call. A Green Recovery Drill Can Still Be Lying covers other ways a restore check reports green. That is the failure mode to design against. The restore itself is fine. The claim about what the restore can recover is the part that rots. The registry: one recovery class per function and family The fix starts with a data file, workflow/effects.py. It registers 40 functions, two of them markers rather than effects, grouped in seven families: journal writes, SMTP, Django mail, Documenso writes, mailbox host writes, IMAP appends and model runs. Each function gets one class for each family it touches, plus a reason. Six classes make up the recovery vocabulary: - journaled_before: a journal entry is written and fsynced before the effect becomes a fact. Restrictive effects (such as a staff deactivation or an access revocation) are prepared this way. If that write fails, only the erasure and custody records refuse the effect; see below. - journaled_after: the entry is written on commit, once the effect is a fact. A crash can lose it, but it can never invent an effect. - provider_read: no entry. A named post-restore function reads the effect back at the provider. - checked_before: no entry. The action reads the provider before it writes, so a repeated run is idempotent: it finds the effect and skips it. - covered_by_review: nothing reads it back. The post-restore review closes what the effect handed out, such as every live confirmation, password-setup and withdrawal link. - marker: an entry about the journal itself, such as the restore point. Not an effect. Three entries copied from the registry as the first PR shipped it show the shape. The gap on the first one comes back below. "agreements.handlers:CreateAgreement.call": {"documenso": Site(PROVIDER_READ, "a restored instance's envelope is read by id, or found by its external key", by=READ_ENVELOPES, gap="9kg.76")}, "agreements.handlers:DistributeAgreement.call": {"documenso": Site(CHECKED_BEFORE, "re-reads the envelope and skips one already PENDING", by="agreements.handlers:DistributeAgreement.call")}, "agreements.keystore:destroy": {"journal": Site(JOURNALED_BEFORE, "custody key destruction, appended before the unlink", ("custody_key_destroyed",))}, The key is the module plus the function's qualified name. The value maps a family to a Site. The by field names the function responsible for recovery, so a reviewer can follow the claim to code instead of trusting a sentence. Site is a frozen dataclass, copied from the registry with its comments: @dataclass(frozen=True) class Site: cls: str why: str kinds: tuple = () # journal kinds written (the journal family only) via: str = "" # the registered function whose journal entry covers a provider or model call by: str = "" # the function that reads the effect back, checks it first, or closes it in the review gap: str = "" # the bead for a known uncovered part after: tuple = () # kinds of a journaled-before site written after the effect The gap field matters more than it looks. It lets the registry admit an uncovered effect without lying about it, and it must carry a tracked-task ID. The after field lists kinds a journaled_before site writes once the effect is done, such as an outcome record, and the test checks it names some but not all of that site's kinds. Why not a runtime decorator? The obvious design is a decorator: mark each effectful function @effect("smtp") and check coverage at runtime. The PR chose call-site discovery instead, for two reasons. First, discovery needs no change to application code. The registry is data and the test is static. Second, a decorator only knows about functions somebody remembered to decorate. That is the same failure as the hand-written list, moved into the source. Discovery reads the code and finds the provider call whether or not anyone thought to mark it. A new send_mail with no registry entry fails the build. The test: find every outward call with ast tests/test_effect_registry.py parses every source file into an abstract syntax tree with Python's ast module and builds a map of {function: {family: calls}}. Its import resolver handles module imports, aliases, relative imports and by-name imports, because from django.core.mail import send_mail as sm is still a mail call. It has nine test functions. The first three check the registry against the source, and the fourth checks that the scanner knows every provider write method: test_9kg62_every_function_with_an_effect_is_registered_and_no_registration_is_stale(sites) test_9kg62_each_class_matches_what_the_function_does(sites) test_9kg62_every_journal_kind_has_a_writer_and_each_restrictive_kind_is_prepared(sites) test_9kg62_the_scanner_knows_every_write_method_of_each_provider_client() Of the other five, four test the scanner on synthetic source (import spellings, kind resolution, lookalike calls, an unreviewed outward module). One checks the real code against the outward-module allow-list. Between them, the tests fail when: - a function with an effect is unregistered, an entry is stale, or the family is wrong; - the class does not match the calls the function makes; - a journaled_before entry is written after the provider call it precedes in the same function; - a checked_before action writes before it reads; - by or via is missing, or gap is not a tracked-task ID; - a journal kind does not resolve to a literal, a constant or a literal caller argument; - the provider client has a write method the scanner does not know; - any module outside a reviewed allow-list of twelve imports a network or process module, or writes, moves or removes a file. The last two are the drift guards for the scanner itself. If someone adds a new write method to the Documenso client, the scanner has no rule for it, and the fourth test fails until a human teaches it. If a thirteenth module starts opening sockets or deleting files, the allow-list test fails. The ordering rule is the least obvious one. "Journaled before" is only true if the journal write precedes the provider call in source order. Here is the idea, reduced to a hypothetical sketch (not the shipped code): # Hypothetical reduction of the ordering check. def first_call_line(fn, matches): lines = [n.lineno for n in ast.walk(fn) if isinstance(n, ast.Call) and matches(n)] return min(lines, default=None) journal_at = first_call_line(fn, is_journal_write) provider_at = first_call_line(fn, is_provider_call) if journal_at is not None and provider_at is not None: assert journal_at < provider_at This only decides order inside one function. Order across functions is not checked. Verifying the AST test with hand mutations A static test that passes on day one proves nothing about whether it can fail. The same rule for a CI gate is in Make the Guard Prove It Can Fail. So the verification was mutation by hand: break the code or the registry on purpose, one change at a time, and confirm the intended test catches it. The registry test and the repository's test-selection test together ran 28 passed, 0 failed. Then 21 hand mutations, each caught by the test meant to catch it. The first round of twelve covered a dropped site, a stale entry, the wrong class, the wrong kind, a bad via, a missing via, a dropped family, a marker that was really a record, an undeclared kind, a restrictive kind never prepared, an empty reason and an injected unregistered send. The second round of nine: a dropped Django mail site, a missing reader, an absent by, a non-task gap, a removed erasure completion caller, FileCopy writing before it reads, the send_begun journal write moved after SMTP, a dynamic journal kind and a removed staff-role prepare. Separately from the 21, an injected module with outward calls was also caught. What writing the registry and its QA pass found The first version of the registry was incomplete, and a separate QA agent pass (static reading only) showed it. That pass returned seven findings. Five were fixed in the PR. One was filed as a tracked task and one was partly fixed. The PR's own list of five findings, below, draws on both the writing and the QA pass and only partly overlaps the QA agent's seven. Each one says what happened to it. Three applicant mails were missing (fixed). The contact confirmation, the password setup and the withdrawal link go through Django's mailer, not the app's correspondence transport. The first version of the registry missed all three. Django mail is its own family in the registry. The post-restore review covers them by superseding every live link they carry. FileCopy was misclassified (fixed). It was registered as read back by the Sent-folder reader. That reader skips filed copies. What FileCopy actually does is search the Sent folder before it files, so it is now checked_before, with an order check behind it. The revocation's host calls are outside the journal (registered as covered_by_review). Password rotation and forwarder removal run before the transaction that prepares the revocation entry. The review's revocation round covers them, so they are registered covered_by_review. A failed restrictive prepare does not stop its effect (recorded, not changed). The failure is logged, counted and refused at readiness. Only the erasure and custody records refuse their effect outright. This was recorded, not changed. A second envelope after a restore had no coverage at all (filed, then closed by a follow-up PR). An agreement created and distributed after the backup has no row once you restore. Nothing read its envelope. The external key is random, so the restored system would create a new agreement and distribute a second envelope to the same signer. The registry could only mark this as a gap, because a runtime fix did not belong in a test-only PR. That last one was the main finding. Writing and reviewing the registry forced every function through the question "who reads this back?", and for envelopes created after the backup the honest answer was "nobody". The test could not fail on this. It could only carry the gap. Closing the gap with a bounded Documenso envelope listing The follow-up PR added an unknown-envelope scan to the post-restore Documenso read, agreements.restore.read_envelopes. It lists the Documenso team's envelopes from every template, newest first, back to one hour before the backup's start, so a template the restored tables do not know is covered too. Any envelope with the app's key shape (ag_ plus 32 hex characters) that no restored agreement or envelope attempt names, by envelope id or external key, opens one review item. The item is keyed by a digest of the envelope id, and the raw id is stored in neither the item nor its audit event. It carries the application only when exactly one verified applicant is a signer. The listing is bounded by a configured page limit (the PR's risk note puts it at 20 pages of 100). If it hits that limit before reaching the backup's start, it reports incomplete:listing_limit, and any status other than ok refuses to lift the restore hold. A truncated listing is treated as incomplete, never as clean. The registry entry's gap was then removed, as the task's acceptance required. That PR's targeted run was 82 passed. The gap is closed in code, pending the provider check described below. Why not journal every create instead? The alternative was a journal entry per envelope create. The listing won for two reasons. It already existed for reconciliation, so no new write path. And it finds a create whose journal append was lost, which a journal entry cannot do by definition. Envelopes without the app's key shape belong to another writer on the same Documenso team and are ignored. The cost of that team-wide listing is volume: another writer's envelopes count toward the page limit. Hitting it fails closed until the system's operator raises the limit or resolves the items by hand. Known blind spots The test's docstring lists its own blind spots: - receivers held in attributes or reached through getattr; - a database write that should be journaled and is not; - whether a named reader actually reads what the entry says. The ordering check also works only inside one function, as noted above. Documenso's real behaviour is also unverified. Its list ordering, default status filter and field names are not confirmed against the installed version, so the scan checks order itself and never trusts a page count. A canary against the real provider is where that gets settled. Resolving an unknown envelope is currently by the operator's note alone, and whether a note is enough is the operator's call. The test is brittle by design. Rename an effectful function, add a provider write method or add a file-writing module and the build fails until someone reviews the change. That is intended: a failing build is the moment the hand-written list used to go stale silently. Also shipped - A journal-ship service copies the effect journal off the production host, so the journal survives the host it describes. It runs with network_mode: none because it only reads and writes files (PR #123, 239 passed in its targeted run). Use this - List every function in your app that calls a provider (mail, e-signature, payments, mailbox APIs) and give it a recovery class for each provider it touches, plus the name of the function that recovers it. - Write a static test that discovers those calls from source, including aliased and relative imports, and fails on any call it can resolve that the registry has not classified; then mutate it by hand until each failure mode has been caught once. - For an effect that nothing reads back and the review does not close, add a post-restore provider listing with a time floor just before the backup's start and a page limit, make a truncated listing keep the restore hold in place, and confirm the provider's list behaviour against the real service. The registry and its AST test are in catalyst-onboarding-v2 PR #117. Frequently asked questions What is an effect journal? An effect journal is an append-only log of outward actions, kept outside the database. After a restore, a post-restore review reads that journal and the providers, then decides whether to lift the restore hold. How do you stop a restored app from repeating an email or envelope? Give every function that calls an outside service one recovery class per provider family in a registry, and name the function that recovers it. A checked_before action reads the provider before it writes, so a repeated run finds the effect and skips it. Where nothing reads an effect back and the review does not close it, a bounded provider listing looks for envelopes the restored database does not name. How can a test catch an effect nobody registered? An AST test parses every source file, follows aliased and relative imports, and fails the build when a function with an effect is unregistered, stale or in the wrong class. It was checked with 21 hand mutations, each caught by the test meant to catch it. It still cannot see receivers held in attributes or reached through getattr. <script type="application/ld+json"> { "@context": "schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "What is an effect journal?", "acceptedAnswer": { "@type": "Answer", "text": "An effect journal is an append-only log of outward actions, kept outside the database. After a restore, a post-restore review reads that journal and the providers, then decides whether to lift the restore hold." } }, { "@type": "Question", "name": "How do you stop a restored app from repeating an email or envelope?", "acceptedAnswer": { "@type": "Answer", "text": "Give every function that calls an outside service one recovery class per provider family in a registry, and name the function that recovers it. A checked_before action reads the provider before it writes, so a repeated run finds the effect and skips it. Where nothing reads an effect back and the review does not close it, a bounded provider listing looks for envelopes the restored database does not name." } }, { "@type": "Question", "name": "How can a test catch an effect nobody registered?", "acceptedAnswer": { "@type": "Answer", "text": "An AST test parses every source file, follows aliased and relative imports, and fails the build when a function with an effect is unregistered, stale or in the wrong class. It was checked with 21 hand mutations, each caught by the test meant to catch it. It still cannot see receivers held in attributes or reached through getattr." } } ] } </script> Related Posts - Every Claim Needs a Shipped Source and an Executable Proof - The Drills Passed. Reality Did Not. - Rehearse the deploy, not the test suite
Total power usage in HWiNFO with data from a Shelly smart plug Some of you out there may already have a PSU that can report power usage to HWiNFO, but for those of us that don't, I've made a small tray app that can read power usage from a Gen2 or Gen3 Shelly smart plug (a local HTTP endpoint that serves JSON) and get it right into HWiNFO. It has a database of currently 524 power supplies, with test results provided by Cybenetics with their written permission, and use that data to calculate the DC side power usage. It can show: - Total AC power usage - Total DC power usage - PSU loss - Power usage of other components (DC side minus known values like CPU, GPU and RAM, so this figure is essentially everything else like chipset, SSDs, fans and so on) - Additional Shellys, for example one for your monitor, so you get total power usage for your desk setup The source code is under the MIT license (the Cybenetics PSU data is not), and everything is available over at Github: eksdeexD/ShellyHwinfoBridge Feedback, questions, and feature requests welcome!
SSRF Egress Proxy: Deny Your Own Public Production Hosts An SSRF filter (a guard against server-side request forgery, which stops a server fetching URLs an attacker chose) that admits only public addresses still lets your server-side fetcher reach your own production hosts, because those hosts have public addresses. This write-up ends with a deny check for an egress proxy that refuses those hosts by name, by every address they resolve to, and again by the address the socket actually connected to, without letting a DNS hiccup lock all egress shut. The case is an onboarding app that fetches links applicants submit as evidence. All of that traffic leaves through an egress proxy (the one process allowed to open outbound connections). The proxy already refused private, loopback, link-local and cloud metadata addresses (the endpoint that hands out instance credentials). Anything with an ordinary public address (global unicast, in IP terms) went through. That rule keeps a fetcher off your internal network. It says nothing about the hosts you run on the public internet. An applicant's link, or a redirect from a harmless-looking page, could aim the fetcher straight at them. Real evidence fetching is still switched off in production. This check had to land before it could be switched on. How the fetcher role is split out of the web container is covered in Docker Container Security Hardening: What Unit Tests Miss. The naive fixes are worse than the gap. Remember every address a production name ever resolved to, and the set grows until the guard refuses everything. Refuse whenever a lookup fails, and one flaky nameserver stops all fetching. The first version of this change did both, and a review agent caught it before merge. Can a public-address SSRF filter still reach your own production hosts? Yes. An SSRF filter that admits only public addresses still lets your fetcher reach your own production hosts, because those hosts have public addresses. A public-address classifier cannot say "public, but ours", so the fix is a second list of host names. The proxy checks that list by name, by every resolved address and by the connected peer. Why the public-address rule cannot express "mine" A global unicast classifier answers one question: is this address on the public internet? Your production hosts answer yes. The classifier has no way to say "public, but ours", so a second list is needed. That list has to name your hosts specifically, and it has to hold up when DNS moves underneath it. The same mismatch, a check that validated a hostname while the fetch routed by address, came up earlier in the custom-feed field of an RSS bar plugin for Omarchy (an opinionated Arch Linux desktop setup by DHH). See Every Verdict Carries the Scope It Actually Ran. What the deny list file must contain The deny list is a secret-free file of logical host names, pointed to by an environment variable. Format rules: - ASCII only, at most 64 KiB and 256 names. - One dotted DNS name per line, with # comments. - Each entry denies itself and every name under it. - An IP address, a single label, or a label written as a 0x hex number (such as 0x7f) makes the file malformed. What three checks does the egress proxy run on each request? The proxy checks every request three times against that list: 1. By name, before any lookup. The requested host is compared with the deny names. A match is refused with host_denied. 2. By resolved address. Every address the target resolves to is compared with every address the deny names resolve to. A match is refused with address_denied. This catches a different name pointing at the same machine. 3. By connected peer. After the socket connects, the peer address is classified again and compared with a fresh resolution of the deny names. A match is refused with peer_denied. This catches a deny name whose DNS answer moves onto the target's address between the check and the connection. The target itself is not resolved again: the proxy connects to the addresses it already checked. Here is the request path from assessments/egress.py (the name check happens inside policy.target()), cut off once the tunnel is accepted: def handle(client, policy: Policy) -> str: upstream = None try: host, port, addresses, extra = policy.target(read_head(client)) if not addresses or not all(classify(ip, extra) for ip in addresses): raise Refusal(403, "address_not_allowed") denied = policy.denied_addresses() if any(canonical(ip) in denied for ip in addresses): raise Refusal(403, "address_denied") for address in addresses: # every answer already passed classify; try each until one connects try: upstream = socket.create_connection((address, port), timeout=CONNECT_SECONDS) break except OSError: continue if upstream is None: raise Refusal(502, "connect_failed") peer = upstream.getpeername()[0] if not classify(peer, extra): raise Refusal(403, "peer_not_allowed") if canonical(peer) in policy.denied_addresses(): # resolved afresh: a DNS change since the check is caught here raise Refusal(403, "peer_denied") reply(client, 200, "ok", peer) # ... tunnel splice, except and finally omitted Two details matter. The address and peer checks run after the public-address check, so they only ever narrow what was already allowed. And denied_addresses() runs twice per connection, one lookup pass of the deny names each time. Each pass runs in one thread and is bounded on its own by the existing 10-second deadline. One spelling per address Address comparison by string fails quietly. ::ffff:203.0.113.10 (an IPv4-mapped IPv6 address, defined in RFC 4291 section 2.5.5.2), ::ffff:cb00:710a and 203.0.113.10 name one IPv4 host. A link-local IPv6 address can also carry a zone suffix such as %eth0. If the deny set holds one spelling and the target arrives in another, the set membership test misses. def canonical(ip: str) -> str: """One spelling per address: the zone dropped and an IPv4-mapped IPv6 address unwrapped, so `::ffff:a.b.c.d`, its hex form and `a.b.c.d` compare equal. Anything that is not an address is "".""" try: addr = ipaddress.ip_address(str(ip).split("%", 1)[0]) except ValueError: return "" if addr.version == 6 and addr.ipv4_mapped is not None: addr = addr.ipv4_mapped return str(addr) Both sides of every comparison go through this function. The tests cover a production address arriving as IPv4, IPv6, ::ffff:a.b.c.d and ::ffff:hhhh:hhhh. Why not list the production addresses instead? A file of IP addresses looks simpler. It breaks the first time a host moves, and it silently stops protecting the new address. Logical names survive a move, and the file stays free of anything sensitive. Resolving at check time handles DNS changes in both directions. A name that moves to a new address is caught by the fresh answer. An address the name pointed at recently stays denied from memory until newer addresses push it out of a capped history. A change between the check and the connect is caught at the peer. The other obvious choice was to exit at startup when the file is broken. That was rejected too. Exiting leaves the fetcher with nothing to act on. A live proxy answering 503 deny_list_unavailable tells the fetcher to retry the item instead of closing it, and the failing healthcheck marks the container unhealthy where an operator can see it. The first version could shut all egress The implementation ran from a Claude Opus 5.5 session coordinating specialist agents. Its first version kept every address a denied name had ever resolved to. The commit message called it a never-shrinking union, chosen deliberately to cover DNS rebinding in both directions. def denied_addresses(self) -> frozenset: answers = resolve_names(self.deny) if any(addresses is None for addresses in answers.values()): raise Refusal(502, "deny_list_unresolved") fresh = {canonical(ip) for addresses in answers.values() for ip in addresses} - {""} with self.lock: if len(self.denied_seen | fresh) > DENY_SEEN_LIMIT: raise Refusal(502, "deny_list_unresolved") self.denied_seen |= fresh return frozenset(self.denied_seen) Read it as an operator would. Two lines can stop all egress. The lookup-failure one recovers only once DNS answers again; the size limit never recovers without a restart. A read-only security and privacy review agent, dispatched on the open pull request, returned these as its first three findings, each rated medium: 1. The latch. Once the remembered set would pass DENY_SEEN_LIMIT (4096), every request returns 502 until a restart. A deny name served from a rotating or geo-DNS pool keeps growing the set. The result stops all fetching, well beyond the one host it meant to block. The review also noted each CONNECT started one thread per name, twice, so an operator error in the list could cause thread storms. 2. One flaky name blocks everything. Any SERVFAIL, timeout or EAI_AGAIN on any deny name made every fetch return 502. A decommissioned name whose nameserver stopped answering, or a resolver outage, becomes a lasting denial of service. The review noted it could never become a silent allow, which is the correct direction; the price was all fetching. 3. A non-resolving name denies nothing, silently. NXDOMAIN and NODATA become an empty answer. A typo in the list, or a split-horizon name the egress container cannot resolve, gives zero protection and raises nothing. Fail-closed and stay-available pull in opposite directions. The first draft chose closed at every fork, including the forks where closed meant closing everything. Each review finding got its own fix-up commit before merge, and each code finding its own test. The third is only partly closed, as the next section says. The bounded version: per-name memory with last-good answers def denied_addresses(self) -> frozenset: answers = resolve_names(self.deny) with self.lock: failed = [name for name, addresses in answers.items() if addresses is None] if any(name not in self.remembered for name in failed): raise Refusal(502, "deny_list_unresolved") for name, addresses in answers.items(): if addresses is not None: self.remember(name, addresses) if failed: logger.info("egress deny_lookup_stale") denied = frozenset(ip for kept in self.remembered.values() for ip in kept) if not denied: # a list in which no name resolves denies nothing at all: refuse rather than pretend raise Refusal(503, "deny_list_resolves_nothing") return denied What changed, finding by finding: - Memory is bounded per name, with no global limit. remember() keeps a name's fresh answer in full, plus earlier addresses up to 64 in all (a fresh answer larger than 64 is kept whole). The oldest address missing from the fresh answer is evicted first. The file is capped at 256 names on load. No global limit remains that could latch the proxy shut. - A failed lookup keeps that name's last good answer. The fallback is logged as one outcome code, deny_lookup_stale, never the name. Resolution now runs as one lookup pass in a single thread. - Refusal is reserved for unknowns. A name whose lookup has never succeeded (SERVFAIL or a timeout, not a clean "no such name") still refuses with 502 deny_list_unresolved, which the fetcher retries. If that name's lookups keep failing, every request keeps refusing until one succeeds; the refusal is retryable, and the startup count shows the gap. If no name in the list resolves at all, every request gets 503 deny_list_resolves_nothing. - Startup reports the gap. A pass at start seeds each name's last good answer and logs deny_names_unresolved=N, a count without names. One name that does not exist, inside a list where others do resolve, still denies nothing. The startup count is the only signal for that case, so it is worth alerting on. When the deny file is missing or malformed A missing, unreadable, oversized, empty or malformed file sends every request a 503 deny_list_unavailable. The fetcher treats that as transient and retries. The code is logged once at start, and the healthcheck (python -m assessments.egress --check) fails while the list does not load. Staging bind-mounts its deny file instead of baking it into the image. Production is meant to mount the operator's file the same way, so staging exercises that path. The real production list is still a pending host-preparation item. A separate test asserts that egress refusal codes never reach the model request the clarification path starts from, any mail, or a non-staff template. Limits that remain - Peer refusal comes after the handshake. A peer_denied connection completes its TCP handshake with the denied host before it is closed. Zero bytes are sent. - The resolver is trusted. A resolver that answered the deny names falsely would hide their addresses. Network isolation, the host allow-list and TLS hostname verification remain the controls for that. - Changes need a restart. The file is read at start. A replaced bind-mounted file keeps a stale inode, so mount the directory or restart. - Hex labels cost a real name. A host such as 0xab.example.org cannot be listed. Listing it makes the whole file malformed, so every request gets 503 deny_list_unavailable. Verification The change added 25 tests in tests/test_egress.py. They cover a name and a name under it refused before lookup, the four address spellings, a DNS change between requests and between check and connect, per-name bounds without a latch, the last good answer, the never-resolved name, one thread per pass, the all-unresolved list with its startup count, and missing, empty, comment-only, malformed, non-ASCII and oversized files. The full suite on the final head passed 1744 tests in 50 min 21 s. The run before the review, which already held the first version's tests, passed 1733. The staging check runs against the compose stack on the dev box. A public stand-in plays the production host. It was refused by name, a name under it was refused, and 1.1.1.1.nip.io and 1.0.0.1.nip.io (names that resolve to the address written in them) were refused by resolved address. A control request to an ordinary public name, which the staging stack allows, tunnelled through. Staging also caught one more wobble. The first run after the review scored 132 PASS, 0 FAIL, 1 NOT RUN: a by-address case missed a single transient lookup. The probe now retries before reporting; it already printed NOT RUN rather than FAIL when the proxy cannot reach public DNS. The final run on a clean tree scored 133 PASS, 0 FAIL, 0 NOT RUN, with the egress healthcheck passing compose up --wait. The merged change, one commit per review finding, is at catalyst-onboarding-v2#101. Frequently asked questions How does an SSRF deny list avoid blocking all egress when DNS fails? A failed lookup keeps the last good answer for that name, and the proxy logs only the outcome code deny_lookup_stale, never the name. A name whose lookup has never succeeded (SERVFAIL or a timeout, not a clean "no such name") refuses with 502 deny_list_unresolved, which the fetcher retries. If no name in the list resolves at all, every request gets 503 deny_list_resolves_nothing. A name that does not exist, inside a list where others resolve, still denies nothing, so the startup count is the signal to alert on. Why does a deny list miss an IPv4-mapped IPv6 address? One host can carry several spellings: ::ffff:203.0.113.10, ::ffff:cb00:710a and 203.0.113.10 all name the same IPv4 host. A string comparison against a single spelling misses the others. The proxy unwraps IPv4-mapped IPv6 and drops any zone suffix such as %eth0 on both sides of every comparison, and the tests cover the IPv4, IPv6, ::ffff:a.b.c.d and ::ffff:hhhh:hhhh forms. Can a DNS change between the check and the connection get past an SSRF deny list? Not the target's own DNS: the proxy connects to the target addresses it already checked, so the target name is not resolved again. If a deny name's DNS answer moves onto that address in the meantime, the peer check catches it as long as that fresh lookup succeeds (a failed lookup falls back to the last good answer): it compares the peer with a fresh resolution of the deny names and refuses with peer_denied before any bytes are sent. The TCP handshake with the denied host still completes first. <script type="application/ld+json"> {"@context": "schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "How does an SSRF deny list avoid blocking all egress when DNS fails?", "acceptedAnswer": {"@type": "Answer", "text": "A failed lookup keeps the last good answer for that name, and the proxy logs only the outcome code deny_lookup_stale, never the name. A name whose lookup has never succeeded (SERVFAIL or a timeout, not a clean \"no such name\") refuses with 502 deny_list_unresolved, which the fetcher retries. If no name in the list resolves at all, every request gets 503 deny_list_resolves_nothing. A name that does not exist, inside a list where others resolve, still denies nothing, so the startup count is the signal to alert on."}}, {"@type": "Question", "name": "Why does a deny list miss an IPv4-mapped IPv6 address?", "acceptedAnswer": {"@type": "Answer", "text": "One host can carry several spellings: ::ffff:203.0.113.10, ::ffff:cb00:710a and 203.0.113.10 all name the same IPv4 host. A string comparison against a single spelling misses the others. The proxy unwraps IPv4-mapped IPv6 and drops any zone suffix such as %eth0 on both sides of every comparison, and the tests cover the IPv4, IPv6, ::ffff:a.b.c.d and ::ffff:hhhh:hhhh forms."}}, {"@type": "Question", "name": "Can a DNS change between the check and the connection get past an SSRF deny list?", "acceptedAnswer": {"@type": "Answer", "text": "Not the target's own DNS: the proxy connects to the target addresses it already checked, so the target name is not resolved again. If a deny name's DNS answer moves onto that address in the meantime, the peer check catches it as long as that fresh lookup succeeds (a failed lookup falls back to the last good answer): it compares the peer with a fresh resolution of the deny names and refuses with peer_denied before any bytes are sent. The TCP handshake with the denied host still completes first."}}]} </script> Also shipped - Same app: an authorized erasure path with an audited tombstone exception and a durable record kept outside the backup. - bobs-big-brain-plugin: installer cleanup confined to validated targets, with recoverable data preserved. - claude-code-plugins: five unsupported SaaS tutorial packs retired, and the security plugin's HTTP method probes constrained to owned fixtures. - diagnostic-pro: homepage contrast and keyboard access fixed. - intent-whop-products: new repo scaffolded with CI and releases on a self-hosted runner. Use this - List your production hosts by name, and compare addresses in one form. Resolve the names at check time, unwrap IPv4-mapped IPv6 and drop the zone on both sides, and refuse subdomains of each entry too. - Check the connected peer, after the resolved answer. Run getpeername() through the same classifier and a fresh deny resolution, so a deny name that moves onto the target's address between check and connect is refused before a byte is sent. - Bound what you remember, per name. Keep the fresh answer plus a capped history, fall back to the last good answer on a failed lookup, and refuse only for names whose lookup never succeeded, a list that resolves to nothing, or a file that does not load, each with a retryable code. Related posts - Docker Container Security Hardening: What Unit Tests Miss: the same app's staging deploy, five hardening controls that passed their own tests, and the review question behind each. - Every Verdict Carries the Scope It Actually Ran: every verdict covers only what it ran, including a request-forgery class closed by deleting the feature. - Rate Limit a Django Signup Form Without Refusing Anyone: the same app again, deferring mail instead of refusing applicants, a fail-safe that does not punish users.
AiSOC is an open-source AI security operations center that logs every agent decision to a replayable ledger. For security and production operations, an agent that cannot prove why it took an action is impossible to audit or trust. beenuar/AiSOC
Pour approfondir l'implémentation : 🔗 floci-io/floci
Seal Plus 是一个开源的 Android 视频、音频下载工具,复制链接就能解析,底层接 yt-dlp,支持 1000 多个网站。 YouTube、Instagram、TikTok、X 等都在支持范围里,画质和音频格式可以自己选,也能下载整个播放列表。 GitHub:MaheshTechnicals/Sealplus 我觉得把字幕一起存下来很方便,可以直接嵌进视频,也能单独下载字幕文件,手动字幕、自动字幕都支持。 只要声音时能提取成 MP3、M4A、FLAC 等格式,封面和音频信息也能写入文件。下载中断后可以接着下,历史记录能搜索。 有现成的安卓安装包,手机上就能操作。只保存自有或已获授权的内容,拿来整理离线学习材料挺合适。
3/ If you want the full thing (module boundaries, catching agent shortcuts, scrubbing through your history), strata is open source: themonk911/strata
Le grand défi reste ouvert : cassez le Blob. Une contradiction interne, une hypothèse cachée, un résultat non reproductible. Argumentez comme un relecteur de revue ; votre nom ira au mur des réfutateurs. The-blob-Principle#13
Ghostty 是由 HashiCorp 创始人 Mitchell Hashimoto 主导的跨平台 GPU 加速终端模拟器。 主语言 Zig,强调原生性能、低延迟渲染与高度可配置性。支持 macOS 与 Linux,在字体渲染、窗口管理、快捷键与主题定制上做了深度优化,目标是兼顾现代体验与极致效率。 项目地址:ghostty-org/ghostty 近期持续稳定增长,已成为不少追求高性能与可定制终端体验的开发者首选之一。
cmuxについて雑に呟いたら、issueが立っていた。すごい。AIで改善活動してる! cmux#33707
Repo: farzinnasiri/vibeworks The goal is to close the loop from idea to manufacturing: design, checks, production files, 3D prints. Agents execute and humans review and decide. Try it with your own agent, star it if you want to follow along, or open an issue with something you'd want to add or change or fix!
AWS FinOps Field Notes (2026-11-03): Automating Orphaned EBS & Storage Cleanup --- title: "AWS FinOps Field Notes (2026-11-03): Automating Orphaned EBS & Storage Cleanup" published: true tags: aws, python, finops, devops --- While auditing cloud infrastructure costs this week, recurring configuration gaps continue to quietly inflate monthly cloud bills. Field Insight: The Hidden Cost Trap Fifty abandoned 100 GB unattached EBS volumes cost $500/month ($6,000/year). Without automated cross-account visibility, volumes left behind by terminated instances quietly accumulate billing. Automated Detection: Auditing Detached EBS Volumes with Boto3 import boto3 def audit_unattached_ebs(region='us-east-1'): ec2 = boto3.client('ec2', region_name=region) volumes = ec2.describe_volumes( Filters=[{'Name': 'status', 'Values': ['available']}] ).get('Volumes', []) print(f"[{region}] Found {len(volumes)} unattached volume(s):") for vol in volumes: print(f" - {vol['VolumeId']} | {vol['Size']} GiB ({vol['VolumeType']})") return volumes if __name__ == '__main__': audit_unattached_ebs() Running one-off audit scripts in single regions does not scale across enterprise AWS Organizations. Sustained cloud cost management requires cross-account role assumption (sts:AssumeRole), cost anomaly correlation, and automated scheduled auditing. - 👉 Download the free open-source CLI on GitHub: aws-finops-auditor-lite - 👉 Need multi-account AWS Organization (sts:AssumeRole) scanning and automated CI/CD schedules? Get the Enterprise AWS FinOps Auditor ($99 one-time)
Ghidra 是 NSA 开源的软件逆向工程框架,目前仍是业界最重要的免费工具之一。 支持多架构反编译、静态分析、脚本扩展(Java / Python)、协作逆向等核心能力,功能覆盖二进制分析、漏洞研究与固件逆向。本月新增超 1.7 万 Star,主语言 Java,Apache 2.0 协议。 项目地址:NationalSecurityAgency/ghidra 虽然官方更新节奏相对稳健,但它依然是许多专业逆向工程师、安全研究员和 CTF 选手的首选开源方案,也是 IDA Pro 之外最完整的免费替代。
The easiest way to host any project on your own VPS, no sysadmin skills required The idea for PanelAlpha Engine came from a common problem these days, which is that hosting an app is still nowhere near as easy as building it has become thanks to AI. Engine is an open source tool that lets your AI agent deploy, manage and maintain projects on your own VPS. With just one command, you can install Engine together with any app, whether it's something you've vibe-coded yourself or a project you found online. Engine supports MCP, so you can manage your projects and hosting just by chatting with your favorite AI agent. Ask it to deploy an app, check logs, set up a domain or manage backups and it'll use Engine to handle all the technical work behind self-hosting. There's also a CLI and REST API if that's more your thing, as well as all the hosting essentials like Docker isolation, SSL, databases, backups, staging environments, monitoring, firewall protection and Cloudflare integrations. It's completely free, licensed under Apache 2.0, and everything runs on your own VPS. There's no hosting platform in the middle so you're free from lock-in. Here's the repo: panelalpha/panelalpha-engine We're still actively working on it so feedback, suggestions and GitHub issues are all very welcome. Especially if you've tried self-hosting before and ran into something that made you want to give up halfway through :)
2/ Corpus: LangChain docs + OpenAI Cookbook, pinned to exact commits. You can also upload your own PDF or Markdown. shisukeUrahara/atlas
I built an open-source tool that lets your coding agent film screen recordings I like Screen Studio-style videos: a smooth cursor, zooms that follow the action, a nice gradient behind the window. Making them by hand means recording, re-recording, then editing. So I built Screenplay, an open-source tool that lets your coding agent do it. Repo (MIT): gmpetrov/agent-screenplay !Storyboard of a Screenplay recording How it works You ask your agent (Claude Code, Codex, Cursor or any MCP client) for a video: /screenplay Make a 30-second walkthrough of https://yoursite.com 1. The agent opens the site, explores it, and writes a short JSON scenario: open this page, click this button, type that, hold here, zoom there. 2. Screenplay runs check, which plays the steps fast without filming, so broken selectors show up before the real take. 3. record plays the scenario in headless Chromium (Playwright), films it, then edits it: smoothed cursor, automatic zooms, click ripples, callouts, spotlight, captions and an optional voice-over. 4. The agent looks at the storyboard image of the take and fixes the scenario if a shot is off. Re-editing the look doesn't need a new take: render. Desktop apps work too (macOS, Windows, Linux): the same kind of scenario is played through the OS accessibility APIs, and the window is filmed with ffmpeg. A scenario Targets are named the way people see them (text=…, role=button[name='…'], label=…), so the agent doesn't need CSS selectors: { "url": "https://app.example.com/inbox", "style": { "background": "dawn" }, "steps": [ { "click": "text=Camille Laurent", "say": "When the AI agent gets stuck, the conversation lands in your inbox." }, { "callout": "role=button[name='Take over']", "text": "Take over from the AI agent" }, { "click": "role=button[name='Take over']", "say": "One click, and the conversation is yours." }, { "type": "Hi Camille, I'm on it.", "into": "placeholder=Write a reply…" }, { "click": "role=button[name='Send']" } ] } Design choices - No AI inside, no API key. Your agent does the thinking; Screenplay only plays, films and edits. It's a CLI plus an MCP server. - Headless first. Coding agents often run in containers without a screen, and they still need to film. - Own renderer. Frames are drawn with @napi-rs/canvas and encoded with ffmpeg (both ship with the package). I didn't want a rendering library whose licence makes companies pay, in an MIT tool. - Voice-over with Cartesia or ElevenLabs, or with your own audio files (from any TTS, local models included). The video waits for each sentence, so the voice sets the pace. Try it npx agent-screenplay init init downloads Chromium once, adds the MCP server to Claude Code, Codex and Cursor, and installs the guide that teaches the agent to write good scenarios. Restart your agent, then ask for a video. It's brand new, so I'd love feedback, especially on the scenario format and the default look. Issues and stars are very welcome: gmpetrov/agent-screenplay Written with an AI agent and published at my request; every fact comes from the repo.
@MohdAna62 @justonedev I posted the HOL Guard MCP guide here so it’s easy to share in Discord: github.com/hashgraph-online/hol-guard/blob/main/d…
Get the SwarmSec Audit Plugin for your Claude Agent swarmsec-app/SwarmSec-Audit-plugin
@helloyuki_ They are totally possible! The leader L was sorta impossible (which was a total bummer), but I have fixed it with my plugin, please check it out CruelAddict/resh
Agent retries are the normal path, not a bug. Design for that: a retry with the same key returns the original receipt instead of dispatching again. A new key is a new request. AMW (Preview, pre revenue). Apache 2.0 (gateway is Fair Source, FSL-1.1). github.com/agentmiddleware
Photon: An independent browser built on the Ladybird engine Hi everyone, I wanted to introduce you to Photon, an independent browser built on the Ladybird browser engine and gpui(the gpu-accelerated framework which powers zed) for the browser ui. The idea behind Photon is pretty straightforward: build a fast, private, and modern browser with a fresh approach to browsing the web. For those unfamiliar with Ladybird, it's an independent browser engine being developed from the ground up, aiming to bring more diversity to the browser engine ecosystem. Photon uses ladybird browser's engine. It's still a work in progress, so don't expect everything to be perfect just yet. But that's also why getting more people to try it out now would be useful. If you're curious about this project, interested in alternative browser engines, or just like trying out new browser projects, give Photon a try. Feedback and bug reports would be especially helpful at this stage. Check out Photon: - Website: photonapp.co.uk - GitHub: PhotonBrowser/photon - X: x.com - Discord: discord.com
80個の条件を機械学習で取り除いてから効果を推定する:DoubleML で「保育所と女性の労働力率」を実装する 何を確かめたいか 2010→2015年に保育所等に通う子どもが増えた市区町村ほど、女性の労働力率は上がったかを、全国1,611市区町村で調べました。この記事では、その分析を Python でどう実装したかに絞って書きます。結果の読み方は note の記事にまとめています。 使った方法は「ダブル機械学習(DML)」です。そろえたい条件(コントロール変数)が多いときに、結果と処置の両方を機械学習で予測し、予測からの外れ(残差)どうしで回帰して効果を推定します。ライブラリは DoubleML を使いました。 コードはすべて GitHub の articles/F16_childcare_dml/ にあり、ここに載せるのはそこから抜き出したものです。 全体の流れ pip install -r requirements.txt cd src python 02_整形.py # data/raw → data/panel_long.csv python 03_分析.py # results/results.json・estimates.csv(2コアで約30分) python 04_図.py # figures/ | ファイル | やること | |---|---| | 01_取得.md | e-Stat「社会・人口統計体系 市区町村データ」の取り方のメモ | | 02_整形.py | 取得した表を「市区町村×年度」の縦長の表にする | | common.py | 結果・処置・80の条件を作る関数 | | 03_分析.py | 単純な回帰・重回帰・DML(3種類の学習器)、頑健性、プラセボ | | 04_図.py | 図6枚 | requirements.txt は pandas・numpy・statsmodels・scikit-learn・lightgbm・doubleml・matplotlib です。 データの取り方 1. e-Stat の市区町村データを取る データは e-Stat の地域統計(市区町村データ)の表示画面から取りました。API キーの登録は要りません。画面で一度「表示」したあと、そのとき送られる api_get_result(POST)のパラメータのうち cols(項目の一覧)と tops(年度)だけを書き換えて送り直しています。どちらも JSON を gzip して base64 にした文字列で、inputNumberOfRows を 2000 にすると全市区町村が1回で返ります(詳しくは src/01_取得.md)。 項目は1回に25項目までなので、3つのまとまり(b1〜b3)に分けて取り、{"2015b1": [[見出し…], [地域, 値…], …], …} の形の JSON に保存しました。 2. 「市区町村×年度」の表にする 02_整形.py では、まとまりごとの表を年度ごとに横に結合し、最後に縦に積みます。 def tidy(rows, y): h = rows[0]; out = [] for r in rows[1:]: code, name = r[0].split('_', 1) rec = {'code': code, 'name': name, 'year': y} for c, v in zip(h[1:], r[1:]): k = c.split('_')[0]; v = v.replace(',', '') try: rec[k] = float(v) except ValueError: rec[k] = np.nan out.append(rec) return pd.DataFrame(out) def is_ward(code): # 政令指定都市の区(例 01101)は除き、市全体(例 01100)を残す。東京23区は残す return code[2] == '1' and code[4] != '0' and code[:2] != '13' 値は「1,234」のようにカンマ入りの文字列で返ってくるので、カンマを外して float にし、変換できないもの(「-」など)は欠損にしています。政令市は市全体と区の両方が入っているため、区を落として市全体だけを残します。東京23区は区ごとに残します。 3. 結果・処置・条件を作る 結果(女性の労働力率)と処置(保育所等に通う子ども)は common.py の関数で作ります。 def lfpr(g): """女性の労働力率(%)=労働力人口(女)÷(労働力人口(女)+非労働力人口(女))""" return 100 * g.F110102 / (g.F110102 + g.F110802) def enroll(g, kodomoen=False): """保育所等に通う子ども(0〜5歳100人あたり)""" n = g.J2506.copy() if kodomoen: n = n + g.J2545.fillna(0) + g.J2546.fillna(0) return 100 * n / g.A1405 80の条件は、2010年の地域の性格(人口・密度・年齢構成・世帯の形・産業・通勤・所得・財政力・学歴・雇用の形など)、2005→10年の変化(前の傾向)、同じ期間の年齢構成の変化、都道府県のダミー(46列)です。一部を抜き出します。 X['log人口'] = np.log(g.A1101) X['log人口密度'] = np.log(g.A1101 / g.B1103) X['女性65歳以上の割合'] = 100 * g.A130302 / g.A110102 # …(中略)… X['前の5年の労働力率の変化'] = lfpr(g) - lfpr(g5) X['前の5年の在所児の変化'] = enroll(g) - enroll(g5) # 財政力指数は東京23区などで空欄。中央値で埋め、空欄だったことを示す列を足す X['財政力指数が空欄'] = X['財政力指数'].isna().astype(float) X['財政力指数'] = X['財政力指数'].fillna(X['財政力指数'].median()) 4. 推定に使う表をまとめる 03_分析.py の build で、差(2015年−2010年)をとった Y・D と条件を1つの表にします。効果の単位は「在所児が100人あたり10人増えたとき」にしたいので、D を10で割っています。 UNIT = 10 # 効果は「0〜5歳100人あたりの在所児が10人増えたとき」で表す MIN_CHILD = 100 # 0〜5歳が100人未満の町村は、割合が大きく振れるので除く def build(a=2010, b=2015, y_fn=lfpr, d_a=None, d_b=None, x_pre=True, extra_drop=None): X = covariates(w) # …(中略)… Y = y_fn(w[b]) - y_fn(w[a]) D = ((d_b if d_b is not None else enroll(w[b])) - (d_a if d_a is not None else enroll(w[a]))) / UNIT df = pd.concat([Y.rename('Y'), D.rename('D'), X, C, P], axis=1) df = df[(w[2010].A1405 >= MIN_CHILD).reindex(df.index).fillna(False)] df = df.replace([np.inf, -np.inf], np.nan).dropna() これで 1,611 市区町村・80列の表になります。 手法の実装 DML の本体 DoubleML の DoubleMLPLR(部分線形モデル)を使います。ml_l が Y を X から予測するモデル、ml_m が D を X から予測するモデルです。score='partialling out' にすると、「Y の残差を D の残差で回帰する」という形の推定になります。 def _dml_fit(df, kind='rf', folds=5, reps=5, seed=SEED): xcols = [c for c in df.columns if c not in ('Y', 'D')] data = dml.DoubleMLData(df, y_col='Y', d_cols='D', x_cols=xcols) ml_l, ml_m = learners(kind) np.random.seed(seed) m = dml.DoubleMLPLR(data, ml_l=ml_l, ml_m=ml_m, n_folds=folds, n_rep=reps, score='partialling out') m.fit(store_predictions=True) ci = m.confint().iloc[0] out = dict(coef=float(m.coef[0]), se=float(m.se[0]), lo=float(ci.iloc[0]), hi=float(ci.iloc[1]), p=float(m.pval[0]), n=len(df), reps=[float(v) for v in np.ravel(m.all_coef)]) # 予測の当てはまり(それぞれの部分の R²) pl = m.predictions['ml_l'][:, 0, 0]; pm = m.predictions['ml_m'][:, 0, 0] out['r2_y'] = float(1 - np.mean((df.Y - pl) ** 2) / np.var(df.Y)) out['r2_d'] = float(1 - np.mean((df.D - pm) ** 2) / np.var(df.D)) return out n_folds=5 が交差適合(データを5つに分け、4つで学習して残り1つの残差を出す)、n_rep=5 がその分け方のくり返し回数です。くり返した推定値は all_coef に入り、coef はその中央値になります。store_predictions=True にしておくと、交差適合での予測が残るので、2つの予測の当てはまり(R²)も出せます。 学習器を3種類用意する 学習器を変えても答えが大きく動かないかを見るため、ランダムフォレスト・LightGBM・Lasso を用意しました。 def learners(kind): if kind == 'rf': m = RandomForestRegressor(n_estimators=200, min_samples_leaf=5, max_features=0.33, n_jobs=-1, random_state=SEED) return m, m if kind == 'lgbm': m = LGBMRegressor(n_estimators=300, learning_rate=0.04, num_leaves=15, min_child_samples=20, subsample=0.8, subsample_freq=1, colsample_bytree=0.7, verbose=-1, random_state=SEED) return m, m if kind == 'lasso': m = make_pipeline(StandardScaler(), LassoCV(cv=5, random_state=SEED, max_iter=20000)) return m, m Lasso は変数の大きさに左右されるので、StandardScaler と組み合わせています。 比べる相手:単純な回帰と重回帰 DML の値だけでは「80の条件をそろえたことで何が変わったか」が分からないので、statsmodels の OLS(不均一分散に強い HC3 の標準誤差)で、何もそろえない回帰と、同じ80列を直線で入れた重回帰も出します。 def ols(df, controls=True): cols = ['D'] + ([c for c in df.columns if c not in ('Y', 'D')] if controls else []) r = sm.OLS(df.Y, sm.add_constant(df[cols])).fit(cov_type='HC3') lo, hi = r.conf_int().loc['D'] return dict(coef=float(r.params['D']), se=float(r.bse['D']), lo=float(lo), hi=float(hi), p=float(r.pvalues['D']), n=len(df)) 頑健性とプラセボ 同じ dml_fit に渡す表や引数を変えるだけで、頑健性の推定がまとめて書けます。 rob['分割 2'] = dml_fit(df, 'rf', folds=2, key='r_分割 2') rob['乱数を変える'] = dml_fit(df, 'rf', seed=SEED + 99, key='r_乱数を変える') rob['人口1万人未満を除く'] = dml_fit(df[np.exp(df['log人口']) >= 10000], 'rf', key='r_人口1万人未満を除く') rob['前の傾向を入れない'] = dml_fit(df.drop(columns=['前の5年の労働力率の変化', '前の5年の在所児の変化']), 'rf', key='r_前の傾向を入れない') プラセボは、Y を「前の5年(2005→10年)の労働力率の変化」に差し替えます。保育所が増える前の変化まで後の在所児の変化で説明できてしまうなら、「もともと伸びていた町で保育所が増えた」だけかもしれない、という確認です。前の傾向の列と、同じ期間の年齢構成の変化の列は外しておきます。 dp = build(x_pre=False) dp['Y'] = (lfpr(w[2010]) - lfpr(w[2005])).reindex(dp.index) dp = dp.drop(columns=['女性65歳以上の割合の変化', '女性15〜64歳の割合の変化', '0〜5歳の割合の変化', '人口増減率']).dropna() R['placebo'] = dml_fit(dp, 'rf', key='placebo') 結果の出し方 推定値は results/results.json にすべて残し、主なものを results/estimates.csv に表にしています。 | 推定 | 係数 | 95%信頼区間 | |---|---|---| | 単純な回帰 | +0.048 | −0.021〜+0.117 | | 重回帰(80列を直線で) | +0.048 | −0.012〜+0.108 | | DML(Lasso) | +0.047 | −0.010〜+0.103 | | DML(ランダムフォレスト) | +0.061 | −0.002〜+0.125 | | DML(LightGBM) | +0.066 | +0.006〜+0.125 | 単位は「在所児が100人あたり10人増えたときの、女性の労働力率の変化(ポイント)」です。プラスだが小さく、区間が0をまたぐものも多い、という結果でした。 推定値の比較図は、DML を青、比べる相手を灰色にして、errorbar で95%信頼区間を描いています(04_図.py)。 !比べ方で推定値はどう変わるか 交差適合の分け方を20通り変えた推定値(n_rep=20)は、all_coef をそのままヒストグラムにしました。範囲は +0.054〜+0.081 で、すべてプラスでした。 !データの分け方を20通り変えた推定値 頑健性とプラセボは、1枚に横向きで並べています。プラセボはオレンジにしました。プラセボの推定は −0.037(−0.119〜+0.047)で、0と区別できませんでした。 !設定を変えた推定と、前の期間でのプラセボ つまずいた点と落とし穴 1. 1回の実行が長いので、推定ごとに結果を保存する DML はランダムフォレストを「5分割×5回×2つの予測」で学習するので、頑健性まで含めると全体で約30分かかります(2コア)。途中で止まってもやり直さずに済むよう、推定ごとに結果を JSON に書き出し、同じキーがあれば読み込むだけにしました。 CACHE_F = RES / '_cache.json' CACHE = json.load(open(CACHE_F)) if CACHE_F.exists() else {} def dml_fit(df, kind='rf', folds=5, reps=5, seed=SEED, key=None): if key and key in CACHE: return CACHE[key] out = _dml_fit(df, kind, folds, reps, seed) if key: CACHE[key] = out; json.dump(CACHE, open(CACHE_F, 'w'), ensure_ascii=False) print('done', key, round(out['coef'], 4), round(out['lo'], 4), round(out['hi'], 4), flush=True) return out 設定を変えたときは、キャッシュを消してから流し直す必要があります。 2. 空欄を落とすと、東京23区がまるごと消える 財政力指数は東京23区などで空欄です。dropna() をそのまま使うと23区がすべて落ちるので、中央値で埋めたうえで「空欄だった」ことを示す列を足しました。 3. 年度によって、ない項目がある 保育所等在所児数の詳細票(J2506)は2017年度まで、0〜5歳人口(A1405)は国勢調査の年だけです。長い差(2010→20年)の確認では、2017年の在所児を2015年の0〜5歳人口で割っています。 g17 = w[2017].copy(); g17['A1405'] = w[2015].A1405 # 2017年の0〜5歳人口はないので2015年の値で割る d3 = build(a=2010, b=2020, d_a=enroll(w[2010]), d_b=enroll(g17, kodomoen=True)) また、2015年は認定こども園への移行が始まった年で、J2506 に含まれない施設に移った分は「減った」ように見えることがあります。長い差では認定こども園(J2545・J2546)を足して確かめました。 4. 小さな町村では割合が大きく振れる 0〜5歳が数十人の町村では、在所児が数人増えるだけで「100人あたり」の値が大きく動きます。0〜5歳が100人未満の町村は除きました(MIN_CHILD = 100)。人口1万人未満を除く頑健性では推定値が +0.028 まで小さくなるので、小さな町の扱いで結果が動くことは意識しておく必要があります。 5. 重要度は、県のダミーをまとめて見る 「何が予測に効いたか」をランダムフォレストの重要度で見るとき、46列の県ダミーは1列ずつだと小さく見えます。合計して1行にまとめてから並べました。 s = pd.Series(rf.feature_importances_, index=xcols) pref = s[[c for c in xcols if c.startswith('県_')]].sum() s = s[[c for c in xcols if not c.startswith('県_')]] s['都道府県(46のダミーの合計)'] = pref Y の予測にいちばん効いたのは「女性65歳以上の割合の変化」(高齢化)、D の予測には「前の5年の在所児の変化」でした。なお、この重要度は全データで当てはめたもので、DML の推定そのものとは別の参考値です。 言えないこと - 80の条件で取り除けない要因(共働きを望む若い世帯の転入、企業の求人など)があれば、推定は偏ります。DML でも、観察されていない交絡は消えません - 結果は15歳以上の女性全体の労働力率で、子育て中の母親だけの値ではありません - 処置は「定員」ではなく「在所児数」で、需要にも左右されます 参考 - note の記事(結果の読み方):note.com - コードと集計結果:github.com/takayuki-0127/japan-open-data-notes/tr… - 出典:政府統計の総合窓口(e-Stat)(e-stat.go.jp 市区町村データ」(総務省統計局)を加工して作成
Natune (ArchiveTune fork): FLAC fix, UI changes, and a bigger release coming in about a week Hey everyone, Some of you may know my fork of ArchiveTune. I've renamed the app to Natune, but the package name is still the same. What's new in the latest build: Fixed the FLAC playback issues many of you reported Fixed several other bugs from your reports A lot of UI changes It's not the biggest update, but I'm working on something bigger and hope to release everything within a week, if all goes well. Try the latest build: github.com/NatuneGroup/ArchiveTune/releases/tag/v… For developers using ArchivePool: I've moved it to a new domain: archivepool.natune.workers.dev You'll need to log in again and change the domain in your setup. Sorry for the inconvenience! Feedback and bug reports are welcome. Thanks for the support!
So, I did a forking thing. Other than bugfixes submitted to upstream, all new features scratch a personal itch. So go fsck yourself if you think I'm taking feature requests. This is the open source way. dineshalphaque/AlphaDeck
REA 逆向飞书会议纪要,功能比原版更强大,客户端已💯开源,模型💯免费,商用魔改无限制👇 Github:wquguru/cicada 官网直达:cicada.indaloop.app 模型直达:chat.ant-ling.com 支持:腾讯会议、飞书会议、钉钉会议、企业微信会议、Zoom、Google Meet、Microsoft Teams、Webex、X Spaces、Discord 语音频道、Telegram 语音聊天、Clubhouse、YouTube 直播、Twitch、B站直播...
Took my Tuya cat feeder (with camera) fully local: Thingino firmware + MQTT, no cloud left My cat's feeder turned out to be a white-label Tuya device with a camera pointed at her bowl, uploading clips to the cloud. Local Tuya could feed, but the camera wouldn't stream until it had phoned home. So I dumped the flash, sniffed how the camera talks to the feeder's MCU, and flashed Thingino plus a small daemon that bridges the feeder to MQTT. It never touches the internet now. HA gets a feed button, portion counts, LED control and a food-low sensor that Tuya never exposed. Video is plain RTSP, straight into my NVR and HA. Write-up: gritter.nl Repo + HA config: WouterGritter/princess-feeder-thingino (It also ran Doom. Every kill feeds her a portion. She wasn't consulted.)
EPUBリーダーはまだ公開してませんが、フォントだけ先に公開 hayatedayon/dokusho-ud-fonts
Open source DFU Blaster alternate I built a free, open-source DFU utility for macOS I’ve been working on DFUUtility, a free, open-source alternative to tools like DFU Blaster for anyone working with Apple devices. It’s a native macOS app that supports: - Putting supported Apple Silicon Macs into DFU mode - Restoring and reviving Macs, iPhones and iPads - Finding, downloading and managing IPSW firmware - Working with multiple connected devices - Tracking restore progress - Device information capture for technicians I’ve tested it on several physical Macs, iPhones and iPads, and it’s now at version 0.12.1. It’s still a community project and a work in progress, but I’d love for other Apple techs to give it a go and share feedback. Free, open source, no subscriptions.