artshelf 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
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>Install Artshelf</title>
7
+ <meta name="description" content="Install Artshelf from npm or a local source checkout.">
8
+ <link rel="stylesheet" href="site.css">
9
+ <script src="theme.js" defer></script>
10
+ </head>
11
+ <body>
12
+ <div class="docs-shell">
13
+ <nav class="global-nav" aria-label="Documentation">
14
+ <a class="site-mark" href="index.html"><strong>Artshelf</strong><span>Artifact retention CLI</span></a>
15
+ <button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle color theme" aria-pressed="false">Dark</button>
16
+ <div class="nav-scroll" aria-label="Documentation sections">
17
+ <div class="nav-section">
18
+ <p class="nav-section-title">Start</p>
19
+ <a href="index.html">Overview</a>
20
+ <a href="install.html" aria-current="page">Install</a>
21
+ <a href="quickstart.html">Quickstart</a>
22
+ </div>
23
+ <div class="nav-section">
24
+ <p class="nav-section-title">Agents</p>
25
+ <a href="agent-usage.html">Agent usage</a>
26
+ <a href="https://github.com/calvinnwq/artshelf/blob/main/skills/artshelf/SKILL.md">Agent skill</a>
27
+ </div>
28
+ <div class="nav-section">
29
+ <p class="nav-section-title">Reference</p>
30
+ <a href="reference.html">CLI reference</a>
31
+ <a href="https://github.com/calvinnwq/artshelf">GitHub</a>
32
+ </div>
33
+ </div>
34
+ </nav>
35
+
36
+ <div class="docs-content">
37
+ <header class="page-top">
38
+ <div class="wrap">
39
+ <nav class="breadcrumbs" aria-label="Breadcrumbs"><a href="index.html">Docs</a><span>/</span><span>Install</span></nav>
40
+ <div class="hero">
41
+ <div>
42
+ <p class="eyebrow">Install</p>
43
+ <h1>Install Artshelf.</h1>
44
+ <p class="lede">
45
+ Artshelf is being prepared for npm distribution under the unscoped
46
+ `artshelf` package name. Source install remains the fallback path.
47
+ </p>
48
+ </div>
49
+ <div class="terminal">
50
+ <div class="terminal-head"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>install</span></div>
51
+ <pre><code>$ npm install -g artshelf
52
+ $ artshelf --version
53
+ $ artshelf doctor</code></pre>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </header>
58
+
59
+ <main class="wrap">
60
+ <article>
61
+ <section>
62
+ <h2>Requirements</h2>
63
+ <div class="grid">
64
+ <div class="card"><h3>Node.js 22+</h3><p>The package declares `node &gt;=22` and CI runs on Node 24.</p></div>
65
+ <div class="card"><h3>Corepack</h3><p>Use Corepack to activate the pinned pnpm version from `packageManager`.</p></div>
66
+ <div class="card"><h3>Git</h3><p>Clone the public repo and keep source installs easy to update with `git pull`.</p></div>
67
+ </div>
68
+ </section>
69
+
70
+ <section>
71
+ <h2>Install From npm</h2>
72
+ <pre><code>npm install -g artshelf
73
+ artshelf --version
74
+ artshelf doctor</code></pre>
75
+ <pre><code>pnpm add -g artshelf
76
+ artshelf --version
77
+ artshelf doctor</code></pre>
78
+ <p>
79
+ The npm package exposes `artshelf` as the primary command and keeps
80
+ `shelf` as a temporary compatibility alias.
81
+ </p>
82
+ <pre><code>npm uninstall -g artshelf</code></pre>
83
+ </section>
84
+
85
+ <section>
86
+ <h2>Install From Source</h2>
87
+ <pre><code>git clone https://github.com/calvinnwq/artshelf.git
88
+ cd artshelf
89
+ corepack enable
90
+ pnpm install --frozen-lockfile
91
+ pnpm run build
92
+ npm link
93
+ artshelf --version
94
+ artshelf doctor</code></pre>
95
+ <p>
96
+ `npm link` connects the local checkout to your global npm bin, so future rebuilds update the `artshelf`
97
+ command.
98
+ </p>
99
+ <pre><code>npm unlink -g artshelf</code></pre>
100
+ </section>
101
+
102
+ <section>
103
+ <h2>Update A Source Install</h2>
104
+ <pre><code>cd artshelf
105
+ git pull
106
+ pnpm install --frozen-lockfile
107
+ pnpm run build
108
+ npm link
109
+ artshelf --version
110
+ artshelf doctor</code></pre>
111
+ </section>
112
+
113
+ <section>
114
+ <h2>Agent Setup</h2>
115
+ <p>
116
+ Agents should prefer the npm install when available. If using a source install,
117
+ ask the user where to clone it before linking the command.
118
+ </p>
119
+ <pre><code>docs/agent-usage.md</code></pre>
120
+ <div class="note">Verify `artshelf --version` and `artshelf doctor` after either install method.</div>
121
+ </section>
122
+
123
+ <section>
124
+ <h2>Preview Docs Locally</h2>
125
+ <pre><code>pnpm docs:serve</code></pre>
126
+ <p>
127
+ This serves the static docs directory at
128
+ <a href="http://127.0.0.1:8080/">127.0.0.1:8080</a>.
129
+ </p>
130
+ </section>
131
+ </article>
132
+ </main>
133
+ <footer class="site-footer"><div class="wrap">Artshelf docs · <a href="quickstart.html">Next: quickstart</a></div></footer>
134
+ </div>
135
+ </div>
136
+ </body>
137
+ </html>
@@ -0,0 +1,142 @@
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>Artshelf Quickstart</title>
7
+ <meta name="description" content="The approval-first Artshelf workflows: register a temp artifact, review everything safely, approve cleanup safely, and purge old trash explicitly.">
8
+ <link rel="stylesheet" href="site.css">
9
+ <script src="theme.js" defer></script>
10
+ </head>
11
+ <body>
12
+ <div class="docs-shell">
13
+ <nav class="global-nav" aria-label="Documentation">
14
+ <a class="site-mark" href="index.html"><strong>Artshelf</strong><span>Artifact retention CLI</span></a>
15
+ <button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle color theme" aria-pressed="false">Dark</button>
16
+ <div class="nav-scroll" aria-label="Documentation sections">
17
+ <div class="nav-section">
18
+ <p class="nav-section-title">Start</p>
19
+ <a href="index.html">Overview</a>
20
+ <a href="install.html">Install</a>
21
+ <a href="quickstart.html" aria-current="page">Quickstart</a>
22
+ </div>
23
+ <div class="nav-section">
24
+ <p class="nav-section-title">Agents</p>
25
+ <a href="agent-usage.html">Agent usage</a>
26
+ <a href="https://github.com/calvinnwq/artshelf/blob/main/skills/artshelf/SKILL.md">Agent skill</a>
27
+ </div>
28
+ <div class="nav-section">
29
+ <p class="nav-section-title">Reference</p>
30
+ <a href="reference.html">CLI reference</a>
31
+ <a href="https://github.com/calvinnwq/artshelf">GitHub</a>
32
+ </div>
33
+ </div>
34
+ </nav>
35
+
36
+ <div class="docs-content">
37
+ <header class="page-top">
38
+ <div class="wrap">
39
+ <nav class="breadcrumbs" aria-label="Breadcrumbs"><a href="index.html">Docs</a><span>/</span><span>Quickstart</span></nav>
40
+ <div class="hero">
41
+ <div>
42
+ <p class="eyebrow">Approval-first workflows</p>
43
+ <h1>Register, review, approve — safely.</h1>
44
+ <p class="lede">
45
+ Artshelf centers on four workflows: register a temp artifact when it is created,
46
+ review everything safely before anything moves, approve cleanup safely from a
47
+ reviewed plan, and purge old trash only from a separate reviewed plan. Start with
48
+ explicit ledger and registry paths when testing.
49
+ </p>
50
+ </div>
51
+ <div class="terminal">
52
+ <div class="terminal-head"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>quickstart</span></div>
53
+ <pre><code>$ mkdir -p /tmp/artshelf-demo
54
+ $ echo "debug output" &gt; /tmp/artshelf-demo/output.txt
55
+ $ artshelf put /tmp/artshelf-demo \
56
+ --reason "quickstart artifact" \
57
+ --ttl 1d \
58
+ --kind scratch \
59
+ --cleanup trash \
60
+ --ledger /tmp/artshelf-ledger.jsonl \
61
+ --registry /tmp/artshelf-registry.json</code></pre>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </header>
66
+
67
+ <main class="wrap">
68
+ <article>
69
+ <section>
70
+ <h2>1. Register a temp artifact</h2>
71
+ <p>Create something worth tracking, then put it on the artshelf the moment it exists:</p>
72
+ <pre><code>mkdir -p /tmp/artshelf-demo
73
+ echo "debug output" &gt; /tmp/artshelf-demo/output.txt
74
+
75
+ artshelf put /tmp/artshelf-demo \
76
+ --reason "quickstart artifact" \
77
+ --ttl 1d \
78
+ --kind scratch \
79
+ --cleanup trash \
80
+ --owner manual \
81
+ --label quickstart \
82
+ --ledger /tmp/artshelf-ledger.jsonl \
83
+ --registry /tmp/artshelf-registry.json \
84
+ --json</code></pre>
85
+ <p>Capture the returned `id` anywhere future cleanup context matters.</p>
86
+ </section>
87
+
88
+ <section>
89
+ <h2>2. Review everything safely</h2>
90
+ <p>Inspect the ledger and preview cleanup. None of these commands move or delete files.</p>
91
+ <pre><code>artshelf list --ledger /tmp/artshelf-ledger.jsonl
92
+ artshelf status --ledger /tmp/artshelf-ledger.jsonl
93
+ artshelf validate --ledger /tmp/artshelf-ledger.jsonl --json
94
+ artshelf due --ledger /tmp/artshelf-ledger.jsonl --json
95
+ artshelf cleanup --dry-run --ledger /tmp/artshelf-ledger.jsonl --json</code></pre>
96
+ <p>
97
+ Dry-run writes a plan under the ledger's `.shelf` directory only when
98
+ cleanup entries exist. If there is nothing to clean up, it reports
99
+ <code>not-created</code> and writes no plan file. Review any generated
100
+ plan id before an execute step.
101
+ </p>
102
+ </section>
103
+
104
+ <section>
105
+ <h2>3. Approve cleanup safely</h2>
106
+ <pre><code>artshelf cleanup --execute \
107
+ --plan-id plan_20260601_120000_ab12 \
108
+ --ledger /tmp/artshelf-ledger.jsonl</code></pre>
109
+ <div class="note">
110
+ Execute is intentionally separate. Agents must not run it unless a human explicitly
111
+ approves the reviewed plan id and ledger that produced it. After execution, Artshelf writes
112
+ a receipt and updates touched records so handled artifacts stop appearing in future due checks.
113
+ Artshelf also records generated plans and receipts in the ledger as Artshelf-owned
114
+ artifacts.
115
+ </div>
116
+ </section>
117
+
118
+ <section>
119
+ <h2>4. Purge old trashed records explicitly</h2>
120
+ <p>
121
+ After cleanup execution, trashed files are quarantined under the ledger's
122
+ <code>.shelf/trash</code> folder. Before physical deletion, run an explicit
123
+ reviewed trash purge plan:
124
+ </p>
125
+ <pre><code>artshelf trash list --ledger /tmp/artshelf-ledger.jsonl
126
+ artshelf trash purge --older-than 7d --dry-run --ledger /tmp/artshelf-ledger.jsonl --json</code></pre>
127
+ <p>
128
+ A fresh quickstart record will usually be newer than the <code>7d</code>
129
+ purge cutoff and report <code>not-created</code>; for real old trash, execute
130
+ only if the dry-run produced entries and the plan id was explicitly reviewed:
131
+ </p>
132
+ <pre><code>artshelf trash purge --execute \
133
+ --plan-id purge_20260601_120000_ab12 \
134
+ --ledger /tmp/artshelf-ledger.jsonl</code></pre>
135
+ </section>
136
+ </article>
137
+ </main>
138
+ <footer class="site-footer"><div class="wrap">Artshelf docs · <a href="agent-usage.html">Next: agent usage</a></div></footer>
139
+ </div>
140
+ </div>
141
+ </body>
142
+ </html>
@@ -0,0 +1,170 @@
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>Artshelf CLI Reference</title>
7
+ <meta name="description" content="Artshelf v1 command reference.">
8
+ <link rel="stylesheet" href="site.css">
9
+ <script src="theme.js" defer></script>
10
+ </head>
11
+ <body>
12
+ <div class="docs-shell">
13
+ <nav class="global-nav" aria-label="Documentation">
14
+ <a class="site-mark" href="index.html"><strong>Artshelf</strong><span>Artifact retention CLI</span></a>
15
+ <button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle color theme" aria-pressed="false">Dark</button>
16
+ <div class="nav-scroll" aria-label="Documentation sections">
17
+ <div class="nav-section">
18
+ <p class="nav-section-title">Start</p>
19
+ <a href="index.html">Overview</a>
20
+ <a href="install.html">Install</a>
21
+ <a href="quickstart.html">Quickstart</a>
22
+ </div>
23
+ <div class="nav-section">
24
+ <p class="nav-section-title">Agents</p>
25
+ <a href="agent-usage.html">Agent usage</a>
26
+ <a href="https://github.com/calvinnwq/artshelf/blob/main/skills/artshelf/SKILL.md">Agent skill</a>
27
+ </div>
28
+ <div class="nav-section">
29
+ <p class="nav-section-title">Reference</p>
30
+ <a href="reference.html" aria-current="page">CLI reference</a>
31
+ <a href="https://github.com/calvinnwq/artshelf">GitHub</a>
32
+ </div>
33
+ </div>
34
+ </nav>
35
+
36
+ <div class="docs-content">
37
+ <header class="page-top">
38
+ <div class="wrap">
39
+ <nav class="breadcrumbs" aria-label="Breadcrumbs"><a href="index.html">Docs</a><span>/</span><span>Reference</span></nav>
40
+ <div class="hero">
41
+ <div>
42
+ <p class="eyebrow">V1 command surface</p>
43
+ <h1>Small commands, explicit cleanup.</h1>
44
+ <p class="lede">
45
+ Artshelf v1 keeps the API narrow: put entries in a ledger, query existing records, write a
46
+ cleanup plan, and use separate reviewed plan ids for cleanup execution and trash purge.
47
+ </p>
48
+ </div>
49
+ <div class="terminal">
50
+ <div class="terminal-head"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>reference</span></div>
51
+ <pre><code>$ artshelf help
52
+ $ artshelf help put
53
+ $ artshelf help cleanup
54
+ $ artshelf --version</code></pre>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ </header>
59
+
60
+ <main class="wrap">
61
+ <article>
62
+ <section>
63
+ <h2>Commands</h2>
64
+ <div class="command-list">
65
+ <div class="command-row"><code>artshelf put &lt;path&gt;</code><p>Append a ledger entry with reason, retention, owner, labels, kind, and cleanup mode.</p></div>
66
+ <div class="command-row"><code>artshelf ledgers list</code><p>List registered ledgers with per-ledger validation status (ok/missing/invalid), entry counts, and warning/error counts so agents can spot stale registry entries without a separate validate pass. Exits non-zero when the registry or any ledger is broken. Add <code>--plain</code> for the fast listing that skips validation.</p></div>
67
+ <div class="command-row"><code>artshelf ledgers add --ledger &lt;path&gt; [--scope repo|user|other]</code><p>Register an existing ledger so Artshelf can review it through one entry point. Scope is inferred from the path when omitted.</p></div>
68
+ <div class="command-row"><code>artshelf list</code><p>Show ledger entries and current status. Use <code>--all</code> and <code>--status active|review-required|trashed|cleanup-refused|resolved</code> to filter registered ledgers.</p></div>
69
+ <div class="command-row"><code>artshelf find --path &lt;path&gt;</code><p>Read-only lookup for existing records by path, owner, labels, and status.</p></div>
70
+ <div class="command-row"><code>artshelf get &lt;id&gt;</code><p>Read-only audit lookup for one ledger record by Artshelf id. Use <code>--all</code> to search registered ledgers.</p></div>
71
+ <div class="command-row"><code>artshelf due</code><p>Classify active entries as kept, due, manual-review, or missing-path. Use <code>--all</code> to read every registered ledger after registry validation.</p></div>
72
+ <div class="command-row"><code>artshelf validate</code><p>Validate JSONL shape, handled cleanup metadata, and warn about missing active, review-required, or trashed target paths. Use <code>--all</code> to report stale registered ledgers.</p></div>
73
+ <div class="command-row"><code>artshelf review</code><p>Run validate, due, and cleanup plan preview. Invalid ledgers and valid ledgers with no cleanup entries report a <code>not-created</code> plan. <code>--all</code> adds an aggregate triage summary (affected ledgers, due, manual-review, missing-path, executable, and skipped counts plus preview plan ids) and states the next safe action, staying read-only.</p></div>
74
+ <div class="command-row"><code>artshelf doctor</code><p>Report machine and registry health: CLI version, selected/default ledger path, selected/global registry path, stale or invalid registered ledgers, and the cleanup safety posture. Read-only; run it after install or when <code>--all</code> commands misbehave. Exits non-zero when the registry or a registered ledger is broken.</p></div>
75
+ <div class="command-row"><code>artshelf status</code><p>Lightweight daily dashboard: single-ledger counts by default, or registry health, total ledgers, and aggregated counts with <code>--all</code>. Reports active, kept, due, manual-review, and missing-path entries plus the pending cleanup count. Read-only and never writes plans or receipts; <code>--all --json</code> is cron-friendly and human output is short enough to paste into a chat.</p></div>
76
+ <div class="command-row"><code>artshelf cleanup --dry-run</code><p>Create and register a cleanup plan without moving files when cleanup entries exist; no-op dry-runs report <code>not-created</code>, and matching dry-runs reuse the existing plan id.</p></div>
77
+ <div class="command-row"><code>artshelf cleanup --execute --plan-id &lt;id&gt;</code><p>Approval-only execution for one reviewed plan id: no daemon, no auto-execute, no global execute, and no fresh live set during execute. Writes a receipt, registers the receipt artifact, and updates touched ledger records.</p></div>
78
+ <div class="command-row"><code>artshelf trash list [--all] [--ledger &lt;path&gt;] [--json]</code><p>List trashed records with target path, receiving receipt provenance, and age.</p></div>
79
+ <div class="command-row"><code>artshelf trash purge --older-than &lt;ttl&gt; --dry-run [--ledger &lt;path&gt;] [--json]</code><p>Create a reviewed age-based trash purge plan from a specific ledger. The reviewed plan can be executed with <code>--execute --plan-id &lt;id&gt;</code>.</p></div>
80
+ <div class="command-row"><code>artshelf trash purge --execute --plan-id &lt;id&gt; [--ledger &lt;path&gt;] [--json]</code><p>Execute a reviewed trash purge plan id for one explicit ledger. <code>--all</code> is not supported for purge; completed receipts are refused, while started receipts may be resumed and reconciled.</p></div>
81
+ <div class="command-row"><code>artshelf resolve &lt;id&gt; --status resolved --reason &lt;text&gt;</code><p>Mark a handled, missing, or no-longer-needed record as manually resolved.</p></div>
82
+ </div>
83
+ </section>
84
+
85
+ <section>
86
+ <h2>Global Options</h2>
87
+ <pre><code>--ledger &lt;path&gt; Use an explicit JSONL ledger
88
+ --registry &lt;path&gt; Use an explicit ledger registry
89
+ --all Read all registered ledgers for supported commands
90
+ ARTSHELF_REGISTRY Override the default ledger registry path
91
+ --json Emit machine-readable JSON
92
+ --help Show help
93
+ --version Show version</code></pre>
94
+ </section>
95
+
96
+ <section>
97
+ <h2>Retention</h2>
98
+ <p>Choose exactly one retention mode when calling `put`.</p>
99
+ <pre><code>--ttl 3d
100
+ --retain-until 2026-06-04T08:28:00Z
101
+ --manual-review</code></pre>
102
+ </section>
103
+
104
+ <section>
105
+ <h2>Cleanup Modes</h2>
106
+ <div class="grid">
107
+ <div class="card"><h3>review</h3><p>Default. Included for review but not moved by execute.</p></div>
108
+ <div class="card"><h3>trash</h3><p>Moved into Artshelf's local trash folder when the approved plan executes.</p></div>
109
+ <div class="card"><h3>delete</h3><p>Accepted in the ledger but refused by v1 execution.</p></div>
110
+ </div>
111
+ <p>
112
+ Executed records move out of the active cleanup flow. `review` becomes
113
+ `review-required`, `trash` becomes `trashed`, and refused delete becomes
114
+ `cleanup-refused`. Manual resolution becomes `resolved`. `artshelf list` keeps
115
+ those records visible for audit.
116
+ </p>
117
+ <p>
118
+ `trash` moves stay in Artshelf quarantine until you run `artshelf trash purge`
119
+ with a reviewed purge plan for the current ledger.
120
+ </p>
121
+ </section>
122
+
123
+ <section>
124
+ <h2>Lookup</h2>
125
+ <p>
126
+ Use <code>find</code> before <code>put</code> when an integration needs idempotent
127
+ artifact registration. It requires at least one selector and never mutates records.
128
+ </p>
129
+ <pre><code>artshelf find --path &lt;path&gt; --owner &lt;agent-or-runtime&gt; --label &lt;task-or-run-id&gt; --json
130
+ artshelf find --all --owner &lt;agent-or-runtime&gt; --json
131
+ artshelf get &lt;id&gt; --json
132
+ artshelf get &lt;id&gt; --all --json</code></pre>
133
+ </section>
134
+
135
+ <section>
136
+ <h2>Storage</h2>
137
+ <p>
138
+ Inside a git repo, Artshelf defaults to `.shelf/ledger.jsonl`. Outside a repo, it
139
+ defaults to `~/.shelf/ledger.jsonl`. Pass `--ledger &lt;path&gt;` for tests, demos,
140
+ and controlled smoke runs.
141
+ </p>
142
+ <p>
143
+ Artshelf also keeps a user-level registry at `~/.shelf/ledgers.json`. Override it
144
+ with <code>--registry &lt;path&gt;</code> or <code>ARTSHELF_REGISTRY</code>. The registry
145
+ is a discovery index for supported `--all` review, status, cleanup dry-run, and trash-list commands; project records stay
146
+ in their own repo-local ledgers.
147
+ </p>
148
+ <pre><code>artshelf ledgers add --ledger &lt;repo&gt;/.shelf/ledger.jsonl --name &lt;project&gt; --scope repo
149
+ artshelf review --all --json
150
+ artshelf status --all --json
151
+ artshelf cleanup --dry-run --all --json
152
+ artshelf trash list --all --json</code></pre>
153
+ <p>
154
+ <code>artshelf ledgers add</code> requires an existing ledger path. Scope may be
155
+ <code>repo</code>, <code>user</code>, or <code>other</code>; when omitted, Artshelf infers
156
+ it from the path. All-mode reads report stale or invalid registered ledgers
157
+ before returning records. Global cleanup dry-run writes plan files only for ledgers
158
+ with cleanup entries, and <code>artshelf trash list --all --json</code> is read-only
159
+ trash discovery. Global cleanup execution and trash purge with <code>--all</code>
160
+ are refused. Execute only a specific reviewed plan id against the ledger that
161
+ produced it.
162
+ </p>
163
+ </section>
164
+ </article>
165
+ </main>
166
+ <footer class="site-footer"><div class="wrap">Artshelf docs · <a href="https://github.com/calvinnwq/artshelf/blob/main/SPEC.md">Read the spec</a></div></footer>
167
+ </div>
168
+ </div>
169
+ </body>
170
+ </html>