<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Inkstone</title><link>https://me.125520.xyz/</link><description>极简双语 Hugo 写作模板，为长读、慢写而生。</description><generator>Hugo</generator><language>zh-cn</language><managingEditor>Jason</managingEditor><copyright>© 2026 Jason</copyright><lastBuildDate>Thu, 07 May 2026 14:15:04 +0000</lastBuildDate><atom:link href="https://me.125520.xyz/tags/%E5%85%83/index.xml" rel="self" type="application/rss+xml"/><item><title>给 Claude Code 接上 Mac 通知 + Bark 推送</title><link>https://me.125520.xyz/claude-code-mac-bark-notify/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/claude-code-mac-bark-notify/</guid><description>把 Windows 版 PowerShell gist 翻译成 macOS 版本，顺手加一条 Bark 推送通道，跑长任务时手机也响。</description><content:encoded><![CDATA[<blockquote>
<p>起因：在 GitHub 上看到一份 <a href="https://gist.github.com/RisenMyth/6297cab96eb9dec912cc6c126cbed965" target="_blank" rel="noopener">Windows 下给 Claude Code 加系统通知的 gist<span class="external-mark" aria-hidden="true">↗</span></a>
，PowerShell 写的，落地 Mac 用不了。索性重写一份 macOS 版，再顺手加一条 <a href="https://day.app/" target="_blank" rel="noopener">Bark<span class="external-mark" aria-hidden="true">↗</span></a>
 推送 —— 长任务跑起来人离开屏幕，手机也能响。</p>
