@surph_ai/cli 0.0.35 → 0.0.37
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 +24 -16
- package/board/server.js +35 -12
- package/package.json +1 -1
- package/scaffold/board/AGENTS.md +20 -4
- package/scaffold/board/README.md +45 -25
- package/scaffold/board/board.json +1 -1
- package/scaffold/board/{index.html → site/index.html} +4 -4
- package/scaffold/index.js +15 -9
- /package/scaffold/board/{assets → site/assets}/css/site.css +0 -0
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
|
|
3
|
-
// the board's folder.
|
|
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
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
|
@@ -26,18 +26,20 @@ const ZIP_NAME = 'site.zip';
|
|
|
26
26
|
const RPC_URL = 'https://rpc.surph.ai';
|
|
27
27
|
// Unzips an uploaded archive in place on S3 (the `sync` action).
|
|
28
28
|
const SYNC_URL = 'https://e3lunom2hka3u6iiotrlzrcez40uwdnk.lambda-url.us-east-1.on.aws/';
|
|
29
|
-
|
|
29
|
+
// Also the board's host name (<slug>.surph-boards.com), so at most 63 characters.
|
|
30
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
|
|
31
|
+
const PLACEHOLDER_SLUG = 'my-board';
|
|
30
32
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
33
|
+
// The board's site: site/ in its folder (boards/<slug>/site/ when live).
|
|
34
|
+
const SITE = path.join(ROOT, 'site');
|
|
33
35
|
|
|
34
|
-
// The files to deploy, as paths relative to
|
|
35
|
-
|
|
36
|
+
// The files to deploy, as paths relative to site/ ('/'-separated) — where
|
|
37
|
+
// they land under boards/<slug>/site/.
|
|
38
|
+
const siteFiles = (dir = SITE, rel = '') => {
|
|
36
39
|
const out = [];
|
|
37
40
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
38
41
|
const name = entry.name;
|
|
39
42
|
if (name.startsWith('.') || name === 'node_modules') continue;
|
|
40
|
-
if (!rel && SKIP_AT_ROOT.has(name)) continue;
|
|
41
43
|
const relPath = rel ? `${rel}/${name}` : name;
|
|
42
44
|
const full = path.join(dir, name);
|
|
43
45
|
if (entry.isDirectory()) out.push(...siteFiles(full, relPath));
|
|
@@ -74,7 +76,7 @@ const zipFiles = (files) => {
|
|
|
74
76
|
const central = [];
|
|
75
77
|
let offset = 0;
|
|
76
78
|
for (const rel of files) {
|
|
77
|
-
const full = path.join(
|
|
79
|
+
const full = path.join(SITE, ...rel.split('/'));
|
|
78
80
|
const data = fs.readFileSync(full);
|
|
79
81
|
const deflated = zlib.deflateRawSync(data, { level: 9 });
|
|
80
82
|
const stored = deflated.length >= data.length;
|
|
@@ -139,7 +141,10 @@ const boardSlug = () => {
|
|
|
139
141
|
fail(`board.json isn't valid JSON: ${error.message}`);
|
|
140
142
|
}
|
|
141
143
|
const slug = typeof board?.slug === 'string' ? board.slug.trim() : '';
|
|
142
|
-
if (!SLUG_RE.test(slug)) fail('board.json needs a "slug": lowercase letters, digits and dashes (e.g. "
|
|
144
|
+
if (!SLUG_RE.test(slug)) fail('board.json needs a "slug": lowercase letters, digits and dashes, at most 63 characters (e.g. "breakledger").');
|
|
145
|
+
// The template's placeholder: publishing under it would overwrite whatever
|
|
146
|
+
// else was published as "my-board".
|
|
147
|
+
if (slug === PLACEHOLDER_SLUG) fail(`board.json's "slug" is still the placeholder "${PLACEHOLDER_SLUG}". Set it to this board's own slug (from its name, e.g. "breakledger"), then deploy.`);
|
|
143
148
|
return slug;
|
|
144
149
|
};
|
|
145
150
|
|
|
@@ -203,12 +208,15 @@ const main = async () => {
|
|
|
203
208
|
fail(`No board.json in ${ROOT}. Run this from the board's folder.`);
|
|
204
209
|
}
|
|
205
210
|
const slug = boardSlug();
|
|
211
|
+
if (!fs.existsSync(SITE) || !fs.statSync(SITE).isDirectory()) {
|
|
212
|
+
fail(`No site/ folder in ${ROOT}. The board's pages and assets go in site/ (site/index.html, site/assets/…).`);
|
|
213
|
+
}
|
|
206
214
|
const files = siteFiles();
|
|
207
|
-
if (!files.length) fail('Nothing to deploy:
|
|
215
|
+
if (!files.length) fail('Nothing to deploy: site/ has no files.');
|
|
208
216
|
|
|
209
217
|
const zip = zipFiles(files);
|
|
210
218
|
fs.writeFileSync(path.join(ROOT, ZIP_NAME), zip);
|
|
211
|
-
console.log(`Zipped ${files.length} file${files.length === 1 ? '' : 's'} into ${ZIP_NAME} (${(zip.length / 1024).toFixed(1)} KB):`);
|
|
219
|
+
console.log(`Zipped ${files.length} file${files.length === 1 ? '' : 's'} from site/ into ${ZIP_NAME} (${(zip.length / 1024).toFixed(1)} KB):`);
|
|
212
220
|
for (const file of files) console.log(` ${file}`);
|
|
213
221
|
|
|
214
222
|
console.log(`\nDeploying the board "${slug}"…`);
|
package/board/server.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The board's local dev server (`npm run dev` → `surph
|
|
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
5
|
// Surph as a local board). Node's built-ins only; needs Node.js 20 or later.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// The folder holds board.json at the top and the board's pages and assets in
|
|
8
|
+
// site/. site/ is the server's base: http://localhost:4180/ is
|
|
9
|
+
// site/index.html. Only site/ and board.json are served — the project's own
|
|
10
|
+
// files (README.md, AGENTS.md, package.json…) aren't.
|
|
11
|
+
//
|
|
12
|
+
// GET /board.json The board (board.json at the folder's top), with
|
|
13
|
+
// CORS so Surph (another address) can read it.
|
|
14
|
+
// Never cached, like everything here.
|
|
15
|
+
// GET /<file> The board's pages and assets, from site/ (/ is
|
|
16
|
+
// site/index.html).
|
|
17
|
+
// GET /__surph/assets The image files in site/assets/ (the icon picker).
|
|
10
18
|
// PUT /board.json Surph's Local board panel saving the board. Only
|
|
11
19
|
// from Surph's own addresses, only this one file,
|
|
12
20
|
// only a JSON object with a config.pages list.
|
|
@@ -70,14 +78,19 @@ const cors = (req) => {
|
|
|
70
78
|
return headers;
|
|
71
79
|
};
|
|
72
80
|
|
|
73
|
-
//
|
|
81
|
+
// The board's site: site/ in this folder, served as the base (it becomes
|
|
82
|
+
// boards/<slug>/site/ when live).
|
|
83
|
+
const SITE = path.join(ROOT, 'site');
|
|
84
|
+
|
|
85
|
+
// A path → its file inside site/, or null (no way out of it, no dot-files,
|
|
86
|
+
// no node_modules).
|
|
74
87
|
const inside = (urlPath) => {
|
|
75
88
|
let rel;
|
|
76
89
|
try { rel = decodeURIComponent(urlPath.split('?')[0]); } catch { return null; }
|
|
77
90
|
if (rel.endsWith('/')) rel += 'index.html';
|
|
78
|
-
const full = path.normalize(path.join(
|
|
79
|
-
if (
|
|
80
|
-
if (path.relative(
|
|
91
|
+
const full = path.normalize(path.join(SITE, rel));
|
|
92
|
+
if (!full.startsWith(SITE + path.sep)) return null;
|
|
93
|
+
if (path.relative(SITE, full).split(path.sep).some((part) => part.startsWith('.') || part === 'node_modules')) return null;
|
|
81
94
|
return full;
|
|
82
95
|
};
|
|
83
96
|
|
|
@@ -138,8 +151,18 @@ const handle = async (req, res) => {
|
|
|
138
151
|
send(res, 405, 'Method not allowed', headers);
|
|
139
152
|
return;
|
|
140
153
|
}
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
const pathname = urlPath.split('?')[0];
|
|
155
|
+
if (pathname === '/board.json') {
|
|
156
|
+
const file = path.join(ROOT, 'board.json');
|
|
157
|
+
if (!fs.existsSync(file)) {
|
|
158
|
+
send(res, 404, 'No board.json in this folder.', headers);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
send(res, 200, req.method === 'HEAD' ? '' : fs.readFileSync(file), { ...headers, 'Content-Type': TYPES['.json'] });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (pathname === '/__surph/assets') {
|
|
165
|
+
const dir = path.join(SITE, 'assets');
|
|
143
166
|
const files = fs.existsSync(dir) ? fs.readdirSync(dir).filter((name) => IMAGE.test(name)).sort() : [];
|
|
144
167
|
send(res, 200, JSON.stringify({ images: files }), { ...headers, 'Content-Type': 'application/json' });
|
|
145
168
|
return;
|
|
@@ -174,7 +197,7 @@ const start = () => {
|
|
|
174
197
|
process.exit(1);
|
|
175
198
|
})
|
|
176
199
|
.listen(PORT, '127.0.0.1', () => {
|
|
177
|
-
console.log(`Board dev server: http://localhost:${PORT} (serving ${ROOT})`);
|
|
200
|
+
console.log(`Board dev server: http://localhost:${PORT} (serving ${ROOT}: board.json and site/)`);
|
|
178
201
|
console.log('In Surph: Settings → Developer → Run Surph as a local board.');
|
|
179
202
|
});
|
|
180
203
|
http.createServer(handle).listen(PORT, '::1').on('error', () => { /* no IPv6 loopback */ });
|
package/package.json
CHANGED
package/scaffold/board/AGENTS.md
CHANGED
|
@@ -16,17 +16,33 @@ Previewing and publishing the board need **Node.js 20 or later**. The pages them
|
|
|
16
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
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
18
|
|
|
19
|
+
## The board's name and slug
|
|
20
|
+
|
|
21
|
+
`board.json` has two identifiers, and they do different jobs:
|
|
22
|
+
|
|
23
|
+
- **`name`** is the label people see (Surph's header, `board.get`). Change it whenever the person asks.
|
|
24
|
+
- **`slug`** is the board's identity in Surph: it names where the board is published (`boards/<slug>/`) and its live address (`https://<slug>.surph-boards.com`), and it's shared across everyone on Surph, so it must be unique. Lowercase letters, digits and dashes only, at most 63 characters.
|
|
25
|
+
|
|
26
|
+
Keep them in step on a board that hasn't been published yet. The person won't think about the slug, so you do:
|
|
27
|
+
|
|
28
|
+
1. **Whenever you set or change the board's name on an unpublished board, set `slug` from it in the same edit:** lowercase, spaces and punctuation turned into single dashes (BreakLedger → `breakledger`, "DC Tech Events" → `dc-tech-events`). Tell the person the slug you chose, in a word: it's the board's address (`https://<slug>.surph-boards.com`).
|
|
29
|
+
2. **A new board's slug starts from its folder name** (`surph board break-ledger` → `break-ledger`). Older boards may still have the placeholder `my-board`: replace it as soon as the board has a name. `npm run deploy` refuses to publish with `my-board`.
|
|
30
|
+
3. **Check the slug is free before you settle on it:** `curl -s -o /dev/null -w "%{http_code}" https://surph.s3.us-east-1.amazonaws.com/boards/<slug>/board.json`. `200` means a board with that slug already exists. If this folder never published it, it's someone else's: pick another (add a short suffix, e.g. `breakledger-dk`) and say so. Anything else (403 or 404) means it's free.
|
|
31
|
+
4. **Is this board published?** Treat it as published if the person says so, or if you ran `npm run deploy` for it. If unsure, ask.
|
|
32
|
+
5. **Once the board is published, don't change `slug`** unless the person asks for a new address. A new slug publishes a second, separate board; the old one stays live under the old slug. Renaming a published board only changes `name`.
|
|
33
|
+
|
|
19
34
|
## The working loop
|
|
20
35
|
|
|
21
|
-
1. **Preview:** run `npm run dev` from this folder and leave it running. It serves the board at `http://localhost:4180` (nothing to install; it uses the Surph CLI through `npx`).
|
|
36
|
+
1. **Preview:** run `npm run dev` from this folder and leave it running. It serves the board at `http://localhost:4180`: the pages in `site/` as its base (`/` is `site/index.html`), plus `/board.json` (nothing to install; it uses the Surph CLI through `npx`).
|
|
22
37
|
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
38
|
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:**
|
|
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.
|
|
39
|
+
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.
|
|
40
|
+
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, and check `slug` first (see above): not `my-board`, and either free or this board's own. Once deployed, the site is live at `https://<slug>.surph-boards.com/`.
|
|
26
41
|
|
|
27
42
|
## What to keep in mind
|
|
28
43
|
|
|
29
44
|
- 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
45
|
- Write data from Surph with `textContent`, never `innerHTML`.
|
|
31
|
-
-
|
|
46
|
+
- `site/` is served at the root of the board's address, locally (`http://localhost:4180/`) and live (`https://<slug>.surph-boards.com/`), so paths like `/assets/…` and app routes work the same in both. It's static: there's no server for `/api/…` calls. The README's "Where the site is served" has the details.
|
|
47
|
+
- 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
48
|
- Before handing off, go through the README's "Checklist before handing off a change".
|
package/scaffold/board/README.md
CHANGED
|
@@ -16,18 +16,38 @@ 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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
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/`** (`assets/css/site.css`, `assets/js/…`).
|
|
35
|
+
- **Only `site/` is published.** Keep notes, drafts and anything else outside it.
|
|
36
|
+
|
|
37
|
+
### Where the site is served
|
|
38
|
+
|
|
39
|
+
`site/` is always served at the root of the board's own address, so `/` is `site/index.html`:
|
|
40
|
+
|
|
41
|
+
| | Address |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| Developing (`npm run dev`) | `http://localhost:4180/` |
|
|
44
|
+
| Live, once deployed | `https://<slug>.surph-boards.com/` (for example `https://breakledger.surph-boards.com/`) |
|
|
45
|
+
|
|
46
|
+
Surph's canvas loads the pages from that address too, and the live address also works on its own in a browser tab. What follows from it:
|
|
47
|
+
|
|
48
|
+
- **Root paths and relative paths both work** (`/assets/css/site.css` or `assets/css/site.css`), the same locally and live.
|
|
49
|
+
- **App routes work.** A page load at an address with no file extension (`/account`, a reload, a shared link) gets `index.html`, so a single-page app with its own router (React Router, Vite's default build) works as it does locally.
|
|
50
|
+
- **The site is static: there's no server behind it.** A script's call to the site's own address that isn't a file (`fetch('/api/…')`) fails. Data comes from Surph through the SDK; a page that needs another server calls it by its full `https://` address, and that server has to allow it (CORS).
|
|
31
51
|
|
|
32
52
|
### Shared styles: `assets/css/site.css`
|
|
33
53
|
|
|
@@ -53,11 +73,11 @@ Surph only shows pages that are registered with the board. The board itself is d
|
|
|
53
73
|
"slug": "my-board",
|
|
54
74
|
"name": "My Board",
|
|
55
75
|
"description": "What the board is about.",
|
|
56
|
-
"logo": "logo.png",
|
|
57
76
|
"contexts": ["<context slug>"],
|
|
58
77
|
"defaultContext": "<context slug>",
|
|
59
78
|
"tools": [],
|
|
60
79
|
"config": {
|
|
80
|
+
"icon": "assets/img/icon.png",
|
|
61
81
|
"instructions": "How the board's chat should behave: its tone, what it helps with.",
|
|
62
82
|
"pages": [
|
|
63
83
|
{ "name": "home", "file": "index.html", "shows": "…", "start": "always", "ops": ["board.get"] }
|
|
@@ -68,15 +88,15 @@ Surph only shows pages that are registered with the board. The board itself is d
|
|
|
68
88
|
|
|
69
89
|
| Field | Meaning |
|
|
70
90
|
| --- | --- |
|
|
71
|
-
| `slug` | The board's
|
|
91
|
+
| `slug` | The board's identity in Surph: where it's published (`boards/<slug>/`) and its live address (`https://<slug>.surph-boards.com`). Unique across Surph; lowercase letters, digits and dashes. A new board's slug starts from its folder name; when the board gets its real name, set the slug from that before publishing (BreakLedger → `breakledger`), and don't change it after (a new slug publishes a separate board). The deploy refuses the old placeholder `my-board`. See `AGENTS.md`, "The board's name and slug". |
|
|
72
92
|
| `name` | The board's name, shown in Surph's header and returned by `board.get`. |
|
|
73
|
-
| `description` |
|
|
74
|
-
| `logo` | An image file in `assets/` (just its name, e.g. `logo.png`), shown in Surph's header. |
|
|
93
|
+
| `description` | The board's opening line in the chat: what a visitor sees first on the board's home, shown as written. Write it to the visitor ("Track every Whatnot break: ask me about spend, sales and ROI."). Without one, the chat greets from the board's contexts, or says "Welcome to <name>!" when it has none. |
|
|
75
94
|
| `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
95
|
| `defaultContext` | The context the chat starts with; else the first in `contexts`. |
|
|
77
96
|
| `tools` | Slugs of Surph tools the board's chat can use. Optional. |
|
|
78
97
|
| `config.instructions` | Instructions from the board's owner for the board's chat: tone, behavior, what it helps with. On a board they take the place of each context's own instructions. |
|
|
79
98
|
| `config.pages` | The board's pages, one registry entry each (below). |
|
|
99
|
+
| `config.icon` | Optional. The board's icon: a file in `site/`, by its path there (`apple-touch-icon.png`, `assets/img/icon.png`). Surph shows it beside the board's name: on each of the chat's replies ("@BreakLedger") and in the header. A small square image works best. |
|
|
80
100
|
|
|
81
101
|
A registry entry:
|
|
82
102
|
|
|
@@ -92,7 +112,7 @@ A registry entry:
|
|
|
92
112
|
| Field | Meaning |
|
|
93
113
|
| --- | --- |
|
|
94
114
|
| `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
|
|
115
|
+
| `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
116
|
| `url` | Instead of `file`, a page's full `https://` address when it lives somewhere else. |
|
|
97
117
|
| `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
118
|
| `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,15 +416,15 @@ A field marked "omitted" is missing from the object, not `null`. Every URL is `h
|
|
|
396
416
|
- **One `ready()` per page load**, called after `connect`.
|
|
397
417
|
- **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
418
|
- **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.
|
|
419
|
+
- **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
420
|
|
|
401
421
|
## Developing locally
|
|
402
422
|
|
|
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
|
|
423
|
+
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` (the pages in `site/` as its base, plus `/board.json`) 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
424
|
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.
|
|
406
|
-
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
|
|
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
|
|
425
|
+
3. Open `http://localhost:4180/` (your `site/index.html`) 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.
|
|
426
|
+
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 icon 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.
|
|
427
|
+
5. **Configure the board from Surph.** Click the **DEV** label to open the **Local board** panel: set the board's name, slug, description and icon (an image in `site/`), 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
428
|
6. Dev mode affects only your browser. Turn it off in the same place to get Surph's own pages back.
|
|
409
429
|
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
430
|
|
|
@@ -412,17 +432,17 @@ A field marked "omitted" is missing from the object, not `null`. Every URL is `h
|
|
|
412
432
|
|
|
413
433
|
`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
434
|
|
|
415
|
-
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
|
|
435
|
+
1. **Reads the board's `slug`** from `board.json` (lowercase letters, digits and dashes). The slug names the board's place in Surph, so it must be this board's own, not the placeholder `my-board` and not another board's (see the `slug` field above); without a valid one it stops before doing anything.
|
|
436
|
+
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
437
|
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
|
|
438
|
+
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
439
|
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
440
|
|
|
421
|
-
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.
|
|
441
|
+
Each step prints what it did; a failure prints why and stops. After a deploy, the site is live at `https://<slug>.surph-boards.com/`, and Surph picks up the new `board.json` within about a minute. A changed file can take a few minutes to replace the old copy there; reload to check.
|
|
422
442
|
|
|
423
443
|
**For coding agents:**
|
|
424
444
|
- **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
|
|
445
|
+
- **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
446
|
- **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
447
|
|
|
428
448
|
## Checklist before handing off a change
|
|
@@ -432,8 +452,8 @@ Each step prints what it did; a failure prints why and stops. After a deploy, Su
|
|
|
432
452
|
- [ ] Data from Surph is written with `textContent`; links and images use only `http(s)` values.
|
|
433
453
|
- [ ] Operations that act for the person run only from their click (a start page's `page.open` aside).
|
|
434
454
|
- [ ] 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.
|
|
455
|
+
- [ ] Every page (in `site/`) links `assets/css/site.css?v=<n>`; if `site.css` changed, `<n>` is bumped in every page.
|
|
436
456
|
- [ ] `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
|
|
457
|
+
- [ ] Opened at `http://localhost:4180/<page>`, every page loads without console errors.
|
|
438
458
|
- [ ] Checked in Surph with dev mode on: the canvas opens on the start page and each page paints with real data.
|
|
439
459
|
- [ ] 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.
|
|
@@ -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
|
|
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
|
|
93
|
+
<li><strong>Run it locally:</strong> <code>npm run dev</code> serves the board at <code>http://localhost:4180</code> (this page is its home) 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
|
@@ -272,15 +272,21 @@ const scaffoldBoard = async (name) => {
|
|
|
272
272
|
shell.cp('-R', path.join(__dirname, `board/README.md`), 'README.md')
|
|
273
273
|
shell.cp('-R', path.join(__dirname, `board/AGENTS.md`), 'AGENTS.md')
|
|
274
274
|
shell.cp('-R', path.join(__dirname, `board/CLAUDE.md`), 'CLAUDE.md')
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
275
|
+
// board.json gets the board's own slug and name from <name> (the folder
|
|
276
|
+
// it was created in), so a new board never carries the template's
|
|
277
|
+
// placeholder slug. The slug is where the board publishes in Surph.
|
|
278
|
+
const boardJson = JSON.parse(utils.readFile(path.join(__dirname, 'board/board.json')))
|
|
279
|
+
const folderName = path.basename(name)
|
|
280
|
+
const boardSlug = slugify(folderName, 0)
|
|
281
|
+
if (/^[a-z0-9][a-z0-9-]*$/.test(boardSlug)) boardJson.slug = boardSlug
|
|
282
|
+
boardJson.name = folderName
|
|
283
|
+
utils.write('board.json', JSON.stringify(boardJson, null, 2) + '\n')
|
|
284
|
+
|
|
285
|
+
// The board's pages and assets go in site/ (boards/<slug>/site/ when live).
|
|
286
|
+
shell.mkdir('-p', 'site/assets/css')
|
|
287
|
+
shell.mkdir('-p', 'site/assets/js')
|
|
288
|
+
shell.cp('-R', path.join(__dirname, `board/site/index.html`), 'site/index.html')
|
|
289
|
+
shell.cp('-R', path.join(__dirname, `board/site/assets/css/site.css`), 'site/assets/css/site.css')
|
|
284
290
|
|
|
285
291
|
await progress.done()
|
|
286
292
|
|
|
File without changes
|