Mentions
Conversations linking to GitHub, newest first. Times are UTC.
Mentions by source
X
Bluesky
Hacker News
Lobsters
V2EX
Qiita
DEV
Mastodon
Latest posts
Claude Code's best answers die in the terminal. Nobody reads a wall. answer-me-with-html turns that wall into one page. Ran it on our export flow: 4 panels, 0 warnings. Blueprint theme is an engineer sheet; paper is easier. Try it: QingYunA/answer-me-with-html Follow @51AITech t.co
The paid AI visibility tools charge $24 to $295 per month. Here's what open source gives you for free. The AI visibility tool market has a pricing floor of $24/month and a ceiling of $295/month. There are at least 15 paid products tracking how brands appear in ChatGPT, Perplexity, Gemini, and Google AI Overviews. Most of them do something useful. None of them do what an open-source site audit does, and none of them have published a calibration study. Here is what each paid tool costs, what it does well, and where open source covers the same ground for free. The paid landscape I checked the pricing pages of the main tools in October 2026. These are the entry prices for plans that include AI answer tracking: | Tool | Entry price | What it tracks | Engines at entry | |---|---|---|---| | Prefer | $24/mo, 30 prompts | Citations, share of voice, sources | 5 | | Otterly | $29/mo, 15 prompts | Brand mentions, citations, GEO audit | 4 | | LLM Pulse | EUR 49/mo, 50 prompts | Citations per URL per prompt per model | 5 | | Peec AI | $95/mo, 50 prompts | Mentions, sources, gap reports | 3 | | Semrush AI Visibility | $99/mo per domain, 25 prompts | Mentions, sources, competitor analysis | 4 | | Scrunch | $250/mo, 125 prompts | Citations, sources, sentiment, site audit | 4 | | AthenaHQ | $295/mo | Citations, sources, brand integrity | 9+ | All of these track citations or mentions over time. That is their core value: they run your prompts across AI engines on a schedule, store the results, and show you trends. None of them audit your site for AI crawlability. Scrunch includes a site audit, but it is a lighter check than a dedicated audit tool. What open source does for free GEO Optimizer (MIT licensed, 1,137 stars) does a deterministic site audit. It checks: - 27 AI crawlers in robots.txt (GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, and 23 more) - llms.txt presence, validity, and content quality - 7 schema types (JSON-LD: Organization, WebSite, Article, FAQ, Product, HowTo, Review) - Content structure: headings, answer blocks, quotable passages, statistics - Metadata: title, description, canonical, Open Graph, dates, byline - Brand entity clarity and Knowledge Graph signals - Google AI readiness: 8 signals (index, canonical, dates, byline, links, viewport, sitemap, snippets) - Negative signals: JS-only content, prompt injection surfaces, CDN/WAF blocking The audit produces a 0-to-100 score across 9 categories, a list of fixes ranked by priority, and a JSON output for programmatic use. It runs as a CLI, a Python library, an MCP server, a GitHub Action, or a free web audit on geoready.dev with no signup. This is the foundation layer. It tells you whether AI crawlers can reach and understand your page. The paid tools tell you whether you appear in AI answers. These are different questions. The honest gap Open source does not do citation tracking. If you want to know whether ChatGPT cited your domain this week, or how your share of voice changed since last month, the open-source tool does not do that. The paid tools do. This is by design. Citation tracking requires running prompts through AI APIs on a schedule, storing the results, and computing share of voice against competitors. That costs API fees and infrastructure. An open-source CLI that you run locally cannot do that without you supplying API keys and building a scheduling layer. The monitoring layer on geoready.dev fills this gap. It runs audits on a schedule, tracks score history, and alerts when signals change. But it does not yet do citation tracking across ChatGPT and Perplexity. That is on the roadmap. What you actually need Most sites need two things, in this order: 1. Audit first. Before you track whether AI engines cite you, check whether they can read you. If GPTBot is blocked in your robots.txt, or your content is rendered client-side and invisible to crawlers, no amount of citation tracking will help. The audit finds these problems. The paid tracking tools do not. 2. Track second. Once the foundation is in place, track whether you appear in AI answers. This is where the paid tools add value. If you can afford $29/month, Otterly is a reasonable entry point. If you want enterprise depth, Profound or AthenaHQ. If you already have Semrush, their AI Visibility add-on is the path of least resistance. If you cannot afford either, run manual prompt tests. Ask ChatGPT, Perplexity, and Google the same question about your category and see if you appear. It does not scale, but it is free and it works for a small site. The calibration question None of the 15 paid tools has published a calibration study. None of them has tested whether their scores, visibility metrics, or share-of-voice numbers predict anything real. They sell numbers. They do not show whether the numbers are valid. I published a calibration study for GEO Optimizer. The result was null: the GEO score does not predict whether Perplexity cites a page (odds ratio 0.9625, p = 0.7172). That is an uncomfortable result, but it is an honest one. If you are paying $95/month for a visibility score, you should ask the vendor to do the same test. If they will not or cannot, you are paying for a number, not a metric. The comparison | Capability | Open source (GEO Optimizer) | Paid tools ($24-$295/mo) | |---|---|---| | Site audit (crawlability) | Yes, 47 checks, 27 crawlers | Limited (Scrunch only) | | Scoring (0-100) | Yes, 9 categories, rubric v2 | Varies by tool | | Citation tracking | No | Yes, core feature | | Share of voice | No | Yes, most tools | | Competitor benchmarking | No | Yes, most tools | | Historical trends | On geoready.dev (SaaS) | Yes, core feature | | Fix recommendations | Yes, ranked by priority | Varies | | MCP server | Yes (12 tools) | No | | GitHub Action | Yes | No | | Calibration study | Yes (null result) | None published | | Cost | Free | $24-$295/mo | If you want to start with the free audit, it is on geoready.dev. No signup, no account. Paste a URL, get a score and a fix list. The CLI is on GitHub, MIT licensed, 2,000+ tests.
GLM-5.3-Flash の新しい重みである Jammed XSFP4 を、DGX Spark 互換機上の TensorFold で実行できるセットアップをまとめました! TP=2 もしくは TP=3 の環境を使える方は、是非お試しください。諸々安定したら、公式にマージを依頼したいと考えています。 github.com/Bizuayeu/GLM-5.3-Flash-DGX-Sparks-BIZ/…
Let an AI assistant leave an inquiry for a small business, starting with one JSON file An AI assistant should not stop at reading about a business: it should be able to leave an inquiry that a human owner gets as an e-mail. When people look for a roofer, a tax advisor or an electrician, more of them now ask an AI assistant instead of a search engine. The assistant then reads websites that were built for humans: hero images, menus, cookie banners, a services page three clicks deep. It has to guess what the business actually does, and even when it gets it right, the conversation ends there. The first step is telling it clearly. The A2A protocol (Agent2Agent, an open standard for agents talking to services) defines an agent card: a small JSON file at a fixed address, /.well-known/agent-card.json. It says who you are, what you offer and where an agent can reach you. Think of it as a business card for machines. We maintain a small open source kit that writes and checks these cards: agent-card-starter. Python standard library only, Python 3.9 and newer, MIT licensed, no account, works offline. Write your first card pip install agent-card-starter agent-card init \ --name "Muster Roofing Ltd" \ --description "Roofer in Musterstadt, repairs and new roofs." \ --website https://www.example.com \ --skill "Flat roof sealing" agent-card validate .well-known/agent-card.json --strict init validates before it writes and never overwrites an existing card without --force. Pass --skill once per service. validate runs the same checks offline, so you can put it in CI. What a card looks like This is the example from the repo, a fictional German roofer, trimmed: { "name": "Beispiel Dachdecker GmbH", "description": "Fiktives Beispiel. Dachdeckerbetrieb für Neubau, Sanierung und Reparatur ...", "version": "1.0.0", "supportedInterfaces": [ { "url": "https://tuer.beispiel-dachdecker.example/mcp", "protocolBinding": "MCP", "protocolVersion": "2025-06-18" } ], "provider": { "organization": "Beispiel Dachdecker GmbH", "url": "https://www.beispiel-dachdecker.example" }, "skills": [ { "id": "flachdach", "name": "Flachdach abdichten", "description": "Abdichtung und Reparatur von Flachdächern ...", "tags": ["flachdach", "abdichtung", "reparatur"] } ] } The field names follow A2A v1.0. One skill per real service, with a stable id. Upload the file to https://<your-domain>/.well-known/agent-card.json and you are readable. What to keep out of the card The card is public. We learned to keep three things out of it: 1. Prices. They change, and an assistant quoting last year's price helps nobody. 2. Phone numbers and personal e-mail addresses. Scrapers read cards too. 3. Promises. "Available tomorrow" is a promise a human should make, not a JSON file. The validator warns about the first two. The third one is on you. Going one step further: an MCP endpoint MCP (Model Context Protocol) is the other open standard in this space. It lets an assistant call small tools on your side. The kit ships a minimal MCP example server over stdio with two tools: leistungen (list services) and anfrage_anlegen (leave an inquiry). Try it from a clone: printf '{"jsonrpc":"2.0","id":1,"method":"tools/list"}\n' | python3 server/mcp_server.py anfrage_anlegen is the part that matters most to a business owner: the assistant leaves an inquiry, a human answers. In the kit it is a stub on purpose, because a public inquiry endpoint attracts spam on day one. Before you expose one, you need HTTP transport, rate limiting, a honeypot field and a privacy notice. The README lists what is missing instead of pretending it is done. There is also an MCP server card example (examples/mcp-server-card.json). It follows the Server Card proposal (SEP-2127), which may still change, so check the current schema before you publish. What this does not do Honest part: nothing here guarantees that an assistant will mention or recommend you. The standards are young and nobody can tell you today how many assistants read agent cards. What the card does is remove guessing. If an assistant looks, it finds clean, current facts at a known address. On our own site we see the crawlers of the large AI assistants fetch these files every day. That is a signal, not a ranking. WordPress Most small businesses do not run Python. For them we built Open Door for AI Assistants (ODAA), a free WordPress plugin that creates the same card, an llms.txt and a read only MCP endpoint from three questions: company name, services, contact e-mail. An optional hosted inquiry service passes inquiries from assistants on to the owner by e-mail, with spam protection and a confirmed address. The plugin is in review at the WordPress.org plugin directory and will be linked here when it is live. Try it, break it, tell us - Repo: flowboxx/agent-card-starter - Install: pip install agent-card-starter - If you publish a card with it, post it in the repo Discussions under "Show and tell". If the validator rejects something valid, open an issue. The kit is maintained by flowboxx, an agency that builds AI doors for businesses.
Freelance Software Developer Available for Projects | Mobile, Web & Backend Development Hey everyone! I'm a freelance software developer with 3 years of professional product development experience, having worked on mobile applications, web applications, and scalable backend systems. I have hands-on experience with technologies like React Native, React.js, Next.js, TypeScript, JavaScript, Java, Kotlin, Node.js, Express.js, Rust, Axum, Tokio, Tailwind CSS, PostgreSQL, MongoDB, and Firebase. I'm currently looking for freelance projects, contract-based work, or remote opportunities. I'm open to building new products, developing backend systems, integrating APIs, or contributing to existing projects. Availability: Open to remote freelance and contract work. Rates: Flexible, depending on project scope and requirements. GitHub: github.com/abhishekce17 LinkedIn: linkedin.com Feel free to DM me if you have any opportunities or projects in mind. Thanks!
bun使ってみているけど、こんなのがあるのか。便利 SocketDev/bun-security-scanner
ADRをregoでlintする AIが書くADRのフォーマットをどう揃えるか 自分のリポジトリでは、設計の決定を ADR(Architecture Decision Records)として docs/adr/ に残している。最近は、その ADR の多くを Claude Code に書いてもらっている。決定を相談して、「これを ADR にしておいて」と頼むと書いてくれる。 ただ、AI に書かせた ADR は、少しずつ自由になっていく。テンプレートを置いて書き方を指示していても、ずれるときはずれる。たとえば、 Decision に、画面のメニュー項目の一覧や定数、構造体の定義まで書いてある。仕様書に近づいて、コードを変えるたびに古くなる Status が Proposed のまま、実装は別の形で進んでいる 決定を変えたときに、古い ADR を書き換えたり、新しい ADR が古い ADR の内容を何の印もなく覆したりしている みたいなこと。自分の ADR を持つリポジトリ7つを読み直したところ、コードとずれていたのはどれも、ADR に書かなくてよい種類の内容だった。決定そのものや、守るべき保証は古くなっていなかった。 このうち2つ目と3つ目は、Status の値や ADR 同士の参照といった構造の問題で、見ればすぐに分かる。厄介なのは1つ目で、節も Status も正しいのに、意味のうえでは仕様書になっている。構造を見ても分からず、読んで意味を判断するしかない。 なので、ADR に何を書き、何を書かないかをかっちり決めて、構造だけでなく意味についても守らせたいと思った。 ガイドラインではなくポリシーで縛りたい AI が書く ADR の質をどう保つかは、ほかの会社でも話題になっている。 以下の記事は、雛形だけで AI に ADR を書かせると読みづらい ADR が頻出したので、過去のレビューの指摘から規約をまとめたガイドラインを作った話。ガイドラインを、ADR を書くスキル、Claude Code の rule、CI の AI レビューから参照させている。記載ルールがレビュー用のプロンプトや過去の指摘に分散していた、という問題意識もおもしろかった。 zenn.dev 以下の記事は、ADR を書く Claude Code の skill を用意し、テンプレートとガイドラインに沿って書かせている。 zenn.dev どちらも参考になるが、ガイドラインやスキルは結局のところ AI へのお願いになる。自分もルールを文章にして、Claude Code が ADR を触るときに読む rule に置いているが、守られることが多くても、守られる保証はない。AI レビューで指摘させるのも、指摘するのがまた LLM なので、見逃すこともあれば、同じ ADR に違うことを言うこともある。 そこで、ルールをガイドラインではなくポリシーとして書くことにした。以下では、ADR が守るべき決まりを「ルール」、それを rego で書いて検査できるようにしたものを「ポリシー」と呼ぶ。 構造のルール(節の有無と順序、Status の値、ADR 同士の参照、リンク先が存在するか)は、ポリシーで機械的に判定し、違反があれば CI を落とす 意味のルール(仕様書になっていないか)は、ポリシーの中から LLM に質問して判定させ、結果を PR に注意として出す 意味の判定には LLM が要るが、何を判定させるかはポリシーに書いて、構造のルールと一緒に管理する。LLM の答えはぶれるので CI は落とさず、注意に留める。こうすれば、書き手が人でも AI でも、構造は同じ基準で弾き、意味のずれも PR の上で気づける。 ルールをregoで書くためにCLIを作った conftest(OPA)は、構造化されたデータをポリシーで検査するためのツールで、こういう検査はまさに専門なので、lint のルールは独自の DSL や YAML の設定で書くより、rego で書くのがよいと思っている。 ポリシーがコードなので、opa test でテストを書ける。条件の組み合わせを設定のキーで表すより、素直に書ける 「置き換えの参照が循環していないか」のような、集合やグラフの規則が書きやすい 実際、前に書いた conftestでMarkdownの構造をバリデーションする でも、remark と conftest で Markdown の構造を検査していた。 ただし conftest は Markdown を読めない。前の記事では、remark で AST の JSON にし、jq でファイル名を足してから conftest に渡すシェルスクリプトを書いていた。このつなぎの部分が弱く、変換に失敗したファイルを見逃して、残りのファイルだけで検査が通ってしまうことがあった。リンク先が存在するかのように、ファイルを見ないとわからないことも、rego からは調べられない。 なので、Markdown の解析と OPA での評価を1つのバイナリにまとめた CLI を作った。 babarot/regomark regomark がやっていることは次のとおり。 Markdown を解析し、最上位のブロックとリンクを行番号付きの JSON にする リンクをリポジトリに対して解決し、リンク先が存在するかを JSON に入れる ポリシーが意味について質問していれば、その質問と対象の原文を LLM に渡して答えをもらう 組み込んだ OPA で、rego のポリシーを評価する。構造の検査だけでなく、LLM の答えを使って ADR の内容をルールと照らし合わせる意味の検査もできる(後述) 違反を行番号付きで出し、deny があれば終了コードで CI を落とす regomark 自体には ADR についての実装はなく、ADR のルールも、LLM に何を判定させるかも、すべて rego のポリシーの側にある。解析から評価までを1つのバイナリで行うので、Node の依存も、conftest につなぐシェルスクリプトも要らなくなった。 ルールを構造と意味に分ける ADR の書き方のルールは、まず文章として .claude/rules/adr.md にまとめ、paths: docs/adr/** で Claude Code が ADR を触るときに読ませている。そのうえで、各項目を rego のポリシーにして regomark で検査している。 ルールの項目と、検査のしかたの対応はこうなっている。 ルール 種類 検査のしかた ADR は意思決定の文書で、仕様書ではない。画面の細部、定数、コード片は書かない 意味 仕様書になっていないかを LLM で判定し、warn にする 作業の手順は書かない 意味 手順書になっていないかを LLM で判定し、warn にする 節は Status、Context、Decision、Consequences の4つ 構造 節の有無と順序を deny で検査する Status の値は Accepted、Rejected、Deprecated、Superseded by ADR-NNN のどれか 構造 値を deny で検査する Status の最初の段落には、値と Partly replaced by ADR-NNN の行だけを書く 構造 段落の形を deny で検査する 決定を変えるときは新しい ADR を書き、古い方に Superseded by か Partly replaced by を付ける 構造 参照先が存在するか、向きが正しいか、循環していないかを deny で検査する 決定を守るテストやパッケージは、ファイルへの相対リンクで名指しする 構造 リンク先のファイルが存在するかを deny で検査する 意味のルールは、ADR がその文書としてふさわしいかを問うもので、ふつうは機械的に検出できない。「ADR が仕様書や手順書になっていないか」はいちばん分かりやすい例で、上で紹介した記事のガイドラインでも、Decision が実装の詳細を抱え込む ADR が問題として挙がっている。こうしたルールは LLM を通して判定する。 一方、構造で判定できるように、ルールの側も機械が読める形に寄せた。regomark は Status の最初の段落を値と参照として読み、理由まで同じ段落に書かれるとどこまでが値なのかが決められなくなるので、その段落には値と参照だけを置く。名指しをリンクにするのも同じで、ADR の本文から関数名を推測して探すより、リンク先が存在するかを見るほうが確実に判定できる。 regomarkの使い方 コマンドは3つ。 regomark check [--config .regomark.yaml] [--format text|github|json] [--judge | --no-judge | --judgments FILE] regomark dump # ポリシーに渡す入力の JSON を出す(テストを書くため) regomark questions # ポリシーが LLM に判定させる質問を出す 設定はリポジトリの直下の .regomark.yaml に書く。 documents: docs/adr # 検査する Markdown のディレクトリ policy: .regomark/policy # rego のポリシーのディレクトリ entrypoint: adr # deny と warn を集めるパッケージ(data.adr) skip_rules: [] # 違反を捨てるルール ID exclude: [] # 検査しない文書(docs/adr/000-*.md など) judge: # ポリシーの質問を LLM でどう判定するか(任意) command: [sh, -c, 'claude -p --output-format json --model sonnet --tools "" --json-schema "$REGOMARK_ANSWER_SCHEMA"'] max_questions: 200 # 1回の実行で聞く質問の上限 regomark check は、違反を path:line: [rule] message の形で出す。ルールを決める前に書かれていた gh-infra の ADR に当てると、こうなる。 $ regomark check docs/adr/002-import-action-selector.md:5: [status-value] Status "Proposed" must be Accepted, Rejected, Deprecated or "Superseded by ADR-NNN" docs/adr/003-nullable-deletion-markers.md:1: [required-sections] missing "## Consequences" docs/adr/003-nullable-deletion-markers.md:1: [required-sections] missing "## Decision" docs/adr/004-deletable-deletion-markers.md:1: [required-sections] missing "## Consequences" docs/adr/004-deletable-deletion-markers.md:1: [required-sections] missing "## Decision" $ echo $? 1 --judge を付けると、ポリシーの質問を LLM で判定し、意味の注意も warning: として並ぶ(後述)。--format github にすると、どちらも GitHub Actions の注釈として PR の差分に付く。終了コードは、deny がなければ 0、あれば 1、設定やポリシーの誤りで検査そのものができなければ 2。warn は終了コードを変えない。 構造のルール ポリシーに渡る入力は regomark dump で見られる。goldmark で取り出した最上位のブロックが、文書の順に、始まりと終わりの行番号付きで並ぶ。 $ regomark dump | jq -c '.documents[1].blocks[:4]' [{"kind":"heading","level":1,"line":1,"end_line":1,"text":"ADR-002: Import action selector for interactive diff viewer","lang":""}, {"kind":"heading","level":2,"line":3,"end_line":3,"text":"Status","lang":""}, {"kind":"paragraph","level":0,"line":5,"end_line":5,"text":"Proposed","lang":""}, {"kind":"heading","level":2,"line":7,"end_line":7,"text":"Context","lang":""}] リンクは regomark がリポジトリに対して解決し、kind(file、dir、missing、outside、external)まで入れて渡す。rego はファイルを読めないので、リンク先が存在するかは regomark の側で調べておく。 Status の値を検査するポリシーはこうなっている。 deny contains {"rule": "status-value", "path": doc.path, "line": adrlib.status_paragraph(doc).line, "message": msg} if { some doc in input.documents s := adrlib.status(doc) not s in allowed_status not regex.match(^Superseded by ADR-\d{3}\.?$, s) msg := $"Status \"{s}\" must be Accepted, Rejected, Deprecated or \"Superseded by ADR-NNN\"" } 違反は rule、path、line、message を持つオブジェクトで返す。行番号は入力のブロックから取るので、違反は Status の値が書かれた行に付く。adrlib はポリシー側に置いたライブラリで、「ある見出しの節に含まれるブロック」を返す関数などを持っている。 置き換えの循環は graph.reachable で書ける。 deny contains {"rule": "replacement-cycle", "path": doc.path, "line": r.line, "message": msg} if { msg := "this ADR is in a cycle of replacements" some doc in input.documents some r in adrlib.refs(doc) some d in numbered d.number == r.target doc.path in graph.reachable(replacement_graph, {d.path}) } 構造のルールは今のところ14ある。 ファイル名、H1、必須の節とその順序 Status の形と、置き換えの参照の置き場所 参照先の ADR が存在するか、参照の向きが正しいか 番号の重複、欠番、000 番 置き換えの循環 Accepted の ADR から、リポジトリ内の存在しないファイルへの相対リンク regomark は独自の組み込み関数を足していない。なので、ポリシーは素の opa test、opa check --strict、Regal でそのまま検査できる。 意味のルール ここからが、普通の lint にはできないところ。「この ADR は仕様書になっていないか」「1本の ADR に2つの決定が入っていないか」のような意味の判定は、rego でも Go でも書けない。LLM なら判定できる。 regomark では、ポリシーが LLM に質問できる。何を聞くかも、答えをどう扱うかも、構造のルールと同じ rego で書く。 質問をポリシーに書く ポリシーに questions を書くと、regomark がそれを LLM に判定させる。質問は次の項目を持つオブジェクトで、「どの文書の、どのブロックについて、何を聞くか」を表す。 id: 質問の種類 path と line: 対象の文書と、その場所 blocks: LLM に見せるブロック。入力の blocks から選ぶ ask: yes か no で答えられる質問 たとえば「Decision の節に、関数名や定数が書かれているか」を聞いて、yes なら注意を出すポリシーはこう書ける。 questions contains {"id": "spec-detail", "path": doc.path, "line": h.line, "blocks": [doc.blocks[i + 1]], "ask": "Decision の節に、関数名や定数が書かれているか"} if { some doc in input.documents some i, h in doc.blocks h.kind == "heading" h.text == "Decision" } warn contains {"rule": "spec-detail", "path": j.path, "line": j.evidence_line, "message": j.reason} if { some j in input.judgments j.id == "spec-detail" j.status == "ok" j.verdict == "yes" } regomark は、質問と、選んだブロックの原文を行番号付きで judge.command(上の設定では claude -p)に渡す。LLM は yes か no と、その理由と、根拠にした行番号で答える。yes の根拠は、渡した行のどれかでなければならない。答えは input.judgments としてポリシーに戻り、上の warn が拾う。 手元で regomark check --judge を実行すると、こうなる。 regomark check --judge 最初に挙げた「Decision に定数や構造体の定義まで書いてある」ADR が、こうして根拠の行と理由つきで PR に出る。節も Status も正しいので、構造のルールだけでは通ってしまっていたものだ。 答えには status が付く。答えが返れば ok、聞かなかったら not_run、コマンドが失敗したら provider_failed、答えの形が崩れていたら invalid_answer。聞けなかったときにどうするか(たとえば provider_failed なら注意を出し、not_run なら何も言わない)も、ポリシーで決める。 LLM の答えを読めるのは warn だけで、答えによって CI が落ちることはない(後述)。 ローカルで意味検査をする(claude -p) 意味検査をするかどうかは、使う人が決める。リポジトリの .regomark.yaml に書けるのは「どう判定させるか」(judge.command)までで、実際に意味検査をするのは --judge を付けたときか、REGOMARK_JUDGE=on のときだけ。人のリポジトリで regomark check を打っても、そこに書かれたコマンドが勝手に実行されることはない。 LLM を呼ぶ回数と実行コストにも気を配っている。 答えは、送った中身とコマンドをキーにキャッシュする。ADR とポリシーを変えなければ、2回目からは LLM を呼ばない 1回の実行で聞く質問が max_questions を超えたら、聞かずに止める コマンドは並列で動かし、時間切れを設ける LLM には ADR の本文を渡すので、本文に指示が書かれていればそれに従うかもしれない。なので、LLM にはリポジトリを変更する手段を持たせず、上の設定でも claude -p に --tools "" を付けている。 CIで意味検査をする(claude-code-action) CI では、claude-code-action のように、ワークフローの1つのステップとして動く LLM に答えさせたい。これは質問ごとに呼べるコマンドではないので、質問をまとめてファイルで渡す。 regomark questions で、質問と LLM に渡すプロンプトをファイルに書き出す 別のジョブで、claude-code-action にすべての質問に答えさせ、答えをファイルに書かせる もう1つのジョブで、チェックアウトし直して regomark check --judgments で答えを読む LLM が検査するチェックアウトに触れると、たとえば skip_rules にルールの ID を足して deny を消せてしまうので、LLM は検査と別のジョブで動かす。答えは質問の中身から作ったキーで照らすので、ADR やポリシーが変わったあとの古い答えは、どの質問にも当たらない。fork からの PR のように secrets がなく LLM のジョブが動かないときは、答えなしで構造のルールだけを検査する。ワークフローの全体は README に置いている。 検査の流れ 1回の regomark check は、LLM で判定する段階と、ポリシーを評価する段階に分かれている。 ポリシーの questions を評価して、LLM に判定させる質問を決める。これは意味検査をするかどうかに関係なく、毎回行う 質問に答える。judge.command で LLM に判定させるか、答えのファイルを読むか、何もしない(すべて not_run) ポリシーを2回評価する。deny は LLM の答えなしの入力で、warn は答えありの入力で評価する 上で書いたとおり LLM の答えは毎回同じとは限らず、同じ ADR がある日は通って次の日は落ちる CI にはしたくないので、LLM の答えは warn にしか渡さず、そのために評価を2回に分けている。答えがあるときとないときで deny の結果が変わるポリシー、つまり deny が LLM の答えを読んでいるポリシーは、終了コード 2 で止める。 $ regomark check regomark: data.docs.deny depends on input.judgments: it reports different violations with them (1) than without them (0); judgments may only feed warn これで、CI が落ちるかどうかは構造のルールだけで決まり、意味検査をやめても、LLM が失敗しても変わらない。意味の判定は、PR の上の注意として人が読む。 ポリシーの書き間違いをどう見つけるか lint でいちばん困るのは、違反があるのに 0 で終わること。CI が緑なら、誰もその ADR を見直さない。rego は、書き間違えてもエラーにならずに「何も見つからなかった」になりやすいので、regomark は次のようなポリシーを、終了コード 2 で止めるようにしている。 パッケージ名の誤り: package adr.links と書くと、違反は data.adr.links.deny に入り、誰も集めない。lib/ の外のモジュールは entrypoint のパッケージ(package adr)でなければならない deny の打ち間違い: deny を denies と書いたモジュールは、違反を1つも出さない。lib/ の外のモジュールは deny、warn、questions のどれかを必ず定義する 関数として書いた deny: OPA はパッケージの値に関数を含めないので、評価の結果から消える どのモジュールも定義していない data の参照: rego では未定義になり、それを使う deny や warn が何も出さない 入力の項目名の打ち間違い: 入力は Go の構造体から生成した JSON Schema で、評価の前に検証する skip_rules の打ち間違い: ポリシーに rule_ids で ID を宣言してもらい、それと照らす 形の崩れた質問: 項目が足りない、入力にないブロックを指している、といった質問は、LLM に渡す前に止める どれも rego としては正しいので、opa check は通ってしまう。regomark のテストでは、Markdown から終了コードまでを通し、「違反があるのに 0 で終わらない」ことを確かめている。 ルールとポリシーを各リポジトリに配る ルールの文章、ポリシー、設定、ワークフローは、自作の gh-infra で各リポジトリに配っている。gh-infra は GitHub のリポジトリの設定やファイルを YAML で宣言して管理するツールで、FileSet を使うと、同じファイルを複数のリポジトリに PR として配れる。 rego のポリシーもただのファイルなので、ディレクトリごと FileSet で同期すれば、足したポリシーは各リポジトリに届き、消したポリシーは消える。ルールの文章とポリシーを同じ PR で変えられて、ルールを足すのに regomark のリリースは要らない。ポリシーを書く regomark と、それを配る gh-infra を組み合わせると、複数のリポジトリの ADR を1か所のルールで揃えられる。 gh-infra については以下の記事で紹介している。 zenn.dev CI では、mise でバージョンを固定して実行している。 - name: Check the ADRs run: mise x github:babarot/regomark@0.2.0 -- regomark check --format github env: mise-action はトークンを渡さないので、共有のランナーでは API のレート制限に当たる MISE_GITHUB_TOKEN: ${{ github.token }} ADR の中のリンクは、テストのファイルを改名するような、ADR を触らない PR でも切れるので、ワークフローはパスで絞らず、すべての PR で動かしている。 まとめ ADR のルールは、Claude Code が読む文章と、regomark が検査する rego のポリシーの2つで持つ形になった。構造のずれは deny で CI が落とし、仕様書に近づいていないかのような意味のずれは、ポリシーから LLM に判定させて warn として PR に出す。何を検査するかも、LLM に何を判定させるかも同じ rego に書いて、同じ PR で変えて配れる。 実際に regomark を使っている例として、自分のリポジトリを2つ挙げておく。どちらも CI では mise で regomark を取ってきて、.regomark/policy/ に置いた rego のポリシーで docs/adr/ を検査している。 リポジトリ ワークフロー ポリシー babarot/oksskolten regomark.yaml .regomark/policy babarot/claude-recall regomark.yaml .regomark/policy oksskolten は前の記事のリポジトリで、ADR の検査を conftest から regomark に移した。ポリシーは構造、Status、参照、リンクのファイルに分けていて、それぞれに *_test.rego のテストがある。 regomark は ADR 専用のツールではなく、内部に ADR についての実装もないので、ほかの Markdown の検査にも使えるはず。作ったばかりで、自分のリポジトリで回している程度だが便利だったので、Markdown を rego で検査したい人は試してみてください。 babarot/regomark
I made a tiny open-source replacement for the LOVINGCOOL / Thermalright LCD apps (20 MB RAM vs 530 MB) Got tired of the vendor apps running 8 background processes just to show a picture, so I wrote LcdWall: one tray app that drives both LOVINGCOOL and Thermalright Trofeo Vision screens. ~20 MB RAM, ~0.1% CPU Images, GIFs, videos, .zt themes One-click import from the vendor apps No installer, no drivers, free and open source (GPL-3.0) Windows 10/11. Tested on the LOVINGCOOL 6.67" and Trofeo Vision 9.16", so tell me if it works on other models. Download + source: RvaByte/LcdWall
Claude Code 2.1.317 (3 Nov): an Agent SDK hook callback that throws no longer has to let the tool call run. Add onFailure: "block" to your guard hooks. And claude -p now exits 1 if a full disk loses its transcript. Do your guards fail open? github.com/anthropics/claude-code/blob/main/CHANG…
Self-nomination: I maintain netip, a Rust library for fast IPv4/IPv6 network set operations with arbitrary masks. It supports containment, intersection, difference and in-place aggregation using only core, with zero dependencies and no allocator. It also provides MAC address types and a Contiguous<T> wrapper for CIDR-only code. One example: subtracting 10.0.0.0/9 from 10.0.0.0/255.0.255.0 produces 10.128.0.0/255.128.255.0, without enumerating individual addresses. This is useful when packet classifiers or policy tools work with non-contiguous masks. Crate · API docs · Example in the announcement
jeffparsons/rangemap rangemapよさそうだな
3million line of code = AgentFOXXY Repo 👉 usmanxg58-byte/AgentFOXXY
🚀 PS5 模拟器 KytyPS5 迎来新进展 开发中版本现已开放下载,Xbox Series X 相关支持也同步引发关注,想尝鲜的玩家可以立即体验 🔗 下载地址 github.com/KytyPS5/KytyPS5/releases/tag/KytyPS5-2…
we are going open source today. If you are interested go to cybersenses/codingdude We have used this internally for quite a couple of projects. If you can benefit use it. #vibecoding #aicoding #AIcommunity #condingdude t.co
quicktui.ai QuickTUI Server preview server2-preview-20261103-154747-3d91be2ef is out. - On Windows, the server executable can now install itself. - Opening it from the desktop now opens the window instead of closing at once. Download: github.com/dualface/quicktui/releases/tag/server2…
@fahadalijaved Releases and source fahadalijaved/OneOS Come hang in the tg for support and sneak peeks at updates t.me
Minecraft Metal Shaders (Ultra Settings) on M4 Air Felt like Vibrant Visuals update is gonna take a lot longer to come out so I tried this Minecraft Metal Shaders mod that I found on Github and it's amazing. It's super optimised and runs smoothly on Ultra settings. Give it a try if you want. Works with Java 26.3+ GaelQuinta/minecraft-metal-shaders
WBTV - Your own media, broadcast as real live TV. I didn't build this because I thought the world was missing another IPTV client. I built it to fill a gap for myself, and I reckon a few of you have the same one. I've kept a big Jellyfin library for years, and I've somehow ended up in the same spot I was in before Jellyfin, back when it was Netflix and the like. Tons of choice, no idea what to watch. It got me thinking about how I used to watch TV, flicking through Freeview on the little portable telly at my parents' house. I never sat there staring at a UI for an hour without committing to anything. I'd just see what was on and watch it, and half the time it was something I'd never have picked myself. That's where it started. Before the world got so over-commercialised, ads weren't that annoying either. These days I run Pi-hole everywhere and haven't seen an ad in years, but I used to quite enjoy an old one. So I went on YouTube, searched things like '90s British TV adverts' and downloaded a load of long compilations. Those now play in proper ad breaks, and it tries to match the era of the ad to the programme. The guide on those cheap Freeview boxes was fun, but the boss for me was always Sky (the original, with the Beehive Bedlam music), so I themed it. There are a few themes, two of them Sky-style, so it's a proper nostalgia fest. I was watching something someone had recorded off the telly, and when the credits rolled it cut to the continuity announcer telling you what was on next, and later that evening there was another one with a synopsis of the programmes. That gave me an idea. Text rolling past wasn't going to cut it, I wanted a real broadcaster voice, so it reads out the programme details and what's coming up in the schedule. The voice is generated locally on your own machine, nothing leaves your network, and you can install other voices with one click. The more I used it, the more it looked like it could be my daily driver, so I started thinking about an all-in-one. I added IPTV support, though I don't really use it myself. TV, film, docs and stand-up were covered pretty early on, but then I remembered flicking up and down through the Sky music channels, which I loved, so I added a music video section that behaves like that. There were radio channels on Freeview and Sky too, so I pointed it at my music collection and it builds genre radio stations (with cover art, and lyrics if you've got .lrc files). While I was in my audio directory I thought about the other stuff I keep there, podcasts, audiobooks, radio interviews, and added those as stations too. Audiobooks work with m4b. Then I figured the real gap was news channels. Like I said I'm not much of an IPTV user, but I wanted to support live YouTube, so I found some live news streams and added those. Running it in the browser has been great, I often have it on one monitor and work on the other during the day, but I wanted to be able to use it on the sofa too, so I built an Android TV app to go with it. It also works as a tuner for Jellyfin, Plex, VLC and so on now (HDHomeRun emulation, plus M3U and XMLTV), though personally I'll carry on using the native clients. I've tinkered with other similar apps in the past and always given up because the setup was long-winded and overcomplicated, so that was the thing I put first. I just want to point it at some directories and be done, and have it do the rest. In practice it reads your .nfo tags, so each studio becomes a channel and each genre a themed one (the horror stuff ends up on The Spooky Channel). There are settings for appearance and behaviour if you want to tweak, but they all have sensible defaults and you don't have to touch them. One container, one volume. It's a young project and just me working on it, so GPU encoding and arm64 are in there but I haven't been able to test them on real hardware yet. The Docker image is amd64 for now. I've definitely missed loads of things in this post, so it's worth looking at the repo, or better still spinning it up and trying it yourself. GitHub: WB2024/WBTV Docker: wb20244/wbtv Happy to have any feedback. P.S. Yes, AI was used while building this, like it is on most software these days. No, it isn't slop. I've been through it with a fine-toothed comb and tested it properly. If that's a dealbreaker for you, that's fair enough, nobody's forcing you to use it. But I'd ask you to look at the code before judging it, rather than going off the label.
G: One menu bar icon, dozens of Mac utilities—volume mixing, system monitoring, window management, clipboard history, screen capture and more. Vorssaint is free, open source and private by default. Install only the features you need. vorssaint/vorssaint-utils
エージェントブラウザって何? ego lite・Claude・agent-browserを比べてみた こんにちは! 私はAIスタートアップでカスタマーサクセスとして働いています。 最近、「エージェントブラウザ」という言葉をよく見かけるようになりました。会社では同僚のエンジニアの方にego liteを勧められ、提供しているサービスの検証をしたい時などに使用しています。爆速で検証が終わるのでとても便利でした。 lite.ego.app ただ、普段Claudeのデスクトップアプリを使用しているのですが、そこでClaude in Chromeが使えたり、Claudeアプリの中のもブラウザがあったので、同僚の方が結構な勢いでego liteを進める理由がわかりませんでした。 そこで調べてみることにしました。 1. エージェントブラウザって何? AIエージェントが操作するためのブラウザだそうです。 ego liteや、Claudeアプリの中のブラウザがここに入ります。 Claude in Chromeはエージェントブラウザそのものではなく、ふだんのChromeをAIで操作できる拡張機能です。 AIが操作するためのブラウザとは ふつうのブラウザは、人が目で見てマウスで操作する前提ですが、エージェントブラウザには、AIが使いやすいように次のような工夫がされているそうです。 ページを文字の一覧にする:入力欄やボタンを抜き出して、AIが読める形で渡す 番号で操作できる:「e3 に入力」のように指示でき、座標やセレクタが要らない 外から命令できる:Claude Codeなどのエージェントが操作の指示を送れる 人の作業と分ける:AI用の作業場所を別に用意して、人のタブを動かさない 人が見張れる:操作中の画面を見たり、途中で止めたりできる なお、どこに何を入力するかを判断するのは、ブラウザではなくAIエージェント側です。 2. 4つのエージェントブラウザ等を比べてみた ツール 正体 操作するAI 動かすブラウザ ログイン状態 人の作業との関係 ego lite ブラウザ本体 外部のエージェント (Claude Code、Codexなど) ego lite内の別の作業場所(Space) Chromeから引き継げる 自分のタブとは分かれて並行 Claude in Chrome Chromeの拡張機能 Claude ふだんのChrome そのまま使える 同じChromeを使う Claudeアプリ内のブラウザ Claudeアプリの機能 Claude アプリ専用のブラウザ アプリ側で別に持つ アプリの中で見ながら代われる agent-browser コマンドで動く部品 外部のエージェント 別に立ち上がるブラウザ 基本は別 裏で動く Claude in Chrome:ふだんのChromeを、Claudeが操作する ふだんのChromeを使うので、ログイン済みのサービスもそのまま操作できます。 Claudeアプリ内のブラウザ:アプリの中の専用ブラウザ Claudeデスクトップアプリの地球儀アイコンから開くブラウザです。ふだんのChromeとは別のブラウザで、ログイン状態もアプリ側で別に持ちます。 Claudeが操作している画面を横で見られて、途中で自分が代わることもできます。人が頼む点ではClaude in Chromeと同じで、ふだんのブラウザと切り離して動く点ではagent-browserに近い、中間の位置づけです。 agent-browser:AIエージェントが使う部品 人が直接使うものではなく、Claude Codeなどのエージェントが作業の途中で呼び出す部品です。たとえばClaude Codeにアプリを作らせて、そのアプリが画面上で正しく動くかを確かめる、といった使い方をします。 ふだんのブラウザとは切り離して、基本は画面を出さずに裏で動きます。そのぶん、ログインが必要なサービスは、agent-browser側で改めてログインしてから操作します。 ego lite:ふだんのブラウザの中で、エージェントと並行作業 ego liteは、citrolabsという開発元が作っているブラウザです。Google Chromeと同じ仕組みを土台にしているので、見た目や使い心地はChromeに近いです。無料で使えて、中身(プログラム)も公開されています。今のところMac専用で、Windowsでは使えません。 ふだんのブラウザとして使いながら、Claude CodeやCodexなどの外部エージェントにも操作させられます。エージェントは「Space」という分かれた作業場所で動くので、自分が開いているタブには触りません。Chromeのログイン状態やCookie、拡張機能も引き継げるので、エージェントのために再ログインする必要がありません。複数のSpaceで、別々の作業を同時に進めることもできます。 つまりego liteは、「ログイン済みの環境は使いたい、でも自分のタブは邪魔されたくない」という悩みに向けたツールです。agent-browserは別ブラウザなのでログインし直しが必要で、Claude in Chromeは自分と同じChromeを使います。ego liteは、その間を埋める位置づけだとわかりました。 3. 結局、どう使い分ければいいの? 違いがわかったところで、自分の仕事に当てはめて使い分けを考えてみました。 こんなとき 向いているもの 理由 ログイン済みの管理画面で、目の前の作業を手伝ってほしい Claude in Chrome ふだんのChromeをそのまま使える ふだんのChromeとは分けて、ログインが必要な作業も任せたい Claudeアプリ内のブラウザ 一度ログインすれば状態が残り、ふだんのChromeとぶつからない 開発中の画面が動くか、エージェント自身に確かめさせたい agent-browser エージェントがコマンドで呼び出せる ログインが必要な自社サービスの検証を、何パターンも並行して進めたい ego lite ログインを引き継いだまま、Spaceごとに並行で動かせる Claude in Chrome:ふだんの作業の延長で頼みたいとき たとえば、ログイン済みの管理画面からお客様の利用状況を集めてまとめてもらう、といった使い方です。自分が見ている前で1つずつ進めてもらう作業に向いています。 ただし、自分と同じChromeを使うので、Claudeが操作している間はそのChromeで別の作業をしにくくなります。 Claudeアプリ内のブラウザ:ふだんのChromeと分けて頼みたいとき たとえば、お客様の会社のWebサイトを調べて整理してもらったり、ログインが必要な管理画面で作業してもらったりする使い方です。ふだんのChromeとは別なので、自分の作業を邪魔されません。 ログイン状態はアプリ側で持つので、最初に一度アプリのブラウザでログインしておけば、次からはそのまま作業を頼めます。 agent-browser:エンジニアが開発の中で使うとき Claude Codeに画面を作らせて、そのままエージェント自身に動作確認までさせる、といった使い方です。人がチャットで頼む道具ではないので、CSの私が直接使う場面は少なそうです。 ego lite:ログインが必要な検証を、まとめて任せたいとき たとえば、自社サービスのテスト環境で「新規登録の流れ」「設定変更の流れ」「エラー時の表示」を、それぞれ別のSpaceでエージェントに同時に確かめてもらう、といった使い方です。Chromeのログイン状態を引き継げるので、エージェントのために毎回ログインする必要もありません。その間、自分は自分のタブで別の作業を続けられます。 ここで、同僚がego liteを勧めてくれた理由がわかった気がしました。私の使い方は、ログインが必要なサービスの検証です。この使い方だと、ログインを引き継げることと、複数の検証を並行して進められることが、そのまま「爆速で終わる」につながっていたのだと思います。 ! ego liteやClaude in Chromeのように、ログイン済みの環境をAIに操作させる場合は、誤操作したときの影響も大きくなります。本番環境ではなくテスト環境で使う、操作を任せるサイトを絞るなど、任せる範囲には注意が必要です!! 4. agent-browserを例に、AIがブラウザを動かす流れを見てみる ところでエージェントブラウザは、どこにどのボタンがあるというのをどのように判断しているのでしょうか。 中の動きが一番わかりやすかったのが、agent-browserです。 結論からいうと、AIは画面を人間のように目で見ているわけではないそうです。agent-browserはHTMLをもとに画面の部品の一覧表を作り、そこに番号を振って、AIとの共通の認識を揃えているみたいな感じらしいです。AIは番号で「どこを操作するか」を伝え、agent-browserはその番号の部品を操作します。画面が変わったら一覧表を作り直し、番号も振り直します。 たとえばお問い合わせフォームにメールアドレスを入れて送信するとき、AIは裏でこんな命令を順番に送っています。 agent-browser open example.com agent-browser snapshot -i agent-browser fill @e3 "test@example.com" agent-browser click @e5 よくわからないのですが!!!!!やっていることは4つだけということです。 ページを開く(open) ページの部品の一覧表をもらう(snapshot) 一覧表を読んで、メールアドレス欄に入力する(fill) 送信ボタンを押す(click) 2で受け取る一覧表は、イメージとしてはこんな形です。入力欄やボタンに、e2 e3 のような番号が振られています。 - 入力欄「会社名」 → e2 - 入力欄「メールアドレス」 → e3 - ボタン「送信」 → e5 AIはこの一覧表を読んで、「メールアドレス欄は e3 だな」「送信ボタンは e5 だな」と判断します。あとは「e3 にこれを入力して」「e5 を押して」と番号で指示するだけです。 レストランにたとえると、agent-browserは番号付きのメニューを渡してくれる店員さんで、AIはメニューを読んで「3番ください」と注文するお客さんです。何を選ぶかを決めるのはお客さん(AI)で、店員さん(agent-browser)は注文どおりに動きます。 つまり、agent-browser自体が「ここはメールアドレス欄だ」と判断しているわけではありません。 agent-browserの役割:ページを一覧表にしてAIに渡すことと、AIが選んだ操作を実行すること AIエージェントの役割:一覧表を読んで、どこに何を入れるかを判断すること この役割分担は、ego liteやClaude in Chromeでも変わりません。 5.終わりに エージェントブラウザのことがわかったので、業務に活用していこうと思います!! 主に検証での使用が多いのですが、活用方法を広げていければと思います。 それとブラウザではなくパソコンをちょくで触れると最近流行りのagent-desktopも使ってみたいです! 参考 citrolabs/ego-lite vercel-labs/agent-browser lilting.ch
Pinning NPU cores on RK3588 buys you 5% — and costs the big model 10% Follow-up to the rk-bench leaderboard (same RK3588, same frozen protocol: PERF_RUN hardware timing, per-layer CPU-fallback verdicts, median of 3 independent process sessions). v0.3.0 added a --core-mask option — rkbench run --core-mask 0_1 calls rknn_set_core_mask after load and records the setting in the report's protocol block, so a masked run can never be mistaken for a default one. Then I measured what core assignment actually does to the board's anchor models (all FP): | Model | default | core 0 only | cores 0_1 | cores all (0_1_2) | | --- | ---: | ---: | ---: | ---: | | YOLOv8n | 37.50 ms | 37.52 ms | 35.65 ms | 35.62 ms | | YOLO11n | 43.67 ms | 44.20 ms | 42.51 ms | 41.58 ms | | YOLOv8s-seg | 104.53 ms | 114.55 ms | 102.15 ms | 100.62 ms | All runs stayed NPU_ONLY, and session spreads were tight (the one 107 ms outlier session inside the seg core-0 run is exactly why the protocol takes medians). Three readings Pinning to one core is free for small detectors and costly for the big one. YOLOv8n and YOLO11n don't notice a single core (within noise). YOLOv8s-seg gets 9.6% slower. If you pin cores to keep concurrent models out of each other's way, the largest model is the one that pays for it. Two cores capture nearly everything. Going from cores 0_1 to all three adds 0.1–2.3% — for these single-stream graphs, the third core is mostly decoration. The gain over default is real but small. all beats the runtime's default assignment by 3.7–5.0%. Interesting in itself — the default apparently does not spread one model's graph across all three cores — but keep the hierarchy straight: on this same board, INT8 quantization was a 1.4–2.0× lever (measured last time). Core assignment is what you tune after precision, not instead of it. The practical rule - Multi-model deployment? Pin each model to its own core(s) for isolation — just never pin a heavy model to exactly one core and expect default performance. - Single-model latency chasing? --core-mask all is worth its ~5%, and now you can measure your own model under any mask with the setting recorded in the report. The leaderboard itself is unchanged: its rows are all default-assignment numbers, and masked runs live in the study doc, not on the board — mixing the two would make every row quietly incomparable. Full study: docs/CORE_MASK_STUDY.md in the repo (data source: data/leaderboard.json). (Disclosure: rk-bench is my own project, Apache-2.0; every number reproduces from the repository.)
@BonoDJblack 补充一个读报告时的区别:仓库说明它从 APK 提取 URL,再结合权限推测可能的数据流向。发现地址和权限能提示风险,但不能单凭这两项证明某类数据已经发出。先把报告当排查线索,再结合运行时网络证据判断。 Verlintas/NOIR
From QuantOrb to Trifecta: How Open Scoring Rules Save AI Agents Inspired by QuantumOrb/QuantOrb (56⭐) - Solana memecoin holder scanner QuantOrb changed how I think about trust scoring. It asks 3 questions: 1. Who really holds it? → 20 wallets can be 4 people (operator clustering) 2. What happens if they sell? → Price falls -67%? 3. What else can hurt you? → Owner powers, liquidity control Same 3 questions for AI agents = Lethal 【entity-Trifecta¦canonical_name=Trifecta】! The Problem: Lethal 【entity-Trifecta¦canonical_name=Trifecta】 Coined by Simon Willison: AI agent becomes data-theft tool when it has ALL THREE: - Private Data (read_gmail) - Untrusted Content (browse_web) - External Communication (send_email) The Solution: QuantOrb-style Open Scoring Like QuantOrb's rules/ - open, fixed, not black box: // rules/【entity-trifecta¦canonical_name=Trifecta】-scoring.js - server's own function const RULES = { privateData:35, untrusted:25, external:20, linked:10, fresh:10 }; function calculateScore(tools) { let score=0; if(tools.includes("read_gmail")) score+=35; if(tools.includes("browse_web")) score+=25; if(tools.includes("send_email")) score+=20; // RED FLAG: force DANGER whatever score if(hasAllThree(tools)) return {score:100, verdict:"DANGER", status:403}; return {score, verdict:"CLEAN", status:200}; }
AgentTM – A time-travel debugger for AI coding agents, built with Go I've been building AgentTM, a local-first developer tool written primarily in Go that records Codex execution history and allows developers to inspect and restore previous workspace states. The current v0.1 implementation includes SQLite-backed event storage, SHA-256 content-addressable snapshots, historical file inspection, code diffs, and isolated checkpoint restoration using Git worktrees. It also includes a React/TypeScript dashboard, with the frontend embedded into the Go binary. Currently, it only captures non-interactive Codex sessions launched through AgentTM. I've tested the end-to-end workflow on Windows, including checkpoint restoration and cancellation handling. Native Linux/macOS testing and broader agent support are still pending. The long-term goal is to support multiple coding agents and allow developers to inspect, rewind, and branch their execution histories. Sharing the current implementation for anyone interested in Go-based developer tooling and agent observability. Repository: Jyotishmoy12/AgentTM
VHP: Vibe-coded Hypertext Preprocessor
1: Vorssaint is a free, open-source macOS toolkit consolidating dozens of utilities in one customizable menu bar app. Choose only the features you need, with no account, telemetry or subscription. Supports Apple Silicon on macOS 14+. vorssaint/vorssaint-utils
@LH @flowerornament @ZedLang We are currently building a cluster of FPGAs . We have our own open source tool flow to generate bitstreams for the kintex7 boards with 2 GB of ram. We also have memory contr The cluster does exist to enable engineer like you to deploy ideas like yours . github.com/openxc7
I built a new RTOS for POSIX and RP2040 from ideas I explored in 1986 — source now available Over the past several months, I've occasionally posted here about revisiting CHARM-II, an RTOS I developed for the Motorola 68000 in 1986. What started as an attempt to restore and understand my old code gradually turned into something different: developing a new RTOS using some of the original design ideas, while reconsidering the kernel, APIs, and platform-specific implementation. I've named the new project ”Karqri”, and I've now released its source code under the MIT license: noboru-takahashi/karqri What currently works Karqri has a shared kernel that runs in two environments: - POSIX: Cooperative task execution using ucontext, with logical ticks at task checkpoints. - Raspberry Pi Pico (RP2040): Preemptive task switching using independent PSP stacks, SVC/PendSV, and hardware timer interrupts. The kernel includes priority-based scheduling, task management, event notifications, and timers. To compare the two implementations, I've created a shared six-task demo. Tasks A–F move along three tracks and coordinate access to a shared critical zone using event notifications. The same workload runs on both platforms, but the scheduling and context-switching mechanisms are different. I've also added automated tests and Pico validation scenarios. The repository includes the source code, build instructions, and validation tools. What I want to explore next Releasing the source is only the first milestone. I'm also interested in making RTOS behavior easier to observe and understand, particularly the timing and causal relationships between task switches, event notifications, waiting, and wakeups. I've started experimenting with capturing Pico execution traces and generating HTML timelines. This is still limited to a specific validation scenario, rather than a general-purpose tracing system. For now, though, I'd especially like to get feedback on the RTOS implementation itself. If you notice any design issues or opportunities for improvement in how the shared kernel and platform-specific code are separated, I'd be interested to hear your thoughts. Previous discussions For context, here are two earlier posts in this community: - Recreated a cooperative RTOS track demo from 40 years ago on POSIX - Follow-up: Why my 1986 RTOS survived a simplistic interpretation of VRTX scheduling Those discussions helped me reconsider some of the original design assumptions. Karqri is still experimental, and I don't consider it production-ready. I'm publishing it now because I think the next stage of development will benefit from feedback beyond my own experiments.
Entra, crea tu cuenta y mete el código al elegir Lifetime: plainweek.eu Si prefieres montártelo en tu servidor, el código está aquí y es gratis siempre: victor-falcon/plain-week
FLUX научили рисовать в 6K без дообучения Обычно FLUX генерит около 1K разрешения — на больших холстах композиция разваливается. AReA решает проблему в два этапа: сначала грубо собирает всю сцену, потом... KooroshRH/AReA huggingface.co
@jrubiopardo For automating the content extraction, I'd test wordpress-export-to-markdown, which Astro links to in its migration guide: lonekorean/wordpress-export-to-markdown 1. In WordPress, use Tools → Export → All Content. Run the converter locally against that XML export. It generates Markdown with frontmatter and can download images and update their references. 2. Import a small sample into a staging Astro project first: a normal post, a page and a page-builder-heavy example. Map the fields to your Astro content schema. Templates, forms and page-builder layouts still need rebuilding. The exporter includes drafts too, so exclude those from public routes. 3. Compare old/new URLs, titles, canonicals, images and internal links. Keep existing paths where possible and set up permanent redirects for changed URLs. Check SEO-plugin fields separately; don't assume the content export carries them all over. Astro's migration guide: docs.astro.build
i love my merry may go check out the english patch here pwnkyderedere/mmm-english-patch t.co
Receiving webhooks with Lambda: signatures, duplicates, SQS Receiving webhooks sounds like the easier half of the job: someone sends you a POST, you accept it, done. In reality, the receiving side carries just as much responsibility as the sending side. The URL is public, anyone can call it, and well-behaved senders deliberately send the same message several times as soon as you answer too slowly once. The previous part of this series covered the sending side with retries via SQS. There I only sketched the receiver, with an in-memory set instead of a real duplicate check. Today I build the full counterpart: verify the signature, detect duplicates with a DynamoDB condition, reply with 202 right away and do the actual work through SQS. This time with AWS CDK. > In short > > A webhook receiver first verifies signature and timestamp against the raw body, remembers the message ID with a conditional DynamoDB write and puts the work on an SQS queue before it answers with 202. The most common trap is the order of steps: if you store the ID but don’t protect the enqueue step, the sender’s next retry gets thrown away as a duplicate, and that is exactly the event you were still missing. What you will learn in this article - How to safely verify an HMAC signature in the Standard Webhooks format - How a conditional DynamoDB write reliably detects duplicate webhooks - Why the receiver answers 202 right away and hands the work to SQS - How to set it all up with AWS CDK, a function URL and Parameter Store - Which mistakes cost you events when receiving webhooks, and how to avoid them What can go wrong when receiving webhooks Before we write code, it pays to look at the risks. A webhook is an HTTP call that a third-party system sends to your URL when something happens, for example “payment received”. You have no control over when it arrives, how often it arrives or who actually sent it. The first risk is forged requests. Your webhook URL shows up in configuration, logs and sometimes in tickets. Anyone who knows it can send you arbitrary JSON documents. Without a check, you then mark a bike rental as paid although no money ever moved. Next come replay attacks. Someone captures a real, correctly signed message and sends it again a week later. The signature is valid, the content too; only the timing is wrong. The third risk is duplicates for perfectly honest reasons. Good senders work “at least once”: if they don’t get a fast 2xx response, they send the same message again. That also happens when you processed it long ago and only the response got lost on the way back. Finally, there are slow responses. The Standard Webhooks specification recommends a sender timeout between 15 and 30 seconds. If your handler first creates an invoice, sends an email and asks three other systems, you will hit that limit quickly and produce the very duplicates you then have to catch. The architecture: receiving webhooks in three steps My receiver consists of two Lambda functions, one DynamoDB table and one SQS queue. The example is a fictional bike rental called “Radstation” that gets payment events from its payment provider. The flow looks like this: 1. Verify: The WebhookReceiver function sits behind a Lambda function URL. It checks signature and timestamp against the raw body. If anything is off, it answers with 401. 2. Remember: Next, it writes the webhook-id to the WebhookInbox table with the condition “must not exist yet”. If the condition fails, the message was already there, and the answer is a friendly 200. 3. Enqueue: New messages go to the PaymentEvents queue, and the sender gets 202 right away. The WebhookWorker function picks them up there and runs the business logic without time pressure. > 🎬 Flow when receiving webhooks with Lambda – this animated diagram only runs on my site: watch it on sascha-ms.de You may know this pattern from the asynchronous API with the job pattern. The difference: here it isn’t your own client that sets the pace but a third-party system that you can neither change nor ask for patience. Why a function URL and not API Gateway? A Lambda function URL is an HTTPS endpoint attached directly to the function, with no extra service in front. For a single webhook endpoint that is enough, because authentication happens through the signature in code anyway. That’s why I set the auth type to NONE. AWS CDK then automatically creates the required resource-based policy with lambda:InvokeFunctionUrl and lambda:InvokeFunction, restricted to calls through the URL. API Gateway pays off as soon as you need per-client throttling, a custom domain with several routes or AWS WAF in front. Function URLs have no built-in throttling. If you are unsure which entry point fits, my comparison REST API or HTTP API will help. Step by step: receiving webhooks with Lambda and CDK Prerequisites: Node.js 22 or newer, an AWS account with a configured AWS CLI and cdk bootstrap run once in your target region. Basic TypeScript knowledge is enough; I explain the rest along the way. Step 1: Create the project I start with an empty folder and install only what is really needed. The AWS SDK v3 already ships with the Lambda Node.js runtime, so esbuild leaves it out of the bundle automatically. mkdir webhook-receiver-lambda && cd webhook-receiver-lambda npm init -y npm install aws-cdk-lib constructs @aws-sdk/client-dynamodb @aws-sdk/client-sqs @aws-sdk/client-ssm npm install -D aws-cdk typescript tsx esbuild @types/node @types/aws-lambda mkdir bin lib src test scripts The package.json gets "type": "module" and a few scripts. The tsconfig.json uses NodeNext so that imports ending in .ts work. You will find both files in full in the example repository. { "app": "npx tsx bin/app.ts" } Step 2: Verify signature and timestamp I follow the format of the open Standard Webhooks specification because many providers already use it. The sender sends three headers: webhook-id, webhook-timestamp (Unix seconds) and webhook-signature. The signed text is <id>.<timestamp>.<body>, using HMAC-SHA256. An HMAC is a check value derived from a shared secret and the content; without the secret, nobody can produce it. // src/signature.ts import { createHmac, timingSafeEqual } from 'node:crypto'; // How far the timestamp may drift from our own clock (5 minutes) export const TOLERANCE_SECONDS = 5 * 60; export type VerifyInput = { secret: string; // format "whsec_<base64>" as in the Standard Webhooks spec id: string | undefined; timestamp: string | undefined; signatureHeader: string | undefined; rawBody: string; nowSeconds?: number; // can be overridden in tests }; export type VerifyResult = | { ok: true } | { ok: false; reason: 'missing-headers' | 'bad-timestamp' | 'too-old' | 'bad-signature' }; function keyFromSecret(secret: string): Buffer { const base64 = secret.startsWith('whsec_') ? secret.slice('whsec_'.length) : secret; return Buffer.from(base64, 'base64'); } // Computes the signature for "<id>.<timestamp>.<rawBody>" (HMAC-SHA256, Base64) export function computeSignature(secret: string, id: string, timestamp: string, rawBody: string): string { return createHmac('sha256', keyFromSecret(secret)) .update(`${id}.${timestamp}.${rawBody}`) .digest('base64'); } export function verifyWebhook(input: VerifyInput): VerifyResult { const { secret, id, timestamp, signatureHeader, rawBody } = input; if (!id || !timestamp || !signatureHeader) return { ok: false, reason: 'missing-headers' }; if (!/^\d{1,12}$/.test(timestamp)) return { ok: false, reason: 'bad-timestamp' }; const now = input.nowSeconds ?? Math.floor(Date.now() / 1000); if (Math.abs(now - Number(timestamp)) > TOLERANCE_SECONDS) return { ok: false, reason: 'too-old' }; const expected = Buffer.from(computeSignature(secret, id, timestamp, rawBody), 'base64'); // The header can carry several signatures ("v1,abc v1,def"), e.g. during a key rotation for (const entry of signatureHeader.split(' ')) { const [version, value] = entry.split(',', 2); if (version !== 'v1' || !value) continue; const candidate = Buffer.from(value, 'base64'); // timingSafeEqual requires buffers of equal length if (candidate.length === expected.length && timingSafeEqual(candidate, expected)) { return { ok: true }; } } return { ok: false, reason: 'bad-signature' }; } Three details make the difference here. For one, timingSafeEqual compares the signatures in constant time. A normal string comparison stops at the first wrong character, and a patient attacker can use those tiny timing differences to guess the signature piece by piece. In addition, the loop accepts several signatures in the header. The sender needs this when rotating its secret and signing with the old and new key in parallel for a while. The third detail is the five-minute tolerance. The specification requires a timestamp check but doesn’t prescribe a value. Five minutes is a common compromise between slightly skewed clocks and protection against replay. Because the timestamp is part of the signed text, nobody can tamper with it afterwards. Step 3: Store the secret in Parameter Store The shared secret belongs neither in code nor as plain text in an environment variable. I store it as a SecureString in SSM Parameter Store. According to the specification, 24 to 64 random bytes with the prefix whsec_ are typical. You then hand the value to the sender, usually through its dashboard. # 32 random bytes, Base64-encoded, prefixed with whsec_ SECRET="whsec_$(openssl rand -base64 32)" aws ssm put-parameter \ --name /radstation/webhooks/payment-secret \ --type SecureString \ --value "$SECRET" echo "$SECRET" # hand it to the sender once, then never print it again The function reads the secret on the first call and keeps it in memory for five minutes. That way, not every webhook costs an extra call, and a rotated secret still arrives promptly. // src/secret.ts import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm'; const ssm = new SSMClient({}); let cached: { value: string; loadedAt: number } | undefined; const MAX_AGE_MS = 5 * 60 * 1000; // reload after 5 minutes so a rotation takes effect // Reads the webhook secret from Parameter Store and caches it briefly export async function getWebhookSecret(): Promise<string> { if (cached && Date.now() - cached.loadedAt < MAX_AGE_MS) return cached.value; const result = await ssm.send( new GetParameterCommand({ Name: process.env.SECRET_PARAMETER_NAME, WithDecryption: true }), ); const value = result.Parameter?.Value; if (!value) throw new Error('Webhook secret missing in Parameter Store'); cached = { value, loadedAt: Date.now() }; return value; } Step 4: The receiver with duplicate detection Now for the core. The order inside the handler is deliberate: first secure the raw body, then verify, then remember, then enqueue. // src/receiver.ts import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda'; import { ConditionalCheckFailedException, DeleteItemCommand, DynamoDBClient, PutItemCommand, } from '@aws-sdk/client-dynamodb'; import { SendMessageCommand, SQSClient } from '@aws-sdk/client-sqs'; import { verifyWebhook } from './signature.ts'; import { getWebhookSecret } from './secret.ts'; const dynamo = new DynamoDBClient({}); const sqs = new SQSClient({}); const TABLE = process.env.INBOX_TABLE!; const QUEUE_URL = process.env.QUEUE_URL!; const KEEP_DAYS = 7; // how long we remember a webhook-id const reply = (statusCode: number, message: string): APIGatewayProxyResultV2 => ({ statusCode, headers: { 'content-type': 'application/json' }, body: JSON.stringify({ message }), }); export const handler = async (event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> => { if (event.requestContext.http.method !== 'POST') return reply(405, 'method not allowed'); // 1. Take the raw body exactly as it arrived – do not parse and re-serialize const rawBody = event.isBase64Encoded ? Buffer.from(event.body ?? '', 'base64').toString('utf8') : (event.body ?? ''); // 2. Verify signature and timestamp (function URLs deliver lower-case header names) const id = event.headers['webhook-id']; const check = verifyWebhook({ secret: await getWebhookSecret(), id, timestamp: event.headers['webhook-timestamp'], signatureHeader: event.headers['webhook-signature'], rawBody, }); if (!check.ok) { console.warn(JSON.stringify({ level: 'warn', msg: 'webhook rejected', reason: check.reason })); return reply(401, 'invalid signature'); } // 3. Detect duplicates: each id may enter the table only once const expiresAt = Math.floor(Date.now() / 1000) + KEEP_DAYS * 24 * 60 * 60; try { await dynamo.send( new PutItemCommand({ TableName: TABLE, Item: { webhookId: { S: id! }, status: { S: 'RECEIVED' }, receivedAt: { S: new Date().toISOString() }, expiresAt: { N: String(expiresAt) }, }, ConditionExpression: 'attribute_not_exists(webhookId)', }), ); } catch (error) { if (error instanceof ConditionalCheckFailedException) { // Already known: acknowledge, but do not enqueue again return reply(200, 'duplicate'); } throw error; } // 4. Put the work on the queue and reply right away try { await sqs.send( new SendMessageCommand({ QueueUrl: QUEUE_URL, MessageBody: rawBody, MessageAttributes: { webhookId: { DataType: 'String', StringValue: id! } }, }), ); } catch (error) { // Roll back the entry, otherwise the sender's next retry would look like a duplicate await dynamo.send(new DeleteItemCommand({ TableName: TABLE, Key: { webhookId: { S: id! } } })); console.error(JSON.stringify({ level: 'error', msg: 'enqueue failed', webhookId: id, error: String(error) })); return reply(503, 'try again later'); } return reply(202, 'accepted'); }; The duplicate check lives in ConditionExpression: 'attribute_not_exists(webhookId)'. DynamoDB evaluates the condition and performs the write atomically. If two identical webhooks arrive at the same moment, exactly one wins, and the other gets a ConditionalCheckFailedException. A read followed by a write would leave a gap that both calls could slip through. Pay attention to the catch block around the enqueue step. Suppose the ID is already in the table but SQS is briefly unreachable. Without the DeleteItem, the sender’s next attempt would count as a duplicate and get a 200. The event would be silently lost. That’s why I roll back the entry and answer with 503, so the sender tries again later. For a duplicate, I answer with 200 instead of an error code. From the sender’s point of view, the message did arrive. A 409 counts as a failure for many providers and triggers further attempts, in the worst case even the deactivation of your endpoint. !Flow chart: verify signature, check webhook-id via DynamoDB condition, enqueue in SQS; responses 401, 200, 503 and 202 Every check has its own response: 401 for forgeries, 200 for duplicates, 503 for outages and 202 for new webhooks. Step 5: The worker processes the queue With standard queues, SQS guarantees “at least once” delivery. So a message can arrive twice behind the queue as well. The worker therefore first checks whether the ID is already marked as done, and it sets the status with a condition at the end. The business logic itself should still be idempotent, meaning it produces the same result when it runs twice. I covered the details in Lambda idempotency with TypeScript. // src/worker.ts import type { SQSBatchResponse, SQSEvent, SQSRecord } from 'aws-lambda'; import { ConditionalCheckFailedException, DynamoDBClient, GetItemCommand, UpdateItemCommand, } from '@aws-sdk/client-dynamodb'; const dynamo = new DynamoDBClient({}); const TABLE = process.env.INBOX_TABLE!; type PaymentEvent = { type: 'payment.succeeded' | 'payment.failed' | string; data: { rentalId: string; amountCents: number }; }; // The actual business logic – in this example: mark a bike rental as paid async function handlePaymentEvent(event: PaymentEvent): Promise<void> { if (event.type === 'payment.succeeded') { console.log(JSON.stringify({ msg: 'rental paid', rentalId: event.data.rentalId, amountCents: event.data.amountCents })); } else { console.log(JSON.stringify({ msg: 'ignored event type', type: event.type })); } } async function processRecord(record: SQSRecord): Promise<void> { const webhookId = record.messageAttributes.webhookId?.stringValue; if (!webhookId) throw new Error('webhookId missing'); // SQS delivers at least once: skip messages that are already done const current = await dynamo.send( new GetItemCommand({ TableName: TABLE, Key: { webhookId: { S: webhookId } }, ConsistentRead: true }), ); if (current.Item?.status?.S === 'PROCESSED') return; // The business logic must still be idempotent (setting "paid" twice does no harm) await handlePaymentEvent(JSON.parse(record.body) as PaymentEvent); // Mark as done; if the condition fails, another invocation was faster try { await dynamo.send( new UpdateItemCommand({ TableName: TABLE, Key: { webhookId: { S: webhookId } }, UpdateExpression: 'SET #s = :done, processedAt = :now', ConditionExpression: '#s <> :done', ExpressionAttributeNames: { '#s': 'status' }, ExpressionAttributeValues: { ':done': { S: 'PROCESSED' }, ':now': { S: new Date().toISOString() } }, }), ); } catch (error) { if (!(error instanceof ConditionalCheckFailedException)) throw error; } } export const handler = async (event: SQSEvent): Promise<SQSBatchResponse> => { const batchItemFailures: SQSBatchResponse['batchItemFailures'] = []; for (const record of event.Records) { try { await processRecord(record); } catch (error) { console.error(JSON.stringify({ msg: 'processing failed', messageId: record.messageId, error: String(error) })); batchItemFailures.push({ itemIdentifier: record.messageId }); } } return { batchItemFailures }; }; With batchItemFailures, the worker reports only the failed messages back to SQS. The successful ones from the same batch are deleted and not processed again. After five failed attempts, a message moves to the dead-letter queue, a holding area for messages that cannot be processed. Step 6: Infrastructure with AWS CDK The stack describes the table, the queues, both functions and the function URL. The table gets a time to live (TTL): DynamoDB deletes items automatically once the time in expiresAt has passed, according to the docs within a few days after that. For duplicate detection this delay is harmless; the ID simply stays known a little longer. Seven days cover the retry schedules of most providers; the example schedule in the specification ends after a little more than three days. // lib/webhook-inbox-stack.ts import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; import { CfnOutput, Duration, RemovalPolicy, Stack, type StackProps } from 'aws-cdk-lib'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources'; import { NodejsFunction, OutputFormat } from 'aws-cdk-lib/aws-lambda-nodejs'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as sqs from 'aws-cdk-lib/aws-sqs'; import type { Construct } from 'constructs'; const here = path.dirname(fileURLToPath(import.meta.url)); const SECRET_PARAMETER = '/radstation/webhooks/payment-secret'; export class WebhookInboxStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // Remembers every webhook-id for 7 days (TTL cleans up automatically) const inbox = new dynamodb.TableV2(this, 'WebhookInbox', { partitionKey: { name: 'webhookId', type: dynamodb.AttributeType.STRING }, billing: dynamodb.Billing.onDemand(), timeToLiveAttribute: 'expiresAt', removalPolicy: RemovalPolicy.DESTROY, // demo only }); const deadLetters = new sqs.Queue(this, 'PaymentEventsDlq', { retentionPeriod: Duration.days(14), }); const queue = new sqs.Queue(this, 'PaymentEvents', { visibilityTimeout: Duration.seconds(60), // at least 6 times the worker timeout deadLetterQueue: { queue: deadLetters, maxReceiveCount: 5 }, }); const common = { runtime: lambda.Runtime.NODEJS_24_X, architecture: lambda.Architecture.ARM_64, memorySize: 256, bundling: { format: OutputFormat.ESM, minify: true, sourceMap: true }, environment: { INBOX_TABLE: inbox.tableName, NODE_OPTIONS: '--enable-source-maps', }, }; const receiver = new NodejsFunction(this, 'WebhookReceiver', { ...common, entry: path.join(here, '../src/receiver.ts'), timeout: Duration.seconds(5), // senders don't wait forever, neither do we environment: { ...common.environment, QUEUE_URL: queue.queueUrl, SECRET_PARAMETER_NAME: SECRET_PARAMETER, }, logGroup: new logs.LogGroup(this, 'ReceiverLogs', { retention: logs.RetentionDays.ONE_MONTH, removalPolicy: RemovalPolicy.DESTROY, }), }); const worker = new NodejsFunction(this, 'WebhookWorker', { ...common, entry: path.join(here, '../src/worker.ts'), timeout: Duration.seconds(10), logGroup: new logs.LogGroup(this, 'WorkerLogs', { retention: logs.RetentionDays.ONE_MONTH, removalPolicy: RemovalPolicy.DESTROY, }), }); // Permissions: as few as possible inbox.grant(receiver, 'dynamodb:PutItem', 'dynamodb:DeleteItem'); inbox.grant(worker, 'dynamodb:GetItem', 'dynamodb:UpdateItem'); queue.grantSendMessages(receiver); receiver.addToRolePolicy( new iam.PolicyStatement({ actions: ['ssm:GetParameter'], resources: [this.formatArn({ service: 'ssm', resource: 'parameter', resourceName: SECRET_PARAMETER.slice(1) })], }), ); worker.addEventSource( new SqsEventSource(queue, { batchSize: 10, reportBatchItemFailures: true }), ); // Public HTTPS endpoint – the signature check in code is the authentication const url = receiver.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.NONE }); new CfnOutput(this, 'WebhookUrl', { value: url.url }); new CfnOutput(this, 'DeadLetterQueueUrl', { value: deadLetters.queueUrl }); } } // bin/app.ts import { App } from 'aws-cdk-lib'; import { WebhookInboxStack } from '../lib/webhook-inbox-stack.ts'; const app = new App(); new WebhookInboxStack(app, 'WebhookInboxStack', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION ?? 'eu-central-1' }, }); The permissions are deliberately tight. The receiver may only write and delete, the worker may only read and update. The receiver’s timeout is five seconds because it no longer does anything slow. If something hangs anyway, the sender should rather get an error and retry than run into its own timeout. Step 7: Test and deploy Before anything goes to the cloud, the unit tests for the signature check run. They need no AWS credentials. // test/signature.test.ts import assert from 'node:assert/strict'; import { test } from 'node:test'; import { computeSignature, verifyWebhook } from '../src/signature.ts'; const secret = 'whsec_' + Buffer.from('a-secret-with-enough-characters-123').toString('base64'); const id = 'msg_2Kx7'; const timestamp = '1793404800'; const rawBody = '{"type":"payment.succeeded","data":{"rentalId":"R-1042","amountCents":1890}}'; const now = 1793404800; test('accepts a valid signature', () => { const signatureHeader = `v1,${computeSignature(secret, id, timestamp, rawBody)}`; assert.deepEqual(verifyWebhook({ secret, id, timestamp, signatureHeader, rawBody, nowSeconds: now }), { ok: true }); }); test('accepts the second signature during a rotation', () => { const signatureHeader = `v1,AAAA v1,${computeSignature(secret, id, timestamp, rawBody)}`; assert.equal(verifyWebhook({ secret, id, timestamp, signatureHeader, rawBody, nowSeconds: now }).ok, true); }); test('rejects a tampered body', () => { const signatureHeader = `v1,${computeSignature(secret, id, timestamp, rawBody)}`; const tampered = rawBody.replace('1890', '1'); assert.deepEqual(verifyWebhook({ secret, id, timestamp, signatureHeader, rawBody: tampered, nowSeconds: now }), { ok: false, reason: 'bad-signature', }); }); test('rejects a timestamp that is too old', () => { const signatureHeader = `v1,${computeSignature(secret, id, timestamp, rawBody)}`; const result = verifyWebhook({ secret, id, timestamp, signatureHeader, rawBody, nowSeconds: now + 600 }); assert.deepEqual(result, { ok: false, reason: 'too-old' }); }); test('rejects missing headers', () => { const result = verifyWebhook({ secret, id: undefined, timestamp, signatureHeader: 'v1,x', rawBody, nowSeconds: now }); assert.deepEqual(result, { ok: false, reason: 'missing-headers' }); }); npm test npx tsc --noEmit npx cdk deploy # Output (shortened): # WebhookInboxStack.WebhookUrl = https://abc123.lambda-url.eu-central-1.on.aws/ For a test against the real URL, I write a small script that signs like a sender. If you call it twice with the same ID, you can see the difference between a new webhook and a duplicate. // scripts/send-test-webhook.ts import { randomUUID } from 'node:crypto'; import { computeSignature } from '../src/signature.ts'; // Usage: WEBHOOK_URL=... WEBHOOK_SECRET=whsec_... npx tsx scripts/send-test-webhook.ts [id] const url = process.env.WEBHOOK_URL; const secret = process.env.WEBHOOK_SECRET; if (!url || !secret) throw new Error('set WEBHOOK_URL and WEBHOOK_SECRET'); const id = process.argv[2] ?? `msg_${randomUUID()}`; const timestamp = String(Math.floor(Date.now() / 1000)); const body = JSON.stringify({ type: 'payment.succeeded', data: { rentalId: 'R-1042', amountCents: 1890 } }); const response = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json', 'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': `v1,${computeSignature(secret, id, timestamp, body)}`, }, body, }); console.log(id, response.status, await response.text()); export WEBHOOK_URL="https://abc123.lambda-url.eu-central-1.on.aws/" export WEBHOOK_SECRET="whsec_..." # same value as in Parameter Store npx tsx scripts/send-test-webhook.ts msg_test_001 npx tsx scripts/send-test-webhook.ts msg_test_001 # msg_test_001 202 {"message":"accepted"} # msg_test_001 200 {"message":"duplicate"} Expected result: the first call returns 202 {"message":"accepted"}, the second 200 {"message":"duplicate"}. The worker logs show exactly one rental paid entry. If you change a single character of the body after signing, you get 401. Common mistakes when receiving webhooks Parsing and re-serializing JSON. If you verify JSON.stringify(JSON.parse(body)), you check different bytes than the sender signed. Whitespace, key order or Unicode escapes are enough to break the match. Always verify the raw body and watch out for isBase64Encoded. Body parser before verification. The same problem in a different outfit: frameworks such as Express like to parse the body automatically. For the webhook route you need the raw text, otherwise you will spend hours hunting a signature bug that doesn’t exist. Reading headers with the wrong casing. Function URLs and HTTP APIs deliver lower-case header names. event.headers['Webhook-Id'] is always undefined there. Business logic inside the request. Every second in the handler increases the risk of a sender timeout and with it the number of duplicates. Anything that takes longer than a few milliseconds belongs behind the queue. Error codes for duplicates. A 4xx for a known webhook looks like a failure to the sender. Depending on the provider, more attempts follow or you get a warning that your endpoint is broken. Remembering IDs for too short a time. If the TTL deletes an ID before the sender has finished its retries, a late duplicate slips through. Check your provider’s documentation for its maximum retry window and add a few days of buffer. Cost and security Cost-wise, the setup stays pleasantly small. Each webhook causes two short Lambda invocations, a few DynamoDB writes and reads in on-demand mode, and SQS requests to send, receive and delete, with Lambda batching the receive and delete calls. The function URL itself costs nothing extra. Parameter Store with SecureString causes KMS calls for decryption, but thanks to the cache only rarely. You can play through what this means for your volume with my Lambda cost calculator. Security-wise, the public URL is the weak spot. The signature check protects your data, but not your wallet: rejected calls still start the function. So set reserved concurrency on the receiver (in CDK via reservedConcurrentExecutions) to keep a flood of requests from blocking your whole account. Check beforehand that your account has enough unreserved concurrency, otherwise the deployment fails. If you need more protection, put API Gateway or CloudFront with AWS WAF in front. Never log the full header block or the secret. In the example, only the ID, the rejection reason and the event type end up in the logs. Payment data has no business in CloudWatch. My take I think the receiving side is underrated. On the sending side, most teams now think about retries because failures there are visible. On the receiving side, failures often surface only when someone asks why a payment was booked twice or not at all. By then the cause is hard to find, because the sender did everything right. The pattern in this article is deliberately simple. If you need a stronger guarantee that ID and message are always stored together, you can write the body straight into the table and forward it via DynamoDB Streams. That is the outbox pattern, and it saves you the compensation via DeleteItem. For most webhook receivers, though, I find the SQS variant easier to understand and to run. Where I disagree: I often hear that an IP allowlist could replace the signature. I see it differently. Provider IP ranges change, run over shared cloud infrastructure and don’t protect against replay. As an additional layer, an allowlist is fine; as the only safeguard, it isn’t. FAQ How do you verify a webhook signature? You compute an HMAC-SHA256 over ID, timestamp and raw body yourself, using the shared secret, and compare it in constant time with the signature in the header. You also check that the timestamp is at most a few minutes old. Which status code should a webhook receiver return? 202 or 200 for new, valid messages, 200 for known duplicates, 401 for invalid signatures and 503 for temporary outages. Many senders treat redirects (3xx) as failures. How long should you store webhook IDs? At least as long as the sender keeps retrying, plus some buffer. For many providers that means a few days. A DynamoDB TTL of seven days is a good starting point. Is a Lambda function URL enough for webhooks? For a single endpoint with the signature check in code, yes. If you need throttling, WAF rules or many routes under a custom domain, API Gateway or CloudFront in front is the better choice. Your next step Pick one webhook endpoint you already run today and answer three questions: Does it verify the signature against the raw body? Does it detect duplicates even when two calls arrive at the same time? Does it respond in less than five seconds? If any answer is “no”, this article gives you the blueprint for the fix. I’d like to know how you handle it: do you store webhook IDs in DynamoDB, in Redis, or do you rely on the idempotency of your business logic? Tell me in the comments. Next in the series: Back to the overview “Serverless patterns in practice” · Previous part: Webhook retries: reliable delivery with SQS and Lambda · Next part: Human in the Loop for AI Agents with Step Functions Sources - Standard Webhooks: specification - AWS Lambda: Control access to function URLs - AWS Lambda: Invoking function URLs (payload format) - Amazon DynamoDB: Condition expressions - Amazon DynamoDB: Time to Live - AWS Lambda: Handling errors for an SQS event source - AWS Systems Manager Parameter Store This post was created with AI assistance. --- 👉 Next step: Curious what your own Lambda functions cost? Try my Lambda cost calculator – it runs right in your browser. Originally published on sascha-ms.de. More articles on serverless, AI in everyday development and leading tech teams: sascha-ms.de
2: Vorssaint is a free, open-source macOS toolkit that brings dozens of utilities—from system monitoring and window management to clipboard history and media tools—into one customizable menu bar app. Local-first, with no accounts, tracking, or subscripti… vorssaint/vorssaint-utils
GBNF in llama.cpp should be used a lot more. github.com/ggml-org/llama.cpp/tree/master/grammars
@ashxhart @cryptosibbers @Dennis_Rye @omarchy @dhh @sonderby @tobi You’re welcome! And I am still pushing TensorFold to be faster, too. Latest PR - the vec4 qmm kernel runs 40 to 52 percent shorter on short generations on a 13-inch M1, with the output byte-identical to before. TensorFold#1990
@Shannnu_18 Sure! V1 - is actually already available and in the open but we re shipping a major update to the repo pretty soon feuersteiner/contextrie
@nisten Use my auto-browser as an alternative for now. I am running through making sure things are protected and it still uses playwright, but my harness is catching a lot of the paths in. LvcidPsyche/auto-browser
DeclarativeUIKitがもうすぐ1年ほど未更新なのか もうコード書かないもんなあ... サードパーティイズデッド! sakiyamaK/DeclarativeUIKit
I built Babel – automatic AI-translated subtitles for Jellyfin Hey! Someone on Reddit suggested I share my project here, so I figured I'd give it a shot! I've been working on a project called Babel, a self-hosted AI subtitle translator that I originally built because I was tired of dealing with missing subtitles in my own media library. I wanted something I could set up once and then mostly forget about. No manually finding source subtitles, translating files or checking whether the AI messed something up. And that's basically what Babel is trying to do. The idea is simple: use real subtitles whenever possible, and only fall back to AI translation when needed. Babel works alongside Bazarr rather than replacing it. Bazarr gets the chance to find subtitles in your preferred language first. If it can't, Babel can step in, find a suitable source subtitle and translate it. It can even extract embedded subtitle tracks directly from MKV files, so you're not limited to external SRT files. The part I've spent the most time on is making the translations reliable. AI models sometimes drop lines, leave dialogue untranslated, mess up subtitle structure or return something completely unexpected. Babel has an automatic QA and recovery system that checks for those problems and tries to fix them before publishing the subtitle. It also preserves the original subtitle timestamps rather than letting the AI rewrite them. Once a translated subtitle is ready, Babel can notify Jellyfin to refresh the library so it becomes available. In my own setup, Sonarr and Radarr trigger the process when new media arrives, Bazarr gets the first chance to find subtitles, and Babel handles the fallback if needed. There's also a web interface where you can manage your library, see translation progress, configure providers and track API costs. It supports OpenAI, Gemini, DeepSeek, OpenRouter, DeepL and local Ollama models, so you can choose between cloud APIs and running models locally. For a recent test, Babel translated an episode with 1,453 subtitle lines in about 136 seconds using gpt-4o-mini, at an estimated API cost of around $0.03. That's just one example from my setup, but it gives you an idea of what it can do. Babel runs in Docker and is completely separate from Jellyfin, so it's not a Jellyfin plugin. The latest release is v2.5.7-beta, and I've mostly been working on reliability, fixing edge cases and making the whole process as hands-off as possible. It's still a beta, and there's definitely room for improvement, but I've been using it myself and wanted to share it with other Jellyfin users. GitHub: hugomossberg/Babel Docker: ghcr.io/hugomossberg/babel:beta I'd love to hear what you think! If you give Babel a try, I'd genuinely love to hear what works and what breaks. Real-world feedback is what helps me improve it.
The Wolf Among Us Remastered - Fix for motion trail graphical issue
LogsTotal - Self-hosted multi-engine Sigma detection for event logs
Building an AI app and need to track model costs? This is 100% open source and ready to drop directly into your own projects: 🔗 ProdMoh/llm-prices #OpenSource #AI #Developers #BuildInPublic
各种大模型的越狱指令 包含32 个针对 26 个以上模型的提示的工作集合,包括完整的系统提示、角色定义、工作区策略、AGENTS.md文件、配置块和 Ollama 模型文件。 togg53192-cmd/jailbreaks