@surph_ai/cli 0.0.34 → 0.0.36

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/board/deploy.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // Deploy a board to Surph (`npm run deploy` → `surph-board-deploy`), run from
3
- // the board's folder. Built up step by step; so far:
2
+ // Deploy a board to Surph (`npm run deploy` → `surph board-deploy`), run from
3
+ // the board's folder. The folder mirrors the live board on S3
4
+ // (boards/<slug>/): board.json at the top, the pages and assets in site/.
4
5
  //
5
- // 1. Zip the board's site: every file in the folder except the project
6
- // files (package.json, package-lock.json, README.md, board.json,
7
- // node_modules), dot-files (.env, .git, .DS_Store) and the zip itself.
8
- // Written to site.zip in the board's folder.
6
+ // 1. Zip the board's site: every file in site/ except dot-files (.env,
7
+ // .DS_Store) and node_modules. Written to site.zip in the board's folder
8
+ // (not inside site/).
9
9
  // 2. Ask Surph for an upload address for it: boards/<slug>/site/site.zip on
10
10
  // S3, the slug from board.json.
11
11
  // 3. Upload site.zip there, then delete the local site.zip (it's kept when
@@ -14,7 +14,8 @@
14
14
  // 5. Upload board.json to boards/<slug>/board.json — last, so the live
15
15
  // board's definition only changes once its site is up.
16
16
  //
17
- // Node's built-ins only (zlib for compression, fetch for the upload; Node 18+).
17
+ // Node's built-ins only (zlib for compression, fetch for the upload). Needs
18
+ // Node.js 20 or later.
18
19
 
19
20
  const fs = require('fs');
20
21
  const path = require('path');
@@ -27,16 +28,16 @@ const RPC_URL = 'https://rpc.surph.ai';
27
28
  const SYNC_URL = 'https://e3lunom2hka3u6iiotrlzrcez40uwdnk.lambda-url.us-east-1.on.aws/';
28
29
  const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
29
30
 
30
- // Left out at the board's top level: the project's own files.
31
- const SKIP_AT_ROOT = new Set(['package.json', 'package-lock.json', 'README.md', 'board.json', ZIP_NAME]);
31
+ // The board's site: site/ in its folder (boards/<slug>/site/ when live).
32
+ const SITE = path.join(ROOT, 'site');
32
33
 