</blockquote>
<h2 id="1-思路">1. 思路</h2>
<p>Claude Code 的 hook 系统暴露一组生命周期事件，对&quot;被通知&quot;的需求，只需要两个：</p>
<ul>
<li><strong><code>Stop</code></strong> —— 模型当前回合结束。任务跑完该响一声。</li>
<li><strong><code>Notification</code></strong> —— 模型需要确认/输入（permission prompt、问题等）。这种更紧急。</li>
</ul>
<p>Hook 命令的 stdin 会拿到一份 JSON：<code>cwd</code>（项目路径）、<code>transcript_path</code>（JSONL 全量对话）、<code>Notification</code> 事件还会带 <code>message</code>。</p>
<p>设计取舍：</p>
<ul>
<li><strong>单文件 Python</strong> —— macOS 自带 <code>python3</code>，不再依赖 jq、curl 的 JSON 转义脏活。</li>
<li><strong>本地通知优先 <code>terminal-notifier</code></strong> —— 装了就用（可点击、有 group 去重），没装回落 <code>osascript</code>，零依赖。</li>
<li><strong>Bark 是可选项</strong> —— 只有当 <code>BARK_KEY</code> 环境变量存在才推。失败静默，不影响 hook 退出码。</li>
<li><strong>不 chmod</strong> —— 原因见末尾&quot;坑&quot;那节。脚本不可执行，靠 <code>python3 path/to/notify.py</code> 调用。</li>
</ul>
<h2 id="2-脚本claudehooksnotifypy">2. 脚本：<code>~/.claude/hooks/notify.py</code></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;Claude Code notification hook for macOS.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Local toast (osascript or terminal-notifier) + optional Bark push.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Usage: notify.py &lt;Stop|Notification|...&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Reads JSON from stdin (Claude Code hook payload):
</span></span></span><span class="line"><span class="cl"><span class="s2">  cwd               -&gt; current working directory
</span></span></span><span class="line"><span class="cl"><span class="s2">  transcript_path   -&gt; path to JSONL transcript (Stop event)
</span></span></span><span class="line"><span class="cl"><span class="s2">  message           -&gt; notification message (Notification event)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Environment variables (all optional):
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_KEY            Bark device key. If unset, Bark push is skipped.
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_SERVER         Bark server (default: https://api.day.app).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_GROUP          Notification group (default: ClaudeCode).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_SOUND          Notification sound (default: minuet).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_ICON           Custom icon URL.
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_STOP_LEVEL     Level for Stop events (default: passive).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_NOTIFY_LEVEL   Level for Notification events (default: timeSensitive).
</span></span></span><span class="line"><span class="cl"><span class="s2">                      Valid: active | timeSensitive | passive | critical.
</span></span></span><span class="line"><span class="cl"><span class="s2">  CLAUDE_NOTIFY_OFF   If set to &#34;1&#34;, suppress all notifications.
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">shutil</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">urllib.request</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">TITLE_PREFIX</span> <span class="o">=</span> <span class="s2">&#34;ClaudeCode&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">MAX_BODY_LEN</span> <span class="o">=</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">read_stdin_json</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">raw</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">raw</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">last_assistant_text</span><span class="p">(</span><span class="n">transcript_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">transcript_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">p</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s2">&#34;replace&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">lines</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">readlines</span><span class="p">()[</span><span class="o">-</span><span class="mi">30</span><span class="p">:]</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="n">lines</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">entry</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;message&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;role&#34;</span><span class="p">)</span> <span class="o">!=</span> <span class="s2">&#34;assistant&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">text</span> <span class="o">=</span> <span class="n">content</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">block</span> <span class="ow">in</span> <span class="n">content</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="nb">dict</span><span class="p">)</span> <span class="ow">and</span> <span class="n">block</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;type&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;text&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">t</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="n">t</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">                        <span class="n">text</span> <span class="o">=</span> <span class="n">t</span>
</span></span><span class="line"><span class="cl">                        <span class="k">break</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">text</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">truncate</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">s</span> <span class="o">=</span> <span class="p">(</span><span class="n">s</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">s</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">s</span><span class="p">[:</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="s2">&#34;…&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_mac_notification</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">tn</span> <span class="o">=</span> <span class="n">shutil</span><span class="o">.</span><span class="n">which</span><span class="p">(</span><span class="s2">&#34;terminal-notifier&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">tn</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="p">[</span><span class="n">tn</span><span class="p">,</span> <span class="s2">&#34;-title&#34;</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="s2">&#34;-message&#34;</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                 <span class="s2">&#34;-sound&#34;</span><span class="p">,</span> <span class="s2">&#34;Glass&#34;</span><span class="p">,</span> <span class="s2">&#34;-group&#34;</span><span class="p">,</span> <span class="s2">&#34;claude-code&#34;</span><span class="p">,</span> <span class="s2">&#34;-ignoreDnD&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">check</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">esc</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\\</span><span class="s2">&#34;</span><span class="p">,</span> <span class="s2">&#34;</span><span class="se">\\\\</span><span class="s2">&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;&#34;&#39;</span><span class="p">,</span> <span class="s1">&#39;</span><span class="se">\\</span><span class="s1">&#34;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">script</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s1">&#39;display notification &#34;</span><span class="si">{</span><span class="n">esc</span><span class="p">(</span><span class="n">body</span><span class="p">)</span><span class="si">}</span><span class="s1">&#34; &#39;</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s1">&#39;with title &#34;</span><span class="si">{</span><span class="n">esc</span><span class="p">(</span><span class="n">title</span><span class="p">)</span><span class="si">}</span><span class="s1">&#34; sound name &#34;Glass&#34;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">[</span><span class="s2">&#34;osascript&#34;</span><span class="p">,</span> <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="n">script</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">check</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_bark_push</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">event</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_KEY&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">key</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_SERVER&#34;</span><span class="p">,</span> <span class="s2">&#34;https://api.day.app&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Notification&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">level</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_NOTIFY_LEVEL&#34;</span><span class="p">,</span> <span class="s2">&#34;timeSensitive&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Stop&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">level</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_STOP_LEVEL&#34;</span><span class="p">,</span> <span class="s2">&#34;passive&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">level</span> <span class="o">=</span> <span class="s2">&#34;active&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;title&#34;</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">body</span> <span class="ow">or</span> <span class="s2">&#34; &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;group&#34;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_GROUP&#34;</span><span class="p">,</span> <span class="s2">&#34;ClaudeCode&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;level&#34;</span><span class="p">:</span> <span class="n">level</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sound&#34;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_SOUND&#34;</span><span class="p">,</span> <span class="s2">&#34;minuet&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">icon</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_ICON&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload</span><span class="p">[</span><span class="s2">&#34;icon&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">icon</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">Request</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">server</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">data</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">method</span><span class="o">=</span><span class="s2">&#34;POST&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json; charset=utf-8&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">r</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_NOTIFY_OFF&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;1&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s2">&#34;Stop&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">read_stdin_json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">cwd</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;cwd&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">project</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">cwd</span><span class="p">)</span> <span class="k">if</span> <span class="n">cwd</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Stop&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">TITLE_PREFIX</span><span class="si">}</span><span class="s2"> - </span><span class="si">{</span><span class="n">project</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">if</span> <span class="n">project</span> <span class="k">else</span> <span class="n">TITLE_PREFIX</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">transcript</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;transcript_path&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">transcript</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">body</span> <span class="o">=</span> <span class="n">last_assistant_text</span><span class="p">(</span><span class="n">transcript</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">body</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="n">body</span> <span class="o">=</span> <span class="s2">&#34;Task completed, please review results.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Notification&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">TITLE_PREFIX</span><span class="si">}</span><span class="s2"> - Needs Attention&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">project</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2"> - </span><span class="si">{</span><span class="n">project</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;message&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> \
</span></span><span class="line"><span class="cl">            <span class="ow">or</span> <span class="s2">&#34;Claude is waiting for your input or approval.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="n">TITLE_PREFIX</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Event received: </span><span class="si">{</span><span class="n">event</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">body</span> <span class="o">=</span> <span class="n">truncate</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">MAX_BODY_LEN</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">send_mac_notification</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">send_bark_push</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</span></span></code></pre></div><p>几个细节值得一提：</p>
<ul>
<li><strong>抓 transcript 倒数往前找</strong> —— 只读最后 30 行 JSONL，倒序遍历，第一条 <code>role == &quot;assistant&quot;</code> 且非空文本就用。<code>content</code> 字段在不同 SDK 版本下可能是 string 也可能是 array，两种都处理。</li>
<li><strong>truncate 用 <code>…</code> 单字符</strong> —— 而不是 <code>...</code> 三个 ASCII 句号，省 2 字节给正文。</li>
<li><strong>Bark <code>level</code> 分场景</strong> —— <code>Stop</code> 默认 <code>passive</code>（不打扰，只入推送列表），<code>Notification</code> 默认 <code>timeSensitive</code>（穿透专注模式）。等长任务跑完手机不必尖叫，但要确认时必须能听见。</li>
<li><strong>失败静默</strong> —— Bark 推不上、osascript 报错、transcript 读不到，全部吃异常返回。Hook 不应该把模型卡死。</li>
</ul>
<h2 id="3-接入-claudesettingsjson">3. 接入 <code>~/.claude/settings.json</code></h2>
<p>把下面这块合进 <code>settings.json</code>，<strong>保留你原有的 <code>env / permissions / enabledPlugins</code> 等字段</strong>：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Notification&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;matcher&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;python3 /Users/you/.claude/hooks/notify.py Notification&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;timeout&#34;</span><span class="p">:</span> <span class="mi">15</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Stop&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;matcher&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;python3 /Users/you/.claude/hooks/notify.py Stop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;timeout&#34;</span><span class="p">:</span> <span class="mi">15</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>matcher: &quot;&quot;</code> 是匹配所有 —— Stop / Notification 这两个事件本来也没有 sub-matcher 概念。<code>timeout: 15</code> 秒兜底，超时不影响主流程。</p>
<div class="callout callout-warn" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></div>
  <div class="callout-body"><p class="callout-title">路径必须绝对</p>
    hook command 的 cwd 不可预期（取决于当时 Claude 在哪个项目里）。<code>~/.claude/hooks/notify.py</code> 这种波浪号不会展开，必须写成 <code>/Users/&lt;你&gt;/.claude/hooks/notify.py</code>。
  </div>
</div>

<h2 id="4-启用-bark">4. 启用 Bark</h2>
<p>Bark 是 iOS 上一个挺老的推送工具，免费版 key 在 <a href="https://day.app/" target="_blank" rel="noopener">day.app<span class="external-mark" aria-hidden="true">↗</span></a>
 拿。装好 app，主页能看到一段类似 <code>https://api.day.app/abc123xyz/</code> 的链接 —— 中间那段就是设备 key。</p>
<p>把它加到 <code>settings.json</code> 的 <code>env</code> 块（hook 进程会继承）：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;env&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;BARK_KEY&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123xyz&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>可选环境变量速查：</p>
<table>
  <thead>
      <tr>
          <th>变量</th>
          <th>默认值</th>
          <th>用途</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>BARK_SERVER</code></td>
          <td><code>https://api.day.app</code></td>
          <td>自建服务器改这个</td>
      </tr>
      <tr>
          <td><code>BARK_GROUP</code></td>
          <td><code>ClaudeCode</code></td>
          <td>通知分组（手机折叠用）</td>
      </tr>
      <tr>
          <td><code>BARK_SOUND</code></td>
          <td><code>minuet</code></td>
          <td>铃声名</td>
      </tr>
      <tr>
          <td><code>BARK_STOP_LEVEL</code></td>
          <td><code>passive</code></td>
          <td>任务完成的等级</td>
      </tr>
      <tr>
          <td><code>BARK_NOTIFY_LEVEL</code></td>
          <td><code>timeSensitive</code></td>
          <td>需要确认的等级</td>
      </tr>
      <tr>
          <td><code>CLAUDE_NOTIFY_OFF</code></td>
          <td>(未设)</td>
          <td>设为 <code>1</code> 临时全关</td>
      </tr>
  </tbody>
</table>
<h2 id="5-验证">5. 验证</h2>
<p>设好 <code>BARK_KEY</code> 后，手动跑一发 Notification 模拟：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">BARK_KEY</span><span class="o">=</span>你的key <span class="nb">echo</span> <span class="s1">&#39;{&#34;cwd&#34;:&#34;/tmp&#34;,&#34;message&#34;:&#34;test&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="p">|</span> python3 ~/.claude/hooks/notify.py Notification
</span></span></code></pre></div><p>正常情况下：</p>
<ol>
<li>桌面右上角弹一条 macOS 通知</li>
<li>手机 Bark 通知到达，分组是 <code>ClaudeCode</code>，级别是 <code>timeSensitive</code></li>
</ol>
<p>如果只想看本地通知不打扰手机，把 <code>BARK_KEY</code> 临时去掉就行。</p>
<h2 id="6-几个坑">6. 几个坑</h2>
<div class="callout callout-warn" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></div>
  <div class="callout-body"><p class="callout-title">macOS 通知权限</p>
    <code>osascript display notification</code> 弹的通知归属到<strong>当前终端 app</strong>（Terminal / iTerm / Ghostty / Warp）。如果完全没声没影：系统设置 → 通知 → 找到对应终端 → 允许通知。
  </div>
</div>

<p><strong>hook 不生效？</strong> Claude Code 的 settings watcher 只盯着会话启动时已经存在的目录。如果 <code>.claude/</code> 是会话开始后才创建的，settings 修改可能不会被 reload。打开一次 <code>/hooks</code> 菜单（会强制 reload），或者重启 Claude Code session。</p>
<p><strong><code>chmod</code> 被 deny 怎么办？</strong> 我的 settings 里 <code>permissions.deny</code> 包含 <code>Bash(chmod:*)</code>，所以脚本不能 <code>chmod +x</code>。解法是 hook command 直接用 <code>python3 path/to/notify.py</code> 调用，不依赖文件可执行位 —— 反而更显式，少一处隐式状态。</p>
<p><strong>通知正文太长？</strong> 脚本里 <code>MAX_BODY_LEN = 200</code>，截到 199 字符 + <code>…</code>。macOS 通知中心本身也会截断，但提前截能避免在 Bark 上看到一串 JSON 转义崩溃的字符。</p>
<p><strong>想再省一点？</strong> Stop 事件每回合都触发，会很吵。两个解法：</p>
<ul>
<li>在 Bark 那条线把 <code>BARK_STOP_LEVEL</code> 已经设成 <code>passive</code> —— 只入推送列表，不响。</li>
<li>想本地也不响：在 <code>send_mac_notification</code> 里检查 <code>event == &quot;Stop&quot;</code> 时不发 osascript 就行（5 行改动）。</li>
</ul>
<h2 id="7-可拓展方向">7. 可拓展方向</h2>
<p>留给以后自己折腾：</p>
<ul>
<li><strong>Telegram / Lark / Slack</strong> —— 把 <code>send_bark_push</code> 改名 <code>send_remote_push</code>，按 <code>REMOTE_KIND</code> 环境变量分发。Bark 只是其中一种。</li>
<li><strong>细化 Notification 文案</strong> —— 当前直接拿模型给的 message。可以判断 <code>tool_name</code>（如果将来 hook payload 加进来）做更精准的标题。</li>
<li><strong>聚合连发</strong> —— 短时间内多次 Stop 合并成一条（debounce 5s）。当前每次都响。</li>
<li><strong>统计</strong> —— 把每次 hook 触发记一行到 <code>~/.claude/hooks/notify.log</code>，看一周下来跑了多少回合、几次需要确认。这些数据其实挺有意思。</li>
</ul>
<hr>
<p>整套配置改一次就一直在了。每次模型响完 / 卡住等输入，桌面右上角&quot;叮&quot;一声，手机口袋里也跟着震一下 —— 长任务跑起来终于可以放心去倒杯水。</p>
]]></content:encoded></item><item><title>T11e Callout 冒烟测试</title><link>https://me.125520.xyz/t11e-callout-smoke/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11e-callout-smoke/</guid><description>callout shortcode 四类 type 与 title 变体联调。</description><content:encoded><![CDATA[<blockquote>
<p>T11e 渲染验证。callout 的 4 种 type（note / tip / warn / danger）由 <code>layouts/partials/icon.html</code> 与 <code>assets/css/components.css</code> 实际支持，其余 type 会落到无图标&quot;裸壳&quot;——本页只列这 4 个。</p>
</blockquote>
<h2 id="1-四种-type带-title">1. 四种 type，带 title</h2>
<div class="callout callout-note" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></div>
  <div class="callout-body"><p class="callout-title">说明</p>
    默认 type。左侧 3px 灰条，中性图标。
  </div>
</div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.7V18h8v-3.3A7 7 0 0 0 12 2z"/></svg></div>
  <div class="callout-body"><p class="callout-title">提示</p>
    绿条绿图标。给读者的小贴士、隐藏用法。
  </div>
</div>

<div class="callout callout-warn" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></div>
  <div class="callout-body"><p class="callout-title">注意</p>
    琥珀色条，提示有副作用、需要权衡的操作。
  </div>
</div>

<div class="callout callout-danger" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg></div>
  <div class="callout-body"><p class="callout-title">谨慎</p>
    朱砂红条 + 浅粉底，与全站 accent 同源，用于不可逆操作。
  </div>
</div>

<h2 id="2-不带-title">2. 不带 title</h2>
<div class="callout callout-note" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></div>
  <div class="callout-body">
    省略 title 时，第一行直接接 body。
  </div>
</div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12.7V18h8v-3.3A7 7 0 0 0 12 2z"/></svg></div>
  <div class="callout-body">
    这种形态适合一句话提醒。
  </div>
</div>

<h2 id="3-嵌套-markdown">3. 嵌套 markdown</h2>
<div class="callout callout-note" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></div>
  <div class="callout-body"><p class="callout-title">嵌套测试</p>
    callout body 走 <code>RenderString</code>，所以 <strong>加粗</strong>、<a href="/about/">内链</a>
、<code>代码</code> 都该正确渲染。
  </div>
</div>

<h2 id="自检清单">自检清单</h2>
<ul>
<li><input disabled="" type="checkbox"> 4 种 type 颜色互不相同（灰 / 绿 / 琥珀 / 朱砂红）</li>
<li><input disabled="" type="checkbox"> icon partial 4 种都能取到 SVG，没有空 <code>&lt;div class=&quot;callout-icon&quot;&gt;</code></li>
<li><input disabled="" type="checkbox"> 带 title 的版本 <code>&lt;p class=&quot;callout-title&quot;&gt;</code> 出现且字重加粗</li>
<li><input disabled="" type="checkbox"> 嵌套 markdown 正确渲染（粗体 / 内链 / inline code）</li>
<li><input disabled="" type="checkbox"> 暗色模式下 4 种 type 仍可读</li>
</ul>
]]></content:encoded></item><item><title>T11f Pullquote 冒烟测试</title><link>https://me.125520.xyz/t11f-pullquote-smoke/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11f-pullquote-smoke/</guid><description>pullquote shortcode 在 prose 容器中的两种形态。</description><content:encoded><![CDATA[<blockquote>
<p>T11f 渲染验证。pullquote 是 E8 之后补的 shortcode，输出 <code>&lt;blockquote class=&quot;pullquote&quot;&gt;</code>，由 <code>prose.css</code> 接样式。本页验证两种形态：带 author / 不带 author。</p>
</blockquote>
<h2 id="1-带-author">1. 带 author</h2>

