Commit fc52491
Eric Bower
·
2026-05-09 12:12:00 -0400 EDT
parent d43b626
docs: site
1 files changed,
+153,
-45
+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> </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> </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> </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> </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;