Build and try out an edrys-Lite lab on your own computer before you share it with your students.
You write your lab in a file (lab.yml), edrys-dev opens it as a class in your browser, and every time you save the file the class updates in all open tabs. No account and no deploy needed while you work.
You need Node.js 20 or newer.
-
Make a folder for your lab and create a file
lab.ymlin it:name: My first lab meta: description: Trying out edrys-dev. modules: - url: https://edrys-labs.github.io/module-markdown-it/index.html config: content: | # Welcome Edit lab.yml, save it, and watch this text change. width: full height: tall
-
Start edrys-dev in that folder:
npx @edryslabs/edrys-dev lab.yml --open
Your browser opens the class. In the terminal you see two addresses:
edrys-dev: /home/you/my-lab/lab.yml http://localhost:3210/?dev class open, share link: http://localhost:3210/?/classroom/… students join at: https://edrys-labs.github.io/?/classroom/… -
Change something in
lab.ymland save. The class updates on its own.
If you use edrys-dev often, install it once with npm install -g @edryslabs/edrys-dev and then start it with just edrys-dev lab.yml --open.
Stop it with Ctrl+C.
-
The
/?devtab is yours. It is the teacher's view of the class. Keep it open: it is the tab that picks up your changes tolab.yml. -
Try the lab as a student or station. Open the share link in a different browser or a private window. In the same browser you would be the teacher again.
-
Let students in from their own computers. Give them the link after
students join at:. It opens the same class on https://edrys-labs.github.io, so students need nothing but a browser, while your/?devtab stays on your computer and keeps followinglab.yml. Keep that tab open while students join: they get the class from the browsers that have it open. With an agent or the tutor, the agent chat in your/?devtab shows this link at the top, with a Copy button. The class's Settings (Members, Communication) give the same link, and below it the address of your tab, which works only on your computer. Three things to know:- Both sides must connect the same way. Without
--localthey do. With--localthe class runs through edrys-dev on your computer, which other computers cannot reach, so edrys-dev prints no link for students. - If you picked a WebSocket server in the class's Settings, the link carries it (
#comm=…); don't cut that part off. - Modules from
http://localhost:…(see below) do not load on the students' computers. edrys-dev warns you about them.
--student-urlpoints the link to another edrys-Lite site, e.g. your own copy; it has to use the same connection settings as the edrys-Lite inside edrys-dev. - Both sides must connect the same way. Without
-
The file wins. Anything you change in the class's Settings dialog is replaced the next time you save
lab.yml. Make your changes in the file. -
Restarting is safe. The same
lab.ymlreopens the same class, so share links you opened before keep working. -
Mistakes are reported. If the class cannot take your lab (for example a YAML typo), the terminal prints
lab error: …. Fix the file and save again. -
Offline? Start with
--local. The class then connects through edrys-dev itself instead of the internet.
Which modules exist and how to configure them is described in the edrys-Lite documentation and the edrys skills.
Publish lab.yml the way you normally would, for example in a GitHub repository with a deploy link to https://edrys-labs.github.io. Nothing from edrys-dev ends up in the file.
If you are also writing a module, put it into a modules/ folder next to the lab, one subfolder per module:
my-lab/
├── lab.yml
└── modules/
└── my-module/
└── index.html
edrys-dev serves this folder on the next port (3211 by default). Use that address in lab.yml:
modules:
- url: http://localhost:3211/my-module/index.htmlWhen you save a file in modules/my-module/, only that module reloads in the class. Errors from your module's page are collected so an AI agent can see them (see below).
These addresses only work on your computer. Publish the module (for example on GitHub Pages) and put its real address into lab.yml before you share the lab.
edrys-dev can work together with an AI coding agent such as Claude Code or GitHub Copilot. The agent edits lab.yml for you, sees whether the class accepts it, and can look into the class: rooms, users, modules and their messages.
The quick way, for a new lab:
npx @edryslabs/edrys-dev init my-labThis makes the folder my-lab with a starter lab.yml, the agent configuration for Claude Code (.mcp.json) and VS Code Copilot (.vscode/mcp.json), instructions for the agent (AGENTS.md, CLAUDE.md), Claude Code settings that let the agent use edrys-dev and edit lab.yml and modules/ without asking each time (.claude/settings.json), and a .gitignore. In a folder that already has some of these files, it keeps them. If the edrys skills (step 2 below) are not installed yet, it offers to install them for you; if they are, it updates them when a newer version is out. Then open the folder in your agent and write "start".
To give your students a tutor as well:
npx @edryslabs/edrys-dev init my-lab --tutorThe agent then starts edrys-dev with the tutor. The tutor needs Claude Code on your computer, also if you work with Copilot. Add --tutor-agent codex to have Codex answer instead of Claude Code. In a folder that already has its agent configuration, add "--tutor" to the args in .mcp.json (and .vscode/mcp.json) yourself, then restart the agent.
By hand:
-
Create a file
.mcp.jsonin your lab folder:{ "mcpServers": { "edrys": { "command": "npx", "args": ["-y", "@edryslabs/edrys-dev", "--mcp", "lab.yml"] } } }If you installed edrys-dev globally,
"command": "edrys-dev", "args": ["--mcp", "lab.yml"]starts faster. -
Install the edrys skills, so the agent knows how labs and modules are written:
npx skills add edrys-labs/skills
-
Start your agent in the lab folder. The agent starts edrys-dev itself; don't start it by hand as well. If the folder has no
lab.ymlyet, edrys-dev creates a starter lab there. Ask the agent for the class address if your browser doesn't open it.
The class tab has an agent chat button (sparkles icon) in its top bar, next to the class chat button. It opens the chat on the right side of the class, like the class chat; drag its left edge to make it wider or narrower. On a phone it fills the screen. What you write there goes straight to the agent. Only you see this chat; it is not the class chat. The agent can answer with text and also with buttons or checkboxes for you to pick from.
When the class cannot take your lab file, for example after a typing mistake, a red note in the chat says so in plain words, with edrys-Lite's message under "Details". It goes away once the file loads again.
Under your message you see "Waiting for the agent…" until the agent picks it up, then "Thinking…". If nothing happens for 30 seconds, the agent may not be listening. Your message is kept and answered once it is.
Some agents don't notice new messages on their own while they are idle. In that case start the chat prompt of the edrys server once (in VS Code Copilot: type /edrys.chat, or /mcp.edrys.chat in older versions), and the agent keeps waiting for your messages.
Chats are saved in .edrys-dev/chats/ next to the lab, so a restarted agent can pick up where you left off. The menu at the top of the chat starts a new chat or reopens an old one. Add .edrys-dev/ to your .gitignore.
For safety, the chat only accepts messages from the class page on your own computer. Other websites or people on your network cannot write to your agent, as long as you keep --host at its default.
| Tool | |
|---|---|
get_state |
the lab file, class address, share links (also the one for students), rooms, users and modules |
goto |
move your class tab to a room, or open a student or station tab |
chat, read_chat |
write to you in the agent chat, read your messages |
read_messages |
messages between modules, and errors from your own modules |
read_module_state |
what the modules share in a room |
read_student_chats |
what students asked the tutor and what it answered (with --tutor) |
run_in_module |
run JavaScript inside one of your own modules, only when you ask for it |
wait_for_event |
wait for the next thing that happens in the class |
Start edrys-dev with --tutor, and your students get a tutor button (robot icon) in the class's top bar. What they ask there is answered by an AI on your computer: Claude Code must be installed and signed in (claude in a terminal). With --tutor-agent codex, Codex answers instead (codex login first). Students need nothing but the class link.
-
It knows the lab. The tutor reads
lab.ymlwith every question, so it always knows the version the class shows. It gives hints and explanations instead of whole solutions. -
Notes only for the tutor. Put a file
tutor.mdnext tolab.yml, for example "Task 2 is about Ohm's law, don't give the number". Students don't receive this file. A student who tries hard may still get the tutor to repeat it, so keep secrets out of it. -
It sees the student's room. With each question it gets what the modules of the student's room share, such as the code in an editor, read through your
/?devtab. It cannot change it or run it. It does not see terminal output or the screen, so it asks the student to paste those. -
It cannot do anything but answer. It runs as a separate
claudewithout tools, MCP servers or your settings. A student cannot reach your files, your shell or the agent that edits your lab. Codex has no switch for "no tools", so edrys-dev turns off each of its tools and your config, runs it read-only, and drops any answer for which it still used a tool. This was checked with Codex 0.165. If a newer version no longer knows one of these settings, the tutor gives no answers, and the terminal says why. -
Your
/?devtab passes the messages on. Keep it open. The button shows only while it is, and only on an edrys-Lite new enough to have it, on your computer and on the students' site alike. -
You watch and switch it in the agent chat. The agent chat button (sparkles icon) in the top bar of your
/?devtab has a Students view: every question and answer as it happens, and a switch that turns the tutor off and on without restarting. While it is off, students don't see their button. -
Each student has their own conversation, which continues with the next question. Chats are saved in
.edrys-dev/tutor/, and the agent keeps its own copy of each conversation (Claude Code and Codex in their usual session folders). Your agent can read them withread_student_chatsand tell you where students get stuck. -
Limits. At most two answers are written at once; a question that finds no free turn within 30 seconds gets "the tutor is busy". Each student can have one open question and up to 20 questions an hour, of up to 2000 characters each. The whole class gets up to 200 questions an hour, not counting those turned away as busy (
--tutor-per-hourchanges it), so a student cannot get past the limits with new names and use up your account. When the class reaches it, the terminal and the Students view say so. -
Cost. Each question uses the account
claudeorcodexis signed in to. Answering students with a personal plan (Claude Pro/Max, ChatGPT) may break its terms, which don't let you make your account available to others. For a class, use an API key: start edrys-dev with it, and only the tutor uses it, not your own agent.ANTHROPIC_API_KEY=sk-ant-... npx edrys-dev lab.yml --tutor CODEX_API_KEY=sk-... npx edrys-dev lab.yml --tutor --tutor-agent codex
The terminal says what pays when edrys-dev starts, e.g.
tutor: on (claude, answers paid with API key from ANTHROPIC_API_KEY). With a key, claude runs in its minimal mode (--bare), which takes only the key and never your login. Codex readsCODEX_API_KEY, notOPENAI_API_KEY.--tutor-modelpicks a cheaper model, e.g.--tutor-model haiku. -
Privacy. Students are told that an AI on your computer answers and that you can read the chat. Questions and answers are encrypted between the student's browser and your tab, on every connection. Other members of the class can't read them, and neither can a WebSocket server that relays the class.
edrys-dev [lab.yml] [options]
| Option | Default | |
|---|---|---|
[lab.yml], -i, --input <file> |
./lab.yml |
the lab file, YAML or JSON |
-o, --open |
off | open the class in the browser |
-p, --port <port> |
3210 |
if it is taken, the next free port is used |
--local |
off | connect the class through edrys-dev instead of the internet; works offline |
-m, --modules <dir> |
./modules, if it exists |
folder with your own modules |
--student-url <url> |
https://edrys-labs.github.io/ |
the edrys-Lite site in the link for students |
--tutor |
off | a tutor chat for students, answered by an AI agent on your computer |
--tutor-agent <claude|codex> |
claude |
the agent that answers: Claude Code or Codex |
--tutor-model <model> |
the agent's default | the model the tutor uses |
--tutor-per-hour <n> |
200 |
questions the tutor answers per hour for the whole class |
--host <host> |
127.0.0.1 |
0.0.0.0 lets others on your network open the class (and, with --mcp, write to your agent) |
--mcp |
off | for AI agents; set it in .mcp.json, see above |
-h, --help |
show help |
no lab file at …: edrys-dev looks forlab.ymlin the current folder. Start it in your lab folder or give the path:edrys-dev path/to/lab.yml.- The share link opens me as the teacher. Open it in another browser or a private window.
- My changes in Settings are gone. They are replaced by
lab.ymlon every save. Put them into the file. - The class doesn't connect / no internet. Try
--local. - Students see an empty class. Keep your
/?devtab open while they join. Check that they got the whole link, including any#comm=…at the end, and that you didn't start with--local. A site set with--student-urlmust use the same connection settings as edrys-dev's edrys-Lite. - A different port than 3210. Another program uses it; edrys-dev took the next free one. The terminal shows the address.
- The agent doesn't answer in the chat. Check that your agent is running in the lab folder with
.mcp.json, and if it does not wake up on its own, start thechatprompt (see above).
To work on edrys-dev itself:
npm install
npm run build # tsc → dist/
npm test # builds, then runs test/*.test.jsHow the parts talk to each other:
flowchart LR
lab[lab.yml] -- watch --> srv
mods[modules/] -- watch --> msrv["module server :3211<br/>(module pages + error script)"]
subgraph srv ["edrys-dev :3210"]
direction TB
static["edrys-Lite dist + reload.js"]
api["/edrys-dev/lab · events · reload · bridge · module-error<br/>chat · tutor · link"]
end
srv -- "reload-lab, reload-module, tutor answers (SSE events)" --> tab["class tab /?dev"]
tab -- "GET lab, republish signed, tutor answers" --> room((class))
tab -- "state, messages, replies, tutor questions (POST bridge)" --> srv
msrv -- module frames --> tab
msrv -. "errors (POST module-error)" .-> srv
srv -- "reload (SSE)" --> peers
room -- "default transport, or --local on :3210" --> peers[student / station tabs]
peers -- "tutor questions" --> room
room -- "tutor questions" --> tab
agent[AI agent] <-- "MCP over stdio (--mcp)" --> srv
srv <-- "question in, answer out (--tutor)" --> tutor["tutor: claude or codex<br/>without tools"]
index.tsreads the options and starts the server, and with--mcpthe MCP server (mcp.ts).server.tsserves edrys-Lite, watches the lab (watch.ts) and talks to the class tab: events go out over SSE, the tab's reports come back overPOST /edrys-dev/bridge.- In the class tab, edrys-Lite's own
DevBridge(src/ts/DevBridge.tsin edrys-Lite, active with?dev) loads and republishes the lab and reports the class.GET /edrys-dev/labalso tells it which views of the agent chat there are (chat: {agent, students}); edrys-Lite then shows itsAgentChat.vuedrawer, which talks tochat.ts(Agent view,/edrys-dev/chat) andtutor.ts(Students view,/edrys-dev/tutor) on the server, and to/edrys-dev/link/eventsfor the students' link. - edrys-dev adds
views/reload.jsto the page: student and station tabs load again when a local module changed. tutor.tsanswers students with--tutor: the class tab passes their questions on over the bridge,tutor.tsrunsclaudeorcodexwithout tools for each one, and the answer goes back to the tab as an event, which sends it to the student through the class.modules.tsserves local modules on the next port and adds the error reporter to their pages.sync.tsis the Yjs sync for--local, on edrys-dev's own port.
edrys-Lite comes from npm as @edryslabs/edrys-lite, pinned to one version: its dist/ is what edrys-dev serves. To try changes to edrys-Lite, build it (npm run build there) and link the checkout with npm install ../edrys-Lite; pin the published version again afterwards. The same goes for @edryslabs/websocket-server, an optional dependency that only --local uses.
Releases are published by .github/workflows/publish.yml: npm version <version> and git push --follow-tags on main. The workflow checks that the tag matches package.json, runs the tests, publishes to npm (a pre-release version like 0.5.0-dev.0 under the next tag, any other under latest) and creates the GitHub release.
Issues and pull requests: https://github.com/edrys-labs/edrys-devserver
MIT. The MCP server and the agent chat follow LiaScript DevServer (MIT).