Writing Scripts
Section titled “Writing Scripts”No boilerplate. No exports. No imports. Write your logic, return a value — Hoody Exec handles everything else. Every parameter you need is automatically injected into your script context.
Basic Script Structure
Section titled “Basic Script Structure”A Hoody Exec script is a plain TypeScript/JavaScript file. Magic comments at the top configure behavior. The rest is your code. Return a value and it becomes the HTTP response.
// @mode worker// @cors reflective// @timeout 5000
// ALL parameters automatically available:// req, res, metadata, shared, console, require, ws// (mainResult is additionally available only in post.js middleware)
const { id } = metadata.parameters; // Dynamic route paramconst user = await fetchUser(id);
// Return value auto-formatted as JSONreturn { user };Key points:
- No
export default— parameters are injected automatically - No
module.exports— just write code at the top level - No imports needed for built-ins —
crypto,fs,path,$,Databaseare pre-injected - Magic comments go at the very top, before any code
- Return a value to send it as the response (or use
resfor full control)
Automatically Available Variables
Section titled “Automatically Available Variables”Every script automatically receives these parameters — no imports, no configuration needed:
Core HTTP Objects
Section titled “Core HTTP Objects”// req - Incoming HTTP requestreq.url // '/api/users/123'req.method // 'GET', 'POST', etc.req.headers // { 'authorization': 'Bearer ...', ... }req.body // Parsed JSON body (if Content-Type: application/json)Response Object
Section titled “Response Object”// res - HTTP response (for full control)res.writeHead(200, { 'Content-Type': 'application/json' })res.end(JSON.stringify({ success: true }))res.statusCode = 404 // Set status coderes.setHeader('X-Custom', 'value')Metadata Object
Section titled “Metadata Object”// metadata - Request context and routing infometadata.executionId // Unique execution IDmetadata.parameters // { id: '123' } from dynamic routes like [id].tsmetadata.clientIp // REAL client IP (see below)metadata.path // '/api/users/123'metadata.method // 'GET', 'POST', etc.metadata.url // Full URLmetadata.query // Parsed query string { search: 'term' }State & Tools
Section titled “State & Tools”// shared - State object (persists in worker mode, resets in serverless)shared.cache = new Map() // Worker: persists across requestsshared.requestCount = 0 // Serverless: reset every request
// console - Loggerconsole.log('message')console.info('info')console.debug('debug')console.error('error')
// require - Module loader (auto-installs missing modules)const axios = require('axios') // Auto-installed if not presentconst lodash = require('lodash') // Auto-installed if not presentWebSocket Context (ws)
Section titled “WebSocket Context (ws)”Available when // @websocket is enabled (worker mode only). Provides full control over WebSocket connections:
// Event handlers — Direct assignment patternws.open = (socket, req) => { ... }ws.message = (socket, data) => { ... }ws.close = (socket, code, reason) => { ... }ws.error = (socket, error) => { ... }
// OR Event emitter patternws.on('open', (socket, req) => { ... })ws.on('message', (socket, data) => { ... })ws.on('close', (socket, code, reason) => { ... })ws.on('error', (socket, error) => { ... })
// Connection managementws.connections // Set of all active WebSocket connections for this hostnamews.broadcast(data) // Send data to ALL connected clientsws.broadcast(data, excludeSocket) // Send to all except one client
// Socket data — available on each socket instancesocket.data.ip // Client IP addresssocket.data.url // Request URLsocket.data.headers // Request headerssocket.data.parameters // Dynamic route parameterssocket.data.executionId // Unique execution ID for this connectionPost Middleware Result (mainResult)
Section titled “Post Middleware Result (mainResult)”// mainResult - ONLY available in post.js middleware// Contains the return value of the main script that just executed// Use it to wrap, transform, or log responses
// post.js example:return { data: mainResult, timestamp: Date.now(), requestId: metadata.executionId};AI Helpers (when @ai enabled)
Section titled “AI Helpers (when @ai enabled)”// Available when // @ai true is set — no imports neededai // Helper namespace with three methods: // ai.generate(opts) — generate a text completion // ai.stream(opts) — stream text chunks // ai.object(opts) — generate structured JSON against a schemaopenai // Pre-configured OpenAI SDK client instancemodel // Pre-configured Vercel AI SDK model (from @ai-model or default)generateText // Vercel AI SDK — generate text completionsstreamText // Vercel AI SDK — stream text responsesgenerateObject // Vercel AI SDK — generate structured JSON objectsSee AI-Powered Scripts for full usage examples and model configuration.
Response Helpers
Section titled “Response Helpers”The res object is enhanced with Express-like convenience methods:
res.json({ data: 'value' }) // Send JSON responseres.send('text') // Send text responseres.html('<h1>Hello</h1>') // Send HTML responseres.redirect('/new-path') // HTTP redirectres.stream(readableStream) // Stream responseres.status(404) // Set status code (chainable)Example with chaining:
// Return a 201 JSON responseres.status(201).json({ created: true, id: 'abc123' });Bun Globals & Node.js Built-ins
Section titled “Bun Globals & Node.js Built-ins”These are pre-injected into every script — no require or import needed:
// $ - Bun.$ for shell commandsconst output = await $`ls -la /home/user`.text()const result = await $`echo "Hello"`.text()
// Database - bun:sqlite Database constructor (require('bun:sqlite').Database)const db = new Database('/hoody/databases/app.db')const rows = db.query('SELECT * FROM users').all()
// Node.js built-ins (pre-injected)crypto.randomUUID() // crypto modulefs.readFileSync('/path/to/file') // fs modulepath.join('/home', 'user') // path module// Also available: http, https, net, tls, child_processReturn Anything
Section titled “Return Anything”Return whatever you want — Hoody Exec automatically handles content types, serialization, and status codes.
// Return Object → Auto-formatted JSON (Content-Type: application/json)return { users: [...], count: 42 };
// Return Array → Auto-formatted JSON arrayreturn [{ id: 1 }, { id: 2 }];
// Return String (HTML detected) → Content-Type: text/htmlreturn "<!DOCTYPE html><html><body>Hello</body></html>";
// Return String (other) → Content-Type: text/plainreturn "Plain text response";
// Return Number → JSON numberreturn 42;
// Return Boolean → JSON booleanreturn true;
// Return Buffer → Auto-detected MIME type (images, PDFs, files)return fs.readFileSync('/path/to/image.png'); // Automatic image/png
// Return Error → 500 status with error detailsreturn new Error("Something went wrong");
// Return Nothing → Empty 204 response// (no return statement or return undefined)For full control, use res directly to bypass auto-handling:
res.writeHead(200, { 'Content-Type': 'application/xml' });res.end('<?xml version="1.0"?><data>Custom</data>');The abstraction: You focus on logic. Hoody Exec handles HTTP protocol details automatically.
Real Client IPs
Section titled “Real Client IPs”Pre-installed Packages
Section titled “Pre-installed Packages”These npm packages are bundled and always available — no installation delay on first use:
| Package | Description |
|---|---|
@ai-sdk/openai | Vercel AI OpenAI provider |
ai | Vercel AI SDK |
axios | HTTP client |
cheerio | HTML parser (jQuery-like) |
cookie | Cookie parser |
dayjs | Date library |
ejs | Template engine |
jsonwebtoken | JWT creation/verification |
lodash | Utility library |
marked | Markdown parser |
mime-types | MIME type detection |
openai | Official OpenAI SDK |
papaparse | CSV parser |
playwright-core | Browser automation (Chromium/Firefox/WebKit) — bring your own browser |
puppeteer-core | Headless Chrome/Firefox automation — bring your own browser |
qrcode | QR code generator |
rss-parser | RSS/Atom feed parser |
sanitize-html | HTML sanitizer |
uuid | UUID generation |
ws | WebSocket client/server |
xml2js | XML ↔ JS object parser |
yaml | YAML parser |
zod | Schema validation (also exposed as z) |
Any other npm package is auto-installed on first require() — no package.json needed.
Script Storage Paths
Section titled “Script Storage Paths”Scripts are stored in instance-specific directories:
/hoody/storage/hoody-exec/scripts/default/1/ # exec-1 (under default subdomain)/hoody/storage/hoody-exec/scripts/default/2/ # exec-2/hoody/storage/hoody-exec/scripts/default/test/ # exec-test# Runtime routing also accepts the execId-only fallback /hoody/storage/hoody-exec/scripts/1/The instance number (1, 2, test, etc.) maps to the hostname:
exec-1→scripts/1/exec-2→scripts/2/exec-test→scripts/test/
To create scripts programmatically, use the scripts/write endpoint:
curl -s -X POST "https://PROJECT-CONTAINER-exec-1.SERVER.containers.hoody.icu/api/v1/exec/scripts/write" \ -H "Content-Type: application/json" \ -d '{ "path": "api/hello.ts", "content": "// @mode serverless\nreturn { hello: \"world\" };", "createDirs": true, "validate": true }'Companion System Prompt Files
Section titled “Companion System Prompt Files”When using AI-powered scripts (// @ai true), you can define system prompts in companion markdown files. These are loaded automatically and injected into the AI context.
Script-Level System Prompt
Section titled “Script-Level System Prompt”Create a .system.md file matching your script name:
scripts/1/├── chat.js # Your AI script├── chat.system.md # System prompt for chat.js (loaded automatically)├── summarize.js└── summarize.system.mdThe file chat.system.md is automatically loaded when chat.js executes. Write your system prompt as plain markdown:
You are a helpful coding assistant. Be concise and provide working code examples.Always explain your reasoning step by step.Directory-Level Default System Prompt
Section titled “Directory-Level Default System Prompt”Create a _system.md file to set a default system prompt for all AI scripts in that directory:
scripts/1/api/├── _system.md # Default system prompt for all scripts in api/├── chat.js # Uses _system.md (no script-level override)├── chat.system.md # Overrides _system.md for chat.js specifically└── translate.js # Uses _system.mdResolution order: Script-level (chat.system.md) takes precedence over directory-level (_system.md).
See AI-Powered Scripts for full details on AI configuration and model selection.
Powered by Bun
Section titled “Powered by Bun”Hoody Exec runs on the Bun runtime:
- 3x faster startup — Native speed optimizations
- Modern JavaScript — Latest ECMAScript features built-in
- Better module system — Improved dependency handling
- Optimized for serverless — Perfect for fast script execution
- Lower memory usage — More efficient runtime