<blockquote class="pullquote">
  真的猛士，敢于直面惨淡的人生。
  <cite>— 鲁迅</cite>
</blockquote>

<h2 id="2-不带-author">2. 不带 author</h2>

<blockquote class="pullquote">
  够用就好，不是审美的天花板，是审美的入场券。
  
</blockquote>

<h2 id="3-inner-含-markdown">3. Inner 含 markdown</h2>

<blockquote class="pullquote">
  <strong>好的写作</strong>，先于 <a href="/about/">风格</a>
，先于 <code>工具</code>。
  <cite>— 某位编辑</cite>
</blockquote>

<h2 id="自检清单">自检清单</h2>
<ul>
<li><input disabled="" type="checkbox"> 两种形态都呈 prose pullquote 样式（左侧大引号 / 居中或左对齐 / 字号大于 body）</li>
<li><input disabled="" type="checkbox"> 带 author 的版本，<code>&lt;cite&gt;— 鲁迅&lt;/cite&gt;</code> 正确出现并比正文字号小</li>
<li><input disabled="" type="checkbox"> 不带 author 的版本，没有空的 <code>&lt;cite&gt;</code></li>
<li><input disabled="" type="checkbox"> 嵌套 markdown 正确渲染（粗体 / 内链 / inline code）</li>
<li><input disabled="" type="checkbox"> 暗色模式下 pullquote 边界与字色都可读</li>
</ul>
]]></content:encoded></item><item><title>T11g Gallery 冒烟测试</title><link>https://me.125520.xyz/t11g-gallery-smoke/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11g-gallery-smoke/</guid><description>gallery shortcode runtime 数据加载与 lightGallery 弹层联调。</description><content:encoded><![CDATA[<blockquote>
<p>T11g 渲染验证。gallery shortcode 自身只吐占位 div，由 <code>shortcode_gallery</code> partial 在 runtime 拉 JSON、调 justifiedGallery 排版、再交给 lightGallery 处理弹层。fixture 见 <a href="/data/smoke/gallery.json"><code>/data/smoke/gallery.json</code></a>
（3 张 placehold.co 占位图）。</p>
</blockquote>
<h2 id="1-单个-gallery">1. 单个 gallery</h2>

  <link
    rel="stylesheet"
    type="text/css"
    href="https://cdn.jsdmirror.com/npm/justifiedGallery@3.8.1/dist/css/justifiedGallery.min.css"
  />
  <link
    rel="stylesheet"
    type="text/css"
    href="https://cdn.jsdmirror.com/npm/lightgallery@2.9.0/css/lightgallery-bundle.min.css"
  />
  <link
    rel="stylesheet"
    type="text/css"
    href="/css/_shortcodes/gallery.css"
  />


