<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title type="text">JTianling&apos;s Blog</title>
<generator uri="https://github.com/mojombo/jekyll">Jekyll</generator>
<link rel="self" type="application/atom+xml" href="https://jtianling.com/en/feed.xml" />
<link rel="alternate" type="text/html" href="https://jtianling.com/en/" />
<updated>2026-05-15T15:34:37+08:00</updated>
<id>https://jtianling.com/en/</id>
<author>
  <name>JTianling</name>
  <uri>https://jtianling.com/en/</uri>
</author>


<entry>
  <title type="html"><![CDATA[cross-agent-teams: a local message bus that lets multiple coding agents talk to each other]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/cross-agent-teams-release.html" />
  <id>https://jtianling.com/en/cross-agent-teams-release.html</id>
  <published>2026-05-01T21:00:00+08:00</published>
  <updated>2026-05-01T21:00:00+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;These days I usually have several coding agents running on the same machine: Claude Code doing the main development, Codex on the side for review or longer jobs, sometimes an opencode session for something else. The tools themselves are fine. What’s actually missing is any official way for them to talk to each other. If Codex finds something it wants to tell the Claude Code session, I have to copy-paste it myself. Claude Code, being a pure TUI agent, has no standard “wake from outside” entry point either — you can leave it a message, but its CLI does not beep, so the user has to switch windows manually to even see it.&lt;/p&gt;

&lt;p&gt;After enough of that friction, I built a small local MCP daemon, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cross-agent-teams-mcp&lt;/code&gt; (the project nickname is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xats&lt;/code&gt;), to give every coding agent on the machine a shared mailbox and wake channel. Each agent still runs in its own native CLI, no harness changes; the daemon only handles “carrying messages” and “knocking on the door”.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href=&quot;https://github.com/jtianling/cross-agent-teams-mcp&quot;&gt;https://github.com/jtianling/cross-agent-teams-mcp&lt;/a&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;what-it-is-in-one-sentence&quot;&gt;What it is, in one sentence&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cross-agent-teams-mcp&lt;/code&gt; is a local MCP daemon. Multiple coding agents on the same machine (Claude Code, Codex, opencode, cursor, …) each register it as an MCP server, and then they can DM each other by name, team, or role, or broadcast within a team. The daemon also pushes messages back to the recipient so it can wake itself up to read them. No external service is involved; everything lives in a single SQLite file at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.cross-agent-teams-mcp/data.db&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;why-i-built-it&quot;&gt;Why I built it&lt;/h3&gt;

&lt;p&gt;The pain points were specific, not “I want a multi-agent framework”:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Calling between Codex and Claude Code meant copy-paste by hand. Context kept getting lost on the round trip.&lt;/li&gt;
  &lt;li&gt;Claude Code has no standard “wake from outside” entry point. You can leave it a message, but the CLI does not notify; the user has to switch back to see it.&lt;/li&gt;
  &lt;li&gt;I’d worked around it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux send-keys&lt;/code&gt;, but that means hand-maintaining a pid-to-pane mapping, and it only works for plain TUI agents — Claude Code is its own thing again.&lt;/li&gt;
  &lt;li&gt;Multiple agents have no shared context, so every new session has to re-paste the same background.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xats&lt;/code&gt; is deliberately narrow: give the coding agents already running on this machine a shared mailbox and wake channel. Nothing else.&lt;/p&gt;

&lt;h3 id=&quot;how-it-differs-from-existing-things&quot;&gt;How it differs from existing things&lt;/h3&gt;

&lt;p&gt;I want to draw a clear line against a few things that are easy to confuse it with:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Claude Code’s built-in sub-agent / Task tool&lt;/strong&gt;: that’s a parent–child relationship — one Claude instance forks a short-lived sub-agent, used and discarded, no cross-harness story. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xats&lt;/code&gt; is horizontal communication between peer agents, each of which is its own long-running session.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codex --remote&lt;/code&gt;&lt;/strong&gt;: that just connects Codex’s TUI to a remote Codex process. Still one agent talking to itself. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xats&lt;/code&gt; doesn’t replace any agent’s runtime.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;autogen / crewai and similar multi-agent frameworks&lt;/strong&gt;: those are orchestrators — the framework owns the agent loop, and you write Python around it. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xats&lt;/code&gt; works the other way around: every agent keeps running in its native CLI (Claude Code is still Claude Code’s TUI, Codex is still Codex’s TUI), and the MCP daemon only handles inbox and wake. No language lock-in, no need to migrate code into a framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One-line positioning: a local message bus for already-running coding agents, not a multi-agent framework.&lt;/p&gt;

&lt;h3 id=&quot;two-technical-bits-worth-mentioning&quot;&gt;Two technical bits worth mentioning&lt;/h3&gt;

&lt;h4 id=&quot;the-claude-code-channel-proxy&quot;&gt;The Claude Code channel-proxy&lt;/h4&gt;

&lt;p&gt;Claude Code has an experimental capability called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notifications/claude/channel&lt;/code&gt;. It only turns on if you launch with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--dangerously-load-development-channels server:&amp;lt;name&amp;gt;&lt;/code&gt;. The trouble is that the daemon itself is an HTTP server, while Claude Code talks to MCP servers over stdio — the two don’t connect directly.&lt;/p&gt;

&lt;p&gt;The fix is a small stdio shim, published alongside the daemon as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cross-agent-teams-channel&lt;/code&gt;. Claude Code starts it as if it were a normal MCP server; internally the shim opens a long-lived connection to the daemon and feeds wake events back to Claude Code over the channel transport. From the user side there’s nothing to configure beyond adding both the daemon and the shim entries to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mcp.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A subtlety worth recording: when the shim starts, it generates a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;channel_session_id&lt;/code&gt; (csid) for itself and registers &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.ppid&lt;/code&gt; (which is the Claude Code CLI’s own pid) on the proxy row in the daemon. Later, when Claude Code’s business side calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_agent&lt;/code&gt; and passes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui_pid=$PPID&lt;/code&gt;, the daemon looks up the matching proxy by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui_pid&lt;/code&gt; and auto-binds the csid for it — the user never copies a csid by hand, and there’s no risk of multiple Claude instances silently mis-binding to each other’s channels (a bug I had to track down once; not at all obvious from the symptoms).&lt;/p&gt;

&lt;h4 id=&quot;pid--tty--pane-tmux-wake&quot;&gt;pid → tty → pane tmux wake&lt;/h4&gt;

&lt;p&gt;For agents without a push channel — opencode, cursor, or a Codex without app-server — the wake path has to fall back to something simple: paste a short line of text into the tmux pane the agent is sitting in.&lt;/p&gt;

&lt;p&gt;The flow is: take &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui_pid&lt;/code&gt; at registration time, look up its tty via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/ttys&lt;/code&gt;, then map tty → pane id with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux list-panes -F&lt;/code&gt;, and cache it. When &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_message&lt;/code&gt; triggers a wake, just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux send-keys&lt;/code&gt; into that pane. The idea is unsurprising; the pitfalls are the interesting part. The tty &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ps&lt;/code&gt; reports on macOS doesn’t always match what tmux uses literally; wrapping with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx&lt;/code&gt; makes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.ppid&lt;/code&gt; point at npm rather than Claude Code; and so on. The current implementation walks the process tree up until it finds the real host pid.&lt;/p&gt;

&lt;h3 id=&quot;what-works-today&quot;&gt;What works today&lt;/h3&gt;

&lt;p&gt;The following are stable and what I use every day:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_agent&lt;/code&gt; with auto-detection of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;claude-code&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codex&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;custom&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_message&lt;/code&gt; for 1→1 DMs by name or agent id&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;broadcast&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;broadcast_to_role&lt;/code&gt; within a team&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_inbox&lt;/code&gt; to read mail&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list_agents&lt;/code&gt; to see who’s around&lt;/li&gt;
  &lt;li&gt;Wake channels: Claude Code via channel push, Codex via app-server WebSocket (when started) or tmux paste otherwise, opencode/cursor/custom via tmux paste&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ui_pid&lt;/code&gt; auto-binds the channel, with the daemon rejecting csid mismatches up front&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two more areas are implemented but not promoted in the README yet, because I haven’t lived on them long enough:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A shared task list (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;task_add&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;task_list&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;task_claim&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;task_complete&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Contracts (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_contract&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subscribe_contract&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff_contracts&lt;/code&gt;) for lightweight cross-agent interface descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll add them to the README once they earn it.&lt;/p&gt;

&lt;h3 id=&quot;getting-started&quot;&gt;Getting started&lt;/h3&gt;

&lt;p&gt;The fastest path is daemon + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpsmgr&lt;/code&gt; in three commands, no hand-editing of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mcp.json&lt;/code&gt; required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code (default)&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 1. Start the daemon (run once, keep it alive — dedicated terminal, tmux, launchd, your call)&lt;/span&gt;
npx &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; cross-agent-teams-mcp@latest daemon &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; 9100 &amp;amp;
&lt;span class=&quot;c&quot;&gt;# 2. From your project, let mcpsmgr install the MCP config (it edits the project&apos;s .mcp.json)&lt;/span&gt;
npx mcpsmgr add jtianling/cross-agent-teams-mcp &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; claude-code
&lt;span class=&quot;c&quot;&gt;# 3. Launch Claude Code with the channel loader (you&apos;ll be prompted to approve the capability)&lt;/span&gt;
claude &lt;span class=&quot;nt&quot;&gt;--dangerously-load-development-channels&lt;/span&gt; server:cross-agent-teams-channel
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Other agents (Codex, opencode, …)&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; cross-agent-teams-mcp@latest daemon &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; 9100 &amp;amp;
npx mcpsmgr add jtianling/cross-agent-teams-mcp     &lt;span class=&quot;c&quot;&gt;# interactive: pick the agent&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Then start the coding agent as usual&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A heads-up: these agents don’t have Claude Code’s channel push transport, so the daemon falls back to tmux paste when delivering wake events (Codex with app-server is the exception). In practice you’ll likely have to nudge the agent to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_inbox&lt;/code&gt; yourself from time to time — something as casual as “check my inbox” works. For more native waking on Codex, run its app-server alongside (covered in “Detailed reference” below).&lt;/p&gt;

&lt;p&gt;Once inside an agent session, you don’t need to memorize tool names — just talk to it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# Agent A: Register me to xats as backend on team default.
# Agent B: Register me to xats as frontend on team default.
#         Send backend a message: the API has changed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The agent picks the right tool (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_agent&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;send_message&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_inbox&lt;/code&gt;, …) on its own.&lt;/p&gt;

&lt;p&gt;Note: the name after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--dangerously-load-development-channels server:&lt;/code&gt; must match the channel shim’s server key in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mcp.json&lt;/code&gt;, which is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cross-agent-teams-channel&lt;/code&gt; by default.&lt;/p&gt;

&lt;h4 id=&quot;detailed-reference&quot;&gt;Detailed reference&lt;/h4&gt;

&lt;p&gt;The daemon listens on 9100 by default; pass &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--port&lt;/code&gt; to change it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; cross-agent-teams-mcp@latest daemon &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; 9300
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;recommended-install-the-mcp-config-with-mcpsmgr&quot;&gt;Recommended: install the MCP config with mcpsmgr&lt;/h5&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx mcpsmgr add jtianling/cross-agent-teams-mcp -a claude-code&lt;/code&gt; writes both Claude Code entries (the HTTP endpoint and the channel shim) into the project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mcp.json&lt;/code&gt; automatically. Drop the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-a&lt;/code&gt; flag to get an interactive picker for the agent type (codex, opencode, cursor, …).&lt;/p&gt;

&lt;p&gt;If you’ve changed the daemon port (e.g. to 9300 above), tweak the daemon URL afterwards via env var or by editing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mcp.json&lt;/code&gt; directly — the file is plain JSON, so post-hoc tweaks are fine.&lt;/p&gt;

&lt;h5 id=&quot;manual-mcpjson&quot;&gt;Manual .mcp.json&lt;/h5&gt;

&lt;p&gt;If you’d rather not involve mcpsmgr, you can hand-write it. Claude Code needs two entries — the HTTP endpoint plus the channel shim:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cross-agent-teams&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:9100/mcp&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cross-agent-teams-channel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-p&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cross-agent-teams-mcp@latest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cross-agent-teams-channel&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;--daemon-url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://127.0.0.1:9100/mcp&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Codex is a little different. Tool calls themselves only need a streamable-http entry in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.codex/config.toml&lt;/code&gt;; no channel proxy:&lt;/p&gt;

&lt;div class=&quot;language-toml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[mcp_servers.cross-agent-teams]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;streamable-http&quot;&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://127.0.0.1:9100/mcp&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But if you want other agents to be able to &lt;em&gt;wake&lt;/em&gt; this Codex thread, instead of only mailing it and waiting for the user to come back, there’s one more step: run Codex’s own app-server alongside the TUI. This uses Codex’s native websocket protocol (the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codex-appserver&lt;/code&gt; transport), and a wake there literally means “start a turn inside that thread” — quite a bit cleaner than tmux paste. The rough shape:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;codex app-server &lt;span class=&quot;nt&quot;&gt;--listen&lt;/span&gt; ws://127.0.0.1:8799     &lt;span class=&quot;c&quot;&gt;# one terminal: app-server&lt;/span&gt;
codex &lt;span class=&quot;nt&quot;&gt;--remote&lt;/span&gt; ws://127.0.0.1:8799                &lt;span class=&quot;c&quot;&gt;# another terminal: TUI connects in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Those two lines aren’t enough on their own — the real setup is more involved. Three key gotchas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--remote&lt;/code&gt;, MCP servers are loaded by the app-server, NOT by the TUI. So the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cross-agent-teams-mcp&lt;/code&gt; entry has to live in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CODEX_HOME&lt;/code&gt; that the &lt;em&gt;app-server&lt;/em&gt; reads at startup (usually the global &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.codex/config.toml&lt;/code&gt;). Setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CODEX_HOME&lt;/code&gt; on the TUI side does nothing.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.codex/config.toml&lt;/code&gt; needs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;experimental_use_rmcp_client = true&lt;/code&gt; at the top, or streamable-http MCP servers won’t load at all.&lt;/li&gt;
  &lt;li&gt;For wake events to actually be injected into the codex thread (instead of still falling through to tmux paste), launch codex via a wrapper that calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pre-register-codex-pane&lt;/code&gt; so the daemon can map this codex process to its tmux pane.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The zsh launcher I personally use (with header comments and prereq notes) lives in a gist — source it or paste it into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.zshrc&lt;/code&gt;: &lt;a href=&quot;https://gist.github.com/jtianling/6c2769f76ffb8fbff8856f90dc7f9554&quot;&gt;https://gist.github.com/jtianling/6c2769f76ffb8fbff8856f90dc7f9554&lt;/a&gt;. The full step-by-step (MCP entries, start order, etc.) is in the repo README under &lt;a href=&quot;https://github.com/jtianling/cross-agent-teams-mcp#let-other-agents-wake-you-codex-appserver-poke&quot;&gt;“Let other agents wake you (codex-appserver poke)”&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once that’s all wired up, Codex calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_agent&lt;/code&gt; from inside its session, and the daemon records its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thread_id&lt;/code&gt; as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codex-appserver&lt;/code&gt; delivery (Codex 0.124.0+ exports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CODEX_THREAD_ID&lt;/code&gt; to MCP tools automatically, so registration just picks it up). After that, when xats pushes a message to this Codex, it goes over websocket — no tmux involved.&lt;/p&gt;

&lt;p&gt;It still works without app-server: the daemon falls back to tmux paste, which is fine but rougher — the wake is literally a line of text pasted into the Codex pane. So my recommendation: if you want this Codex to be wakeable by peer agents, follow the README and wire up app-server + launcher together.&lt;/p&gt;

&lt;p&gt;opencode and cursor have no dedicated wake transport and rely on tmux paste; their configs live under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docs/configs/&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;whats-next-loose-plans-not-commitments&quot;&gt;What’s next (loose plans, not commitments)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Short term: get contracts polished enough to be useful as lightweight cross-agent interface descriptions; actually live on the shared task list.&lt;/li&gt;
  &lt;li&gt;Medium term: cross-machine. Right now everything is strictly localhost; once it goes off-box, auth, isolation, and proxying all need a fresh design pass.&lt;/li&gt;
  &lt;li&gt;Not planning to do: an agent orchestration framework. The boundary stays at transport + inbox; how agents collaborate is left to the user’s own prompts and workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;already-dogfooding-it&quot;&gt;Already dogfooding it&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xats&lt;/code&gt; is already in use on its own development. Several releases leading up to 0.5.0 — including the channel-proxy refactor and the collapse of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_*_self&lt;/code&gt; into a single unified &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_agent&lt;/code&gt; — were done with Claude Code + Codex + xats working together: Claude Code wrote part of the implementation, Codex did review and ran independent checks alongside, and they coordinated progress and findings through xats instead of through me as a human relay. That experience is more convincing than any abstract list of features.&lt;/p&gt;

&lt;p&gt;If you’re also juggling several coding agents on the same machine and have hit the “they can’t talk to each other” wall, give it a try. Feedback welcome.&lt;/p&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[Agent of Empires: A tmux-Based AI Agent Session Manager]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/agent-of-empires-intro.html" />
  <id>https://jtianling.com/en/agent-of-empires-intro.html</id>
  <published>2026-03-30T15:21:39+08:00</published>
  <updated>2026-03-30T15:21:39+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;I’ve been trying out all kinds of AI coding agents lately. After going back and forth between them, I’ve realized that the real pain point often isn’t the model itself — it’s managing multiple agents at once: who’s working on which branch, what task is each one running, is one stuck waiting for input, do I need to take over, and will it keep running after I close the window? Managing all of this by hand gets messy fast once you have more than a couple of agents going.&lt;/p&gt;

&lt;p&gt;After using &lt;a href=&quot;https://github.com/njbrake/agent-of-empires&quot;&gt;Agent of Empires&lt;/a&gt; for about half a month, I think it’s worth a proper write-up. The name is a bit grand (AoE for short), but what it does is quite practical: rather than building an entire “AI IDE” from scratch, it builds on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git worktree&lt;/code&gt; — tools that already work great — to create a session manager specifically for AI agents. AoE recently hit v1.0.0 and has become quite mature overall.&lt;/p&gt;

&lt;p&gt;Repository links:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Upstream: &lt;a href=&quot;https://github.com/njbrake/agent-of-empires&quot;&gt;https://github.com/njbrake/agent-of-empires&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;My fork: &lt;a href=&quot;https://github.com/jtianling/agent-of-empires&quot;&gt;https://github.com/jtianling/agent-of-empires&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;in-one-sentence&quot;&gt;In One Sentence&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Agent of Empires&lt;/code&gt; is essentially an AI agent dispatch panel for your terminal. It runs different agents in their own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux sessions&lt;/code&gt; and can leverage &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git worktree&lt;/code&gt; to work on multiple branches of the same project simultaneously. Currently supported agents include Claude Code, Codex CLI, Gemini CLI, OpenCode, Cursor CLI, Copilot CLI, Mistral Vibe, Pi.dev, Factory Droid CLI, and more — basically all the mainstream terminal AI agents are covered.&lt;/p&gt;

&lt;h3 id=&quot;why-i-find-it-interesting&quot;&gt;Why I Find It Interesting&lt;/h3&gt;

&lt;p&gt;When doing AI-assisted programming nowadays, the natural workflow is no longer “me and one agent going back and forth.” Instead, it’s more like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;One agent writing features&lt;/li&gt;
  &lt;li&gt;One agent running tests or fixing lint&lt;/li&gt;
  &lt;li&gt;One agent doing code review&lt;/li&gt;
  &lt;li&gt;Another agent looking up docs or organizing thoughts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this scenario, just opening lots of terminal tabs technically works, but you quickly run into problems: directories get mixed up, branches get crossed, state is hard to track after closing windows, and it’s not easy to tell at a glance whether an agent is busy, waiting, or finished.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Agent of Empires&lt;/code&gt; takes a systematic approach to these problems. Here are the features I find most practical:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Built on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux&lt;/code&gt;, so sessions persist — agents keep running after you close the TUI&lt;/li&gt;
  &lt;li&gt;Integrates with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git worktree&lt;/code&gt; for parallel work on different branches&lt;/li&gt;
  &lt;li&gt;Has both a TUI and CLI — you don’t need to stay in the interface full-time&lt;/li&gt;
  &lt;li&gt;Shows agent status (Running / Waiting / Idle), and you can attach directly to take over&lt;/li&gt;
  &lt;li&gt;Supports Docker sandbox for more isolated agent environments&lt;/li&gt;
  &lt;li&gt;Built-in diff view to review agent git changes right in the TUI&lt;/li&gt;
  &lt;li&gt;Supports sending messages to agents (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aoe send&lt;/code&gt;) without needing to attach&lt;/li&gt;
  &lt;li&gt;Per-project &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.aoe/config.toml&lt;/code&gt; for hooks and project-level settings&lt;/li&gt;
  &lt;li&gt;Sound notifications when agent status changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, it doesn’t try to replace the terminal — it just makes “managing lots of agent terminals at once” much smoother.&lt;/p&gt;

&lt;p&gt;What I personally find most valuable is that when I don’t want my coding agents to keep getting interrupted, I can keep them running persistently on a Mac I use as a server. Even when I leave my desk — whether I’m commuting or at home — I can SSH into that Mac, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aoe&lt;/code&gt;, and instantly resume my entire working environment. This is also why I haven’t switched to powerful local agent management tools like cmux: while local window splitting and agent management is very convenient, you can’t easily resume a remote session.&lt;/p&gt;

&lt;h3 id=&quot;installation-and-basic-usage&quot;&gt;Installation and Basic Usage&lt;/h3&gt;

&lt;p&gt;The upstream README offers several installation methods. The simplest is probably:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  https://raw.githubusercontent.com/njbrake/agent-of-empires/main/scripts/install.sh &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or if you use Homebrew or Nix:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Homebrew&lt;/span&gt;
brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;aoe

&lt;span class=&quot;c&quot;&gt;# Nix&lt;/span&gt;
nix run github:njbrake/agent-of-empires
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once installed, the most direct way to start is:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aoe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also add sessions directly from the command line:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Add a session for the current project&lt;/span&gt;
aoe add &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Add a session in a new worktree / branch&lt;/span&gt;
aoe add &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; feat/my-feature &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Start in sandbox mode&lt;/span&gt;
aoe add &lt;span class=&quot;nt&quot;&gt;--sandbox&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What I like most about this design is that it doesn’t hide the underlying machinery. It’s still &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux sessions&lt;/code&gt; underneath, so if anything goes wrong, you can always attach directly and inspect — no “close the UI, everything becomes a black box” feeling.&lt;/p&gt;

&lt;h3 id=&quot;who-is-it-for&quot;&gt;Who Is It For&lt;/h3&gt;

&lt;p&gt;I think it’s especially suitable for these scenarios:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You’re already comfortable with a terminal workflow, or at least don’t mind &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;You run multiple AI agents on the same project&lt;/li&gt;
  &lt;li&gt;You actively use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git worktree&lt;/code&gt; or multi-branch parallel development&lt;/li&gt;
  &lt;li&gt;You want agents that run persistently, not as one-shot Q&amp;amp;A tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the flip side, if you don’t usually work in the terminal and aren’t willing to accept the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux&lt;/code&gt; layer of abstraction, the learning curve might feel steep. This tool clearly isn’t made for people who never want to touch a terminal.&lt;/p&gt;

&lt;h3 id=&quot;what-i-changed-in-my-fork&quot;&gt;What I Changed in My Fork&lt;/h3&gt;

&lt;p&gt;I started forking fairly early on, with about 90+ commits in total. Early additions included a profile mechanism, terminal title management, and various tmux interaction fixes. Currently, the changes in my fork beyond upstream focus on a few daily experience improvements:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session navigation keybindings&lt;/strong&gt; – The upstream AoE has fairly basic navigation between tmux sessions. I added a full suite: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+.&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+,&lt;/code&gt; for quick session switching (cycling across groups), number keys (1-99) to jump directly to a specific session in both TUI and tmux, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+b b&lt;/code&gt; to go back to the previous session (similar to vim’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+^&lt;/code&gt;). Inside tmux, I also added vi-style pane navigation (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+b h/j/k/l&lt;/code&gt;), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+;&lt;/code&gt; to cycle through panes, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+Q&lt;/code&gt; for one-key detach. With all these shortcuts combined, jumping between multiple agents feels much smoother — you rarely need to go back to the TUI list to select.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notification Bar&lt;/strong&gt; – Inspired by &lt;a href=&quot;https://github.com/asheshgoplani/agent-deck&quot;&gt;Agent Deck&lt;/a&gt;, it displays real-time status of all sessions (Running / Waiting / Idle) with status icons directly in the tmux status bar. This way, even when you’re attached to one agent session, you can see at a glance whether other sessions are waiting for your input, without switching back to the TUI. Combined with quick-switch, jumping to a session also automatically acknowledges the Waiting status.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Restart&lt;/strong&gt; – Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;R&lt;/code&gt; to restart the agent pane directly without destroying and recreating the entire session. The main use case is agents like Claude Code that need a restart to refresh skill configurations — after editing a skill, just press one key instead of manually exiting and reopening. For Claude Code and Codex, I also implemented graceful resume restart: it persists the resume token so the agent can restore its conversation state after restarting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Narrow-screen layout&lt;/strong&gt; – On narrow terminals (iPhone portrait, Mac split-screen, small tmux panes), the TUI automatically hides the preview panel and shows only the session list at full width. When attaching to a session with split panes, the agent pane auto-zooms for a usable full-screen view; returning to a wide terminal auto-unzooms. Pane-switch keybindings (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+b h/j/k/l&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+;&lt;/code&gt;) are zoom-aware, so switching panes while zoomed re-zooms the target pane seamlessly.&lt;/p&gt;

&lt;h3 id=&quot;overall-impression&quot;&gt;Overall Impression&lt;/h3&gt;

&lt;p&gt;After using it for about half a month, I think the most valuable thing about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Agent of Empires&lt;/code&gt; isn’t how many models it supports, but that it addresses a very practical problem: when AI agents go from “occasional use” to “running several in parallel,” what you really need to manage is sessions, state, isolation, and context — not a fancier chat window.&lt;/p&gt;

&lt;p&gt;Since reaching v1.0.0, the upstream completeness is quite high. With diff view, per-repo config, sound notifications, and send message all in place, daily use requires very little extra tinkering. If you already work in the terminal and have started getting used to running multiple agents in parallel, this tool is definitely worth a serious try.&lt;/p&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[dual-yazi: Adding the Dual-Pane Mode I Wanted to Yazi]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/yazi-dual-pane-mode.html" />
  <id>https://jtianling.com/en/yazi-dual-pane-mode.html</id>
  <published>2026-03-17T21:13:55+08:00</published>
  <updated>2026-03-17T21:13:55+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;While managing files in the terminal recently, I’ve been feeling a bit torn: on one hand, &lt;a href=&quot;https://github.com/sxyazi/yazi&quot;&gt;Yazi&lt;/a&gt;, as a next-generation terminal file manager, is genuinely excellent — fast, with great preview capabilities, and a flexible plugin system; on the other hand, if you’ve spent years using dual-pane file managers like Midnight Commander or Total Commander as I have, many operational habits are already ingrained in your muscle memory.&lt;/p&gt;

&lt;p&gt;So I went ahead and forked Yazi to create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dual-yazi&lt;/code&gt;: &lt;a href=&quot;https://github.com/jtianling/dual-yazi&quot;&gt;https://github.com/jtianling/dual-yazi&lt;/a&gt;. It’s not a ground-up rewrite of a file manager, but rather an added workflow layer on top of the original Yazi that I wanted most: fixed dual panes, independent tabs per pane, direct cross-pane copy/move, and toggling between single-pane/dual-pane and preview modes.&lt;/p&gt;

&lt;p&gt;For me, this was a natural direction. I love Yazi’s modern TUI file management experience, but I’ve always missed the directness of traditional dual-pane managers — having two directories side by side, always in view. Now I’ve finally brought these two worlds together.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/images/2026/dual-yazi-screenshot.png&quot; alt=&quot;dual-yazi dual-pane mode screenshot&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Repository links:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Yazi official repository: &lt;a href=&quot;https://github.com/sxyazi/yazi&quot;&gt;https://github.com/sxyazi/yazi&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;My fork: &lt;a href=&quot;https://github.com/jtianling/dual-yazi&quot;&gt;https://github.com/jtianling/dual-yazi&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;why-yazi-is-worth-the-effort&quot;&gt;Why Yazi Is Worth the Effort&lt;/h3&gt;

&lt;p&gt;If you haven’t tried Yazi yet, I think it’s one of the most worthwhile terminal file managers to try in recent years.&lt;/p&gt;

&lt;p&gt;The official README lists many features, but what I personally value most are: it’s genuinely fast, with thorough async I/O and task scheduling; the preview capabilities are comprehensive — not just text, but images, PDFs, archives, and syntax-highlighted code are all covered; and the Lua plugin system is open enough that you never feel like “there are lots of features, but I can’t modify anything.”&lt;/p&gt;

&lt;p&gt;In other words, it’s not a simple tool that can only do basic &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hjkl&lt;/code&gt; directory browsing, but a terminal file manager that can seriously serve as a daily driver. The reason I want to add dual-pane support is precisely because I think the core is already good enough to build my desired interaction on top of.&lt;/p&gt;

&lt;h3 id=&quot;why-i-still-want-dual-panes&quot;&gt;Why I Still Want Dual Panes&lt;/h3&gt;

&lt;p&gt;Tabs are useful, of course, and single-pane + preview already covers many scenarios. But it’s still different from a true dual-pane workflow.&lt;/p&gt;

&lt;p&gt;For tasks like sorting a downloads directory, comparing two directory structures, filtering files from one location to move to another, or archiving files while browsing a project directory, the dual-pane mental model is simply more direct: left is source, right is target, whichever pane has focus is where operations take effect. You don’t need to constantly remember “which tab was my target directory in,” and you don’t need to switch back and forth to confirm your current context.&lt;/p&gt;

&lt;p&gt;This is why many classic file managers still have loyal users today. They may not be the most modern, but for the problem of “batch file management,” the workflow is incredibly smooth.&lt;/p&gt;

&lt;h3 id=&quot;what-dual-yazi-has-now&quot;&gt;What dual-yazi Has Now&lt;/h3&gt;

&lt;h3 id=&quot;1-dual-pane-mode&quot;&gt;1. Dual-Pane Mode&lt;/h3&gt;

&lt;p&gt;The program starts in fixed dual-pane mode by default, with a pane on each side and the left pane focused initially. Each pane has its own header and status bar, so it’s not simply slicing the original interface in half — each pane is treated as a complete browsing context.&lt;/p&gt;

&lt;p&gt;The default dual-pane layout favors directory browsing: each pane shows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent + current&lt;/code&gt;, meaning the parent directory column plus the current directory column, with preview hidden by default. The reason is simple — in dual-pane mode, the most common need is usually quick operations between two directories, not viewing large previews.&lt;/p&gt;

&lt;p&gt;Pane switching follows familiar conventions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tab&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-w w&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-w Ctrl-w&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-w h&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-w l&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-per-pane-independent-tabs&quot;&gt;2. Per-Pane Independent Tabs&lt;/h3&gt;

&lt;p&gt;Dual panes and tabs are not mutually exclusive. In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dual-yazi&lt;/code&gt;, each pane independently maintains its own set of tabs, with its own tab cursor and state. This means you don’t just have two fixed directories — you effectively have two independent workspaces that don’t interfere with each other.&lt;/p&gt;

&lt;p&gt;I really like this aspect. For example, the left pane can permanently hold tabs for source code, documents, and downloads, while the right pane holds tabs for archives, temporary directories, and target directories. The directness of traditional dual-pane file managers is preserved, but with an additional layer of flexible organization.&lt;/p&gt;

&lt;p&gt;More importantly, Yazi’s original tab operations are mostly preserved. The default keymap includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; to create a new tab&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-c&lt;/code&gt; to close the current tab; exits if it’s the last tab&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1-9&lt;/code&gt; to switch tabs&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;]&lt;/code&gt; to cycle through tabs&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;}&lt;/code&gt; to swap tabs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tab_rename&lt;/code&gt; action still exists but doesn’t have a default keybinding — you can bind it yourself in the keymap if needed.&lt;/p&gt;

