# Vibe — Agent Build & Deploy Guide > Vibe is Facilio's app platform. AI agents (Claude, GPT, anything) can author a static web app in **any language or framework**, integrate Facilio APIs via the `@facilio/vibe-sdk`, and ship it to a live URL with the `@facilio/vibe-cli`. This file tells you everything you need to do that end-to-end. Read it once before you start; refer back to the **Action Catalog** section every time you need a connection slug, action slug, or payload shape. --- ## 0. What you are building A **Vibe app** = a folder of static files (HTML/CSS/JS, possibly bundled from React/Vue/Svelte/Solid/Preact/vanilla — your choice) hosted on a Facilio-managed subdomain like `https://.vibe.facilio.com`. Hard constraints — non-negotiable: 1. **The build output folder must contain an `index.html` at its root.** This is a constraint on the *output* of your build step, NOT a constraint on how you write your source code. Author your app in any framework, any folder structure, any number of files (TS, TSX, Vue SFCs, Svelte, Solid, plain JS, whatever) — then run your normal build command (`npm run build`, `yarn build`, `pnpm build`, `vite build`, `next export`, `astro build`, …). Whatever folder that build writes to (e.g. `dist/`, `build/`, `out/`, `public/`) is what gets uploaded, and *that* folder is the one that must have `index.html` at its root. JS/CSS/asset files alongside it are fine and expected — they get loaded by ` ``` --- ## 4. `vibe.json` — project config Place at the project root. The CLI reads/writes this file. ```json { "name": "my-app", "app": "my-app", "build": { "publish": "dist" } } ``` | Field | Required | Notes | |-----------------|----------|------------------------------------------------------------------------------------------------| | `name` | optional | Human-readable name. Shown in `vibe app list`. | | `app` | yes (after first deploy) | The `linkName` of the app on the server. Written automatically by `vibe app create`. | | `build.publish` | yes | Folder containing built static files. Must contain `index.html`. Default: `dist`. | `vibe app create` will scaffold or patch this file for you — you don't have to write it by hand. --- ## 5. CLI commands in detail ### `vibe login` Run once per machine. Uses OAuth2 device flow — works on laptops, over SSH, and inside containers. Writes a token to `~/.vibe/credentials.json` (chmod 600). For CI / non-interactive: `vibe login --api-key -` (read the key from stdin). ### `vibe app create` **For agent use, always pass flags so the command is non-interactive** — never trigger the prompts: ```bash vibe app create --name "My App" # optional: --description "..." --logo ./logo.png ``` If run with no flags it falls back to interactive prompts (name, description, logo path, output directory). Agents should not rely on that path because it requires the user to type — defeats the point of automation. Inputs: - **`--name`** (required for non-interactive use) — human-readable name. The server derives the `linkName` (subdomain) from this. - **`--description`** — optional. - **`--logo`** — optional path to png/jpg/svg/webp/gif/ico, ≤750 KB. - **Output directory** — only prompted in interactive mode. Defaults to `dist`. Non-interactive use assumes `dist`; if your build emits elsewhere, edit `vibe.json` `build.publish` after the create call. After success, `vibe.json` is created (or patched) with `app: ` so subsequent `vibe deploy` calls don't need any flags. The app's live URL is printed. You only run this **once per app**. If you're shipping a new version of an existing app, skip this step. ### `vibe app list` Prints all apps in the org (linkName, name, URL, status, last-published timestamp). ### `vibe deploy` In the project root: 1. Reads `vibe.json` → finds the `build.publish` directory. 2. Zips its contents (level-9 deflate). 3. POSTs to vibe-server, uploads the zip, triggers publish, polls until `DEPLOYED` or `FAILED`. 4. Prints the live URL and the immutable versioned URL. Flags: - `--prod` — mark this deployment as production. - `--app ` — override the `app` value from `vibe.json` (e.g. shipping the same build to a different app). **You are responsible for running your build first.** The CLI does not invoke `npm run build` / `vite build` / etc. Build, then deploy: ```bash npm run build && vibe deploy ``` ### `vibe whoami` Prints the email and server of the active session. Use this in scripts to sanity-check login state. ### `vibe logout` Removes the stored token. --- ## 6. Integrating Facilio APIs via the SDK ### Setup (modern bundler — Vite / webpack / Next.js / esbuild / etc.) ```ts import { createVibe } from '@facilio/vibe-sdk'; const vibe = createVibe(); // serverURL defaults to window.location.origin ``` Because every deployed Vibe app is served from `https://.vibe.facilio.com`, the SDK talks to the same origin — no config needed. Cookies flow automatically. ### Auth ```ts // Trigger login (redirects the browser to identity-service): vibe.login(); // Trigger logout: vibe.logout(); // Read the current user (null if not signed in): const me = await vibe.getCurrentUser(); // Boolean check: const ok = await vibe.isAuthenticated(); ``` **Shape of `getCurrentUser()` response** (when signed in): ```json { "user": { "uid": 1, "email": "xyz@facilio.com", "name": "xxxxx", "username": "xyz" }, "org": { "orgId": 1 } } ``` Returns `null` when not signed in. Access fields as `me.user.email`, `me.user.name`, `me.user.uid`, `me.user.username`, `me.org.orgId` — note the nesting under `user` / `org`. Don't read `me.email` directly; it doesn't exist at the top level. ### Redirect to login when `getCurrentUser()` says unauthorized Use `getCurrentUser()` as the single source of truth for "is the user signed in?" — and **only** that call drives the login redirect. If it returns `null` (the SDK's way of saying the underlying `/api/runtime/getCurrentUser` returned 401), send the user to the login screen with `vibe.login()`. Recommended bootstrap pattern (run on app mount): ```ts const me = await vibe.getCurrentUser(); if (!me) { vibe.login(); // browser navigates away to identity-service; nothing below runs return; } // ...render authenticated UI using me.user.email, me.org.orgId, etc. ``` Or — if you want the user to see a "Log in" button instead of an automatic redirect — render the button when `me === null` and wire its `onClick` to `vibe.login()`. Either pattern is fine; just never leave a `null` user case unhandled. **Do not** wire `vibe.login()` into the catch block of every action call. A 401 from `executeAction` or any other endpoint is just an error — surface it via `err.message`. The login redirect belongs only on the `getCurrentUser()` path. ```ts try { const result = await vibe.executeAction('facilio-cmms', 'list-assets'); // ...use result } catch (err) { showError(err.message); // do NOT call vibe.login() here } ``` See `vibe-react-test/src/App.jsx` for a working pattern. ### Calling Facilio data — `executeAction` This is the **only** way you should access Facilio data: ```ts const result = await vibe.executeAction(connectionSlug, actionSlug, payload); ``` - `connectionSlug` — identifies which Facilio product/connector to talk to (e.g. `facilio-cmms`). - `actionSlug` — identifies the specific operation (e.g. `list-assets`, `create-workorder`). - `payload` — JSON object with the action's inputs. Defaults to `{}` if the action takes none. Returns the parsed JSON response. The shape is **action-specific**; the SDK does not normalize it. A typical action returns `{ response: { data: [...] } }` — see the smoke-test example below — but always confirm by inspecting the action's schema in the MCP catalog. **Where do the slugs and payload shapes come from?** Not from your imagination — from the MCP connector at: ``` https://mcp.facilio.com/mcp ``` This is the source of truth. It exposes every available connection, every action, the input schema (so you know exactly what `payload` keys to send), and the response shape. AI agents (Claude, GPT, any MCP-aware client) connect to it and discover the catalog at runtime. **Workflow when you need to call data:** 1. Query the MCP server to discover the right `connectionSlug` and `actionSlug` for what the user wants. 2. Read the action's input schema; build the `payload` to match. 3. Call `vibe.executeAction(connectionSlug, actionSlug, payload)`. 4. Read the action's response schema; parse the result accordingly. Do NOT hardcode action lists from this file — they evolve. Re-discover via MCP each time the user asks for new functionality. ### Reference call (taken verbatim from the test app) ```js const result = await vibe.executeAction('facilio-cmms', 'list-assets'); const { response } = result; const assets = response?.data ?? []; ``` ### Raw HTTP (escape hatch) If you genuinely need an endpoint that's not exposed as an action, `vibe.fetch(path, init)` is a thin wrapper around `fetch()` that auto-attaches `credentials: 'include'` and redirects to `login()` on 401: ```ts const res = await vibe.fetch('/api/runtime/...'); ``` Prefer `executeAction` whenever possible — it's the supported, schema-discoverable surface. ### Error handling All SDK methods throw `VibeError` (has `.message`, `.status?`). Wrap UI calls in try/catch and surface `err.message`: ```ts try { const data = await vibe.executeAction('facilio-cmms', 'list-assets'); } catch (err) { // err.message is human-readable; err.status is the HTTP status if available } ``` --- ## 7. End-to-end example (React + Vite) The folder `vibe-react-test/` is a working reference. Skim: - `vibe-react-test/index.html` — the mandatory entry point, just a `
`. - `vibe-react-test/src/main.jsx` — mounts React. - `vibe-react-test/src/App.jsx` — uses `createVibe()`, `vibe.getCurrentUser()`, `vibe.login()`, `vibe.logout()`, and `vibe.executeAction('facilio-cmms', 'list-assets')`. - `vibe-react-test/vibe.json` — `{"name":"newcheck","app":"newcheck","build":{"publish":"dist"}}`. - `vibe-react-test/package.json` — vite build command. From scratch, that whole app ships in: ```bash npm create vite@latest my-app -- --template react cd my-app npm install npm install @facilio/vibe-sdk # edit src/App.jsx to use vibe.login() etc. vibe login # one-time vibe app create --name "My App" # one-time; writes vibe.json npm run build vibe deploy ``` After the first deploy the CLI prints something like: ``` ✔ Deployed v1 Live: https://my-app.vibe.facilio.com Archive: https://my-app.vibe.facilio.com/v/1 ``` Subsequent `vibe deploy` calls bump the version and keep the live URL stable. --- ## 8. Minimal non-React example (vanilla HTML) If the user just wants a one-page tool, you don't need a build step at all. Make a folder: ``` dist/ ├── index.html └── app.js ``` `dist/index.html`: ```html Asset list
    ``` `dist/app.js`: ```js const vibe = VibeSDK.createVibe(); document.getElementById('load').onclick = async () => { const user = await vibe.getCurrentUser(); if (!user) return vibe.login(); const { response } = await vibe.executeAction('facilio-cmms', 'list-assets'); document.getElementById('list').innerHTML = (response?.data ?? []).map(a => `
  • ${a.name} #${a.id}
  • `).join(''); }; ``` `vibe.json`: ```json { "name": "asset-list", "app": "asset-list", "build": { "publish": "dist" } } ``` Then `vibe login → vibe app create → vibe deploy`. Done — no bundler, no transpile. --- ## 9. Checklist before you call `vibe deploy` - [ ] `vibe login` succeeded (`vibe whoami` prints your email). - [ ] `vibe app create` has been run at least once; `vibe.json` contains `"app": ""`. - [ ] The folder named in `vibe.json` `build.publish` exists and contains **`index.html`** at its root. - [ ] Any Facilio data access in the code uses `vibe.executeAction(...)` with slugs discovered from `https://mcp.facilio.com/mcp` — not invented. - [ ] Login flow is wired: `getCurrentUser()` checked on load, `login()` / `logout()` available to the user. --- ## 10. Pitfalls to avoid - **Don't confuse "`index.html` must exist in the output dir" with "write everything in `index.html`."** Your source can be hundreds of files across `src/`. After `npm run build` (or whatever your tool uses), the bundler emits an `index.html` plus chunked JS/CSS/asset files into the publish folder — that emitted `index.html` is what satisfies the constraint. - **Don't** ship a publish folder without `index.html` at its root — the deploy will technically succeed but the URL serves nothing useful. If your bundler emits to `build/` or `out/` instead of `dist/`, set `build.publish` to that folder. - **Don't** invent connection or action slugs. If you don't know the slug, query the MCP server. - **Don't** call `fetch('/api/runtime/connections/.../execute', ...)` directly — use `vibe.executeAction`, which handles URL encoding, auth redirects, and error shapes for you. - **Don't** put the OAuth client secret in your app. The SDK and CLI never need it; identity flows happen out-of-band. - **Don't** run `vibe app create` more than once for the same app. If you need to ship to an existing app, just `vibe deploy` (the `app` field in `vibe.json` is enough). - **Don't** assume the live URL pattern. Read it from the deploy output (`Live: ...`) — the CLI prints it. --- ## 11. Reference index | File | Purpose | |------------------------------------------------|-------------------------------------------------------------------------| | `vibe-cli/README.md` | CLI command reference, env vars, login flow internals. | | `vibe-cli/src/commands/` | Source for `login`, `logout`, `whoami`, `app`, `deploy` commands. | | `vibe-sdk/README.md` | SDK API surface, install, plain-HTML usage. | | `vibe-sdk/src/index.ts` | The full SDK — `createVibe`, `login`, `logout`, `getCurrentUser`, `executeAction`, `fetch`. | | `vibe-sdk/examples/smoke-test/` | Minimal vanilla-HTML SDK smoke test. | | `vibe-react-test/` | Working React + Vite + SDK reference app, already deployed once. | | `https://mcp.facilio.com/mcp` | **Authoritative catalog** of `connectionSlug`, `actionSlug`, payload schemas. Query this whenever you need to call data. |