<div
  class="shortcode-gallery"
  shortcode="gallery"
  id="gallery-1"
  data="/data/smoke/gallery.json"
></div>

<h2 id="自检清单">自检清单</h2>
<ul>
<li><input disabled="" type="checkbox"> 浏览器 Network 面板：<code>/data/smoke/gallery.json</code> 200 OK</li>
<li><input disabled="" type="checkbox"> CDN 注入：jQuery / justifiedGallery / lightGallery (+ thumbnail / zoom 插件) 都加载成功</li>
<li><input disabled="" type="checkbox"> DOM：<code>#gallery-1</code> 内被 JS 替换成 3 个 <code>&lt;a&gt;&lt;img&gt;&lt;/a&gt;</code>，按 justifiedGallery 排成一行</li>
<li><input disabled="" type="checkbox"> 点击任一缩略图：lightGallery 全屏弹层打开，能在 3 张图之间左右切换</li>
<li><input disabled="" type="checkbox"> 弹层底部出现 <code>subHtml</code> 注入的标题块（&ldquo;Smoke fixture #1/2/3&rdquo;）</li>
<li><input disabled="" type="checkbox"> 暗色模式下背景与控件可读</li>
<li><input disabled="" type="checkbox"> Console 无 <code>lightGallery</code> / <code>justifiedGallery</code> 相关错误</li>
</ul>
]]></content:encoded></item><item><title>T11h Include 冒烟测试</title><link>https://me.125520.xyz/t11h-include-smoke/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11h-include-smoke/</guid><description>include shortcode readFile + safeHTML 路径联调。</description><content:encoded><![CDATA[<blockquote>
<p>T11h 渲染验证。include shortcode 走 <code>readFile | safeHTML</code>，把项目根相对路径下的文件内容当 HTML 直接插入。fixture 在 <code>assets/snippets/hello.html</code> — 一段标题 / 列表 / 链接的 HTML 片段。</p>
</blockquote>
<h2 id="1-include-一个-html-片段">1. include 一个 HTML 片段</h2>
<p>下面这一块是被 include 进来的 HTML：</p>