&lt;p&gt;All these operations now apply to the current pane rather than sharing a global tab list.&lt;/p&gt;

&lt;h3 id=&quot;3-cross-pane-copymove-mc-style-f5f6&quot;&gt;3. Cross-Pane Copy/Move (MC-style F5/F6)&lt;/h3&gt;

&lt;p&gt;This was one of the features I most wanted to add.&lt;/p&gt;

&lt;p&gt;The original Yazi already has the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; yank/paste workflow, which works well. But if you’re used to dual-pane managers where you select items on the left and press F5 to copy to the right or F6 to move — you’d want it as a single-key action, not yank, switch pane, then paste.&lt;/p&gt;

&lt;p&gt;Here’s what’s provided:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F5&lt;/code&gt;: Copy selected items to the other pane’s current directory&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F6&lt;/code&gt;: Move selected items to the other pane’s current directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are direct cross-pane operations that don’t go through the yank register. If nothing is explicitly selected, they operate on the hovered file. The advantage is that it doesn’t disrupt the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; workflow, while providing more MC-like muscle memory in the dual-pane context.&lt;/p&gt;

&lt;p&gt;I also mapped &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F7&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F8&lt;/code&gt; to more traditional semantics: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F7&lt;/code&gt; creates a file or directory, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F8&lt;/code&gt; trashes items; for permanent deletion, use the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D&lt;/code&gt;. With this setup, the terminal file management experience really does feel more like a classic dual-pane manager.&lt;/p&gt;

