@nynb/sandpaper 0.1.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.
- package/LICENSE +21 -0
- package/README.md +119 -0
- package/bin/brain-inject.js +23 -0
- package/bin/brain-stamp-check.js +36 -0
- package/bin/cli.js +62 -0
- package/brain/README.md +99 -0
- package/brain/assets/brain.css +472 -0
- package/brain/assets/brain.js +189 -0
- package/brain/assets/theme.css +88 -0
- package/package.json +19 -0
- package/public/sp-markdown.js +172 -0
- package/public/toolbar.css +220 -0
- package/public/toolbar.js +564 -0
- package/skill/sandpaper/SKILL.md +114 -0
- package/skill/sandpaper/commands/canvas.md +31 -0
- package/skill/sandpaper/commands/decide.md +16 -0
- package/skill/sandpaper/commands/help.md +25 -0
- package/skill/sandpaper/commands/init.md +113 -0
- package/skill/sandpaper/commands/learn.md +11 -0
- package/skill/sandpaper/commands/log.md +9 -0
- package/skill/sandpaper/commands/open.md +15 -0
- package/skill/sandpaper/commands/plan.md +16 -0
- package/skill/sandpaper/commands/serve.md +8 -0
- package/skill/sandpaper/commands/stamp.md +25 -0
- package/skill/sandpaper/commands/sync.md +17 -0
- package/skill/sandpaper/commands/theme.md +12 -0
- package/src/claude.js +226 -0
- package/src/edit.js +113 -0
- package/src/server.js +327 -0
- package/src/setup.js +564 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Narayan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Sandpaper
|
|
2
|
+
|
|
3
|
+
A **living project brain** for Claude Code projects. Sandpaper scaffolds a small static
|
|
4
|
+
site in `brain/` — a cover, three lenses (product / engineering / project), and three
|
|
5
|
+
books (log, decisions, learnings) — and teaches your agent to keep it current. After every
|
|
6
|
+
substantive turn, Claude *stamps* the brain: logs the work, refreshes the digest a fresh
|
|
7
|
+
session reads to rehydrate, flips plan tasks, records decisions and gotchas. The brain is
|
|
8
|
+
the **eyes** (state made visible); the terminal stays the **mouth** (steering). It never
|
|
9
|
+
copies your docs — every entry **links** to the canonical anchor in your spec or source.
|
|
10
|
+
|
|
11
|
+
No framework, no build step, no database. Plain HTML on disk, maintained by the agent,
|
|
12
|
+
readable by you.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# in your repo (npm package coming — GitHub install for now):
|
|
18
|
+
npx github:codevalley/sandpaper install-skill
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then, inside Claude Code:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
/sandpaper:init # discovers your repo, asks a few questions, fills the brain
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
And to view it:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx github:codevalley/sandpaper open # serves the repo + opens brain/index.html
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
(An npm package is coming as `@nynb/sandpaper`, which will shorten these to `npx @nynb/sandpaper <cmd>`.)
|
|
34
|
+
|
|
35
|
+
## What you get
|
|
36
|
+
|
|
37
|
+
- **`brain/`** — a multi-page static site: a **cover** (`index.html`) with a
|
|
38
|
+
machine-readable `#brain-state` digest, a one-line NOW, and derived progress;
|
|
39
|
+
**lenses** (`product/`, `engineering/`, `project/` — the plan board); **books**
|
|
40
|
+
(`log.html`, `decisions.html`, `learnings.html`). One `assets/theme.css` re-skins
|
|
41
|
+
everything.
|
|
42
|
+
- **The canvas** — a whiteboard on the cover. When Claude produces an explanation worth
|
|
43
|
+
keeping, it lands there as a readable board instead of scrolling past in the terminal
|
|
44
|
+
(last 5 kept, older ones fold away).
|
|
45
|
+
- **The refine toolbar** — serve the brain (or any HTML doc) locally and get an on-page
|
|
46
|
+
overlay: **Sand** (a scoped AI edit of the element you click), **Hands** (direct edit,
|
|
47
|
+
no AI), **Sling** (copies a terminal-ready instruction for bigger work). The file on
|
|
48
|
+
disk stays the single source of truth; the page live-reloads from it.
|
|
49
|
+
|
|
50
|
+
## The CLI (plumbing — no AI)
|
|
51
|
+
|
|
52
|
+
| command | what it does |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `sandpaper install-skill` | install the `/sandpaper:*` commands + hooks into this repo (also scaffolds `brain/`) |
|
|
55
|
+
| `sandpaper init` | scaffold `brain/` — assets, `.sandpaper/manifest.json`, a starter multi-page skeleton |
|
|
56
|
+
| `sandpaper upgrade` | bring an existing brain up to date (assets · hooks · commands · canvas), preserving your `theme.css` |
|
|
57
|
+
| `sandpaper rebuild` | full reset — back up the old brain to `brain.bak-<date>/`, lay down a fresh skeleton |
|
|
58
|
+
| `sandpaper doctor` | health-check a setup: assets, digest, links, source meta, manifest, hooks |
|
|
59
|
+
| `sandpaper open` | serve this repo's brain + open it in a browser |
|
|
60
|
+
| `sandpaper <doc.html \| dir>` | serve any doc or directory with the on-page refine toolbar |
|
|
61
|
+
| `sandpaper help` | usage |
|
|
62
|
+
|
|
63
|
+
Serves on `127.0.0.1:4848` by default (`$SANDPAPER_PORT` or the manifest's pinned `port`
|
|
64
|
+
override it; the server auto-bumps if the port is taken). `install-skill --no-hooks`
|
|
65
|
+
skips hook wiring and prints the settings snippet instead.
|
|
66
|
+
|
|
67
|
+
## The commands (intelligence — inside Claude Code)
|
|
68
|
+
|
|
69
|
+
`/sandpaper:help` lists them all.
|
|
70
|
+
|
|
71
|
+
**Maintain the brain**
|
|
72
|
+
|
|
73
|
+
- `/sandpaper:stamp` — the full update after a substantive turn (log · NOW · digest · decisions · learnings · plan)
|
|
74
|
+
- `/sandpaper:log` — add one work-log row (the heartbeat)
|
|
75
|
+
- `/sandpaper:plan` — add or flip a task/initiative on the plan board
|
|
76
|
+
- `/sandpaper:decide` — record a decision, or open/resolve a question
|
|
77
|
+
- `/sandpaper:learn` — record a gotcha or verdict
|
|
78
|
+
- `/sandpaper:canvas` — elevate an explanation into a board on the cover's canvas
|
|
79
|
+
- `/sandpaper:sync` — reconcile the brain against the code; find and flag drift
|
|
80
|
+
|
|
81
|
+
**Set up & run**
|
|
82
|
+
|
|
83
|
+
- `/sandpaper:init` — discover the repo, run a short wizard, generate the brain
|
|
84
|
+
- `/sandpaper:open` — start the server and open the brain
|
|
85
|
+
- `/sandpaper:serve` — serve the brain (or any doc) with the refine toolbar
|
|
86
|
+
- `/sandpaper:theme` — re-skin from a brand colour or preset
|
|
87
|
+
|
|
88
|
+
## The auto-updating brain
|
|
89
|
+
|
|
90
|
+
Two hooks keep the brain current without prodding. `install-skill` wires them into
|
|
91
|
+
`.claude/settings.json` (merged, deduped, your existing settings preserved); pass
|
|
92
|
+
`--no-hooks` to opt out, or remove the entries later to disable.
|
|
93
|
+
|
|
94
|
+
- **SessionStart** (`brain-inject.js`) — surfaces the brain's digest so a fresh `claude`
|
|
95
|
+
rehydrates from project state automatically.
|
|
96
|
+
- **Stop** (`brain-stamp-check.js`) — if a turn ends with uncommitted project changes but
|
|
97
|
+
nothing changed under `brain/`, it blocks once and asks the agent to stamp.
|
|
98
|
+
Self-limiting: it never loops, and the agent can decline by stopping again.
|
|
99
|
+
|
|
100
|
+
## Publishing the brain
|
|
101
|
+
|
|
102
|
+
`brain/` is always publishable — point GitHub Pages, Vercel, Netlify, or Cloudflare at
|
|
103
|
+
the folder as-is; there is no build step. Links out of the brain (into your source and
|
|
104
|
+
specs) are written relative, and each page carries a `sandpaper:source` meta (set
|
|
105
|
+
automatically from your git origin); when the brain is served *away* from its repo, an
|
|
106
|
+
on-page resolver detects it and rewrites out-links to your source host at click time
|
|
107
|
+
instead of 404ing. See the deploy guide that ships inside every scaffolded brain:
|
|
108
|
+
[`brain/README.md`](brain/README.md).
|
|
109
|
+
|
|
110
|
+
## Conventions & requirements
|
|
111
|
+
|
|
112
|
+
- **Node ≥ 18**, ESM, **zero runtime dependencies** — built-ins only.
|
|
113
|
+
- The document on disk is the single source of truth; the page reflects disk, never ahead of it.
|
|
114
|
+
- The brain **links, never copies** — roadmap, risks, and specs stay canonical in your docs.
|
|
115
|
+
- The server binds to `127.0.0.1` only.
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Sandpaper SessionStart hook — surface the brain digest so a fresh `claude` rehydrates
|
|
3
|
+
// from the brain FIRST, automatically (no "read the brain" prompt needed).
|
|
4
|
+
// Zero deps; prints the digest to stdout, which Claude Code adds to the session context.
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const html = readFileSync(join(process.cwd(), 'brain', 'index.html'), 'utf8');
|
|
10
|
+
const m = html.match(/<script type="application\/json" id="brain-state">([\s\S]*?)<\/script>/);
|
|
11
|
+
if (!m) process.exit(0);
|
|
12
|
+
const d = JSON.parse(m[1]);
|
|
13
|
+
const out = [
|
|
14
|
+
`🪵 Sandpaper brain · ${d.project} · ${d.phase} · session ${d.session} · stamped ${d.updated}`,
|
|
15
|
+
`NOW — ${d.focus?.one || ''}${d.focus?.ref ? ` (${d.focus.ref})` : ''}`,
|
|
16
|
+
`Recent: ${(d.worklog || []).map((w) => w.one).join(' · ')}`,
|
|
17
|
+
(d.open && d.open.length) ? `Needs you: ${d.open.join(', ')}` : '',
|
|
18
|
+
`Canvas (board-first) — when your reply would be a substantial summary or explanation (a recap, an architecture, a comparison, a walkthrough, an analysis), the BOARD IS THE REPLY: write the elevated version as the current board in brain/index.html's <!-- BRAIN:CANVAS --> region (the .whiteboard; demote the previous to Earlier, cap 5) and leave just ONE line in the terminal — "📋 <gist> → on the canvas". Don't write it twice. Short answers and back-and-forth stay in the terminal. See the CANVAS discipline in the skill; /sandpaper:canvas forces one.`,
|
|
19
|
+
`Read brain/index.html to navigate. Stamp the brain after substantive turns (CLAUDE.md → "The project brain").`,
|
|
20
|
+
].filter(Boolean).join('\n');
|
|
21
|
+
process.stdout.write(out + '\n');
|
|
22
|
+
} catch { /* no brain / unreadable — stay silent, never break the session */ }
|
|
23
|
+
process.exit(0);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Sandpaper Stop hook — the brain's immune system. When a turn changed project files but DID NOT
|
|
3
|
+
// touch the brain, block once and tell the agent to stamp it. Automatic; no user prodding.
|
|
4
|
+
//
|
|
5
|
+
// Self-limiting (never loops):
|
|
6
|
+
// 1. `stop_hook_active` in the hook payload → we're already in a continuation, so allow the stop.
|
|
7
|
+
// 2. The check is idempotent — once the agent stamps (brain/ changes), it no longer fires.
|
|
8
|
+
// 3. The agent can override ("this turn needs no brain update") by simply stopping again.
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
let input = {};
|
|
13
|
+
try { input = JSON.parse(readFileSync(0, 'utf8') || '{}'); } catch { /* no/!json stdin */ }
|
|
14
|
+
if (input.stop_hook_active) process.exit(0); // guard #1: don't re-block a continuation
|
|
15
|
+
|
|
16
|
+
let changed = [];
|
|
17
|
+
try {
|
|
18
|
+
changed = execFileSync('git', ['status', '--porcelain'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
19
|
+
.split('\n').filter(Boolean).map((l) => l.slice(3).trim());
|
|
20
|
+
} catch { process.exit(0); } // not a git repo / git missing — stay silent
|
|
21
|
+
|
|
22
|
+
const isProject = (f) => /\.(js|css|html|md|json)$/.test(f) && !f.startsWith('.sandpaper') && !f.includes('node_modules');
|
|
23
|
+
const proj = changed.filter(isProject);
|
|
24
|
+
const brainTouched = proj.some((f) => f.startsWith('brain/')); // guard #2: stamped → won't fire
|
|
25
|
+
const nonBrain = proj.filter((f) => !f.startsWith('brain/') && f !== 'CLAUDE.md');
|
|
26
|
+
|
|
27
|
+
if (nonBrain.length && !brainTouched) {
|
|
28
|
+
const reason =
|
|
29
|
+
`🪵 The brain isn't stamped. This turn changed ${nonBrain.length} project file(s) ` +
|
|
30
|
+
`(${nonBrain.slice(0, 6).join(', ')}${nonBrain.length > 6 ? ', …' : ''}) but nothing under brain/. ` +
|
|
31
|
+
`Before finishing, STAMP the brain (CLAUDE.md → "The project brain"): prepend one log row, refresh the ` +
|
|
32
|
+
`cover NOW + #brain-state digest, flip any plan-board tasks, add a decision/learning if one applies — then commit. ` +
|
|
33
|
+
`If this turn genuinely warrants no brain update, just stop again.`;
|
|
34
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason }) + '\n');
|
|
35
|
+
}
|
|
36
|
+
process.exit(0);
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Sandpaper CLI. Subcommands are the "plumbing" (no AI); a bare path falls through to `serve`.
|
|
3
|
+
// sandpaper install-skill | init | doctor | open | help | <doc.html|dir>
|
|
4
|
+
import { resolve, dirname, join } from 'node:path';
|
|
5
|
+
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { execFile } from 'node:child_process';
|
|
8
|
+
import { startServer } from '../src/server.js';
|
|
9
|
+
import { installSkill, scaffold, doctor, upgrade, rebuild } from '../src/setup.js';
|
|
10
|
+
|
|
11
|
+
const PKG = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
13
|
+
|
|
14
|
+
// Starting port: $SANDPAPER_PORT wins, else the repo's pinned .sandpaper/manifest.json "port",
|
|
15
|
+
// else 4848. The server auto-bumps from here if it's taken, so multiple repos never collide.
|
|
16
|
+
const startPort = () => {
|
|
17
|
+
if (process.env.SANDPAPER_PORT) return Number(process.env.SANDPAPER_PORT);
|
|
18
|
+
try { const m = JSON.parse(readFileSync(join(process.cwd(), '.sandpaper', 'manifest.json'), 'utf8')); if (m.port) return Number(m.port); } catch {}
|
|
19
|
+
return 4848;
|
|
20
|
+
};
|
|
21
|
+
const port = startPort();
|
|
22
|
+
|
|
23
|
+
const usage = () => console.log(`
|
|
24
|
+
🪵 sandpaper — a living project brain
|
|
25
|
+
|
|
26
|
+
sandpaper install-skill install the /sandpaper commands + hooks into this repo
|
|
27
|
+
sandpaper init scaffold brain/ (assets + manifest + a starter cover)
|
|
28
|
+
sandpaper upgrade bring an existing brain up to date (assets · hooks · commands · canvas)
|
|
29
|
+
sandpaper rebuild full reset — back up the old brain + lay down a fresh skeleton
|
|
30
|
+
sandpaper doctor health-check a Sandpaper setup
|
|
31
|
+
sandpaper open serve this repo's brain + open it in a browser
|
|
32
|
+
sandpaper <doc.html | dir> serve with the on-page refine toolbar
|
|
33
|
+
sandpaper help this
|
|
34
|
+
|
|
35
|
+
Fresh repo? → sandpaper install-skill, then /sandpaper:init in Claude Code.
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
const serve = async (target, openBrowser) => {
|
|
39
|
+
const isDir = statSync(target).isDirectory();
|
|
40
|
+
const url = await startServer(target, port, { brain: isDir });
|
|
41
|
+
console.log(`\n 🪵 Sandpaper\n ↳ ${isDir ? 'serving' : 'editing'} ${target}\n ↳ open ${url}\n`);
|
|
42
|
+
if (openBrowser) {
|
|
43
|
+
const u = isDir && existsSync(join(target, 'brain', 'index.html')) ? url + 'brain/index.html' : url;
|
|
44
|
+
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
45
|
+
execFile(opener, [u], () => {}); // best-effort; ignore failures
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
(async () => {
|
|
50
|
+
try {
|
|
51
|
+
if (!cmd || cmd === 'help' || cmd === '-h' || cmd === '--help') return usage();
|
|
52
|
+
if (cmd === 'install-skill') return installSkill(process.cwd(), PKG, { noHooks: rest.includes('--no-hooks') });
|
|
53
|
+
if (cmd === 'init') return scaffold(process.cwd(), PKG);
|
|
54
|
+
if (cmd === 'upgrade' || cmd === 'update') return upgrade(process.cwd(), PKG);
|
|
55
|
+
if (cmd === 'rebuild' || cmd === 'reset') return rebuild(process.cwd(), PKG);
|
|
56
|
+
if (cmd === 'doctor') return doctor(process.cwd());
|
|
57
|
+
if (cmd === 'open') return serve(process.cwd(), true);
|
|
58
|
+
const target = resolve(process.cwd(), cmd);
|
|
59
|
+
if (!existsSync(target)) { console.error(`\n sandpaper: unknown command or path: ${cmd}`); usage(); process.exit(1); }
|
|
60
|
+
return serve(target, false);
|
|
61
|
+
} catch (e) { console.error(' sandpaper:', e.message); process.exit(1); }
|
|
62
|
+
})();
|
package/brain/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Deploying the brain
|
|
2
|
+
|
|
3
|
+
## What this folder is
|
|
4
|
+
|
|
5
|
+
This folder is Sandpaper's own living brain: a small static site the terminal agent stamps
|
|
6
|
+
after each substantive turn (see `../CLAUDE.md` → "The project brain").
|
|
7
|
+
|
|
8
|
+
- `index.html` — the cover: NOW, the canvas (boards), digest, recent log, browse.
|
|
9
|
+
- `log.html` — append-only work log (the heartbeat).
|
|
10
|
+
- `decisions.html` — decisions + open questions (the why).
|
|
11
|
+
- `map.html` — components, architecture (linked), glossary.
|
|
12
|
+
- `learnings.html` — gotchas & verdicts.
|
|
13
|
+
- `product/` · `engineering/` · `project/` — the lenses; `wiki/` — the settled prose docs.
|
|
14
|
+
|
|
15
|
+
Styled by `assets/theme.css` + `assets/brain.css`, with a little vanilla JS in
|
|
16
|
+
`assets/brain.js`. No framework, no build step, no server-side anything. It is
|
|
17
|
+
**always publishable**: point any static host at this folder as-is and it works.
|
|
18
|
+
|
|
19
|
+
One design choice shapes everything below: the brain **links, never copies**. Canonical
|
|
20
|
+
truth lives in the parent repo — the spec docs (`../sandpaper.html`, `../engg-spec.html`),
|
|
21
|
+
source files, `package.json` — and the brain references them with relative paths (`../…`)
|
|
22
|
+
so they resolve on disk and whenever the whole repo is served.
|
|
23
|
+
|
|
24
|
+
## Two deploy shapes
|
|
25
|
+
|
|
26
|
+
### 1. Whole-repo deploy (recommended for public repos)
|
|
27
|
+
|
|
28
|
+
Serve the repo root and visit `/brain/`. Every out-of-brain link resolves: spec HTML docs
|
|
29
|
+
render with working `#anchors`, source files are viewable. GitHub Pages serving the repo
|
|
30
|
+
root does this perfectly.
|
|
31
|
+
|
|
32
|
+
### 2. Brain-only deploy (site root = this folder)
|
|
33
|
+
|
|
34
|
+
The relative `../` refs can't resolve — there's nothing above the root. The built-in
|
|
35
|
+
resolver in `assets/brain.js` handles it. Each page's head carries:
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<meta name="sandpaper:source" content="https://github.com/codevalley/sandpaper/blob/HEAD/" data-pkg="sandpaper" />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
On load, the page probes `../package.json` and checks its `name` against `data-pkg`.
|
|
42
|
+
If the probe fails (or the name doesn't match), the page knows it is detached, and
|
|
43
|
+
out-links open the source-host copy instead (rewritten at click time). Source and meta
|
|
44
|
+
files render fine on GitHub's blob view; spec **HTML** docs land on blob *source* view —
|
|
45
|
+
unrendered. Use the whole-repo shape if you want rendered specs. With no meta configured,
|
|
46
|
+
out-links dim with a tooltip instead of 404ing.
|
|
47
|
+
|
|
48
|
+
The meta is written automatically by `npx sandpaper init` / `upgrade` from the git origin
|
|
49
|
+
(or `package.json` → `"repository"`). `npx sandpaper doctor` verifies it is present and
|
|
50
|
+
consistent across pages.
|
|
51
|
+
|
|
52
|
+
## Deployed brains are read-only
|
|
53
|
+
|
|
54
|
+
The refine toolbar (Sand / Hands / Sling) is injected only by the local `sandpaper`
|
|
55
|
+
server — a deployed brain has no toolbar and can't be edited from the page. By design:
|
|
56
|
+
the public copy is for reading.
|
|
57
|
+
|
|
58
|
+
## Recipes
|
|
59
|
+
|
|
60
|
+
**GitHub Pages (simplest)** — Settings → Pages → Source: *Deploy from a branch*, branch
|
|
61
|
+
`main`, folder `/ (root)`. That's the whole-repo shape — visit
|
|
62
|
+
`https://<owner>.github.io/<repo>/brain/`. For the brain-only shape, use Source:
|
|
63
|
+
*GitHub Actions* with this workflow:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
name: Deploy brain
|
|
67
|
+
on: { push: { branches: [main] } }
|
|
68
|
+
permissions: { contents: read, pages: write, id-token: write }
|
|
69
|
+
jobs:
|
|
70
|
+
deploy:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
environment: { name: github-pages, url: "${{ steps.deployment.outputs.page_url }}" }
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v4
|
|
75
|
+
- uses: actions/upload-pages-artifact@v3
|
|
76
|
+
with: { path: brain } # 'path: .' switches to the whole-repo shape
|
|
77
|
+
- id: deployment
|
|
78
|
+
uses: actions/deploy-pages@v4
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Vercel** — New Project → import the repo. Root Directory = repo root (or `brain/` for
|
|
82
|
+
brain-only), Framework Preset = *Other*, no build command, Output Directory = `./`.
|
|
83
|
+
|
|
84
|
+
**Netlify** — New site from Git. No build command. Publish directory: `brain` (or the
|
|
85
|
+
repo root).
|
|
86
|
+
|
|
87
|
+
**Cloudflare Pages** — Connect the repo. No build command. Build output directory:
|
|
88
|
+
`brain` (or `/`).
|
|
89
|
+
|
|
90
|
+
## Privacy
|
|
91
|
+
|
|
92
|
+
Deploying the whole repo publishes **all** of its files, not just the brain. Brain-only
|
|
93
|
+
publishes just this folder — but its out-links point at the source host, which must be
|
|
94
|
+
public for them to work. Either way, assume everything the brain links to is visible.
|
|
95
|
+
Don't deploy a brain whose repo isn't ready to be read.
|
|
96
|
+
|
|
97
|
+
Remember what the brain *is*: distilled internal state. The canvas boards are derived
|
|
98
|
+
from working conversations, and the log/decisions/learnings record real reasoning —
|
|
99
|
+
read them with publishing eyes before pointing a host at this folder.
|