<section class="include-fixture-demo">
  <h3>Inkstone include fixture</h3>
  <p>This block was loaded from <code>assets/snippets/hello.html</code> via the <code>include</code> shortcode.</p>
  <ul>
    <li>Heading rendered as <code>&lt;h3&gt;</code></li>
    <li>Inline code rendered as <code>&lt;code&gt;</code></li>
    <li>External anchor: <a href="https://example.com" target="_blank" rel="noopener">example.com</a></li>
  </ul>
</section>


<h2 id="自检清单">自检清单</h2>
<ul>
<li><input disabled="" type="checkbox"> 上方区块以 HTML 形式渲染，<strong>不是源码</strong>（没有可见的 <code>&lt;section&gt;</code> 标签字符）</li>
<li><input disabled="" type="checkbox"> 标题 &ldquo;Inkstone include fixture&rdquo; 用 <code>&lt;h3&gt;</code> 渲染</li>
<li><input disabled="" type="checkbox"> 列表三项都呈 <code>&lt;li&gt;</code>，里面的 <code>&lt;code&gt;</code> 走站点 inline-code 样式</li>
<li><input disabled="" type="checkbox"> 末项的链接（&ldquo;example.com&rdquo;）是真链接（鼠标悬停状态有 <code>target=&quot;_blank&quot;</code>）</li>
<li><input disabled="" type="checkbox"> Hugo 构建无 readFile 错误</li>
</ul>
]]></content:encoded></item><item><title>T11i Include-Code 冒烟测试</title><link>https://me.125520.xyz/t11i-include-code-smoke/</link><pubDate>Tue, 05 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11i-include-code-smoke/</guid><description>include-code shortcode 读文件 + Chroma 高亮 + language 切换联调。</description><content:encoded><![CDATA[<blockquote>
<p>T11i 渲染验证。include-code shortcode 走 <code>readFile</code>，把内容包成 fenced code 再 <code>markdownify</code>，最终走 Chroma 高亮。本页验证两种语言（python / yaml）的 fixture 切换。</p>
</blockquote>
<h2 id="1-pythonassetssnippetshellopy">1. Python（<code>assets/snippets/hello.py</code>）</h2>


<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">gcd</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Return the greatest common divisor of a and b.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="n">b</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span> <span class="o">%</span> <span class="n">b</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">a</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">gcd</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">18</span><span class="p">))</span>
</span></span></code></pre></div>

<h2 id="2-yamlassetssnippetsconfigyaml">2. YAML（<code>assets/snippets/config.yaml</code>）</h2>


<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">logging</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">level</span><span class="p">:</span><span class="w"> </span><span class="l">info</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">features</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">search</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">rss</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sitemap</span><span class="w">
</span></span></span></code></pre></div>

<h2 id="自检清单">自检清单</h2>
<ul>
<li><input disabled="" type="checkbox"> 两段都呈 <code>&lt;pre&gt;&lt;code class=&quot;language-...&quot;&gt;</code> 而不是源码字符</li>
<li><input disabled="" type="checkbox"> Python 段：<code>def</code> / <code>if __name__</code> 等关键字被 Chroma 着色，docstring 也染色</li>
<li><input disabled="" type="checkbox"> YAML 段：键、字符串、数字按 yaml 规则着色（不退化成纯文本）</li>
<li><input disabled="" type="checkbox"> 行高 / 字号 / 背景与站点其他 fenced 代码块一致</li>
<li><input disabled="" type="checkbox"> 暗色模式两段都仍可读（语法配色与背景对比足够）</li>
<li><input disabled="" type="checkbox"> Hugo 构建无 readFile 错误</li>
</ul>
]]></content:encoded></item><item><title>T11d 代码 Tab 切换冒烟测试</title><link>https://me.125520.xyz/t11d-code-tabs-demo/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11d-code-tabs-demo/</guid><description>tab + highlight / fenced code 组合用法：跨平台命令、多语言、多配置格式。</description><content:encoded><![CDATA[<blockquote>
<p>T11d 渲染验证。展示 <code>tab</code> shortcode 与代码块组合时的常见三种场景。
如果切换 tab 后代码块没有跟随切换，说明 tab.css 的 <code>:checked+label+.shortcode-tab-item</code> 选择器没生效。</p>
</blockquote>
<h2 id="1-跨平台命令切换macos--windows--linux">1. 跨平台命令切换（macOS / Windows / Linux）</h2>
<p>最常见的用法 —— 同一个目的，不同 shell 给不同命令。</p>

  <link rel="stylesheet" type="text/css" href="/css/_shortcodes/tab.css" />


<div class="shortcode-tab" style="justify-content: center;">
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304342166-0"
      value="🍎 macOS"
      name="1778163304342166" checked="checked" 
    />
    <label for="1778163304342166-0">🍎 macOS</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">podman compose <span class="nb">exec</span> openclaw-gateway <span class="se">\
</span></span></span><span class="line"><span class="cl">  node dist/index.js channels login --channel feishu
</span></span></code></pre></div>
    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304342166-1"
      value="🪟 Windows"
      name="1778163304342166"
    />
    <label for="1778163304342166-1">🪟 Windows</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">podman</span> <span class="n">compose</span> <span class="n">exec</span> <span class="nb">openclaw-gateway</span> <span class="p">`</span>
</span></span><span class="line"><span class="cl">  <span class="n">node</span> <span class="n">dist</span><span class="p">/</span><span class="n">index</span><span class="p">.</span><span class="py">js</span> <span class="n">channels</span> <span class="n">login</span> <span class="p">-</span><span class="n">-channel</span> <span class="n">feishu</span>
</span></span></code></pre></div>
    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304342166-2"
      value="🐧 Linux"
      name="1778163304342166"
    />
    <label for="1778163304342166-2">🐧 Linux</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo podman compose <span class="nb">exec</span> openclaw-gateway <span class="se">\
</span></span></span><span class="line"><span class="cl">  node dist/index.js channels login --channel feishu
</span></span></code></pre></div>
    </div>
</div>

<p>使用配套 APP 扫描生成的二维码进行后续配置即可。</p>
<h2 id="2-多语言代码示例切换同一算法">2. 多语言代码示例切换（同一算法）</h2>
<p>适合「同一个概念，不同语言怎么写」的科普场景。</p>



<div class="shortcode-tab" style="justify-content: center;">
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304349713-0"
      value="Python"
      name="1778163304349713" checked="checked" 
    />
    <label for="1778163304349713-0">Python</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">gcd</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="n">b</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span> <span class="o">%</span> <span class="n">b</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">a</span>