&lt;h3 id=&quot;4-singledual-toggle&quot;&gt;4. Single/Dual Toggle&lt;/h3&gt;

&lt;p&gt;While I want dual panes most of the time, it’s not always necessary to see both sides.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-w o&lt;/code&gt; toggles between dual-pane and single-pane mode. One thing I care about here: when switching to single-pane, the other pane isn’t reset — just hidden. Its directory, cursor, selection, and history are all preserved. Switching back to dual-pane restores everything as it was.&lt;/p&gt;

&lt;p&gt;This means single-pane mode isn’t “exiting dual-pane” but rather “temporarily focusing attention on the current pane.” You can go single-pane when you need the full width to view content, then switch back to dual-pane for two-directory operations without any context loss.&lt;/p&gt;

&lt;p&gt;Also, in single-pane mode, the pane concept still exists. You can still switch the active pane, and you can still target &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F5&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F6&lt;/code&gt; at the currently hidden pane. I find this quite practical.&lt;/p&gt;

&lt;h3 id=&quot;5-preview-toggle&quot;&gt;5. Preview Toggle&lt;/h3&gt;

&lt;p&gt;Dual-pane mode defaults to directory browsing, but sometimes you want a quick preview.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-w p&lt;/code&gt; toggles between two layouts in dual-pane mode:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Directory mode: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent + current&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Preview mode: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;current + preview&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can switch both sides to a more “content viewing” layout without leaving dual-pane mode. Use directory mode while organizing files, switch to preview mode when you need to check file contents — both stay within the dual-pane workflow.&lt;/p&gt;

