开发者
How to Build a Browser Tool and Sell It on Gumroad — A Complete Guide
I built 24 browser-based tools. Here is the complete technical guide for building your own and selling PRO licenses on Gumroad. Architecture: One HTML File + GitHub Pages + Gumroad No frameworks, no backend, no monthly costs. One HTML file on GitHub Pages. Gumroad handles payments. The PRO Flow User hits free limit → PRO modal Buy button → Gumroad popup ( window.open , NOT target=_blank ) Payment → Gumroad postMessage with license key message listener catches key Key verified via Gumroad API ( /v2/licenses/verify ) localStorage saves PRO (in try-catch!) Gotchas From 24 Tools postMessage security: Check d.success && d.purchase , never just d.license_key . I had this bug in 17 files. localStorage: Wrap in try-catch . Uncaught throws crash the whole page. CDN scripts: Always <script defer> . Without it, slow CDN blocks rendering. Gumroad publish: curl returns false every time. Use PowerShell. Buy button: Popup connects the page to Gumroad. Redirect breaks postMessage . Packed 5 templates with everything pre-configured: Browser Tools Starter Kit ($19)
AI 资讯
peektea narrows its gaze 👀 filter-as-you-type and hidden files
Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is...
开发者
How do you decide a side project is "good enough" to ship instead of polishing forever?
Solo dev here. My biggest bottleneck isn't building, it's deciding when something is done. I keep polishing past the point of diminishing returns and delay shipping for weeks over things no user would notice. For those who ship regularly: - What's your actual "ship it" threshold? - Do you use a hard rule (a deadline, a checklist, a launch date you can't move), or is it a feel thing? - Has shipping earlier than felt comfortable ever hurt you? Trying to build a saner habit around this. How do you draw the line? submitted by /u/IcyButterscotch8351 [link] [留言]
开发者
Winner Announcement Delayed for the Google I/O 2026 Writing Challenge
Hey all, we have a quick update for everyone who participated in the Google I/O Writing...
开发者
How to add eslint-disable comments in pug code inside a Vue SFC file?
Hi! I'm having some trouble with eslint-disable comments for HTML elements defined inside a Vue SFC pug template, eslint do not recognize them and keeps throwing warnings. What I've tried so far: Comments inside the pug template, both // and //- <template lang="pug"> // eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -- Standard video player click-to-play-pause behavior video(@click="togglePause" ...) </template> <template lang="pug"> //- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -- Standard video player click-to-play-pause behavior video(@click="togglePause" ...) </template> My next options are not optimal, but I ran out of ideas: A comment inside the script setup tag (it is placed before the template in the file): <script setup> //eslint-disable vuejs-accessibility/no-static-element-interactions </script> A comment at the very top of the file, before any other code <!-- eslint-disable vuejs-accessibility/no-static-element-interactions --> <script setup></script> <template lang="pug"></template> None of this worked. The only way I managed to make this work was creating overrides in .eslintrc.cjs: // Since eslint-disable comments do not work for HTML elements inside pug we // must include those overrides here. overrides: [ { // Standard video player click-to-pause behavior files: ["src/components/common/SimpleMp4Viewer.vue"], rules: { "vuejs-accessibility/no-static-element-interactions": "off" } } ] Do you know if I am missing something here? The eslint related packages I have in my projects are: dependencies "eslint-config-prettier": "^10.1.8", devDependencies "@rushstack/eslint-patch": "^1.8.0", "@vue/eslint-config-prettier": "^9.0.0", "eslint": "^8.57.0", "eslint-define-config": "^2.1.0", "eslint-plugin-unused-imports": "^4.4.1", "eslint-plugin-vue": "^9.27.0", "eslint-plugin-vue-pug": "^0.6.2", "eslint-plugin-vuejs-accessibility": "^2.5.0", Thank you! submitted by /u/bcons-php-Console [link] [留言]
开发者
Studied how the News Feed works in Instagram and other social media platforms.
One important concept I learned is Fanout, which is basically how posts are distributed to user's feeds. Fanout Push When a user creates a post, the system immediately pushes that post to the feed cache of all followers. This is very fast because the feed is already prepared when users open the app. Fanout Pull Instead of precomputing feeds, the system generates the feed when a user opens the application by fetching posts from accounts they follow. It saves storage and avoids unnecessary work for accounts with huge follower counts. Now real system user Hybrid Approach For normal users with a few hundred followers, Fanout Push works well because the cost is manageable and feed loading is fast. For celebrities like Virat Kohli with 250M+ followers, pushing every post to every follower's feed cache would be extremely expensive. Many followers may not even open the app, so a lot of storage and compute would be wasted. That's why large scale systems often use Fanout Pull (or a hybrid approach) for such accounts. But How Does the Feed Know to Fetch Celebrity Posts? A question I had was: If my normal friends' posts are already present in my feed cache through Fanout Push, how does the system know that it should also fetch posts from celebrity accounts? One possible approach is that the social graph stores metadata about accounts. Celebrity or high follower accounts can be marked differently. When a user opens the app, the Feed Service: Loads the feed generated through Fanout Push. Checks the accounts the user follows in the Social Graph. Identifies celebrity accounts that use Fanout Pull. Fetches their latest posts separately. Merges both results and then applies recommendation algorithms before returning the final feed. Simplified Flow User Creates Post Post Service Store in Database Fanout Service Check Social Graph & User Preferences (blocked users, muted users, close friends, etc.) Create Fanout Tasks Message Queue Fanout Workers (Push or Pull Strategy) I'm still learn
AI 资讯
ACID vs BASE: What Database Guarantees Actually Promise
When people say a database is "ACID-compliant" or "eventually consistent," they are making promises about what happens when things go wrong — concurrent writes, crashes, network failures. ACID and BASE are the two vocabularies for those promises, and knowing the difference tells you what you can and cannot rely on. What ACID guarantees ACID is the contract that traditional transactional databases — PostgreSQL, MySQL/InnoDB, Oracle, SQLite — make about a transaction (a group of operations treated as one unit). The four letters: Atomicity : the whole transaction succeeds or none of it does. If a bank transfer debits one account but the credit fails, the debit is rolled back. There is no half-done state. Consistency : a committed transaction moves the database from one valid state to another, never violating its declared rules (constraints, foreign keys, types). It will not let you, say, leave a foreign key pointing at a row that does not exist. Isolation : concurrent transactions do not step on each other. The result is as if they ran one at a time, even when they actually ran in parallel. (In practice databases offer tunable isolation levels — from "read committed" to "serializable" — trading strictness for speed.) Durability : once the database says "committed," that data survives a crash or power loss. It has been written somewhere persistent, not just held in memory. The payoff is that you can reason about your data simply: after a successful commit, the world is exactly what you asked for. The cost is coordination, which gets expensive when data is spread across many machines. What BASE trades away BASE is the model many distributed and NoSQL systems adopt — think Cassandra, DynamoDB, or Riak — when they need to scale across many nodes and stay up through failures. The acronym is deliberately a chemistry pun on ACID, and it stands for: Basically Available : the system answers requests even during partial failures, possibly with stale or incomplete data rather tha
AI 资讯
How Git Actually Stores Your Code: Blobs, Trees, and Commits
Most people picture Git as a tool that records changes — a stack of diffs layered on top of each other. That mental model is wrong, and it makes Git feel mysterious. Git is really a small key-value database that stores snapshots, and once you see the four object types it uses, commands like reset , checkout , and rebase stop being magic. Git is a content-addressed object store Everything Git tracks lives in .git/objects as an object, and every object has an ID that is the hash of its own content. By default that hash is a 40-character SHA-1 digest (newer Git supports SHA-256). The same bytes always produce the same ID, so the ID is the content's address — change one byte and you get a completely different object. This is why Git data is effectively immutable: you never edit an object in place, you create a new one with a new name. You can look inside any object with git cat-file . The -t flag prints the type, -p pretty-prints the content: $ git cat-file -t 3b18e512 blob $ git cat-file -p 3b18e512 hello world There are exactly four object types: blob , tree , commit , and tag . A blob is just file contents — raw bytes, with no filename and no metadata. The blob for README.md knows nothing about being named README.md ; it only knows what's inside. A tree is a directory listing. It maps names to other objects: each entry has a mode (like a file vs. an executable vs. a subdirectory), a name, and the hash of either a blob (a file) or another tree (a subdirectory). Trees are how Git represents folder structure. Inspecting one shows exactly that: $ git cat-file -p HEAD^ { tree } 100644 blob a906cb... README.md 040000 tree fe8e3b... src A commit ties it together. A commit object points to exactly one top-level tree (the full state of your project at that moment), plus the hash of its parent commit (or parents, for a merge), the author and committer with timestamps, and the commit message. Running git cat-file -p HEAD shows these fields in plain text. Because each commit nam
AI 资讯
I built a Windows tool that turns screenshots into one searchable PDF — here's what I learned
For months I had the same annoying problem: folders full of screenshots I couldn't actually use. Lecture slides, PDFs I own, scanned pages — all just images . I couldn't Ctrl-F them, couldn't copy a line out, couldn't get my OS to index them. A picture of text is useless the moment you need to find something in it. So I built CapDrop to automate the whole chain on Windows. This is a write-up of how it works under the hood and the bugs that nearly broke me. The core idea You draw a capture box over a page, pick a page key (Page Down, arrow keys), set an interval, and walk away. CapDrop then: Captures each page on the interval Presses the page key for you to advance Auto-crops margins and toolbars out of every shot Runs OCR locally Binds everything into a single PDF with a real text layer The result is one document you can search, not a pile of images. The stack Electron for the app shell and capture/UI (I already had window management, hotkeys, and floating-bubble export working — no reason to rewrite). A Python OCR sidecar (RapidOCR) spawned as a child process. OCR runs 100% locally; nothing is ever uploaded. jimp for auto-crop, with a 12px safety pad so edge text never gets clipped. pdf-lib to bind the pages and inject the OCR text layer. The Electron + Python-sidecar split was a deliberate choice. People kept telling me to rewrite the whole thing in Python "for the OCR," but the Electron app already had everything except OCR. Adding a sidecar was a few hundred lines; a rewrite would've been months. The bug that cost me two days After adding the OCR pipeline, my global capture hotkey developed a 4-second delay on the first press. Cold, every time. I guessed wrong twice — thumbnail size, then a race condition. Both were dead ends. The only thing that actually found it was instrumenting the hot path with timing logs. The culprit: a fs.readFile of a tiny 749-byte settings.json on every hotkey press. On a cold start that read was taking 2–4 seconds — Windows Defender's
AI 资讯
MD5 Is Broken — Stop Using It for Passwords (Use SHA256 Instead)
MD5 Is Broken — Stop Using It for Passwords (Use SHA256 Instead) MD5 was invented in 1991. It's 2026, yet I still see developers using MD5 for password hashing in production systems. Let’s break down why this is dangerous and what you should use instead. What Is a Hash Function? A hash function takes any input and produces a fixed-length output called a digest or hash . It is a one-way function , meaning you cannot reverse it to get the original input. Example: ```text id="hash1" Input: "password123" MD5: 482c811da5d5b4bc6d497ffa98491e38 SHA256: ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f Even a small input change completely changes the output. --- # Why MD5 Is Broken MD5 generates a **128-bit hash**, which was considered secure decades ago. Today, it's extremely weak. Modern GPUs can compute **billions of MD5 hashes per second**, making brute-force attacks trivial. --- ## The Rainbow Table Problem Attackers use precomputed databases called **rainbow tables**. These tables map common passwords → their hash values. So if you hash: ```text "password123" → MD5 → known value An attacker can instantly look it up. Collision Vulnerabilities Researchers have demonstrated that two different inputs can produce the same MD5 hash . This breaks the core security guarantee of hash functions. SHA256 — The Better Choice SHA256 produces a 256-bit hash and is part of the SHA-2 family. It is currently considered cryptographically secure. Example: ```text id="sha1" "hello" → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 Even tiny changes completely change the output: ```text "hello" → 2cf24dba... "Hello" → 185f8db3... But Wait — Don’t Use SHA256 for Passwords Either This is where many developers make a mistake. SHA256 is not suitable for password hashing . Why? Because it is too fast . Fast hashing allows attackers to brute-force passwords quickly using GPUs. What You Should Use Instead For password storage, use: bcrypt scrypt Argon2 (recommended
产品设计
[Showoff Saturday] I built a real-time collaborative pixel canvas with regional teams
I built a small real-time web experiment called WeSearch Canvas. It’s a shared public pixel canvas where everyone sees the same board and can place one pixel every few seconds. No signup. No feed. No account system. Users are automatically grouped into regional teams, and regions climb the board based on activity. The product idea is simple, but the interesting part for me is the interaction loop: - extremely low-friction entry - real-time shared state - cooldown-based contribution - region/team identity without account creation - social pressure to invite others from your area - canvas archive for past boards I’m trying to keep it lightweight and avoid the usual overbuilt social-app trap. The question is whether the loop is strong enough without accounts, profiles, chat, or notifications. I’d appreciate web/product feedback on: First-load clarity Real-time interaction feel Mobile usability Whether the region mechanic is obvious Any obvious scaling or abuse issues I should plan around early Link: https://wesearch.press/canvas submitted by /u/OGMYT [link] [留言]
AI 资讯
No Trading Firewall: The Publish Gate That Blocks Token Calls
No Trading Firewall Disclosure: AI tools were used for source collection and editorial review. The article was written by a human author, who checked the facts, code, and conclusions. Crypto risk disclosure: This article is a technical explanation, not investment advice. It is not a recommendation to buy, sell or hold any cryptoasset. A no-trading firewall belongs at the publish transition, not in a footer. A draft can be repaired quietly. A public DEV update changes the blast radius, so the pipeline should ask a narrower question before it sends published:true : did the AI-assisted article stay technical, or did it become a token call? The artifact below is a publish-gate test trace. It does not prove legal compliance, DEV acceptance, or model judgment. It only records why a draft can stay editable while the public transition stays blocked. Publish Transition The firewall is easier to audit when the transition is explicit: draft_update: operation: update published: false default: allow repair work to continue public_publish: operation: update published: true default: require clean test trace and human approval Forem's API documentation describes article create and update transport, including the published state. A successful transport is not editorial approval. The gate sits before transport, and it should be stricter when an update moves from draft maintenance to public publication. Test Set The firewall needs a test set, not just a list of forbidden words. These rules are the author's editorial model, not DEV-native, SEC-native, FINRA-native, FTC-native, or OpenAI-native labels. Test case Input excerpt Expected rule Decision Safe output Public transition allowed? T-PRICE-01 "ETH will rip after the next unlock" trading.price_prediction fail Explain the unlock mechanism without forecasting price no T-HOLD-02 "keep holding and farm the safer yield route" trading.buy_sell_hold_call and trading.yield_promise fail Describe signer, slashing, withdrawal, and protocol-ris
AI 资讯
Building a Multi-Agent Security Framework for Kubernetes: Autonomous Detection, Investigation, and Remediation
Kubernetes is the industry standard for scaling cloud-native workloads While it offers tremendous scalability and flexibility, securing Kubernetes environments remains a significant challenge. Organizations often rely on a collection of disconnected security tools to handle vulnerability scanning, runtime monitoring, compliance validation, and incident response. As clusters grow in complexity, security teams face increasing alert fatigue, delayed response times, and difficulties correlating security events across multiple layers of the platform. Recent advancements in Agentic AI present an opportunity to rethink Kubernetes security. Instead of relying solely on static rules and isolated security products, organizations can deploy a collaborative network of AI-powered security agents that continuously monitor, investigate, and remediate threats. This blog explores how a Multi-Agent Security Framework can transform Kubernetes security operations through autonomous detection, investigation, and remediation. The Problem with Traditional Kubernetes Security Modern Kubernetes environments generate security signals from multiple sources: Runtime security tools Container vulnerability scanners Admission controllers Network monitoring systems Compliance platforms Cloud security posture management tools Each system produces valuable information, but most operate independently. Consider a common scenario: A container begins executing suspicious commands. A runtime security platform detects the behavior and raises an alert. However, determining whether the threat is critical requires additional context: Is the pod exposed externally? Does the workload have excessive privileges? Can it access sensitive namespaces? Is lateral movement possible? Does it violate organizational policies? Answering these questions often requires multiple tools and human intervention. This is where multi-agent systems become valuable. What is a Multi-Agent Security Framework? A Multi-Agent Security Fr
AI 资讯
Next.js 16.2: 400% Faster Dev Startup, Faster Rendering, and Deeper Tooling for AI Agents
Vercel has released Next.js 16.2, featuring performance enhancements that make development startup 400% faster and rendering up to 60% quicker. The update includes AI-assisted development tools, improved Turbopack efficiency, and better error reporting. Migration from Next.js 15 is supported, and compatibility is set for Node.js 20.9 and TypeScript 5.1 or newer. By Daniel Curtis
AI 资讯
🚀 Building an Online Quiz Platform: My Final Year BCA Project
Hello Developers! 👋 I recently completed my Bachelor of Computer Applications (BCA). For my final-year project, I built an Online Quiz Platform — a web application designed to make both conducting and taking quizzes simple, interactive, and efficient. This project allowed me to apply the concepts I learned throughout my degree and gain practical experience in full-stack web development. 🌐 Live Demo Project Link: nitinsmali / Online_Quiz My final year project is an Online Quiz Web Application designed for an user-friendly experience across devices. 🌐 Online Quiz System 🚀 Live Demo 🔗 https://onlinequiz-project.xo.je/online_quiz/ 🧠 About The Project The Online Quiz System is a full-stack web application designed to provide an interactive and engaging online quiz experience. Users can register, log in, attempt quizzes, track scores, and view leaderboard rankings in real time. This project was developed to strengthen concepts in: Full-Stack Web Development Frontend & Backend Integration Database Management Authentication Systems Hosting & Deployment Real-World Application Flow ✨ Features 🔐 Authentication System User Registration Secure Login System Session Handling Password Management 📚 Quiz Management Category-Based Quizzes Dynamic Questions Timer-Based Quiz System Automatic Score Calculation 🏆 User Performance Leaderboard Rankings User Profile Dashboard Quiz Score Tracking 💬 Feedback System Feedback Submission Database Storage 📱 Responsive UI Mobile-Friendly Design Interactive User Experience Clean Interface 🛠️ Tech Stack Frontend HTML5 CSS3 JavaScript Backend PHP Database MySQL Development Tools XAMPP Git & GitHub Hosting InfinityFree 📂 Project Structure … View on GitHub 📌 Project Overview The Online Quiz Platform is a web-based application that allows users to participate in quizzes, answer multiple-choice questions, and receive instant results. The primary goal of this project was to create a system that eliminates manual quiz evaluation and provides a smooth online
AI 资讯
How I built a multilingual news SPA in vanilla JS — architecture notes
NewsScope is a real-time news search engine: search a topic, filter by language, category and country, get live results from the NewsData.io API. No React, no bundler, no npm dependencies — just HTML, CSS and vanilla ES2020+. This post is about a few specific decisions in the architecture that I think are worth sharing. The module structure The JS is 9 files, each with a single responsibility, loaded in dependency order directly in index.html : config.js → i18n.js → data.js ↓ ↓ ↓ helpers.js → geo.js → ui.js ↓ ↓ ↓ render.js → api.js → main.js Every module only uses things defined in modules loaded before it. main.js registers all event listeners and calls init() — it's the only file that touches everything. config.js is the smallest file in the project, since it only defines the state object and two constants. All app state lives in a single flat object in config.js , accessed as a global: const S = { apiKey : '' , query : '' , activeQuery : '' , language : ' es ' , category : '' , country : '' , results : [], nextPage : null , loading : false , hasSearched : false , error : null , }; No state management library. When something changes, the relevant render function gets called explicitly. Simple, and easy to trace. Translating search intent, not just the UI Most i18n stops at labels and button text. NewsScope has 10 predefined topic shortcuts (AI, Climate, Economy, Cybersecurity…) that trigger a search. If a user picks "Cybersecurity" while the app is set to Japanese, the keyword sent to the API should be in Japanese — not a transliteration of the English word. The solution is a TOPIC_KEYWORDS map in data.js : const TOPIC_KEYWORDS = { ai : { es : ' inteligencia artificial ' , en : ' artificial intelligence ' , ja : ' 人工知能 ' , ar : ' الذكاء الاصطناعي ' , /* 7 more */ }, cyber : { es : ' ciberseguridad ' , en : ' cybersecurity ' , ja : ' サイバーセキュリティ ' , /* 8 more */ }, // 8 more topics }; One string per language, per topic. Switching the UI language and then selecting a
AI 资讯
I built/played with two language tools and it changed how I think about “learning vs translating”
I didn’t expect to care this much about language tools. I started messing around with two different projects, Linguaboard and Parley , mostly out of curiosity. What I got was a surprisingly clear look at two very different ways we interact with language as developers and builders. Linguaboard: translation as exploration, not just output Linguaboard isn’t trying to give you the translation. Instead, it feels more like it’s saying: “Here are several valid ways this could be expressed, pick what fits your intent.” That shift is subtle but important. Most translation tools optimize for a single “correct” answer. Linguaboard leans into ambiguity in a way that actually helps you understand nuance instead of hiding it. I found myself thinking less like: “What does this mean?” and more like: “How should this sound in context?” Parley: learning through interaction, not memorization Parley takes a completely different angle. Instead of treating language as something to decode, it treats it as something to use. You’re not just passively consuming translations, you’re engaging with patterns, context, and recall in a more active loop. What stood out to me is how quickly it shifts you out of “study mode” and into “usage mode.” It feels closer to building intuition than studying rules. The interesting contrast What I didn’t expect is how well these two complement each other: Linguaboard → helps you understand nuance and meaning Parley → helps you internalize and use language One is about interpretation, the other about retention through interaction. Put together, they highlight something a lot of dev tools miss: Language work isn’t one problem. It’s at least two: understanding, and using. Why this matters (especially for devs) If you’re building anything with multilingual UX, AI translation, or global audiences, you’ve probably hit this wall: Translation APIs give you “correct” text But correctness ≠ clarity, tone, or intent These tools made that gap feel very obvious to me. And o
AI 资讯
Premium micro-interactions in React 19 (without the jank)
There's a specific kind of bad animation I notice immediately: the count-up stat that stutters as it ticks, the progress bar that lags a frame behind your scroll, the "active" tab underline that snaps instead of glides. None of it is broken, exactly. It just feels cheap. And nine times out of ten, the cause is the same — the animation is being driven through React state, so every frame triggers a re-render, and the main thread can't keep up. I build motion-heavy interfaces for a living, mostly in Next.js 16 and React 19, and I've landed on a small set of patterns that stay smooth because they bypass React's render loop entirely . They lean on Motion — the library formerly known as Framer Motion. It went independent and got renamed in 2025, so the package is now motion and the import you want is motion/react , not framer-motion ( the APIs are identical, only the import path changed ). Here are three I reach for constantly, plus the reduced-motion discipline that should wrap all of them. The mental model: MotionValues over state The single idea that fixes most jank: a MotionValue is a value Motion tracks outside of React. When it changes, Motion updates the DOM directly via transform or opacity — it does not call setState , so your component doesn't re-render. That's the whole trick. A number ticking from 0 to 4,200 should touch the DOM ~60 times a second and re-render React zero times. If a value changes every frame, it should live in a MotionValue, not in useState . State is for things that change when a user does something; MotionValues are for things that change continuously. Keep that line in your head and the rest of this falls out naturally. 1. A reading-progress bar with useScroll + useSpring The bar at the top of an article that fills as you read. The naive version listens to scroll events and sets state — which is exactly the re-render trap. Motion's useScroll hands you scroll position as a MotionValue already, so there's nothing to re-render. useScroll retu
AI 资讯
Building a Resilient Real-Time Chat System with WebRTC, Faye, and WebSockets: A Practical End-to-End
Building a Resilient Real-Time Chat System with WebRTC, Faye, and WebSockets: A Practical End-to-End Building a Resilient Real-Time Chat System with WebRTC, Faye, and WebSockets: A Practical End-to-End Tutorial In this tutorial you’ll build a small, resilient real-time chat system that works across browsers, mobile devices, and constrained networks. You’ll learn how to architect a client/server model with signaling, leverage WebRTC data channels for peer-to-peer messaging when available, and fall back to a robust WebSocket-based relay when direct peer connections fail. The focus is on practical patterns, testable code, and observability to keep a live chat running under load or flaky networks. Key takeaways Understand when to use WebRTC data channels vs. WebSocket relays for real-time chat Implement a signaling server to establish WebRTC connections safely and efficiently Build a resilient message delivery pipeline with idempotent processing and retry semantics Instrument the system for latency, jitter, and message loss to avoid silent failures Test end-to-end flows with simulated network conditions and automated resilience tests Overview of architecture Clients (web and mobile) connect to a signaling server to negotiate WebRTC peers and/or WebSocket sessions. If a direct WebRTC peer connection is possible, use a data channel for low-latency chat messages. If WebRTC is blocked (firewalls, NAT), route messages through a relay server using WebSockets. The relay can also bridge peers that can’t connect directly. A lightweight message store (in-memory for demo, with an optional Redis-backed queue in production) provides at-least-once delivery guarantees and retry capabilities. Observability: metrics for connection attempts, handshake times, message delivery latency, retry counts, and error rates. Tracing spans help diagnose cross-service flows. Tech stack (example) Frontend: TypeScript, WebRTC DataChannel, WebSocket Backend: Node.js with Express for signaling API, ws or
AI 资讯
Lessons from open-sourcing a CLI agent messaging layer (320 stars in a week)
About a week ago I open-sourced agmsg , a ~500-line bash + SQLite tool that lets CLI AI agents message each other directly. I built it for a dumb reason: I was tired of being the human copy-paste relay between Claude Code and Codex — selecting code in one terminal, pasting it into the other, carrying replies back, all day. I expected a few stars from friends and nothing else. Instead it went 5 → 320 in a week, picked up forks, derivative projects, and pull requests from people I've never met. That gap between what I expected and what happened is the interesting part, so here's the honest retrospective: the numbers, what worked, what flopped, and what genuinely surprised me. The numbers In about a week, with no budget and no audience to speak of: GitHub stars: 5 → 320 Forks: 0 → 15 3 derivative projects — someone ported the idea to shogi (agmsg-shogi), someone wrapped it as an MCP server (agmsg-mcp), someone rewrote it in Go (agmsg-go) Pull requests from outside contributors — support for Gemini CLI, Antigravity, and now GitHub Copilot CLI, plus a fix for role-isolation race conditions None of this came from one big spike. It came from a sequence of posts across channels, some of which worked and some of which completely didn't. What worked Leading with a video, not an explanation. The first post that got traction wasn't a description of the architecture — it was a 23-second clip of two Claude Code instances autonomously playing tic-tac-toe over agmsg, with no human input. People stop scrolling for a moving picture of agents doing something on their own. The text underneath could be short; the video did the work. A relatable problem, stated plainly. "I became a copy-paste relay between two AIs" landed because a lot of people are quietly doing exactly that right now. I didn't open with the technical design. I opened with the annoyance. The design was the payoff, not the hook. Using a long-form post as the landing pad. Timeline posts are good at reach and bad at depth.