</span></span></code></pre></div>
    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304349713-1"
      value="TypeScript"
      name="1778163304349713"
    />
    <label for="1778163304349713-1">TypeScript</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">gcd</span><span class="p">(</span><span class="nx">a</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">b</span>: <span class="kt">number</span><span class="p">)</span><span class="o">:</span> <span class="kt">number</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="nx">b</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="nx">b</span><span class="p">,</span> <span class="nx">a</span> <span class="o">%</span> <span class="nx">b</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">a</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>
    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304349713-2"
      value="Go"
      name="1778163304349713"
    />
    <label for="1778163304349713-2">Go</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">gcd</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">b</span><span class="p">,</span><span class="w"> </span><span class="nx">a</span><span class="o">%</span><span class="nx">b</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">a</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div>
    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304349713-3"
      value="Rust"
      name="1778163304349713"
    />
    <label for="1778163304349713-3">Rust</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">gcd</span><span class="p">(</span><span class="k">mut</span><span class="w"> </span><span class="n">a</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">b</span>: <span class="kt">u64</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="kt">u64</span> <span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">while</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">b</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="n">b</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">t</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">a</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div>
    </div>
</div>

<h2 id="3-多配置格式切换yaml--toml--json">3. 多配置格式切换（YAML / TOML / JSON）</h2>
<p>「随你喜欢哪种格式」型的配置示例。</p>



<div class="shortcode-tab" style="justify-content: flex-start;">
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304351396-0"
      value="YAML"
      name="1778163304351396" checked="checked" 
    />
    <label for="1778163304351396-0">YAML</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">channels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">feishu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div>
    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304351396-1"
      value="TOML"
      name="1778163304351396"
    />
    <label for="1778163304351396-1">TOML</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">server</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">host</span> <span class="p">=</span> <span class="s2">&#34;0.0.0.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">port</span> <span class="p">=</span> <span class="mi">8080</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">channels</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;feishu&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">enabled</span> <span class="p">=</span> <span class="kc">true</span>
</span></span></code></pre></div>
    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304351396-2"
      value="JSON"
      name="1778163304351396"
    />
    <label for="1778163304351396-2">JSON</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;server&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;host&#34;</span><span class="p">:</span> <span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="nt">&#34;port&#34;</span><span class="p">:</span> <span class="mi">8080</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;channels&#34;</span><span class="p">:</span> <span class="p">[{</span> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;feishu&#34;</span><span class="p">,</span> <span class="nt">&#34;enabled&#34;</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>
    </div>
</div>

<h2 id="4-tab--highlight-shortcode带-title-条">4. tab + highlight shortcode（带 title 条）</h2>
<p>如果想给代码块加个标题条（Hugo <code>highlight</code> shortcode 自带），把 fenced block 换成 <code>{{&lt; highlight &gt;}}</code> 即可：</p>



<div class="shortcode-tab" style="justify-content: center;">
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304352387-0"
      value="开发环境"
      name="1778163304352387" checked="checked" 
    />
    <label for="1778163304352387-0">开发环境</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight-wrapper">
    <div class="highlight-title">
      <span>.env.development</span>
    </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">DATABASE_URL</span><span class="o">=</span>postgres://localhost:5432/inkstone_dev
</span></span><span class="line"><span class="cl"><span class="nv">DEBUG</span><span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOG_LEVEL</span><span class="o">=</span>debug</span></span></code></pre></div>
</div>

    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304352387-1"
      value="生产环境"
      name="1778163304352387"
    />
    <label for="1778163304352387-1">生产环境</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <div class="highlight-wrapper">
    <div class="highlight-title">
      <span>.env.production</span>
    </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">DATABASE_URL</span><span class="o">=</span>postgres://prod-db:5432/inkstone
</span></span><span class="line"><span class="cl"><span class="nv">DEBUG</span><span class="o">=</span><span class="nb">false</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOG_LEVEL</span><span class="o">=</span>warn</span></span></code></pre></div>
</div>

    </div>
</div>

<h2 id="自检清单">自检清单</h2>
<ul>
<li><input disabled="" type="checkbox"> tab 切换：点不同 label，代码块跟随切换（CSS only，不依赖 JS）</li>
<li><input disabled="" type="checkbox"> 选中态：当前 tab 的 label 下有 2px 下划线</li>
<li><input disabled="" type="checkbox"> 嵌套：fenced code block 与 <code>{{&lt; highlight &gt;}}</code> 都能正常渲染</li>
<li><input disabled="" type="checkbox"> emoji label：<code>🍎 / 🪟 / 🐧</code> 正常显示</li>
<li><input disabled="" type="checkbox"> <code>position</code> 属性：第 3 节用 <code>position=&quot;start&quot;</code>，labels 应靠左对齐</li>
<li><input disabled="" type="checkbox"> 暗色模式：切到 dark theme 后，下划线颜色对调（<code>--color-shortcode-tab-label-bottom</code>）</li>
</ul>
]]></content:encoded></item><item><title>T11a 学术核心冒烟测试</title><link>https://me.125520.xyz/t11a-smoke-test/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11a-smoke-test/</guid><description>mathjax / mermaid / markmap / antv-g2 / pseudocode 五件套联调。</description><content:encoded><![CDATA[<blockquote>
<p>这是 T11a 的渲染验证页面。如果你看到的是源码而不是图，说明对应的 shortcode 没接好。</p>
</blockquote>
<h2 id="1-mathjax">1. MathJax</h2>
<p>行内：质能方程 \(E = mc^2\)。</p>
<p>行间：</p>
<p>$$
\int_{-\infty}^{\infty} e^{-x^2},dx = \sqrt{\pi}
$$</p>
<h2 id="2-mermaid">2. Mermaid</h2>
<pre class="mermaid" data-pagefind-ignore>
  flowchart LR
  A[content] --&gt; B[markup hook]
  B --&gt; C[Page.Store hasMermaid]
  C --&gt; D[scripts.html init]
</pre>

<h2 id="3-markmap">3. Markmap</h2>
<div class="markmap-container" data-pagefind-ignore>
  <div class="markmap">
    <script type="text/template">
      - T11
  - T11a 学术核心
    - mathjax
    - mermaid
    - markmap
    - antv-g2
    - pseudocode
  - T11b 媒体
  - T11c 内容工具
    </script>
  </div>
</div>

<h2 id="4-pseudocode">4. Pseudocode</h2>

  <link
    rel="stylesheet"
    type="text/css"
    href="/css/_shortcodes/pseudocode.css"
  />


<div data-pagefind-ignore>
  <pre
    class="shortcode-pseudocode"
    data-indent-size="1.2em"
    data-comment-delimiter="// "
    data-line-number="true"
    data-line-number-punc=":"
    data-no-end="true"
    data-title-prefix="算法"
  >
    