&lt;p&gt;I find this more ergonomic than a fixed dual-pane layout. Otherwise, dual-pane easily becomes a mode that can only “move files” rather than a main interface you can use long-term.&lt;/p&gt;

&lt;h3 id=&quot;6-undoredo&quot;&gt;6. Undo/Redo&lt;/h3&gt;

&lt;p&gt;This feature is also crucial.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F5&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F6&lt;/code&gt; in a dual-pane manager are very convenient, but to confidently manage files in bulk, you need an “undo button.” &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dual-yazi&lt;/code&gt; now supports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u&lt;/code&gt; for undo and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-r&lt;/code&gt; for redo. Not just rename, create, copy, move, and trash — the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;copy_to&lt;/code&gt; / &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;move_to&lt;/code&gt; operations from dual-pane mode also go into the undo stack.&lt;/p&gt;

&lt;p&gt;Of course, undo currently isn’t universal. Permanent delete &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;D&lt;/code&gt;, shell commands, bulk rename, and operations performed by plugins are not covered by this undo system. But for the file management actions I use most frequently, it’s more than sufficient.&lt;/p&gt;

&lt;h3 id=&quot;how-to-try-it&quot;&gt;How to Try It&lt;/h3&gt;

&lt;p&gt;The easiest path is via my personal Homebrew tap:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;jtianling/tap/dual-yazi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew tap jtianling/tap&lt;/code&gt; first and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install dual-yazi&lt;/code&gt; — same result. It works on macOS (Apple Silicon / Intel) and Linuxbrew. There’s no prebuilt bottle yet, so the first install compiles the Rust project from source — about 5-10 minutes on my machine. Upgrades go through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew upgrade dual-yazi&lt;/code&gt; like anything else.&lt;/p&gt;