33
- // The files to deploy, as paths relative to the board's folder ('/'-separated).
34
- const siteFiles = (dir = ROOT, rel = '') => {
34
+ // The files to deploy, as paths relative to site/ ('/'-separated) — where
35
+ // they land under boards/<slug>/site/.
36
+ const siteFiles = (dir = SITE, rel = '') => {
35
37
  const out = [];
36
38
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
37
39
  const name = entry.name;
38
40
  if (name.startsWith('.') || name === 'node_modules') continue;
39
- if (!rel && SKIP_AT_ROOT.has(name)) continue;
40
41
  const relPath = rel ? `${rel}/${name}` : name;
41
42
  const full = path.join(dir, name);
42
43
  if (entry.isDirectory()) out.push(...siteFiles(full, relPath));
@@ -73,7 +74,7 @@ const zipFiles = (files) => {
73
74
  const central = [];
74
75
  let offset = 0;
75
76
  for (const rel of files) {
76
- const full = path.join(ROOT, ...rel.split('/'));
77
+ const full = path.join(SITE, ...rel.split('/'));
77
78
  const data = fs.readFileSync(full);
78
79
  const deflated = zlib.deflateRawSync(data, { level: 9 });
79
80
  const stored = deflated.length >= data.length;
@@ -202,12 +203,15 @@ const main = async () => {
202
203
  fail(`No board.json in ${ROOT}. Run this from the board's folder.`);
203
204
  }
204
205
  const slug = boardSlug();
206
+ if (!fs.existsSync(SITE) || !fs.statSync(SITE).isDirectory()) {
207
+ fail(`No site/ folder in ${ROOT}. The board's pages and assets go in site/ (site/index.html, site/assets/…).`);
208
+ }
205
209
  const files = siteFiles();
206
- if (!files.length) fail('Nothing to deploy: the board has no site files.');
210
+ if (!files.length) fail('Nothing to deploy: site/ has no files.');
207
211
 
208
212
  const zip = zipFiles(files);
209
213
  fs.writeFileSync(path.join(ROOT, ZIP_NAME), zip);
210
- console.log(`Zipped ${files.length} file${files.length === 1 ? '' : 's'} into ${ZIP_NAME} (${(zip.length / 1024).toFixed(1)} KB):`);
214
+ console.log(`Zipped ${files.length} file${files.length === 1 ? '' : 's'} from site/ into ${ZIP_NAME} (${(zip.length / 1024).toFixed(1)} KB):`);
211
215
  for (const file of files) console.log(` ${file}`);
212
216
 
213
217
  console.log(`\nDeploying the board "${slug}"…`);
@@ -224,7 +228,13 @@ const main = async () => {
224
228
  console.log(`Uploaded: ${boardUrl.split('?')[0]}`);
225
229
  };
226
230
 
227
- const run = () => main().catch((error) => fail(`Deploy failed: ${error.message}`));
231
+ const MIN_NODE = 20;
232
+
233
+ const run = () => {
234
+ const major = Number(process.versions.node.split('.')[0]);
235
+ if (major < MIN_NODE) fail(`Deploying a board needs Node.js ${MIN_NODE} or later; this is ${process.version}. Install a newer Node.js (https://nodejs.org) and try again.`);
236
+ return main().catch((error) => fail(`Deploy failed: ${error.message}`));
237
+ };
228
238
 
229
239
  // `surph board-deploy` calls run(); running this file directly runs it too.
230
240
  if (require.main === module) run();
package/board/server.js CHANGED
@@ -1,12 +1,18 @@
1
1
  #!/usr/bin/env node
2
- // The board's local dev server (`npm run dev` → `surph-board-dev`): serves
3
- // the board folder it's run from (the current directory) at
2
+ // The board's local dev server (`npm run dev` → `surph board-server`):
3
+ // serves the board folder it's run from (the current directory) at
4
4
  // http://localhost:4180 for Surph's dev mode (Settings → Developer → Run
5
- // Surph as a local board). Node's built-ins only.
5
+ // Surph as a local board). Node's built-ins only; needs Node.js 20 or later.
6
6
  //
7
- // GET /<file> This folder's files, never cached, with CORS so
8
- // Surph (another address) can read board.json.
9
- // GET /__surph/assets The image files in assets/ (the logo picker).
7
+ // The folder mirrors a live board on S3 (boards/<slug>/): board.json at the
8
+ // top, the pages and assets in site/. Only those two are served — the
9
+ // project's own files (README.md, AGENTS.md, package.json…) aren't.
10
+ //
11
+ // GET /board.json The board, with CORS so Surph (another address)
12
+ // can read it. Never cached, like everything here.
13
+ // GET /site/<file> The board's pages and assets (site/ in this folder).
14
+ // GET / Redirects to /site/ (its index.html).
15
+ // GET /__surph/assets The image files in site/assets/ (the logo picker).
10
16
  // PUT /board.json Surph's Local board panel saving the board. Only
11
17
  // from Surph's own addresses, only this one file,
12
18
  // only a JSON object with a config.pages list.
@@ -70,14 +76,20 @@ const cors = (req) => {
70
76
  return headers;
71
77
  };
72
78
 
73
- // A path inside this folder, or null (no way out of it, no dot-files).
79
+ // The board's site: site/ in this folder (boards/<slug>/site/ when live).
80
+ const SITE = path.join(ROOT, 'site');
81
+
82
+ // A /site/… path → its file inside site/, or null (no way out of it, no
83
+ // dot-files, no node_modules).
74
84
  const inside = (urlPath) => {
75
85
  let rel;
76
86
  try { rel = decodeURIComponent(urlPath.split('?')[0]); } catch { return null; }
87
+ if (rel !== '/site' && !rel.startsWith('/site/')) return null;
88
+ rel = rel.slice('/site'.length) || '/';
77
89
  if (rel.endsWith('/')) rel += 'index.html';
78
- const full = path.normalize(path.join(ROOT, rel));
79
- if (full !== ROOT && !full.startsWith(ROOT + path.sep)) return null;
80
- if (path.relative(ROOT, full).split(path.sep).some((part) => part.startsWith('.') || part === 'node_modules')) return null;
90
+ const full = path.normalize(path.join(SITE, rel));
91
+ if (!full.startsWith(SITE + path.sep)) return null;
92
+ if (path.relative(SITE, full).split(path.sep).some((part) => part.startsWith('.') || part === 'node_modules')) return null;
81
93
  return full;
82
94
  };
83
95
 
@@ -138,8 +150,22 @@ const handle = async (req, res) => {
138
150
  send(res, 405, 'Method not allowed', headers);
139
151
  return;
140
152
  }
141
- if (urlPath.split('?')[0] === '/__surph/assets') {
142
- const dir = path.join(ROOT, 'assets');
153
+ const pathname = urlPath.split('?')[0];
154
+ if (pathname === '/') {
155
+ send(res, 302, '', { ...headers, Location: '/site/' });
156
+ return;
157
+ }
158
+ if (pathname === '/board.json') {
159
+ const file = path.join(ROOT, 'board.json');
160
+ if (!fs.existsSync(file)) {
161
+ send(res, 404, 'No board.json in this folder.', headers);
162
+ return;
163
+ }
164
+ send(res, 200, req.method === 'HEAD' ? '' : fs.readFileSync(file), { ...headers, 'Content-Type': TYPES['.json'] });
165
+ return;
166
+ }
167
+ if (pathname === '/__surph/assets') {
168
+ const dir = path.join(SITE, 'assets');
143
169
  const files = fs.existsSync(dir) ? fs.readdirSync(dir).filter((name) => IMAGE.test(name)).sort() : [];
144
170
  send(res, 200, JSON.stringify({ images: files }), { ...headers, 'Content-Type': 'application/json' });
145
171
  return;
@@ -158,7 +184,14 @@ const handle = async (req, res) => {
158
184
 
159
185
  // Start serving the current folder. localhost may be reached over IPv4 or
160
186
  // IPv6: listen on both loopbacks.
187
+ const MIN_NODE = 20;
188
+
161
189
  const start = () => {
190
+ const major = Number(process.versions.node.split('.')[0]);
191
+ if (major < MIN_NODE) {
192
+ console.error(`The board dev server needs Node.js ${MIN_NODE} or later; this is ${process.version}. Install a newer Node.js (https://nodejs.org) and try again.`);
193
+ process.exit(1);
194
+ }
162
195
  http.createServer(handle)
163
196
  .on('error', (error) => {
164
197
  console.error(error.code === 'EADDRINUSE'
@@ -167,7 +200,7 @@ const start = () => {
167
200
  process.exit(1);
168
201
  })
169
202
  .listen(PORT, '127.0.0.1', () => {
170
- console.log(`Board dev server: http://localhost:${PORT} (serving ${ROOT})`);
203
+ console.log(`Board dev server: http://localhost:${PORT} (serving ${ROOT}: board.json and site/)`);
171
204
  console.log('In Surph: Settings → Developer → Run Surph as a local board.');
172
205
  });
173
206
  http.createServer(handle).listen(PORT, '::1').on('error', () => { /* no IPv6 loopback */ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surph_ai/cli",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "./types.d.ts",
@@ -0,0 +1,32 @@
1
+ # Surph board: notes for coding agents
2
+
3
+ This folder is a **Surph board**: static web pages that Surph shows in its canvas, beside the Surph chat. The person you're working with may not be technical. Explain steps in plain words, and say what a command does before you run it.
4
+
5
+ **`README.md` is the full reference.** Read it before writing or changing a page: it lists every operation Surph answers, the item shape, the rules for pages, and how `board.json` works. This file covers setup and the working loop.
6
+
7
+ ## Setup: Node.js
8
+
9
+ Previewing and publishing the board need **Node.js 20 or later**. The pages themselves don't.
10
+
11
+ 1. Check with `node -v`.
12
+ 2. If it's missing or older than 20, tell the person that Node.js is needed to preview the board inside Surph and to publish it, and offer to install it. Install only with their OK.
13
+ - **Mac:** prefer a user-level install that needs no admin password, such as [fnm](https://github.com/Schniz/fnm) (`curl -fsSL https://fnm.vercel.app/install | bash`, then `fnm install --lts`). Homebrew (`brew install node`) is fine if Homebrew is already installed.
14
+ - **Windows:** `winget install OpenJS.NodeJS.LTS` (Windows may ask the person to approve once).
15
+ - **Linux:** the distribution's package manager, or fnm as on Mac.
16
+ 3. A fresh install may not be visible in the current terminal. If `node -v` still fails afterwards, open a new terminal session (or load the shell profile) and check again.
17
+ 4. If an install isn't possible (declined, or blocked on a work computer), point them to the installer at https://nodejs.org and wait for them.
18
+
19
+ ## The working loop
20
+
21
+ 1. **Preview:** run `npm run dev` from this folder and leave it running. It serves the board at `http://localhost:4180`: `board.json`, and the pages in `site/` under `/site/` (nothing to install; it uses the Surph CLI through `npx`).
22
+ 2. **See it in Surph:** ask the person to open Surph, go to **Settings → Developer**, turn on **Run Surph as a local board**, and keep the address `http://localhost:4180`. Surph reloads as this board.
23
+ 3. **Configure the board** (name, contexts, tools, instructions): the person can click the **DEV** label in Surph's canvas header to open the Local board panel, which saves `board.json` for you. You can also edit `board.json` directly; the README describes every field.
24
+ 4. **Change pages:** the pages and their assets live in `site/` (`site/index.html`, `site/assets/…`). Edit them there and keep `board.json`'s `config.pages` in step (every page listed, `shows` written as plain facts, `ops` matching the page's `host.rpc` calls). The person reloads Surph to see the change.
25
+ 5. **Publish:** `npm run deploy`. **Only when the person asks you to in this conversation.** It replaces the live board for everyone who uses it. Check the board in dev mode first.
26
+
27
+ ## What to keep in mind
28
+
29
+ - Pages talk to Surph only through the Surph SDK (`host.rpc(…)`) and only with the operations the README lists. Don't invent operations or fields; if a page needs data Surph doesn't offer, say so.
30
+ - Write data from Surph with `textContent`, never `innerHTML`.
31
+ - Everything in `site/` (except dot-files) gets published by `npm run deploy`, and nothing outside it except `board.json`. Keep drafts, notes and secrets out of `site/`.
32
+ - Before handing off, go through the README's "Checklist before handing off a change".
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -16,18 +16,23 @@ This project is a **Surph board**: a set of web pages that Surph shows in its **
16
16
  ## Project layout
17
17
 
18
18
  ```
19
- index.html The board's home page (edit it; it starts as an intro to this setup)
20
- <page>.html Each further page sits next to it (items.html, events.html, …)
21
- assets/
22
- css/site.css Styles shared by every page in the board
23
- js/ Scripts shared by the board's pages (optional)
19
+ site/ The board's site: everything here is published, nothing else is
20
+ index.html The board's home page (edit it; it starts as an intro to this setup)
21
+ <page>.html Each further page sits next to it (items.html, events.html, …)
22
+ assets/
23
+ css/site.css Styles shared by every page in the board
24
+ js/ Scripts shared by the board's pages (optional)
24
25
  board.json The board itself: name, contexts, instructions, and its pages (see "Registering pages")
25
- package.json `npm run dev` serves this folder locally; `npm run deploy` publishes the board (see "Deploying")
26
+ package.json `npm run dev` serves the board locally; `npm run deploy` publishes it (see "Deploying")
26
27
  README.md This file
28
+ AGENTS.md, CLAUDE.md Notes for coding agents
27
29
  ```
28
30
 
29
- - **Pages sit at the top of the folder.** A page's registry entry names its file without a folder, so a page can't live in a subfolder.
30
- - **Everything else a page loads, apart from the SDK, goes under `assets/`**, linked with relative paths (`assets/css/site.css`, `./items.html`).
31
+ The folder mirrors the live board in Surph's storage: `board.json` at the top, the pages and assets in `site/`.
32
+
33
+ - **Pages sit at the top of `site/`.** A page's registry entry names its file without a folder, so a page can't live in a subfolder of `site/`.
34
+ - **Everything else a page loads, apart from the SDK, goes under `site/assets/`**, linked with relative paths from the page (`assets/css/site.css`, `./items.html`).
35
+ - **Only `site/` is published.** Keep notes, drafts and anything else outside it.
31
36
 
32
37
  ### Shared styles: `assets/css/site.css`
33
38
 
@@ -71,7 +76,7 @@ Surph only shows pages that are registered with the board. The board itself is d
71
76
  | `slug` | The board's short id. |
72
77
  | `name` | The board's name, shown in Surph's header and returned by `board.get`. |
73
78
  | `description` | One line about the board. |
74
- | `logo` | An image file in `assets/` (just its name, e.g. `logo.png`), shown in Surph's header. |
79
+ | `logo` | An image file in `site/assets/` (just its name, e.g. `logo.png`), shown in Surph's header. |
75
80
  | `contexts` | Slugs of the Surph contexts the board draws on. Their items are what `board.get` and the chat's lookups return. They're live Surph contexts; find a context's slug in its address (`/c/<slug>`). |
76
81
  | `defaultContext` | The context the chat starts with; else the first in `contexts`. |
77
82
  | `tools` | Slugs of Surph tools the board's chat can use. Optional. |
@@ -92,7 +97,7 @@ A registry entry:
92
97
  | Field | Meaning |
93
98
  | --- | --- |
94
99
  | `name` | Short, stable label. The chat's model and saved conversations refer to the page by this name, and the canvas header shows it, so don't rename it casually. Unique within the board. |
95
- | `file` | The HTML file's name only, with no folder (`events.html`). It's found next to `board.json`: on your local server while you develop, and wherever the board's files are hosted when it's live. |
100
+ | `file` | The HTML file's name only, with no folder (`events.html`). It's found in `site/`: on your local server while you develop, and in the board's `site/` folder in Surph's storage when it's live. |
96
101
  | `url` | Instead of `file`, a page's full `https://` address when it lives somewhere else. |
97
102
  | `shows` | What the page displays, as plain facts. The chat's model reads this to judge when the page fits. Describe the content, not instructions: write "Every item in the board's newest 25, …", not "Use this when the user asks about…". |
98
103
  | `start` | Optional. Marks a page the canvas opens on before the chat starts: `"always"`, `"context"` (a chat that has a context) or `"no-context"` (a chat without one). A board needs at least one. A start page may route on load to another page with [`page.open`](#pageopen) and `replace: true`. |
@@ -396,33 +401,33 @@ A field marked "omitted" is missing from the object, not `null`. Every URL is `h
396
401
  - **One `ready()` per page load**, called after `connect`.
397
402
  - **Don't invent operations or fields.** If a page needs data the operations above don't provide, say so in your hand-off; a new operation needs a change in Surph first.
398
403
  - **Keep the registry in step.** A page added, renamed or removed, or with changed `rpc` calls, needs its registry entry in your hand-off.
399
- - **Plain, static files.** No build step is needed or expected. Scripts can live inline or under `assets/js/`; keep the SDK `<script>` tag as given.
404
+ - **Plain, static files.** No build step is needed or expected. Scripts can live inline or under `site/assets/js/`; keep the SDK `<script>` tag as given.
400
405
 
401
406
  ## Developing locally
402
407
 
403
- 1. Run `npm run dev` (nothing to install). It starts the board dev server from the Surph CLI (`npx @surph_ai/cli board-server`), which serves this folder at `http://localhost:4180` with caching off, lets Surph read it from another address, and lets Surph's Local board panel save `board.json`. It only listens on your own machine.
408
+ 1. Run `npm run dev` (nothing to install). It starts the board dev server from the Surph CLI (`npx @surph_ai/cli board-server`), which serves the board at `http://localhost:4180` (`board.json`, and `site/` under `/site/`) with caching off, lets Surph read it from another address, and lets Surph's Local board panel save `board.json`. It only listens on your own machine.
404
409
  2. Keep it running while you work; the terminal shows a line each time Surph saves `board.json`.
405
- 3. Open `http://localhost:4180/` to check a page loads without console errors. It shows its empty layout there (`hosted` is false); real data only appears inside the Surph canvas.
410
+ 3. Open `http://localhost:4180/site/` to check a page loads without console errors. It shows its empty layout there (`hosted` is false); real data only appears inside the Surph canvas.
406
411
  4. **Run Surph as your board with dev mode.** Keep `npm run dev` running. In Surph, open **Settings → Developer**, turn on **Run Surph as a local board**, and keep the address `http://localhost:4180`. Surph reloads as the board `board.json` describes: its name and logo in the header, its contexts and instructions in the chat, and its pages in the canvas (it opens on your start page, the Pages menu lists your pages, and the chat's model sees their `shows` lines). The status under the address says what loaded, or why it couldn't read `board.json`; until it loads, Surph runs as usual. A **DEV** label in the canvas header shows your board is loaded. While it's on, anything the chat saves goes to your own space in Surph, not to a board's shared space.
407
- 5. **Configure the board from Surph.** Click the **DEV** label to open the **Local board** panel: set the board's name, slug, description and logo (an image in `assets/`), pick its contexts (yours, or public ones by search) and which one the chat starts with, pick its tools, and write its instructions. The panel also lists the pages and flags a missing `shows` line or an operation the host doesn't answer. **Save and reload** writes `board.json` through the dev server and reloads Surph as the updated board. Pages and any other fields are kept as they are in the file; edit pages in `board.json` itself.
412
+ 5. **Configure the board from Surph.** Click the **DEV** label to open the **Local board** panel: set the board's name, slug, description and logo (an image in `site/assets/`), pick its contexts (yours, or public ones by search) and which one the chat starts with, pick its tools, and write its instructions. The panel also lists the pages and flags a missing `shows` line or an operation the host doesn't answer. **Save and reload** writes `board.json` through the dev server and reloads Surph as the updated board. Pages and any other fields are kept as they are in the file; edit pages in `board.json` itself.
408
413
  6. Dev mode affects only your browser. Turn it off in the same place to get Surph's own pages back.
409
414
  7. In Surph, keep your browser's DevTools open with **Network → Disable cache** checked, or the canvas may keep showing an older copy of a page after you change it. After editing `board.json` by hand, reload Surph so it reads the file again.
410
415
 
411
416
  ## Deploying
412
417
 
413
- `npm run deploy` (`npx @surph_ai/cli board-deploy`, from the Surph CLI) publishes the board to Surph. Run it from the board's folder. It needs Node 18 or later and nothing else installed. In order, it:
418
+ `npm run deploy` (`npx @surph_ai/cli board-deploy`, from the Surph CLI) publishes the board to Surph. Run it from the board's folder. It needs Node.js 20 or later and nothing else installed. In order, it:
414
419
 
415
420
  1. **Reads the board's `slug`** from `board.json` (lowercase letters, digits and dashes). The slug names the board's place in Surph; without a valid one it stops before doing anything.
416
- 2. **Zips the site** into `site.zip`: every file in the folder except `package.json`, `package-lock.json`, `README.md`, `board.json`, `node_modules`, dot-files (`.env`, `.git`, `.DS_Store`) and `site.zip` itself. It lists the files it zipped.
421
+ 2. **Zips the site** into `site.zip`: every file in `site/`, except dot-files (`.env`, `.DS_Store`) and `node_modules`. Nothing outside `site/` is included (`board.json` goes up on its own in step 5). It lists the files it zipped.
417
422
  3. **Uploads `site.zip`** to the board's `site/` folder in Surph's storage, then deletes the local `site.zip`. If the upload fails, the zip is kept to look at.
418
- 4. **Unzips it there**, so the pages and `assets/` sit in the board's `site/` folder, where each page's `file` is found.
423
+ 4. **Unzips it there**, so the board's `site/` folder in Surph's storage holds the same files as your `site/`, where each page's `file` is found.
419
424
  5. **Uploads `board.json`**, last, so the live board's definition only changes once its pages are up. If an earlier step fails, `board.json` isn't uploaded and the live board keeps its previous version.
420
425
 
421
426
  Each step prints what it did; a failure prints why and stops. After a deploy, Surph picks up the new `board.json` within about a minute.
422
427
 
423
428
  **For coding agents:**
424
429
  - **Deploying publishes the board to everyone who uses it.** Don't run `npm run deploy` unless the person asks you to in this conversation. Check the board with dev mode first (see [Developing locally](#developing-locally)).
425
- - **Everything in the folder outside the list above is uploaded.** Keep drafts, exports, archives and secrets out of the board's folder, or in dot-files, which are never deployed.
430
+ - **Everything in `site/` is uploaded.** Keep drafts, exports, archives and secrets out of `site/` (anywhere else in the board's folder is fine; dot-files are never deployed).
426
431
  - **A deploy replaces the live `board.json` with the local one.** Make sure `slug` is the board you mean to update: a deploy under another board's slug overwrites that board.
427
432
 
428
433
  ## Checklist before handing off a change
@@ -432,8 +437,8 @@ Each step prints what it did; a failure prints why and stops. After a deploy, Su
432
437
  - [ ] Data from Surph is written with `textContent`; links and images use only `http(s)` values.
433
438
  - [ ] Operations that act for the person run only from their click (a start page's `page.open` aside).
434
439
  - [ ] The SDK `<script>` tag is the v1 URL above, unchanged.
435
- - [ ] Every page links `assets/css/site.css?v=<n>`; if `site.css` changed, `<n>` is bumped in every page.
440
+ - [ ] Every page (in `site/`) links `assets/css/site.css?v=<n>`; if `site.css` changed, `<n>` is bumped in every page.
436
441
  - [ ] `board.json`'s `config.pages` lists every page: `name`, `file` (or `url`), `shows` written as facts, `ops` matching the file's `rpc` calls, and one `start` page.
437
- - [ ] Opened at `http://localhost:4180/`, every page loads without console errors.
442
+ - [ ] Opened at `http://localhost:4180/site/<page>`, every page loads without console errors.
438
443
  - [ ] Checked in Surph with dev mode on: the canvas opens on the start page and each page paints with real data.
439
444
  - [ ] Not deployed unless the person asked for it; if deployed, `npm run deploy` finished without errors and its file list holds only the board's site.
@@ -9,5 +9,8 @@
9
9
  "keywords": [],
10
10
  "author": "",
11
11
  "license": "ISC",
12
- "type": "commonjs"
12
+ "type": "commonjs",
13
+ "engines": {
14
+ "node": ">=20"
15
+ }
13
16
  }
@@ -68,7 +68,7 @@
68
68
  </head>
69
69
  <body>
70
70
  <h1>your surph board</h1>
71
- <p class="lead">This page is part of a Surph board. Surph shows it in the <strong>canvas</strong>, the panel beside its chat, and answers its requests for data. Edit <code>index.html</code> to make it your board's home page.</p>
71
+ <p class="lead">This page is part of a Surph board. Surph shows it in the <strong>canvas</strong>, the panel beside its chat, and answers its requests for data. Edit <code>site/index.html</code> to make it your board's home page.</p>
72
72
 
73
73
  <div class="status" id="status">
74
74
  <span class="dot" aria-hidden="true"></span>
@@ -90,9 +90,9 @@
90
90
  <h2>Next steps</h2>
91
91
  <ol class="steps">
92
92
  <li><strong>Read <code>README.md</code>.</strong> It lists every operation the host answers, the item shape, and the rules a page follows. Point your coding agent at it.</li>
93
- <li><strong>Run it locally:</strong> <code>npm run dev</code> serves this folder at <code>http://localhost:4180</code> with caching off.</li>
94
- <li><strong>Add pages</strong> next to this one (<code>items.html</code>, <code>events.html</code>…), and list each in <code>board.json</code> with its <code>name</code>, <code>file</code>, <code>shows</code> and the <code>ops</code> it calls. Shared styles go in <code>assets/css/site.css</code>.</li>
95
- <li><strong>See them in Surph:</strong> open <strong>Settings → Developer</strong> and turn on dev mode for <code>http://localhost:4180</code>. The canvas then shows this folder's pages, with real data.</li>
93
+ <li><strong>Run it locally:</strong> <code>npm run dev</code> serves the board at <code>http://localhost:4180</code> (this page is <code>/site/</code>) with caching off.</li>
94
+ <li><strong>Add pages</strong> in <code>site/</code>, next to this one (<code>items.html</code>, <code>events.html</code>…), and list each in <code>board.json</code> with its <code>name</code>, <code>file</code>, <code>shows</code> and the <code>ops</code> it calls. Shared styles go in <code>site/assets/css/site.css</code>.</li>
95
+ <li><strong>See them in Surph:</strong> open <strong>Settings → Developer</strong> and turn on dev mode for <code>http://localhost:4180</code>. The canvas then shows the board's pages, with real data.</li>
96
96
  </ol>
97
97
 
98
98
  <script src="https://surph.s3.us-east-1.amazonaws.com/admin/desktop/js/wave-sdk/v1/surph-wave.js"></script>
package/scaffold/index.js CHANGED
@@ -270,15 +270,15 @@ const scaffoldBoard = async (name) => {
270
270
  utils.write('package.json', JSON.stringify(pkgJson, null, 2) + '\n')
271
271
 
272
272
  shell.cp('-R', path.join(__dirname, `board/README.md`), 'README.md')
273
- shell.cp('-R', path.join(__dirname, `board/index.html`), 'index.html')
273
+ shell.cp('-R', path.join(__dirname, `board/AGENTS.md`), 'AGENTS.md')
274
+ shell.cp('-R', path.join(__dirname, `board/CLAUDE.md`), 'CLAUDE.md')
274
275
  shell.cp('-R', path.join(__dirname, `board/board.json`), 'board.json')
275
276
 
276
- shell.mkdir('-p', 'assets')
277
- shell.mkdir('-p', 'assets/css')
278
- shell.mkdir('-p', 'assets/js')
279
- shell.cp('-R', path.join(__dirname, `board/assets/css/site.css`), 'assets/css/site.css')
280
-
281
- // shell.cp('-R', path.join(__dirname, `board/assets/js/wave-sdk/v1/surph-wave.js`), 'assets/js/surph-wave.js')
277
+ // The board's pages and assets go in site/ (boards/<slug>/site/ when live).
278
+ shell.mkdir('-p', 'site/assets/css')
279
+ shell.mkdir('-p', 'site/assets/js')
280
+ shell.cp('-R', path.join(__dirname, `board/site/index.html`), 'site/index.html')
281
+ shell.cp('-R', path.join(__dirname, `board/site/assets/css/site.css`), 'site/assets/css/site.css')
282
282
 
283
283
  await progress.done()
284
284