\begin{algorithm}
\caption{欧几里得算法 (gcd)}
\begin{algorithmic}
\PROCEDURE{Gcd}{$a, b$}
  \WHILE{$b \neq 0$}
    \STATE $t \gets b$
    \STATE $b \gets a \bmod b$
    \STATE $a \gets t$
  \ENDWHILE
  \RETURN $a$
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}

  </pre>
</div>

<h2 id="5-antv-g2">5. AntV G2</h2>
<p>(antv-g2 需要 data 文件存在；这里仅占位调用，未提供 script= 时跳过具体图表，但应能正确加载 g2 库。)</p>
<h2 id="6-主题切换跟随05-04-theme-broadcast">6. 主题切换跟随（05-04-theme-broadcast）</h2>
<p>切到本页后点侧栏「主题」按钮，应当看到：</p>
<ul>
<li><input disabled="" type="checkbox"> mermaid 流程图配色立即翻转（neutral ↔ dark），无双图重影</li>
<li><input disabled="" type="checkbox"> 若有 antv-g2 真实 chart，配色立即翻转</li>
<li><input disabled="" type="checkbox"> 切 5 次后 <code>window.antvG2Charts.length</code> 不增长（防内存泄漏）</li>
<li><input disabled="" type="checkbox"> 切换时 console 无 error</li>
<li><input disabled="" type="checkbox"> markmap 节点文字 / 链接 / 代码块跟随切换（CSS token 继承到
<code>&lt;foreignObject&gt;</code>；分支线条因库内 inline stroke 不跟随，已知限制见
<code>.trellis/spec/frontend/themed-components.md</code> §4）</li>
</ul>
]]></content:encoded></item><item><title>T11b 媒体嵌入冒烟测试</title><link>https://me.125520.xyz/t11b-smoke-test/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11b-smoke-test/</guid><description>bilibili / video / youtube / iframe / swiper / image-compare 六件套联调。</description><content:encoded><![CDATA[<blockquote>
<p>这是 T11b 的渲染验证页面。如果 iframe / video player / swiper / image-compare 任一不出现，说明对应 shortcode 没接好。</p>
</blockquote>
<h2 id="1-bilibili">1. Bilibili</h2>

  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; margin: 1em 0; background: var(--color-bg); color-scheme: light dark;"
  >
    <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://player.bilibili.com/player.html?autoplay=0&amp;bvid=BV1uv411q7Mv&amp;danmaku=0&amp;muted=0&amp;poster=1&amp;t=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0; background: var(--color-bg); color-scheme: light dark;" title="Bilibili 视频"
    ></iframe>
  </div>

<h2 id="2-videojshtml5-视频播放器">2. Video.js（HTML5 视频播放器）</h2>


<video
  class="video-js vjs-big-play-centered vjs-fluid"
  data-setup='{
    "controls": true,
    "autoplay": false,
    "preload": "auto",
    "loop": false
  }'>
  <source src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/360/Big_Buck_Bunny_360_10s_1MB.mp4" type="video/mp4"></source>
  <p class="vjs-no-js">请启用 JavaScript 并升级到支持 HTML5 视频的浏览器以观看此视频</p>
</video>

<h2 id="3-youtube">3. YouTube</h2>

  <div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; margin: 1em 0; background: var(--color-bg); color-scheme: light dark;"
  >
    <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0; background: var(--color-bg); color-scheme: light dark;" title="Demo"
    ></iframe>
  </div>

<h2 id="4-iframe通用嵌入">4. Iframe（通用嵌入）</h2>

<div
  style="
    position: relative;
    overflow: hidden;
    width: 100%;
    padding-top: 56.25%;
    background: var(--color-bg);
  "
>
    <iframe
      style="
        position: absolute;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        width: 100%;
        height: 100%;
        background: var(--color-bg);
        color-scheme: light dark;
      "
      frameborder="0"
      src="https://example.com"
      allowfullscreen="true"
    ></iframe>
</div>

<h2 id="4a-iframe白名单-hostcodepen-主题桥">4a. Iframe（白名单 host：CodePen 主题桥）</h2>
<p>切换站点暗色后该 iframe 应自动重载到 dark 主题；首屏请求即应带上 <code>theme-id=dark</code>。
非白名单（example.com）保持原样不重载。</p>

<div
  style="
    position: relative;
    overflow: hidden;
    width: 100%;
    padding-top: 56.25%;
    background: var(--color-bg);
  "
>
    <iframe
      data-src="https://codepen.io/MrGrigri/embed/XQmWBv?default-tab=result"
      data-theme-bridge
      style="
        position: absolute;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        width: 100%;
        height: 100%;
        background: var(--color-bg);
        color-scheme: light dark;
      "
      frameborder="0"
      allowfullscreen="true"
    ></iframe>
    <script>
      (function (el) {
        var HOSTS = [{"match":"codepen.io","param":"theme-id"},{"match":"codesandbox.io","param":"theme"},{"match":"stackblitz.com","param":"theme"},{"match":"replit.com","param":"theme"}];
        var theme = (window.getTheme && window.getTheme()) || 'light';
        try {
          var u = new URL(el.dataset.src);
          var host = HOSTS.find(function (h) { return u.host.indexOf(h.match) !== -1; });
          if (host) u.searchParams.set(host.param, theme === 'dark' ? 'dark' : 'light');
          el.src = u.toString();
        } catch (_) { el.src = el.dataset.src; }
      })(document.currentScript.previousElementSibling);
    </script>
</div>

<h2 id="5-swiper">5. Swiper</h2>
<p>(swiper 需要 data 文件；此处仅占位，验证 swiper-bundle JS/CSS 是否注入。完整测试需把
<code>assets/data/cn/2020-06-06-bayesian-optimization/active-gp.json</code> 这种 data 引用进来，
真实 post 迁回时再做。)</p>
<h2 id="6-image-compare">6. Image Compare</h2>
<p>(image-compare 需要 before/after 图片；此处仅占位，验证 image-compare-viewer 是否注入。)</p>

  <link
    rel="stylesheet"
    type="text/css"
    href="https://cdn.jsdmirror.com/npm/image-compare-viewer@1.6.2/dist/image-compare-viewer.min.css"
  />
  <link
    rel="stylesheet"
    type="text/css"
    href="/css/_shortcodes/image-compare.css"
  />


<div class="shortcode-image-compare-container">
  <div
    class="shortcode-image-compare" data-show-labels="true" data-label-before="Before" data-label-after="After" data-large-max-width="100%" data-middle-max-width="100%" data-small-max-width="100%">
    <img src="/test/before.jpg" />
    <img src="/test/after.jpg" />
  </div>