&lt;p&gt;One thing to be aware of: the binaries are still named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yazi&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ya&lt;/code&gt;, the same as upstream, and the formula declares &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conflicts_with &quot;yazi&quot;&lt;/code&gt;. The upside is that every keymap, plugin, and doc that references the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yazi&lt;/code&gt; command keeps working unchanged; the cost is that the two can’t coexist. If you already have the official Yazi installed:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew uninstall yazi
brew &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;jtianling/tap/dual-yazi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’d rather skip brew, or you’re used to building Rust projects from source, you can also build it directly:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/jtianling/dual-yazi.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;dual-yazi
cargo &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--path&lt;/span&gt; ./yazi-fm
yazi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re already using the original Yazi, you can treat this as an experimental branch to try. Whether or not you like the dual-pane workflow, half an hour of use should be enough to tell.&lt;/p&gt;

&lt;p&gt;In short, I’m not trying to prove that “dual-pane is inherently better than single-pane.” I just wanted to bring a file management habit I’ve used for many years back into a modern tool I already love. For me, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dual-yazi&lt;/code&gt; is one step closer to fitting Yazi into my daily workflow.&lt;/p&gt;

&lt;p&gt;If you also like Yazi’s speed, preview capabilities, and plugin system, but can’t quite let go of the directness of dual-pane file managers, give this fork a try.&lt;/p&gt;

&lt;h3 id=&quot;why-a-fork-not-a-plugin&quot;&gt;Why a Fork, Not a Plugin&lt;/h3&gt;

&lt;p&gt;One more note: initially, I actually tried to build this using Yazi’s built-in plugin system. The plugin route would be friendlier to users and wouldn’t require rebasing against upstream every time, so by all accounts it should have been the preferred approach.&lt;/p&gt;

