Commit fc52491

Eric Bower  ·  2026-05-09 12:12:00 -0400 EDT
parent d43b626
docs: site
1 files changed,  +153, -45
M website/index.html
+153, -45
  1@@ -255,6 +255,27 @@ h2 {
  2   font-weight: 700;
  3   margin-bottom: 1rem;
  4   color: var(--text);
  5+  display: flex;
  6+  align-items: center;
  7+  gap: 0.5rem;
  8+}
  9+
 10+h2 .anchor {
 11+  color: var(--text-dim);
 12+  opacity: 0;
 13+  transition: opacity 0.15s ease;
 14+  font-weight: 400;
 15+  font-size: 0.85em;
 16+  text-decoration: none;
 17+}
 18+
 19+h2:hover .anchor {
 20+  opacity: 1;
 21+}
 22+
 23+h2 .anchor:hover {
 24+  color: var(--green);
 25+  text-decoration: none;
 26 }
 27 
 28 h3 {
 29@@ -497,6 +518,7 @@ code {
 30     <div class="logo">pici <span>— terminal-first CI</span></div>
 31     <ul>
 32       <li><a href="#how">How It Works</a></li>
 33+      <li><a href="#infra">Infra</a></li>
 34       <li><a href="#features">Features</a></li>
 35       <li><a href="#beta">Beta</a></li>
 36     </ul>
 37@@ -638,9 +660,10 @@ code {
 38           and on the CI runner</strong>. No YAML, no DSL, no "it works on my machine" gap.
 39         </p>
 40         <p>
 41-          <strong>zmx</strong> is the job engine. Each <code>zmx run</code> spawns a parallel terminal session.
 42-          <code>zmx wait "*"</code> blocks until they all finish. Run tasks sequentially by just calling them
 43-          one after another, or in parallel; it's bash, you control the flow.
 44+          <a href="https://zmx.sh"><strong>zmx</strong></a> is the job engine. Each <code>zmx run</code> spawns
 45+          a parallel terminal session. <code>zmx wait "*"</code> blocks until they all finish.
 46+          Run tasks sequentially by just calling them one after another, or in parallel; it's bash,
 47+          you control the flow.
 48         </p>
 49         <ul class="check-list">
 50           <li>Same <code>zmx run</code> commands locally and in CI</li>
 51@@ -743,58 +766,124 @@ code {
 52     <p style="text-align:center; margin-top: 1.5rem; color: var(--text-dim);">
 53       Same <code>pico.sh</code>. Same <code>zmx attach</code>. Same experience. Your infra or ours.
 54     </p>
 55+    <div style="text-align:center; margin-top: 1rem;">
 56+      <span style="background: var(--surface); padding: 0.3rem 0.8rem; border-radius: 4px; font-size: 0.85rem; color: var(--yellow);">
 57+        Bring your own isolation — docker, namespaces, bare metal. You choose.
 58+      </span>
 59+    </div>
 60+  </div>
 61+</section>
 62+
 63+<!-- ============================================================
 64+     POWERED BY deploy.pico.sh
 65+     ============================================================ -->
 66+<section id="infra">
 67+  <div class="container">
 68+    <p class="section-label">Infrastructure</p>
 69+    <h2>Powered by deploy.pico.sh</h2>
 70+    <div class="two-col">
 71+      <div>
 72+        <p>
 73+          <strong>ci.pico.sh</strong> runs on <strong>deploy.pico.sh</strong> — our SSH VM service
 74+          on hardware we own. Not AWS. Not GCP. Not a cloud provider.
 75+        </p>
 76+        <p>
 77+          Push a <code>docker-compose.yml</code> to an SSH endpoint and your containers are live.
 78+          Label a service and it gets a public HTTP URL. No cloud console, no CLI SDK,
 79+          no provider lock-in.
 80+        </p>
 81+        <p>
 82+          The same platform that runs your CI runs your apps.
 83+        </p>
 84+        <ul class="check-list">
 85+          <li>Our hardware — not a hyperscaler</li>
 86+          <li>Docker Compose via <code>git push</code></li>
 87+          <li>Expose HTTP services with compose labels</li>
 88+          <li>Limited inventory — when it's gone, it's gone</li>
 89+        </ul>
 90+      </div>
 91+      <div class="terminal">
 92+        <div class="terminal-bar">
 93+          <span class="dot red"></span>
 94+          <span class="dot yellow"></span>
 95+          <span class="dot green"></span>
 96+          <span style="margin-left: 0.5rem;">deploy.pico.sh</span>
 97+        </div>
 98+        <div class="terminal-body">
 99+          <div><span class="comment">--- docker-compose.yml ---</span></div>
100+          <div>services:</div>
101+          <div>  api:</div>
102+          <div>    image: myapp:latest</div>
103+          <div>    labels:</div>
104+          <div>      pico.http.expose: "true"</div>
105+          <div>      pico.http.host: "api.myapp.pico.sh"</div>
106+          <div>&nbsp;</div>
107+          <div><span class="prompt">$ </span><span class="cmd">git remote add deploy ssh://deploy.pico.sh/myapp</span></div>
108+          <div><span class="prompt">$ </span><span class="cmd">git push deploy main</span></div>
109+          <div>&nbsp;</div>
110+          <div><span class="info">→ containers deployed</span></div>
111+          <div><span class="info">→ api.myapp.pico.sh is live</span></div>
112+        </div>
113+      </div>
114+    </div>
115   </div>
116 </section>
117 
118 <!-- ============================================================
119-     FEATURES GRID
120+     FEATURES
121      ============================================================ -->
122 <section id="features">
123   <div class="container">
124     <p class="section-label">Features</p>
125-    <h2>Everything else just works.</h2>
126+    <h2>Built for terminals, agents, and humans.</h2>
127     <div class="feature-grid">
128       <div class="feature-card">
129-        <div class="icon">🔌</div>
130-        <h3>SSH-First</h3>
131-        <p>No HTTP APIs. No webhooks to configure. No OAuth tokens. SSH keys are your auth, SSH pubsub is your event bus.</p>
132-      </div>
133-      <div class="feature-card">
134-        <div class="icon">📊</div>
135-        <h3>JSONL Status Stream</h3>
136-        <p><code>pici monitor | curl -sd"$line" $WEBHOOK</code> — pipe build status to Discord, Slack, or any endpoint.</p>
137-      </div>
138-      <div class="feature-card">
139-        <div class="icon">🧹</div>
140-        <h3>Auto-Cancel on Push</h3>
141-        <p>New commit? The old job is killed automatically. No stale builds wasting resources.</p>
142+        <div class="icon">🤖</div>
143+        <h3>AI-Agent Friendly</h3>
144+        <p>
145+          Terminal workflows, rsync + SSH pubsub, JSONL status streams — everything an agent
146+          needs to trigger builds, monitor progress, and attach to failures. No OAuth flows,
147+          no API rate limits, no webhooks. An agent can start as many jobs as it wants
148+          and read results as structured text.
149+        </p>
150       </div>
151       <div class="feature-card">
152-        <div class="icon">🗑️</div>
153-        <h3>Auto Garbage Collection</h3>
154-        <p>Old sessions cleaned up automatically. No manual cleanup scripts.</p>
155+        <div class="icon">🔌</div>
156+        <h3>SSH-First</h3>
157+        <p>
158+          No HTTP APIs to expose. No webhooks to configure. No OAuth tokens to manage.
159+          SSH keys are your auth. SSH pubsub is your event bus.
160+          If SSH works, CI works.
161+        </p>
162       </div>
163       <div class="feature-card">
164-        <div class="icon">📦</div>
165-        <h3>Live Artifacts</h3>
166-        <p>HTML session pages generated during the build, not just after. See progress in real-time.</p>
167+        <div class="icon">📄</div>
168+        <h3>Static Site Artifacts</h3>
169+        <p>
170+          Build artifacts are plain HTML + CSS. No JavaScript. No app server. No build step.
171+          Serve the directory with any static host — nginx, s3, pgs.sh, <code>python -m http.server</code>.
172+          Zero runtime dependencies.
173+        </p>
174       </div>
175       <div class="feature-card">
176         <div class="icon">🔐</div>
177         <h3>Build Attestation</h3>
178-        <p>Automatic provenance: runner hostname, OS, arch, repo, branch, commit, workspace checksum.</p>
179+        <p>
180+          Automatic provenance baked into every job: runner hostname, OS, arch, repo, branch,
181+          commit, and workspace checksum. Supply-chain ready without extra tooling.
182+        </p>
183       </div>
184     </div>
185   </div>
186 </section>
187 
188 <!-- ============================================================
189-     COMPARISON — Other CI vs pici
190+     COMPARISON
191      ============================================================ -->
192 <section id="compare">
193   <div class="container">
194     <p class="section-label">Comparison</p>
195-    <h2>Other CI gives you logs. pici gives you a shell.</h2>
196+    <h2>The difference is the terminal.</h2>
197     <div class="code-compare">
198       <div>
199         <div class="label bad">Other CI</div>
200@@ -806,15 +895,16 @@ code {
201             <span style="margin-left: 0.5rem;">build log</span>
202           </div>
203           <div class="terminal-body">
204-            <div class="output">$ npm test</div>
205-            <div class="output">PASS src/utils.test.js</div>
206-            <div class="error">FAIL src/db.test.js</div>
207-            <div class="error">  ● connects to database</div>
208-            <div class="error">    TimeoutError: connect ETIMED OUT</div>
209+            <div class="output">$ go test ./...</div>
210+            <div class="output">ok    myapp/db    12.4s</div>
211+            <div class="error">FAIL  myapp/api (14.2s)</div>
212+            <div class="error">Error: connection refused</div>
213             <div class="output">...</div>
214-            <div class="comment"># ...that's all you get.</div>
215-            <div class="comment"># Re-run the job? Wait 12 min.</div>
216-            <div class="comment"># Enable "debug mode"? Where's that?</div>
217+            <div>&nbsp;</div>
218+            <div class="comment"># ← that's all you get</div>
219+            <div class="comment"># ← can't inspect the environment</div>
220+            <div class="comment"># ← can't rerun just the failing step</div>
221+            <div class="comment"># ← re-run entire pipeline? wait 10 min</div>
222           </div>
223         </div>
224       </div>
225@@ -825,21 +915,21 @@ code {
226             <span class="dot red"></span>
227             <span class="dot yellow"></span>
228             <span class="dot green"></span>
229-            <span style="margin-left: 0.5rem;">zmx attach ci.myrepo.test</span>
230+            <span style="margin-left: 0.5rem;">zmx attach ci.myrepo.api</span>
231           </div>
232           <div class="terminal-body">
233-            <div class="output">$ npm test</div>
234-            <div class="output">PASS src/utils.test.js</div>
235-            <div class="error">FAIL src/db.test.js</div>
236-            <div class="error">  ● connects to database</div>
237-            <div class="error">    TimeoutError: connect ETIMED OUT</div>
238+            <div class="output">$ go test ./...</div>
239+            <div class="output">ok    myapp/db    12.4s</div>
240+            <div class="error">FAIL  myapp/api (14.2s)</div>
241+            <div class="error">Error: connection refused</div>
242             <div>&nbsp;</div>
243-            <div><span class="comment"># you're already in the session</span></div>
244-            <div><span class="prompt">$ </span><span class="cmd">env | grep DB_HOST</span></div>
245+            <div class="comment"># you're in the session — inspect freely</div>
246+            <div><span class="prompt">$ </span><span class="cmd">cat .env</span></div>
247             <div class="output">DB_HOST=postgres.internal:5432</div>
248             <div><span class="prompt">$ </span><span class="cmd">nc -zv postgres.internal 5432</span></div>
249-            <div class="output">Connection refused</div>
250-            <div><span class="comment"># ↑ found it — network config issue</span></div>
251+            <div class="error">Connection refused</div>
252+            <div><span class="comment"># ↑ found it — wrong hostname</span></div>
253+            <div><span class="comment"># ↑ press ↑ to rerun the test after fixing</span></div>
254           </div>
255         </div>
256       </div>
257@@ -882,6 +972,24 @@ code {
258 </footer>
259 
260 <script>
261+// Auto-generate anchor links for all h2 elements
262+document.querySelectorAll('h2').forEach(h2 => {
263+  const section = h2.closest('section, [id]');
264+  if (section && section.id) {
265+    const a = document.createElement('a');
266+    a.href = '#' + section.id;
267+    a.className = 'anchor';
268+    a.textContent = '#';
269+    a.title = 'Copy link to section';
270+    a.addEventListener('click', e => {
271+      e.preventDefault();
272+      history.replaceState(null, null, '#' + section.id);
273+      window.scrollTo({ top: h2.offsetTop - 80, behavior: 'smooth' });
274+    });
275+    h2.appendChild(a);
276+  }
277+});
278+
279 function handleSignup(e) {
280   e.preventDefault();
281   const form = e.target;