clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

main
forge / guide / index.html
index.html 29.9K
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>forge: your code, its reviews, CI and sites, on your own server</title>7<meta name="description" content="forge keeps your code, its reviews, CI results, sites and releases on your own server, as a static site. Changes go through git and ssh.">8<meta name="theme-color" content="#e9e9e9" media="(prefers-color-scheme: light)">9<meta name="theme-color" content="#08090b" media="(prefers-color-scheme: dark)">10<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cstyle%3Ecircle%7Bstroke:%231c2127%7Dpath%7Bstroke:%233148f6%7D@media (prefers-color-scheme:dark)%7Bcircle%7Bstroke:%23e2e4e7%7D%7D%3C/style%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M16 15 12 7 8 15Zm-1 0L11.5 8'/%3E%3C/svg%3E">11<link rel="preload" href="fonts/open-sans.woff2" as="font" type="font/woff2" crossorigin>12<link rel="stylesheet" href="guide.css">13<script src="guide.js" defer></script>14</head>15<body>16<a class="skip" href="#main">skip to the guide</a>17<header class="bar">18<div class="wrap">19<a class="brand" href="./"><svg class="mark" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M16 15 12 7 8 15Zm-1 0L11.5 8"/></svg>forge <span>guide</span></a>20<nav aria-label="guide"><a href="./" aria-current="page">guide</a><a href="internals.html">internals</a></nav>21</div>22</header>2324<section class="hero" aria-labelledby="top">25<div class="wrap">26<div class="hero-text">27<p class="eyebrow">a personal git forge</p>28<h1 id="top"><svg class="mark" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M16 15 12 7 8 15Zm-1 0L11.5 8"/></svg>forge</h1>29<p class="lede">Your code, its reviews, CI results, sites and releases, on your own server, as a static site. Changes go through git and ssh.</p>30<p class="cta"><a class="btn go" href="#what">start reading</a><a class="btn" href="#cli">cheat sheet</a><a class="btn" href="internals.html">internals</a></p>31</div>32<figure class="term" aria-label="A terminal session: attest, push for review, deploy a preview">33<div class="chrome" aria-hidden="true">~/greet (cat/flags)</div>34<pre><span class="line" style="--i:0"><span class="p">$ </span>forge attest --stack</span><span class="line o" style="--i:1">afd124b running .forge/ci: parse --name</span><span class="line" style="--i:2">afd124b <span class="ok">pass</span> in 1s, signed</span><span class="line o" style="--i:3">80b9dda running .forge/ci: add --shout</span><span class="line" style="--i:4">80b9dda <span class="ok">pass</span> in 2s, signed</span><span class="line" style="--i:5"><span class="p">$ </span>forge push -t "Flags"</span><span class="line r" style="--i:6">remote: review #2: <span class="u">https://src.rest/cat/greet/reviews/2/</span></span><span class="line" style="--i:7"><span class="p">$ </span>forge site deploy docs</span><span class="line o" style="--i:8">site docs: deployed 942ff39 -&gt; <span class="u">https://greet-docs--cat-flags.pages.src.rest/</span></span></pre>35</figure>36</div>37</section>3839<div class="wrap page">40<nav class="toc" aria-label="on this page">41<div>42<h2>on this page</h2>43<ol>44<li><a href="#what"><span>01</span>what forge is</a></li>45<li><a href="#browse"><span>02</span>browse</a></li>46<li><a href="#reviews"><span>03</span>reviews</a></li>47<li><a href="#ci"><span>04</span>ci</a></li>48<li><a href="#sites"><span>05</span>sites and releases</a></li>49<li><a href="#people"><span>06</span>people</a></li>50<li><a href="#cli"><span>07</span>day to day</a></li>51<li><a href="#not-yet"><span>08</span>not yet</a></li>52</ol>53</div>54</nav>5556<main id="main" class="log">5758<section class="commit" id="what" aria-labelledby="what-h">59<header><p class="ref"><b>01</b> what forge is</p><h2 id="what-h"><a href="#what">Your forge, on your own server</a></h2></header>60<div class="prose">61<p>forge keeps your repositories on your own server and publishes them as a static site: every branch, file and commit, the reviews on them, the CI result of every commit, and each repo's sites and releases. The pages work without JavaScript and update while you read them.</p>62<p>Changes go through git and ssh. You push code, reviews, sites and releases; you run an ssh command to make a repo, land a review or invite someone. On the web you read. Sign in with a passkey to comment and approve. A push that adds a password or a key is refused.</p>63</div>64<div class="panel">65<div class="panel-head"><b>what you do, and what you get</b></div>66<div class="table">67<table class="rows">68<thead><tr><th scope="col">you</th><th scope="col">with</th><th scope="col">the forge shows</th></tr></thead>69<tbody>70<tr><th scope="row">push code</th><td data-l="with"><code>git push</code></td><td data-l="the forge shows">a page for every branch, directory, file and commit</td></tr>71<tr><th scope="row">push a branch</th><td data-l="with"><code>forge push</code></td><td data-l="the forge shows">a review, with a page for each revision</td></tr>72<tr><th scope="row">run CI</th><td data-l="with"><code>forge attest</code></td><td data-l="the forge shows">a signed pass or fail on each commit</td></tr>73<tr><th scope="row">publish a site</th><td data-l="with"><code>forge site deploy</code></td><td data-l="the forge shows">the site on its own address, and a preview per branch</td></tr>74<tr><th scope="row">cut a release</th><td data-l="with"><code>forge release publish</code></td><td data-l="the forge shows">downloads with signed checksums</td></tr>75<tr><th scope="row">invite someone</th><td data-l="with"><code>ssh git@src.rest invite</code></td><td data-l="the forge shows">a one-time link to join, as a collaborator or reader</td></tr>76</tbody>77</table>78</div>79</div>80<p class="prose">The examples use <a href="https://src.rest/">src.rest</a>, where forge hosts itself as <code>cat/forge</code>. <code>cat</code> runs it; <code>fox</code> is another user.</p>81</section>8283<section class="commit" id="browse" aria-labelledby="browse-h">84<header><p class="ref"><b>02</b> browse</p><h2 id="browse-h"><a href="#browse">Every branch, file, commit and review is a page</a></h2></header>85<div class="prose">86<p>URLs are paths: <code>/cat/greet/tree/main/greet.sh/</code> is that file on <code>main</code>, and every branch has the same pages. Markdown is rendered and code is highlighted. The home page lists people, recent activity and repos by topic.</p>87</div>88<div class="panel">89<div class="panel-head"><b>a repo's pages</b><span class="end"><span class="n">https://src.rest/cat/greet/...</span></span></div>90<div class="table">91<table class="rows">92<thead><tr><th scope="col">page</th><th scope="col">what is on it</th></tr></thead>93<tbody>94<tr><td><code>/cat/greet/</code></td><td>open reviews, recent commits, the README</td></tr>95<tr><td><code>tree/&lt;branch&gt;/</code>, <code>blob/&lt;branch&gt;/&lt;path&gt;/</code></td><td>files and directories on any branch</td></tr>96<tr><td><code>log/&lt;branch&gt;/</code>, <code>commit/&lt;sha&gt;/</code></td><td>history, each commit with its diff and CI result</td></tr>97<tr><td><code>reviews/</code>, <code>reviews/&lt;id&gt;/</code></td><td>reviews, as stacks, and each review's diffs and discussion</td></tr>98<tr><td><code>wiki/</code>, <code>sites/</code>, <code>releases/</code></td><td>the wiki from <code>wiki/</code>, deployed sites and previews, downloads</td></tr>99<tr><td><code>insights/</code></td><td>pushes, reviews and CI results over time, charted</td></tr>100<tr><td><code>atom.xml</code></td><td>a feed of the newest commits on <code>main</code></td></tr>101</tbody>102</table>103</div>104</div>105<div class="prose">106<ul>107<li><strong>Search.</strong> <kbd>/</kbd> searches a repo's commits, reviews, wiki, paths and file contents; <kbd>t</kbd> finds a file on the current branch.</li>108<li><strong>The keyboard.</strong> <kbd>j</kbd> <kbd>k</kbd> move through a list, <kbd>n</kbd> <kbd>N</kbd> through a review's commits, <kbd>[</kbd> <kbd>]</kbd> up and down a stack, <kbd>v</kbd> marks a file viewed. <kbd>?</kbd> lists them all.</li>109<li><strong>Live.</strong> An open page updates itself after a push, without losing your place.</li>110<li><strong>Feeds.</strong> Every repo has an Atom feed of its newest commits.</li>111<li><strong>Diagrams.</strong> <code>```mermaid</code> fences in any markdown file are drawn as diagrams, without JavaScript.</li>112<li><strong>Blame and history.</strong> A file on <code>main</code> shows who last changed each line, and every commit that changed it with the review it landed in.</li>113</ul>114</div>115</section>116117<section class="commit" id="reviews" aria-labelledby="reviews-h">118<header><p class="ref"><b>03</b> reviews</p><h2 id="reviews-h"><a href="#reviews">Push a branch and it is a review</a></h2></header>119<p class="prose">Any branch other than <code>main</code> is a review once pushed: its commits' diffs in order, plus a combined diff. A branch made from another review's branch stacks on top of it.</p>120<pre class="code"><span class="dim">$</span> git switch -c cat/flags121<span class="dim">$</span> forge push -t "parse --name" -d "Flags, parsed by hand"122<span class="dim">remote: review #2: https://src.rest/cat/greet/reviews/2/</span></pre>123<figure class="plate">124<ol class="graph" aria-label="A stack: two reviews, each two commits, on top of main">125<li class="layer"><ol><li class="c"><code>6771779</code><span class="s">README: document flags</span><span class="pill">cat/help</span></li><li class="c"><code>dff8bca</code><span class="s">add --help</span></li></ol><p class="tag"><b>#3<span class="t"> Usage and --help</span></b><small>layer 2, on cat/flags</small></p></li>126<li class="layer"><ol><li class="c"><code>80b9dda</code><span class="s">add --shout</span><span class="pill">cat/flags</span></li><li class="c"><code>afd124b</code><span class="s">parse --name</span></li></ol><p class="tag"><b>#2<span class="t"> parse --name</span></b><small>layer 1, on main</small></p></li>127<li class="layer base"><ol><li class="c"><code>895bc48</code><span class="s">README: be polite</span><span class="pill main">main</span></li></ol></li>128</ol>129<figcaption><b>fig. 1</b>a stack: cat/help was branched from cat/flags</figcaption>130</figure>131<div class="prose">132<ul>133<li><strong>Revisions.</strong> Each push is a revision. The page shows what changed since the last one, commit by commit, even after a rebase.</li>134<li><strong>Stacks.</strong> <code>forge push --stack</code> pushes every layer, lowest first; <kbd>[</kbd> and <kbd>]</kbd> move between layers on the page.</li>135<li><strong>Status.</strong> Open, draft (<code>--draft</code>, or a <code>wip/</code> branch), merged once it is in <code>main</code>, closed when its branch is deleted.</li>136<li><strong>Reading.</strong> Files fold, can be marked viewed and stay viewed until they change.</li>137</ul>138</div>139<figure>140<pre class="code"><span class="chg">1: 4146e8e ! 1: afd124b parse --name</span>141<span class="dim"> @@ tests.txt</span>142 + ## wiki/Design.md ##143 - Flags are parsed by hand; no getopt.144 - Output goes to stdout, errors to stderr.145<span class="add"> ++- `--name` takes the next argument verbatim.</span>146<span class="same">2: ff819c4 = 2: 80b9dda add --shout</span>147<span class="same">3: d70bd83 = 3: dff8bca add --help</span>148<span class="chg">4: ef1d881 ! 4: 6771779 README: document flags</span>149<span class="del"> -+ --shout uppercase the greeting</span>150<span class="add"> ++ --shout UPPERCASE the greeting</span></pre>151<figcaption><b>fig. 2</b>what changed between two revisions, shortened: <code>=</code> unchanged, <code>!</code> changed</figcaption>152</figure>153<div class="prose">154<h3>Comments and approvals</h3>155<p>Signed in, you comment and approve at the end of a review page, or comment on a line with the <b>+</b> in the combined diff. From the terminal:</p>156</div>157<pre class="code"><span class="dim">$</span> forge comment 2 greet.sh:12 -m "Quote the name?"158<span class="dim">$</span> forge approve 2</pre>159<div class="prose">160<p>An approval is for the code as it is: rewording a commit keeps it, changing the code marks it stale. You can't approve your own review. After a new revision, older comments are marked outdated and keep the line they quoted. A repo can require someone else's approval before <code>main</code> moves.</p>161<h3>Approval rules</h3>162<p>How much approval, and from whom, can depend on what a change touches. <code>.forge/approvals</code> on <code>main</code> gives each path a rule; a change needs every rule its files take. The rules are read from <code>main</code> as it was, so a review can't loosen its own.</p>163</div>164<pre class="code"><span class="dim"># paths rule</span>165.forge/ bin/forge 2166lib/identity.ts 1 from fox167lib/ 1 + agent168*.md wiki/ none169* 1</pre>170<div class="prose">171<p>A review page starts with what landing needs: CI, each rule and who has approved, and the latest agent review.</p>172<h3>Agent reviews</h3>173<p><code>forge critique</code> runs the reviewer <code>main</code> names, <code>.forge/review</code>, over a review on your machine: Claude Code, Codex or a script of your own. It gets the diff and the project's rules in <code>.forge/review.md</code>. Its findings become comments marked <b>agent</b>, some <b>blocking</b>, and it signs what is still open into a git note, as CI does. The next run looks only at what changed since and carries the open findings forward until the reviewer, or you with <code>--resolve</code>, says they are dealt with. A rule's <code>+ agent</code> wants one with nothing blocking.</p>174<h3>Landing</h3>175<p><code>forge land</code> checks CI (and approval, if required), fast-forwards <code>main</code> and deletes the landed branches. Over ssh the forge does it itself, no clone needed:</p>176</div>177<pre class="code"><span class="dim">$</span> forge land 2178<span class="dim">$</span> ssh git@src.rest land cat/greet 2</pre>179<p class="prose"><code>main</code> only fast-forwards, so rebase a review onto <code>main</code> before landing; <code>git rebase -i --update-refs</code> moves every layer of a stack with it.</p>180</section>181182<section class="commit" id="ci" aria-labelledby="ci-h">183<header><p class="ref"><b>04</b> ci</p><h2 id="ci-h"><a href="#ci">CI runs on your machine and is signed</a></h2></header>184<p class="prose"><code>forge attest</code> runs <code>.forge/ci</code> on your machine, on a clean checkout of each commit. It signs the result with your SSH key. <code>forge push</code> uploads the results, and every commit, review and branch shows pass or fail and who signed it. Failures are shown too.</p>185<figure class="plate annotated">186<pre class="code"><span class="key">forge-attestation 1</span>187type ci188commit ff819c459d6f222c...189<span class="key">tree 69a735fe3cc0a8a8...</span><span class="n">1</span>190script .forge/ci 99a0d5ea2eee...<span class="n">2</span>191<span class="key">result pass</span><span class="n">3</span>192exit 0193started 2026-10-18T02:00:14Z194duration 0s195runner cat@laptop<span class="n">4</span>196system Darwin 25.5.0 arm64197env host198log sha256:6cc79019... 4 lines199<span class="dim">|ok greet --name fox</span>200<span class="dim">|ok greet --shout</span>201<span class="dim">-----BEGIN SSH SIGNATURE-----</span><span class="n">5</span>202<span class="dim">U1NIU0lHAAAAAQAAADMAAAALc3No...</span></pre>203<ol>204<li><strong>The files, not the commit.</strong> A reworded or reordered commit with the same files keeps its result, and <code>forge attest</code> skips what already passed unless you pass <code>--force</code>.</li>205<li><strong>The script</strong>, by hash: change <code>.forge/ci</code> and it is a different claim.</li>206<li><strong>The result</strong> and exit code.</li>207<li><strong>The runner</strong> as the signer describes it: user, machine and system, plus the Nix lock file if there is one.</li>208<li><strong>The signature</strong> over everything above. Log lines start with <code>|</code>, so output can't fake a field.</li>209</ol>210<figcaption><b>fig. 3</b>a signed CI result, shortened</figcaption>211</figure>212<pre class="code"><span class="dim">$</span> forge attest --stack <span class="dim"># every commit on top of main</span>213<span class="dim">$</span> forge verify <span class="dim"># each commit's results, and who signed them</span></pre>214<div class="verdict">215<div class="yes"><h3>What a verified pass proves</h3><ul><li>Someone with a key on this forge claims these exact files passed this exact script, on a machine they describe.</li><li>Nobody edited it since: turning <code>result fail</code> into <code>result pass</code> breaks the signature.</li><li>Who signed it.</li></ul></div>216<div class="no"><h3>What it doesn't</h3><ul><li>That the person is honest. Someone can sign a pass for tests they never ran.</li><li>That the machine was clean or the setup reproducible, unless Nix pinned it.</li><li>Anything about commits nobody tested.</li></ul></div>217</div>218<div class="prose">219<p>To make it stronger, keep the key in hardware: a Secure Enclave key through <a href="https://github.com/maxgoedjen/secretive">Secretive</a>, or a FIDO <code>sk-</code> key. Add a <code>flake.nix</code> so CI runs under <code>nix develop</code>. Have a second person run <code>forge attest</code> too; results from several people add up.</p>220<p>A repo can require a verified pass before <code>main</code> moves (for the new tip or every new commit) and for a release's tag. Pushes and lands without one are refused.</p>221<p>CI can also report numbers about a commit, a line each to <code>$FORGE_METRICS</code>: <code>bundle_kb 412 KB</code>, <code>tests 318</code>. They are signed with the result, and the repo's insights chart each one commit by commit on <code>main</code>.</p>222</div>223</section>224225<section class="commit" id="sites" aria-labelledby="sites-h">226<header><p class="ref"><b>05</b> sites and releases</p><h2 id="sites-h"><a href="#sites">Sites and releases ship with the repo</a></h2></header>227<div class="prose">228<p>Any repo can publish static sites: docs, a Docusaurus build, a small app. List each in <code>.forge/sites</code> with its output directory and build command, then deploy:</p>229</div>230<pre class="code"><span class="dim"># .forge/sites: name, output, build command (- for none)</span>231docs build npm ci &amp;&amp; npm run build232233<span class="dim">$</span> forge site deploy docs</pre>234<div class="prose">235<p>On <code>main</code> this publishes the site; on any other branch, a preview of that branch, linked from its review. Deleting the branch removes its previews. Every deploy is kept, so you can roll back. This guide is a site of <code>cat/forge</code>.</p>236<p>Each site gets its own hostname, so sites can't read each other's cookies or storage:</p>237</div>238<div class="panel">239<div class="panel-head"><b>where a site is served</b><span class="end"><span class="n">*.pages.src.rest</span></span></div>240<div class="table">241<table class="whole">242<thead><tr><th scope="col">deploy</th><th scope="col">address</th></tr></thead>243<tbody>244<tr><td><code>cat/greet</code>, site <code>docs</code></td><td><code>greet-docs<wbr>.pages.src.rest</code></td></tr>245<tr><td><code>fox/dotfiles</code>, site <code>home</code></td><td><code>dotfiles-home-fox<wbr>.pages.src.rest</code></td></tr>246<tr><td><code>docs</code> on the branch <code>cat/flags</code></td><td><code>greet-docs--cat-flags<wbr>.pages.src.rest</code></td></tr>247<tr><td><code>cat/forge</code>, site <code>guide</code></td><td><code>forge-guide<wbr>.pages.src.rest</code>, this page</td></tr>248</tbody>249</table>250</div>251</div>252<div class="prose">253<p>The forge's owner gets the short names. Sites are served like Cloudflare Pages: <code>_headers</code> and <code>_redirects</code> work, <code>/about</code> finds <code>about.html</code>, a missing page gets the nearest <code>404.html</code> and previews aren't indexed.</p>254<h3>Releases</h3>255<p>A release is a tag's files plus a <code>SHA256SUMS</code> signed with your key, bound to the repo and tag. The releases page shows who signed, and anyone can verify a download:</p>256</div>257<pre class="code"><span class="dim">$</span> forge release publish v1.0 greet.sh greet-1.0.tar.gz258<span class="dim">$</span> forge release verify --url https://src.rest/cat/greet/releases/download/v1.0/ greet.sh259<span class="dim">ok greet.sh: matches SHA256SUMS of cat/greet v1.0, signed by cat</span></pre>260<p class="prose">Downloads keep stable links: <code>/cat/<wbr>greet/<wbr>releases/<wbr>download/<wbr>latest/<wbr>greet.sh</code> is always the newest. A private repo's releases are for its readers only.</p>261</section>262263<section class="commit" id="people" aria-labelledby="people-h">264<header><p class="ref"><b>06</b> people</p><h2 id="people-h"><a href="#people">Who can do what</a></h2></header>265<div class="prose">266<p>A user is a name and an SSH key; the same key signs their CI results, deploys and releases. An admin invites someone with a link:</p>267</div>268<pre class="code"><span class="dim">$</span> ssh git@src.rest invite fox --repo cat/012 --as collaborator269<span class="dim">https://src.rest/login/#invite=...</span></pre>270<p class="prose">The link works once, for a week: <code>fox</code> opens it, pastes a public key and can add a passkey. Each repo's owner decides who else works on it:</p>271<pre class="code"><span class="dim">$</span> ssh git@src.rest collaborators cat/012 add fox <span class="dim"># may push anything</span>272<span class="dim">$</span> ssh git@src.rest readers cat/012 add fox <span class="dim"># may read it while it is private</span>273<span class="dim">$</span> ssh git@src.rest who cat/012274<span class="dim">owner cat275collaborator fox</span></pre>276<div class="panel">277<div class="panel-head"><b>who can do what</b><span class="end">on <code>cat/012</code></span></div>278<div class="table">279<table class="rows three">280<thead><tr><th scope="col"></th><th scope="col">owner, collaborators</th><th scope="col">readers, and any user of a public repo</th><th scope="col">anyone</th></tr></thead>281<tbody>282<tr><th scope="row">read pages, clone</th><td class="y" data-l="owner, collab.">yes</td><td class="y" data-l="readers, users">yes</td><td class="y" data-l="anyone">a public repo</td></tr>283<tr><th scope="row">comment, approve</th><td class="y" data-l="owner, collab.">yes</td><td class="y" data-l="readers, users">yes</td><td class="n" data-l="anyone">no</td></tr>284<tr><th scope="row">push a review, a preview, CI results</th><td class="y" data-l="owner, collab.">yes</td><td class="y" data-l="readers, users">under their own name, <code>fox/...</code></td><td class="n" data-l="anyone">no</td></tr>285<tr><th scope="row">move <code>main</code>, land, tag, deploy, release</th><td class="y" data-l="owner, collab.">yes</td><td class="n" data-l="readers, users">no</td><td class="n" data-l="anyone">no</td></tr>286<tr><th scope="row">make it private or public, choose readers</th><td class="y" data-l="owner, collab.">yes</td><td class="n" data-l="readers, users">no</td><td class="n" data-l="anyone">no</td></tr>287<tr><th scope="row">choose collaborators, remove it</th><td class="y" data-l="owner, collab.">the owner</td><td class="n" data-l="readers, users">no</td><td class="n" data-l="anyone">no</td></tr>288</tbody>289</table>290</div>291</div>292<div class="prose">293<h3>Private repos</h3>294<p>A private repo is visible only to its owner, collaborators and readers: over ssh, on the web when signed in and over HTTPS with a token. To everyone else it doesn't exist.</p>295</div>296<pre class="code"><span class="dim">$</span> ssh git@src.rest new --private notes297<span class="dim">$</span> ssh git@src.rest private cat/012 <span class="dim"># or public, after a check of its whole history for secrets</span></pre>298<div class="prose">299<h3>Passkeys and tokens</h3>300<p>There are no passwords. Passkeys and tokens are made over ssh; lose one and make another.</p>301</div>302<pre class="code"><span class="dim">$</span> ssh git@src.rest passkey <span class="dim"># a one-time link that adds a passkey</span>303<span class="dim">$</span> ssh git@src.rest token --repo cat/012 <span class="dim"># for curl and HTTPS clones, shown once</span>304<span class="dim">$</span> ssh git@src.rest sessions <span class="dim"># where you are signed in; rm signs one out</span></pre>305</section>306307<section class="commit" id="cli" aria-labelledby="cli-h">308<header><p class="ref"><b>07</b> day to day</p><h2 id="cli-h"><a href="#cli">Day to day</a></h2></header>309<div class="cheat panels">310<section class="panel" aria-labelledby="cs-setup"><h3 class="panel-head" id="cs-setup">once</h3><dl>311<div><dt>git config --global forge.signingkey ~/.ssh/id_ed25519.pub</dt><dd>The key that signs CI results and deploys; a <code>.pub</code> path signs through ssh-agent.</dd></div>312<div><dt>ssh git@src.rest signers &gt; ~/.config/forge/allowed_signers</dt><dd>Then point <code>forge.allowedsigners</code> at it, so <code>forge verify</code> can check signatures.</dd></div>313<div><dt>ssh git@src.rest new greet "say hello"</dt><dd>Make a repo under your name; prints the remote to add. <code>--private</code> makes it private.</dd></div>314<div><dt>git push git@src.rest:cat/greet.git main</dt><dd>Or just push: the repo is created if it doesn't exist.</dd></div>315<div><dt>forge import https://github.com/you/thing.git</dt><dd>Copy a repo in (set <code>forge.host</code> first). <code>--branches</code> takes every branch.</dd></div>316</dl></section>317<section class="panel" aria-labelledby="cs-review"><h3 class="panel-head" id="cs-review">a review</h3><dl>318<div><dt>git switch -c cat/topic</dt><dd>Branch under your name; once pushed, it is a review.</dd></div>319<div><dt>forge push -t "Title"</dt><dd>Push the branch and the CI results. <code>--draft</code>, <code>--ready</code>, <code>--target &lt;branch&gt;</code>, <code>-d</code> for a description.</dd></div>320<div><dt>forge push --stack</dt><dd>Push every layer of a stack, lowest first.</dd></div>321<div><dt>forge reviews</dt><dd>Open and draft reviews; <code>forge checkout &lt;id&gt;</code> fetches one.</dd></div>322<div><dt>forge comment [&lt;id&gt;] [path:line] -m "..."</dt><dd>Comment on a review, or on one line of it. Without <code>-m</code>, reads stdin or opens <code>$EDITOR</code>.</dd></div>323<div><dt>forge approve [&lt;id&gt;]</dt><dd>Approve the review as it is now.</dd></div>324<div><dt>forge critique [&lt;id&gt;]</dt><dd>An agent review: run <code>main</code>'s <code>.forge/review</code>, post its findings, sign what is open. <code>--full</code>, <code>--fix</code>, <code>--resolve &lt;id&gt;</code>.</dd></div>325<div><dt>forge land [&lt;id&gt;]</dt><dd>Check CI and the approval rules, move <code>main</code> forward, delete the landed branches.</dd></div>326<div><dt>ssh git@src.rest land cat/greet 7</dt><dd>Have the forge land review 7, no clone needed, with the same checks. <code>forge land --server</code> does it from a clone.</dd></div>327</dl></section>328<section class="panel" aria-labelledby="cs-ci"><h3 class="panel-head" id="cs-ci">ci</h3><dl>329<div><dt>forge attest [--stack] [rev...]</dt><dd>Run and sign CI for commits (default <code>HEAD</code>; <code>--stack</code>, everything on top of <code>main</code>). <code>--force</code> reruns what passed.</dd></div>330<div><dt>forge verify [rev...]</dt><dd>Show each commit's results and who signed them.</dd></div>331</dl></section>332<section class="panel" aria-labelledby="cs-sites"><h3 class="panel-head" id="cs-sites">sites</h3><dl>333<div><dt>forge site deploy [name...]</dt><dd>Build and publish from a clean checkout of <code>HEAD</code>: the site on <code>main</code>, a preview elsewhere. <code>--here</code> builds in your work tree.</dd></div>334<div><dt>forge site push &lt;dir&gt; &lt;name&gt;</dt><dd>Publish a directory you built yourself.</dd></div>335<div><dt>forge site url &lt;name&gt; [branch]</dt><dd>Where a site or preview is served.</dd></div>336</dl></section>337<section class="panel" aria-labelledby="cs-releases"><h3 class="panel-head" id="cs-releases">releases</h3><dl>338<div><dt>forge release publish &lt;tag&gt; &lt;file&gt;...</dt><dd>The tag's files with signed checksums, pushed with the tag and your CI results; again to replace them.</dd></div>339<div><dt>forge release verify &lt;file&gt;...</dt><dd>Check downloads against their checksums, and the signature against the forge's keys.</dd></div>340<div><dt>forge release ls, rm &lt;tag&gt;</dt><dd>This repo's releases; delete one.</dd></div>341</dl></section>342<section class="panel" aria-labelledby="cs-people"><h3 class="panel-head" id="cs-people">people, over ssh</h3><dl>343<div><dt>ssh git@src.rest help</dt><dd>Lists the commands you can run.</dd></div>344<div><dt>ssh git@src.rest invite fox --repo cat/012 --as collaborator</dt><dd>Admins only: a one-time invite link. <code>invites</code> lists pending ones.</dd></div>345<div><dt>ssh git@src.rest collaborators cat/012 add fox</dt><dd>Or <code>readers</code>; <code>rm</code> takes it back, and no argument lists them.</dd></div>346<div><dt>ssh git@src.rest who cat/012</dt><dd>Its owner, collaborators and readers.</dd></div>347<div><dt>ssh git@src.rest ls</dt><dd>Your repos, and the private ones you may read.</dd></div>348<div><dt>ssh git@src.rest passkey</dt><dd>A link that adds a passkey; <code>token</code> for curl and HTTPS clones.</dd></div>349</dl></section>350</div>351</section>352353<section class="commit" id="not-yet" aria-labelledby="not-yet-h">354<header><p class="ref"><b>08</b> not yet</p><h2 id="not-yet-h"><a href="#not-yet">What isn't there</a></h2></header>355<ul class="gaps">356<li><b>Issues.</b> No bug or task tracker yet.</li>357<li><b>Notifications.</b> No email or push notifications; open pages update themselves.</li>358<li><b>CI on the server.</b> By design: CI runs on your machine, and the forge checks signatures.</li>359</ul>360</section>361362</main>363</div>364365<footer class="end">366<div class="wrap">367<svg class="mark" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M16 15 12 7 8 15Zm-1 0L11.5 8"/></svg>368<p>This guide is <code>guide/</code> in <a href="https://src.rest/cat/forge/">cat/forge</a>. How it works inside: <a href="internals.html">the internals</a>.</p>369</div>370</footer>371</body>372</html>