&lt;p&gt;But once I actually started, I realized Yazi’s plugins are essentially Lua scripts running on top of a Rust state machine: they can draw UI and compose existing commands, but they can’t change the underlying data structures, and they can’t add new commands. The things I wanted — independent tabs per pane, one-key cross-pane copy/move, single/dual toggle, preview toggle inside dual mode, undo/redo — pretty much all required changes at that lower level. It wasn’t something a layout tweak could solve.&lt;/p&gt;

&lt;p&gt;There are pure-plugin attempts out there, which take the approach of drawing the two existing tabs side by side. Visually it looks like dual-pane, but “switching panes” is really just switching tabs, cross-pane copy has to be simulated with “switch over → paste → switch back” (visible flicker), and you can’t have truly independent tabs on each side. It ends up feeling more like a “two-view viewer” than a real dual-pane file manager.&lt;/p&gt;

&lt;p&gt;So in the end I went with the fork. The cost is keeping it in sync with upstream myself, but in exchange every interaction can be shaped exactly the way I want. For me, that trade-off is worth it.&lt;/p&gt;

</content>
</entry>

<entry>
  <title type="html"><![CDATA[Spec Only, No Code: Perhaps This Will Become a New Way to Release Software]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/spec-first-software-release.html" />
  <id>https://jtianling.com/en/spec-first-software-release.html</id>
  <published>2026-03-14T21:52:56+08:00</published>
  <updated>2026-03-14T21:52:56+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;I recently came across &lt;a href=&quot;https://github.com/openai/symphony&quot;&gt;openai/symphony&lt;/a&gt;, and my first reaction wasn’t “I want to try this implementation” — it was “this release approach is interesting.”&lt;/p&gt;

&lt;p&gt;Because the first option in its README isn’t teaching you how to install the official implementation. Instead, it directs you to build your own based on &lt;a href=&quot;https://github.com/openai/symphony/blob/main/SPEC.md&quot;&gt;SPEC.md&lt;/a&gt;. Of course, they also included an experimental Elixir reference implementation, but that’s listed as the second option. This ordering itself is telling: what OpenAI really wants to convey first may not be a specific piece of code, but the design of the system.&lt;/p&gt;

&lt;p&gt;I think this is worth noting. As code agents become increasingly capable, the “release” of some software in the future may not necessarily require a complete source code package. Instead, it could start with a sufficiently clear spec, letting everyone implement it in their own preferred language and runtime environment. Code will still exist, but it’s starting to look more like an instance of the spec rather than the sole deliverable.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;why-i-find-this-interesting&quot;&gt;Why I Find This Interesting&lt;/h3&gt;

&lt;p&gt;Previously, when we saw an open source project, the default understanding was: the source code in the repository is the product itself, and documentation merely helps you understand and use it. Even if a project had protocols, language standards, or design documents, those were usually supporting players.&lt;/p&gt;

&lt;p&gt;But &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Symphony&lt;/code&gt; felt different to me this time. Its README puts “Option 1. Make your own” front and center, pointing to that lengthy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SPEC.md&lt;/code&gt;. This isn’t “if you’re interested, you can also check out the design docs” — it’s clearly positioning “implement it yourself based on this spec” as a first-class entry point.&lt;/p&gt;

&lt;p&gt;This shift in posture is interesting because it implies a new premise: for some software, the act of “implementation” itself is becoming cheap — cheap enough that authors can now reasonably assume that readers don’t necessarily have to use the official code and can instead bring the author’s ideas into their own familiar tech stack.&lt;/p&gt;

&lt;p&gt;Though admittedly, OpenAI also gets you to consume more tokens this way — killing two birds with one stone.&lt;/p&gt;

&lt;h3 id=&quot;why-this-is-starting-to-work-today&quot;&gt;Why This Is Starting to Work Today&lt;/h3&gt;

&lt;p&gt;I think the core variable is that code agents have gotten stronger.&lt;/p&gt;

&lt;p&gt;A few years ago, “just implement it yourself based on the spec” would have sounded more like a joke. Because no matter how clearly a spec was written, there was still a massive amount of mechanical, tedious implementation work between document and working code. Many people didn’t fail to understand the design — they simply didn’t have the energy to fully realize it.&lt;/p&gt;

&lt;p&gt;But things are different now. Especially for a project like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Symphony&lt;/code&gt;, whose core value isn’t some hard-to-replicate low-level algorithm, but rather a set of system boundary definitions and workflow designs: what’s the problem, what’s the goal, what are the core components, what’s the domain model, how are in-repo contracts like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WORKFLOW.md&lt;/code&gt; organized, how is configuration parsed, how do the agent runner and issue tracker coordinate. Once these things are written into a spec, the essential “structural skeleton” has been laid out.&lt;/p&gt;

&lt;p&gt;The remaining code is often more like translating that skeleton into a particular language and engineering style. And “translation” is precisely what code agents are getting increasingly good at.&lt;/p&gt;

&lt;h3 id=&quot;this-is-different-from-traditional-standards&quot;&gt;This Is Different from Traditional “Standards”&lt;/h3&gt;

&lt;p&gt;Of course, “spec first, implementation second” isn’t a new concept. Protocols, language standards, file format standards have always existed. But what I think is different this time is that it’s not a mature ecosystem slowly distilling a standards document after years of evolution.&lt;/p&gt;

&lt;p&gt;It’s more like treating the spec as the primary deliverable from the very beginning.&lt;/p&gt;

&lt;p&gt;And there’s an additional premise that wasn’t as strong before: the publisher can now reasonably assume that the reader has a capable enough coding agent nearby to quickly turn the spec into a working version. In other words, this spec isn’t just targeting patient human engineers who read documentation — it’s also targeting an implementation agent that can start working at any time.&lt;/p&gt;

&lt;p&gt;This transforms “spec” from a static, archival document into a publication medium that can be directly executed upon.&lt;/p&gt;

&lt;h3 id=&quot;what-i-think-might-emerge&quot;&gt;What I Think Might Emerge&lt;/h3&gt;

&lt;p&gt;I’m increasingly feeling that a certain class of software projects may genuinely move toward this “spec-first” release approach.&lt;/p&gt;

&lt;p&gt;That is, the core content an author publishes would become:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A sufficiently clear spec&lt;/li&gt;
  &lt;li&gt;A document describing system boundaries and constraints / workflow prompts&lt;/li&gt;
  &lt;li&gt;A set of tests or examples that can verify behavior&lt;/li&gt;
  &lt;li&gt;Perhaps a reference implementation, but it wouldn’t necessarily be the only implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then on the user’s end, it’s no longer just “git clone and run it,” but rather “hand the spec to my own agent and let it generate a version in my preferred language, framework, and deployment approach.”&lt;/p&gt;

&lt;p&gt;The benefits are significant.&lt;/p&gt;

&lt;p&gt;First, different teams can more naturally align with their own tech stacks. Some want Elixir, some want Go, some want Rust, some just want to absorb the core ideas into their existing system — none of them need to be tightly bound to the official implementation.&lt;/p&gt;

&lt;p&gt;Second, the author’s focus shifts from “this specific code” to “this design.” This way, a project’s influence can sometimes actually be greater, because what others adopt isn’t just a repository but the methodology behind it.&lt;/p&gt;

&lt;p&gt;Third, for many tools in the agent era, the lifespan of a reference implementation may not matter as much as before. What truly endures may be the spec, the tests, and the workflow constraints; the actual code can be continually localized, rewritten, and replaced.&lt;/p&gt;

&lt;h3 id=&quot;of-course-this-doesnt-suit-all-software&quot;&gt;Of Course, This Doesn’t Suit All Software&lt;/h3&gt;

&lt;p&gt;That said, I don’t think this path works everywhere.&lt;/p&gt;

&lt;p&gt;If a project’s core value lies in extremely detailed performance optimization, heavy engineering accumulation, or an interactive experience that’s hard to fully describe in text, then a spec alone clearly isn’t enough. There are also things like model weights, specific training results, and processed datasets that can’t simply be “given a spec and rebuilt.”&lt;/p&gt;

&lt;p&gt;So the types of things most likely to go spec-first, I’d guess, are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Orchestration / workflow tools&lt;/li&gt;
  &lt;li&gt;Protocol and service glue layers&lt;/li&gt;
  &lt;li&gt;Systems where enterprises really just want to implement the ideas, not necessarily stick to the official implementation&lt;/li&gt;
  &lt;li&gt;Infrastructure components that already emphasize contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, if specs are truly to become the primary deliverable, I think they’ll need to be paired with more supporting materials — conformance tests, standard example inputs and outputs, and even implementation hints for agents. Otherwise, no matter how long the spec is, different implementations will easily drift apart.&lt;/p&gt;

&lt;h3 id=&quot;a-change-i-find-quite-fresh&quot;&gt;A Change I Find Quite Fresh&lt;/h3&gt;

&lt;p&gt;Linus Torvalds once said, “Talk is cheap, show me the code.” That quote might not hold as well today. Because now, “code is cheap.” As code agents mature, we may start seeing a different reality: the truly stable part isn’t necessarily a particular piece of source code, but the spec behind it.&lt;/p&gt;

&lt;p&gt;Source code becomes a momentary projection of the spec — it can be implemented in this language, reimplemented in that one; organized this way today, rewritten with a different framework tomorrow. As long as the spec and behavioral constraints remain, what the author truly wanted to express is still there.&lt;/p&gt;

&lt;p&gt;If things really develop in this direction, then the very act of “releasing software” may look quite different from today. What gets released wouldn’t just be code, but design, constraints, and a specification clear enough for an agent to continue producing code from.&lt;/p&gt;

&lt;p&gt;I think this is quite interesting. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Symphony&lt;/code&gt; is certainly far from the endpoint of this trend, and it still comes with a reference implementation. But at least it has brought something that previously didn’t look like a “formal release approach” very naturally to the forefront.&lt;/p&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[net-use Released: Monitor Which IPs a macOS App Actually Connects To]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/net-use-release.html" />
  <id>https://jtianling.com/en/net-use-release.html</id>
  <published>2026-03-13T14:52:52+08:00</published>
  <updated>2026-03-13T14:52:52+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;Sometimes when setting up a firewall whitelist for an app, the hardest part isn’t configuring rules — it’s not knowing which addresses the app actually connects to. And many modern apps aren’t just a single main process; they spawn helpers, renderers, crash reporters, and other child processes, so monitoring a single PID often isn’t enough. So I wrote a small tool called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net-use&lt;/code&gt; that tracks the remote IPs accessed by a specified app and its entire process tree in real time, outputting deduplicated results.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href=&quot;https://github.com/jtianling/net-use&quot;&gt;https://github.com/jtianling/net-use&lt;/a&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;in-one-sentence&quot;&gt;In One Sentence&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net-use&lt;/code&gt; is a network connection monitoring tool for macOS. It uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proc_pidfdinfo&lt;/code&gt; system call to enumerate socket information, capturing TCP/UDP remote addresses accessed by a specified app and all its child processes in real time. To better suit the firewall whitelist use case, IPv4 addresses are aggregated to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/24&lt;/code&gt; subnets by default, while IPv6 addresses are kept in full.&lt;/p&gt;

&lt;h3 id=&quot;why-i-built-this&quot;&gt;Why I Built This&lt;/h3&gt;

&lt;p&gt;My need was simple: I wanted to know what addresses an app is actually accessing and compile the results into a whitelist I can use directly.&lt;/p&gt;

&lt;p&gt;For a quick glance at network connections, there are plenty of tools that can get the job done. But once you need to pin it down to a specific app — especially a desktop app that spawns many child processes — things get complicated: processes change, PIDs change, connections appear dynamically, and you have to deduplicate manually. So I just built a dedicated tool for this.&lt;/p&gt;

&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;

&lt;p&gt;Installation:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cargo &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;net-use
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The simplest way is to launch TUI mode directly:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;net-use
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After launching, you can browse installed apps, filter by typing, and press Enter to start monitoring. During monitoring, you can export to a file, copy to clipboard, toggle sort order, and switch between subnet-aggregated and raw IP display.&lt;/p&gt;

&lt;p&gt;If you prefer not to use the interface, you can go straight to CLI:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Monitor by Bundle ID&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;net-use &lt;span class=&quot;nt&quot;&gt;--bundle&lt;/span&gt; com.google.Chrome &lt;span class=&quot;nt&quot;&gt;--no-tui&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Monitor by process name&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;net-use &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; curl &lt;span class=&quot;nt&quot;&gt;--no-tui&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Monitor by PID&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;net-use &lt;span class=&quot;nt&quot;&gt;--pid&lt;/span&gt; 1234 &lt;span class=&quot;nt&quot;&gt;--no-tui&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Output is a deduplicated address list, for example:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;142.250.80.0/24
172.217.14.0/24
2607:f8b0:4004:800::200e
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ready to use directly as a whitelist.&lt;/p&gt;

&lt;h3 id=&quot;a-few-other-useful-features&quot;&gt;A Few Other Useful Features&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Can monitor apps that haven’t launched yet — starts collecting automatically once they start&lt;/li&gt;
  &lt;li&gt;Historical data persists after the app exits; data continues to accumulate when it reappears&lt;/li&gt;
  &lt;li&gt;Supports pause/resume monitoring&lt;/li&gt;
  &lt;li&gt;Supports persisting historical results to a file&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;

&lt;p&gt;This tool currently only supports macOS and requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt;, since reading process socket information requires elevated privileges.&lt;/p&gt;

&lt;p&gt;It’s also polling-based, defaulting to once every 200ms, so extremely short-lived connections could theoretically be missed. Additionally, some XPC services launched via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchd&lt;/code&gt; may not fall entirely within the same process tree — that’s another current limitation.&lt;/p&gt;

&lt;p&gt;In short, if you also have the need to know “which IPs an app is actually connecting to,” give it a try. For me at least, I finally don’t have to stare at Activity Monitor while manually compiling whitelists anymore.&lt;/p&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[skillsmgr Released]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/skillsmgr-release.html" />
  <id>https://jtianling.com/en/skillsmgr-release.html</id>
  <published>2026-02-03T00:00:00+08:00</published>
  <updated>2026-02-03T00:00:00+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;While organizing skills for various AI coding tools recently, I realized each tool has its own directory and format, and switching between them gets confusing fast. So I built a unified management tool: skillsmgr. It installs skills centrally into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.skills-manager/&lt;/code&gt; and deploys them to projects through a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.agents/skills/&lt;/code&gt; directory, currently supporting 44 tools.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href=&quot;https://github.com/jtianling/skills-manager&quot;&gt;https://github.com/jtianling/skills-manager&lt;/a&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;in-one-sentence&quot;&gt;In One Sentence&lt;/h3&gt;

&lt;p&gt;skillsmgr is a unified skills manager — install skills once into a local repository, then deploy them to any project or globally on demand, supporting 44 AI coding tools.&lt;/p&gt;

&lt;h3 id=&quot;highlights&quot;&gt;Highlights&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Central repository, deploy anywhere&lt;/strong&gt; — Skills are installed once into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.skills-manager/&lt;/code&gt;. After that, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt; lets you interactively pick from all locally installed skills and deploy them to any project or globally — no need to remember the original repo URL or path every time.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Custom groups for batch management&lt;/strong&gt; — Organize your own skills into named groups (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--group my-tools&lt;/code&gt;). Deploy an entire group to a project with a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add --group&lt;/code&gt; command, making it easy to maintain and share personal skill collections.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Zip archive support&lt;/strong&gt; — Install skills directly from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.zip&lt;/code&gt; files or Anthropic’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.skill&lt;/code&gt; packages, which makes it simple to package and share skill bundles outside of GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;deployment-model&quot;&gt;Deployment Model&lt;/h3&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;project/
├── .agents/
│   └── skills/
│       ├── code-review -&amp;gt; ~/.skills-manager/official/anthropic/skills/code-review
│       └── example-skill -&amp;gt; ~/.skills-manager/custom/example-skill
├── .claude/
│   └── skills -&amp;gt; ../.agents/skills
└── .cursor/
    └── skills -&amp;gt; ../.agents/skills
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All skills deploy to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.agents/skills/&lt;/code&gt;. Native tools read that directory directly. Non-native tools use a symlink bridge to their legacy skill path. You can also use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--copy&lt;/code&gt; to deploy project-local copies instead.&lt;/p&gt;

&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx skillsmgr setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Install official Anthropic skills&lt;/span&gt;
npx skillsmgr &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;anthropic

&lt;span class=&quot;c&quot;&gt;# Install from a GitHub repository&lt;/span&gt;
npx skillsmgr &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;owner/repo

&lt;span class=&quot;c&quot;&gt;# Install from a local directory or zip archive&lt;/span&gt;
npx skillsmgr &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./my-skill
npx skillsmgr &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./skills-archive.zip

&lt;span class=&quot;c&quot;&gt;# Navigate to your project and deploy interactively&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;your-project
npx skillsmgr init

&lt;span class=&quot;c&quot;&gt;# Add a specific skill to a specific tool&lt;/span&gt;
npx skillsmgr add code-review &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; claude-code

&lt;span class=&quot;c&quot;&gt;# Deploy globally&lt;/span&gt;
npx skillsmgr add code-review &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; claude-code

&lt;span class=&quot;c&quot;&gt;# Inspect deployed skills&lt;/span&gt;
npx skillsmgr list &lt;span class=&quot;nt&quot;&gt;--deployed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;supported-tools&quot;&gt;Supported Tools&lt;/h3&gt;

&lt;p&gt;Currently supports 44 AI coding tools. The interactive selector shows 16 major tools (Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, Cline, Windsurf, etc.), with 28 more available via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-a&lt;/code&gt; flag. Native tools read &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.agents/skills/&lt;/code&gt; directly; non-native tools are bridged via symlinks.&lt;/p&gt;

&lt;h3 id=&quot;features&quot;&gt;Features&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Unified management: all skills installed to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.skills-manager/&lt;/code&gt;, organized into official / community / custom categories&lt;/li&gt;
  &lt;li&gt;Multi-tool deployment: one set of skills deployed to multiple tools through a unified directory + symlink bridges&lt;/li&gt;
  &lt;li&gt;Symlinks by default: skill updates sync automatically; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--copy&lt;/code&gt; mode also available&lt;/li&gt;
  &lt;li&gt;Custom groups: batch manage and deploy your own skill collections with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--group&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Interactive selector: supports search, group collapsing, and vim-style shortcuts&lt;/li&gt;
  &lt;li&gt;Global deployment: use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-g&lt;/code&gt; to deploy to agent user-level directories&lt;/li&gt;
&lt;/ul&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[rulesmgr Released: One Set of Rules, Synced Across Multiple Tools]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/rules-manager-release.html" />
  <id>https://jtianling.com/en/rules-manager-release.html</id>
  <published>2026-02-03T00:00:00+08:00</published>
  <updated>2026-02-03T00:00:00+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;While using AI coding tools like Claude Code, Cursor, and Cline recently, I kept running into the same tedious problem: each tool has its own rules directory with slightly different formats, and team conventions need to be copy-pasted across multiple locations, easily drifting apart over time. So I built a small tool called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rulesmgr&lt;/code&gt; to manage a single set of rules and deploy them to multiple tools at once.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href=&quot;https://github.com/jtianling/rules-manager&quot;&gt;https://github.com/jtianling/rules-manager&lt;/a&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;in-one-sentence&quot;&gt;In One Sentence&lt;/h3&gt;

&lt;p&gt;Maintain a single rule template, select your target tools, and it automatically generates the corresponding directory structure, with sync support in copy mode.&lt;/p&gt;

&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx rulesmgr setup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This creates example rule templates in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.rules-manager/&lt;/code&gt;, with a structure like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/.rules-manager/
├── 01-tech-stack.md
├── 02-coding-principles.md
├── 03-architecture.md
├── 04-testing.md
├── 05-git-commit.md
├── 06-code-review.md
└── languages/
    ├── typescript-coding-style.md
    ├── python-coding-style.md
    └── ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;deploy-to-a-project&quot;&gt;Deploy to a Project&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Interactive mode&lt;/span&gt;
npx rulesmgr init

&lt;span class=&quot;c&quot;&gt;# Specify tools and language&lt;/span&gt;
npx rulesmgr init &lt;span class=&quot;nt&quot;&gt;--tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;claude-code,cursor &lt;span class=&quot;nt&quot;&gt;--lang&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;typescript

&lt;span class=&quot;c&quot;&gt;# Use copy mode&lt;/span&gt;
npx rulesmgr init &lt;span class=&quot;nt&quot;&gt;--tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;claude-code &lt;span class=&quot;nt&quot;&gt;--copy&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Deploy .gitignore&lt;/span&gt;
npx rulesmgr init &lt;span class=&quot;nt&quot;&gt;--gitignore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;sync-rules-in-copy-mode&quot;&gt;Sync Rules in Copy Mode&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npx rulesmgr &lt;span class=&quot;nb&quot;&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;supported-tools&quot;&gt;Supported Tools&lt;/h3&gt;

&lt;p&gt;Currently supports Claude Code, Cursor, Cline, Roo Code, Kilo Code, Windsurf, OpenCode, TRAE, Goose, Antigravity, and more. See the repository README for the full directory mapping.&lt;/p&gt;

&lt;p&gt;Feel free to try it out and share feedback. More tools and templates will be added over time.&lt;/p&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[Ollama Rerank Adapter]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/ollama-rerank-adapter.html" />
  <id>https://jtianling.com/en/ollama-rerank-adapter.html</id>
  <published>2025-12-17T00:00:00+08:00</published>
  <updated>2025-12-17T00:00:00+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;A lightweight HTTP service that wraps Ollama’s Rerank model into a standard Rerank API, enabling Dify and other applications to use local Ollama models for document reranking.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jtianling/dify-ollama-rerank-adapter&quot;&gt;https://github.com/jtianling/dify-ollama-rerank-adapter&lt;/a&gt;&lt;/p&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[A Chrome Extension to Add Search to the DeepSeek Website]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/add-search-to-deepseek.html" />
  <id>https://jtianling.com/en/add-search-to-deepseek.html</id>
  <published>2025-12-08T00:00:00+08:00</published>
  <updated>2025-12-08T00:00:00+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;DeepSeek is great to use, but the company has never paid much attention to improving the user experience. For example, the DeepSeek website has never had a search function for conversation history. So I built one. It’s not published on the Chrome Web Store — it’s open source, feel free to grab it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jtianling/deepseek-search-chrome&quot;&gt;https://github.com/jtianling/deepseek-search-chrome&lt;/a&gt;&lt;/p&gt;
</content>
</entry>

<entry>
  <title type="html"><![CDATA[A Programming Language Syntax Comparison Website]]></title>
  <link rel="alternate" type="text/html" href="https://jtianling.com/en/programming-language-comparison.html" />
  <id>https://jtianling.com/en/programming-language-comparison.html</id>
  <published>2025-12-02T00:00:00+08:00</published>
  <updated>2025-12-02T00:00:00+08:00</updated>
  <author>
    <name>JTianling</name>
    <uri>https://jtianling.com/en/</uri>
  </author>
  <content type="html">&lt;p&gt;I’ve always had to work with multiple programming languages simultaneously — either learning a new language while working on side projects, while also developing a company project, or writing both frontend and backend code in different languages within the same project. During these times, I always wished there was a website that could display syntax examples of the languages I’m using side by side, so my brain wouldn’t get mixed up when switching between them. After all these years, thanks to the maturity of large language models, building such a website has become incredibly easy. I’m posting the link here for anyone with the same need.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jtianling.com/programming-language-comparison&quot;&gt;https://www.jtianling.com/programming-language-comparison&lt;/a&gt;&lt;/p&gt;
</content>
</entry>

</feed>