</div>

]]></content:encoded></item><item><title>T11c 内容/工具冒烟测试</title><link>https://me.125520.xyz/t11c-smoke-test/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/t11c-smoke-test/</guid><description>figure / tab / flex / song / douban-card / copy-to-clipboard / button / details / highlight / admonition 联调。</description><content:encoded><![CDATA[<blockquote>
<p>T11c 渲染验证。gallery / include / include-code 因依赖外部 data/file，仅在真实 post 中验证。</p>
</blockquote>
<h2 id="1-figure显式调用版">1. Figure（显式调用版）</h2>


<figure
  class="shortcode-figure">
  <img
    class="lazyload"
    data-src="https://placehold.co/600x300"
    alt="placeholder"
    title="占位图（显式调用 figure shortcode）"
    data-large-max-width="100%"
    data-middle-max-width="100%"
    data-small-max-width="100%"
    data-large-max-height="100%"
    data-middle-max-height="100%"
    data-small-max-height="100%"
  />
    <figcaption>占位图（显式调用 figure shortcode）</figcaption>
</figure>

<h2 id="2-tab">2. Tab</h2>

  <link rel="stylesheet" type="text/css" href="/css/_shortcodes/tab.css" />


<div class="shortcode-tab" style="justify-content: center;">
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304353273-0"
      value="第一项"
      name="1778163304353273" checked="checked" 
    />
    <label for="1778163304353273-0">第一项</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <p>这是第一个标签的内容。</p>

    </div>
    <input
      type="radio"
      class="shortcode-tab-input"
      id="1778163304353273-1"
      value="第二项"
      name="1778163304353273"
    />
    <label for="1778163304353273-1">第二项</label>
    <div class="shortcode-tab-item">
      <div style="margin-bottom: var(--spacing-p-margin-y);"></div>
      <p>这是第二个标签的内容。</p>

    </div>
</div>

<h2 id="3-flex">3. Flex</h2>

  <link rel="stylesheet" type="text/css" href="/css/_shortcodes/flex.css" />


<div
  class="shortcode-flex"
  style="
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: center;
  align-content: center;
  row-gap: 16px;
  column-gap: 16px;"
>
  
  <div
  class="shortcode-flex-item"
  style="
  align-self: auto;
  flex-basis: auto;
  flex-grow: 1;
  flex-shrink: 1;
  order: 0;"
>
  <p>左边</p>

</div>

  <div
  class="shortcode-flex-item"
  style="
  align-self: auto;
  flex-basis: auto;
  flex-grow: 1;
  flex-shrink: 1;
  order: 0;"
>
  <p>右边</p>

</div>


</div>

<h2 id="4-song">4. Song</h2>

  <link
    rel="stylesheet"
    type="text/css"
    href="https://cdn.jsdmirror.com/npm/aplayer@1.10.1/dist/APlayer.min.css"
  />
  <link rel="stylesheet" type="text/css" href="/css/_shortcodes/song.css" />
  <script src="https://cdn.jsdmirror.com/npm/aplayer@1.10.1/dist/APlayer.min.js"></script>
  <script src="https://cdn.jsdmirror.com/npm/meting@2.0.2/dist/Meting.min.js"></script>
  <script>
    window.meting_api = "https:\/\/api.i-meto.com\/meting\/api?server=:server\u0026type=:type\u0026id=:id\u0026auth=:auth\u0026r=:r";
  </script>


<meting-js
  id="22845161" server="netease" type="song">
</meting-js>

<h2 id="5-douban-card">5. Douban Card</h2>

  <link
    rel="stylesheet"
    type="text/css"
    href="/css/_shortcodes/douban-card.css"
  />


<div
  class="shortcode-douban-card shortcode-douban-card-book"
  id="shortcode-douban-card-book-34922183"
>
  <div class="shortcode-douban-card--middle">
    <div class="shortcode-douban-card--title">
      <a href="https://book.douban.com/subject/34922183/" target="_blank">人类群星闪耀时</a>
    </div>
    <div class="shortcode-douban-card--stars-rating">
      <span class="shortcode-douban-card--logo-dou">豆</span>
      <span class="shortcode-douban-card--logo-rating">豆瓣评分</span>
      <div class="shortcode-douban-card--stars shortcode-douban-card--stars-9"></div>
      <span class="shortcode-douban-card--rating">9.0</span>
    </div>
    <div class="shortcode-douban-card--tags">
      历史 / 茨威格
    </div>
    <div class="shortcode-douban-card--summary">
      14 个人类历史的高光时刻。
    </div>
  </div>
  <div class="shortcode-douban-card--right">
    <img src="https://placehold.co/100x150" />
  </div>
</div>

<h2 id="6-copy-to-clipboard">6. Copy to clipboard</h2>

  <link rel="stylesheet" type="text/css" href="/css/_shortcodes/button.css" />

<a class="shortcode-copy-to-clipboard" data-clipboard-text="print(&#34;hello, inkstone&#34;)" data-clipboard-success-message="复制成功" data-clipboard-error-message="复制失败" role="button">复制这段
</a>

<h2 id="7-button">7. Button</h2>

  <link rel="stylesheet" type="text/css" href="/css/_shortcodes/button.css" />

<a class="shortcode-button"href="/posts/"target="_blank"
  role="button">前往文章列表</a>

<h2 id="8-details">8. Details</h2>

<details>
  <summary>
    <span>展开看细节</span>
  </summary>
  <p>这里是被折叠起来的内容，点击 summary 展开。</p>
</details>

<h2 id="9-highlight带-title">9. Highlight（带 title）</h2>



<div class="highlight-wrapper">
    <div class="highlight-title">
      <span>例：欧几里得算法</span>
    </div>

  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">gcd</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="n">b</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span> <span class="o">%</span> <span class="n">b</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">a</span></span></span></code></pre></div>
</div>

<h2 id="10-admonition">10. Admonition</h2>

  <link
    rel="stylesheet"
    type="text/css"
    href="/css/_shortcodes/admonition.css"
  />
  <div
    class="shortcode-admonition shortcode-admonition-note "
  >
      <div class="shortcode-admonition-title">提醒</div>
      <div class="shortcode-admonition-content">这是一个 admonition 块，shortcode-admonition CSS 与 callout 各自独立（T12 polish 时再决定是否合并）。</div>
  </div>


  <div
    class="shortcode-admonition shortcode-admonition-warning "
  >
      <div class="shortcode-admonition-content">可折叠 admonition：默认收起，点击展开。</div>
  </div>

]]></content:encoded></item></channel></rss>