CLI Reference
One CLI — @facilio/cli (binary: facilio) — with exactly two products underneath it: Vibe and Connections. One facilio login covers both. There is no separate MCP server, no external catalog to install, no other endpoint to point at — everything you need to build a Vibe app and everything you need to discover / execute Facilio actions lives in this one CLI.
| Group | Namespace | What it does |
|---|---|---|
| Auth (top-level) | facilio <cmd> | Shared session for both products: login, logout, whoami, switch, accounts. |
| 1. Vibe | facilio vibe <cmd> | Build and deploy Vibe apps: deploy, app, db, function, jobs, agent, connection. |
| 2. Connections | facilio connections <cmd> | Discover and run actions across 1000+ integrated apps: search, schemas, list, link, unlink, wait, execute. This is the authoritative catalog of every connection, action, and payload schema — same session as facilio login, no separate service. |
Coming from @facilio/vibe-cli?
The old package (binary: vibe) is deprecated. Mental model for the migration: prefix facilio before every vibe command, and drop the vibe prefix from login / logout / whoami since those are now shared across products. See Migrating from @facilio/vibe-cli.
Install#
# Install globally so `facilio` is on PATHnpm install -g @facilio/cli
# Or run via npx without a global installnpx @facilio/cli <command>If npm install -g fails with a permissions error, reconfigure npm's global prefix into your home dir (don't use sudo):
mkdir -p ~/.npm-globalnpm config set prefix ~/.npm-globalexport PATH="$HOME/.npm-global/bin:$PATH"npm install -g @facilio/cliMigrating from @facilio/vibe-cli#
The old package is deprecated. To migrate:
npm uninstall -g @facilio/vibe-clinpm install -g @facilio/cliYour existing vibe.json files keep working unchanged — only the binary name and command layout changed. On first facilio login, the CLI reads any credentials left behind by the old vibe binary (keychain service facilio-vibe-cli, or ~/.vibe/credentials.json) and migrates them into the new locations (service facilio-cli, or ~/.facilio/credentials.json) — you don't need to re-authenticate.
Auth commands (top-level)#
Auth commands shared across every Facilio product. One facilio login covers Vibe, Connections, and everything else — you never re-authenticate per product.
facilio login#
Authenticate with Facilio using OAuth2 device flow. Works on laptops, over SSH, and inside containers.
facilio loginFlow:
- The CLI prints a code and opens a browser tab.
- You click Approve in the browser. (This is the one human action in the whole workflow.)
- The CLI polls until approval lands, then prints
Logged in as <email>.
Run once per machine.
Flags#
| Flag | Notes |
|---|---|
--api-key <key> | Log in with an API key instead of the browser flow. Use - to read from stdin. Preferred for CI. |
--region <key> | Region for --api-key logins (US, UK, AE, AU, …). Resolves the right server URLs. Defaults to US. |
--server <url> | Pin the vibe-server URL for this session (overrides the region table). |
--client-id <id> | Override the OAuth client id (default: FACILIO_CLIENT_ID). |
--identity-url <url> | Identity-service base URL (default: FACILIO_IDENTITY_URL). |
Where credentials are stored#
The CLI persists the token to the OS-provided secret store by default, with a 0600 file fallback only when no OS store is available:
| Platform | Storage | Mechanism |
|---|---|---|
| macOS | Keychain | security CLI — service facilio-cli, account default |
| Linux | Secret Service / libsecret | secret-tool (needs libsecret-tools) |
| Windows | Credential Manager (DPAPI) | Per-user encrypted ciphertext written to ~/.facilio/credentials.dpapi |
| Fallback | ~/.facilio/credentials.json (mode 0600) | Used when no OS store is available (headless Linux without libsecret, etc.) |
facilio whoami reports which sink is in use. To force the file fallback (e.g. in CI without a session keyring), set FACILIO_NO_KEYCHAIN=1.
Headless / SSH / Docker: if no browser opens, the CLI prints a URL and a code — open the URL on any device.
facilio whoami#
Print the email and server of the active session. Use in scripts to sanity-check login state.
$ facilio whoamiyou@yourcompany.com • https://app.facilio.comExits non-zero if not logged in.
facilio logout#
Revoke the session server-side (best effort) and remove the stored token from the OS keychain (or the file fallback, whichever holds it).
facilio logout1. Vibe commands#
Build and deploy Vibe apps. Every Vibe command lives under facilio vibe <cmd> and is scoped to the app in your current directory's vibe.json.
vibe.json — project config#
Place at the project root. The CLI reads and writes this file.
{ "name": "my-app", "app": "my-app", "build": { "publish": "dist" }}| Field | Required | Notes |
|---|---|---|
name | optional | Human-readable name. Shown in facilio vibe app list. |
app | yes (after first deploy) | The linkName of the app on the server. Written automatically by facilio vibe app create. |
build.publish | yes | Folder containing built static files. Must contain index.html. Default: dist. |
facilio vibe app create scaffolds or patches this file for you — you don't need to write it by hand.
facilio vibe app create#
Create a new Vibe app on the server. Writes (or patches) vibe.json in the current directory.
facilio vibe app createThe command prompts interactively for name, description, logo, and output directory. Every field except name is optional — press Enter to skip.
Flags#
All flags are optional; anything you don't pass is prompted for.
| Flag | Notes |
|---|---|
--name | Human-readable name. Server derives the linkName (subdomain) from this. |
--description | Optional description shown in facilio vibe app list. |
--logo | Path to a png/jpg/svg/webp/gif/ico file, ≤ 750 KB. |
Output directory#
Only prompted in interactive mode. Defaults to dist. If your bundler emits elsewhere (build/, out/, public/), edit vibe.json build.publish after the create call.
Result#
vibe.jsonis created (or patched) with"app": "<linkName>"so subsequentfacilio vibe deploycalls don't need any flags.- The app's live URL is printed.
Run this once per app. If you're shipping a new version of an existing app, skip this step.
facilio vibe app list#
List all Vibe apps in the org.
$ facilio vibe app listLINK NAME NAME STATUS LAST PUBLISHED URLmy-dashboard My Dashboard DEPLOYED 2026-06-25 10:14 https://my-dashboard.vibe.facilio.comasset-list Asset list DEPLOYED 2026-06-24 18:02 https://asset-list.vibe.facilio.comfacilio vibe app thumbnail <file>#
Upload a thumbnail image for the current app. The server persists the returned URL as the app row's logoUrl — the same field the Vibe app listing renders as the thumbnail.
facilio vibe app thumbnail ./assets/thumbnail.png✔ Thumbnail uploaded. URL: https://cdn.facilio.com/vibe/apps/my-dashboard/thumbnail.pngAccepted formats: png, jpg, jpeg, webp, gif, svg — content-type inferred from the extension. Max size 2 MB.
| Flag | Notes |
|---|---|
--app <linkName> | Override the app target from vibe.json. |
facilio vibe app logo <file>#
Upload a logo image for the current app and print its public URL. Unlike thumbnail, the URL is not persisted to any DB column — the caller decides what to do with it (embed in generated content, patch onto another row, etc.). Same file constraints as thumbnail.
facilio vibe app logo ./assets/logo.svg✔ Logo uploaded. URL: https://cdn.facilio.com/vibe/apps/my-dashboard/logo.svg| Flag | Notes |
|---|---|
--app <linkName> | Override the app target from vibe.json. |
facilio vibe deploy#
Zip the publish folder, upload it, and publish it as a new version.
# In the project root, after running your buildfacilio vibe deployWhat it does:
- Reads
vibe.json→ finds thebuild.publishdirectory. - Zips its contents (level-9 deflate).
- POSTs to vibe-server, uploads the zip, triggers publish, polls until
DEPLOYEDorFAILED. - Prints the live URL and the immutable versioned URL.
✔ Deployed v3 Live: https://my-dashboard.vibe.facilio.com Archive: https://my-dashboard.vibe.facilio.com/v/3The live URL is stable across deploys. The versioned URL is immutable — useful for rollback links or sharing a snapshot.
Flags#
| Flag | Notes |
|---|---|
--prod | Mark this deployment as production. |
--app <linkName> | Override the app value from vibe.json (e.g., shipping the same build to a different app). |
You run your build, not the CLI#
The CLI does not invoke npm run build, vite build, etc. Build first, then deploy:
npm run build && facilio vibe deployHard constraint on the publish folder#
The folder named by build.publish must contain index.html at its root. JS, CSS, and asset files alongside it are fine and expected — they're loaded by <script src=...> / <link rel=...> tags inside index.html as your bundler emits them. No index.html in the publish folder → the deploy succeeds but the URL serves nothing useful.
Database — facilio vibe db#
Each app can have its own dedicated database: a Postgres schema plus a scoped role and login user, provisioned on demand. All vibe db commands are app-scoped — they resolve the target app from --app or vibe.json, and add --app <linkName> to override.
facilio vibe db create#
Provision the database for the current app. Idempotent — re-running returns the existing schema/role/user rather than creating a second one. Run this once before importing tables.
facilio vibe db create✓ Database ready. Schema: vibe_ab12cd… Role: vibe_ab12cd…_rw User: vibe_ab12cd…_usrThe schema is named after the app and is isolated — no other app can see it.
facilio vibe db import#
Create a table by importing a CSV file. Columns are inferred from the CSV header and data server-side. Requires facilio vibe db create first.
facilio vibe db import --file customers.csv --table customers✓ Imported 128 rows into vibe_ab12cd….customers Columns: 5| Flag | Required | Notes |
|---|---|---|
--file <path> | yes | Path to the CSV file. Prompted if omitted. |
--table <name> | no | Target table name. Defaults to a sanitized form of the file name. |
--app <linkName> | no | Override the app target from vibe.json. |
The import is not atomic (CREATE TABLE followed by chunked INSERTs) — a mid-way failure can leave a partially populated table.
facilio vibe db tables (alias ls)#
List the tables in the app's database.
facilio vibe db tablesNAME TYPE ROWS--------- ----- ----customers table 128orders table 512facilio vibe db describe <table> (alias desc)#
Show a table's columns (name, type, nullable) and its total row count.
facilio vibe db describe customers→ vibe_ab12cd….customers (128 rows)COLUMN TYPE NULLABLE------ ------- --------id integer noname text yesemail text yesFunctions — facilio vibe function (alias fn)#
Functions are server-side handlers you author, compile to WASM, and invoke. They can run SQL against the app's database and call Facilio connections. Functions are app-scoped: the logical name you choose (e.g. workorderlist) is unique within your app, and the backend uploads it under an app-unique physical name so functions in different apps never collide or see each other. Every command resolves the app from --app or vibe.json.
Lifecycle: create → build → run. Updating re-uploads the source and requires a rebuild.
Writing a function#
For the authoritative, always-current version of this format, run facilio vibe function instructions — it prints the guide straight from ai-studio, so it never drifts from a hand-copied template.
A function is a JS/TS module built on @facilio/studio-functions. Register one or more named handlers, then call server.execute():
import StudioFunctions, { secret } from "@facilio/studio-functions";
const server = new StudioFunctions({ name: "workorderlist", version: "1.0.0" });
server.addHandler({ name: "list", description: "List open work orders", parameters: { limit: { description: "Max rows", type: "number" }, }, execute: async (args) => { const schema = secret("SCHEMA"); // this app's DB schema const dbUser = secret("DB_USER"); // its DB login user // ...run SQL / call connections, then return any JSON-serializable value return { rows: [] }; },});
server.execute();Secrets are injected by the backend at run time and read with secret("KEY") — the caller never passes them:
| Secret | What it is |
|---|---|
CONNECTIONS_TOKEN | Token to reach Facilio connections. |
AGENTS_TOKEN | Token to reach the agents (ai-studio) service. |
SCHEMA | The app's provisioned DB schema (present once facilio vibe db create has run). |
DB_USER | The app's DB login user. |
facilio vibe function instructions (alias guide)#
Print the authoritative guide for writing a function. vibe-server fetches it from ai-studio (the same source the platform uses), so it's always current — prefer this over any static example when authoring. Org-level; takes no --app.
facilio vibe function instructions# capture it to a file, or hand it to an AI agent:facilio vibe function instructions > function-guide.mdfacilio vibe function create <name>#
Upload a new function from a code file. Fails if a function with that name already exists in the app.
facilio vibe function create workorderlist --code ./workorderlist.js --description "List open work orders"| Flag | Required | Notes |
|---|---|---|
--code <path> | yes | Path to the function source file. Prompted if omitted. |
--package <path> | no | Optional package.json for the function's dependencies. |
--description <text> | no | Stored with the function; shown in facilio vibe function list. |
--app <linkName> | no | Override the app target from vibe.json. |
facilio vibe function update <name>#
Replace an existing function's source. Fails if it doesn't exist. Re-uploading invalidates the previous build — run facilio vibe function build again afterward. Same flags as create.
facilio vibe function update workorderlist --code ./workorderlist.jsfacilio vibe function build <name>#
Compile the uploaded source to WASM. Synchronous — may take a while. On success it lists the discovered handler names.
facilio vibe function build workorderlist✓ Built "workorderlist". Built at: 2026-07-03T09:14:02Z WASM size: 480321 bytes Handlers: list Run one with `facilio vibe function run workorderlist <handler>`.facilio vibe function run <name> <handler> (alias exec)#
Execute one handler of a built function. --args is a JSON object of the handler's arguments. Secrets are injected by the backend (see above) — you never pass them. Prints the handler's return value.
facilio vibe function run workorderlist list --args '{"limit": 20}'| Flag | Notes |
|---|---|
--args <json> | Handler arguments as a JSON object. Defaults to {}. |
--app <linkName> | Override the app target from vibe.json. |
facilio vibe function list (alias ls)#
List the app's functions.
facilio vibe function listNAME BUILT DESCRIPTION------------- ----- ---------------------workorderlist yes List open work ordersfacilio vibe function get <name> (alias show)#
Show a function's build state, description, and source. Pass --code-only to print just the code — handy for redirecting to a file.
facilio vibe function get workorderlistfacilio vibe function get workorderlist --code-only > workorderlist.jsfacilio vibe function delete <name> (alias rm)#
Delete a function and all its artifacts.
facilio vibe function delete workorderlistRunning from a deployed app: functions can also be invoked from the browser at runtime via
vibe.executeFunction. The backend resolves which app you're in from the subdomain, so browser calls run only that app's functions.
Scheduled Jobs — facilio vibe jobs (alias job)#
Scheduled jobs run one of your app's built functions on a recurring schedule — cron or fixed interval — without a browser being open. Every fire executes as the app's dedicated public user (the one facilio vibe app create provisions), so the function's identity, tokens, and DB access work exactly like an authenticated request. Jobs are app-scoped — resolved from --app or vibe.json.
Typical loop: create a job → it fires on schedule → observe Last run on the CLI, or the row it writes in your app's DB / UI.
Product-level bounds#
Two hard bounds the server enforces on every create and update — they're a product decision, not a technical limit:
| Bound | Value | Why |
|---|---|---|
| Min interval between fires | 15 minutes | Consecutive fires of the same job cannot be closer than this. For interval schedules, intervalSeconds >= 900. For cron, the CLI parses your expression and rejects it if two consecutive slots are under 15 min apart. |
| Max timeout per fire | 15 minutes (900 s) | A single fire cannot claim more than 15 min of wall clock — after that the runner is cancelled and the fire is recorded as failed. Also the default when --timeout is omitted. |
Prerequisites#
Before you can schedule a job:
- The app must exist (
facilio vibe app create). - The function must be built (
facilio vibe function build <name>). - The app must have been promoted to production — scheduled jobs run against the prod function name (
<name>_<uuid>), never against preview. If you schedule a job on an app that only exists on preview, every fire will fail with "function not found".
facilio vibe jobs create <name>#
Schedule a new job. Requires --function and exactly one of --cron or --interval.
# Cron — every day at 9 AM (in the org's timezone; falls back to UTC)facilio vibe jobs create daily-report \ --function sendDailyReport \ --handler default \ --cron '0 0 9 * * *' \ --payload '{"recipients":["ops@example.com"]}' \ --timeout 300
# Interval — every 30 minutes, minimal flagsfacilio vibe jobs create healthcheck \ --function pingUpstream \ --interval 1800
# Create as PAUSED — no scheduler row until you `resume`facilio vibe jobs create monthly-cleanup \ --function purgeStale \ --cron '0 0 3 1 * *' \ --paused| Flag | Required | Notes |
|---|---|---|
--function <name> | yes | Logical function name from facilio vibe function list. Must be built. |
--handler <name> | no | Entry point inside the function. Omit → SDK default. |
--cron <expr> | one of these | Spring 6-field cron: second minute hour day-of-month month day-of-week. Consecutive fires must be ≥ 15 min apart. |
--interval <seconds> | one of these | Fixed-delay in seconds. Must be ≥ 900. |
--timeout <seconds> | no | Wall-clock per fire, 1..900. Defaults to 900 (15 min). |
--payload <json> | no | JSON object forwarded verbatim as the function's args at every fire. |
--paused | no | Create in paused state; scheduler row is written only when you resume. |
--app <linkName> | no | Override the app target from vibe.json. |
Cron vs interval — which to pick#
| Reach for cron when... | Reach for interval when... |
|---|---|
| Timing matters to the outside world — "9 AM daily", "1st of the month", "Monday 8 AM". | The exact moment doesn't matter — "roughly every N minutes". |
| You want fires aligned to wall-clock ticks. | You want guaranteed spacing regardless of how long the last run took. |
| You care about "which day" or "which weekday". | Health checks, polling, periodic sync. |
Same 15-min floor and 900-s timeout ceiling apply to both. Cron expressions are interpreted in the org's configured timezone (from your Facilio account settings), falling back to UTC.
facilio vibe jobs list (alias ls)#
List every job configured for the app. Newest-first.
facilio vibe jobs listNAME FUNCTION SCHEDULE STATUS LASTRUN------------ -------------- ------------- ------- ---------------------------------daily-report sendDaily... cron 0 0 9... active 2026-07-17T03:00:04Z (success)healthcheck pingUpstream every 1800s active —facilio vibe jobs get <name> (alias show)#
Full detail — schedule, timeout, payload, and last-run outcome including any error.
facilio vibe jobs get daily-reportdaily-report (id 1, status=active) Function : sendDailyReport › default Schedule : cron 0 0 9 * * * Timeout : 300s Payload : {"recipients":["ops@example.com"]} Last run : 2026-07-17T03:00:04Z — successIf a fire failed, Last run shows failed and Error shows the failure reason (truncated to ~2 KB). That's the single most useful signal for debugging.
facilio vibe jobs update <name>#
Partial PATCH — pass only the flags you want to change. Everything else stays as it was. Immutable fields (name, functionName) can't be updated — delete + recreate to change either.
# Change the schedulefacilio vibe jobs update daily-report --cron '0 30 9 * * *'
# Replace the payloadfacilio vibe jobs update daily-report --payload '{"recipients":["ops@example.com","cto@example.com"]}'
# Bump the timeoutfacilio vibe jobs update healthcheck --timeout 60| Flag | Notes |
|---|---|
--handler <name> | Change the handler entry point. |
--cron <expr> | Switch to a cron schedule. Rejects --interval on the same call. |
--interval <seconds> | Switch to an interval schedule. Rejects --cron on the same call. |
--timeout <seconds> | Change the wall-clock timeout. Same 1..900 bound. |
--payload <json> | Replace the payload (JSON object). |
--status <active\|paused> | Enable / disable firing without deleting the row. |
--app <linkName> | Override the app target from vibe.json. |
Passing no flags is rejected — nothing to update.
facilio vibe jobs pause <name>#
Sugar for update --status paused. Stops firing but keeps the row so you can resume later without losing the schedule or payload.
facilio vibe jobs pause daily-reportBehind the scenes: cancels the scheduled_tasks row so a paused job incurs zero scheduler wakeups. The Vibe_App_Jobs row stays intact.
facilio vibe jobs resume <name>#
Sugar for update --status active. Re-creates the scheduler row using the job's current schedule; first fire is at "now + interval" (interval jobs) or the next cron slot (cron jobs).
facilio vibe jobs resume daily-reportfacilio vibe jobs delete <name> (alias rm)#
Remove the job and cancel its scheduler row.
facilio vibe jobs delete daily-reportFailure model — keep it simple#
- A failed fire keeps firing on schedule. No exponential backoff, no max-retries pause — the job stays
activeand fires again next slot.LAST_RUN_STATUS+LAST_RUN_ERRORon the row (visible viajobs get) record the last outcome. - Broken job? Pause it manually.
facilio vibe jobs pause <name>. Fix the function or the payload, thenresume. - Wall-clock timeout hits? The runner is cancelled with an interrupt, the fire is recorded as
failed: timed out after Ns, and the job reschedules to its next slot as usual.
Common rejection reasons#
| Message | Cause |
|---|---|
intervalSeconds must be at least 900 | Interval schedule shorter than 15 min. |
cronExpression must schedule fires at least 15 minutes apart | Cron expression whose consecutive slots are too close. |
timeoutSeconds must be at most 900 | Timeout above the 15-min ceiling. |
function '<name>' not found in app <linkName> | Function doesn't exist. Build it first. |
app has no public user provisioned | Legacy app that predates the public-user feature — redeploy the app to provision one. |
job '<name>' already exists in app <linkName> (409) | Job name isn't unique per app. Pick a different name or update the existing one. |
Agents — facilio vibe agent (alias agents)#
Agents are LLM-backed handlers registered against an app and addressed by a logical name (hello-agent) you pick at creation. Every agent is app-scoped — resolved from --app or vibe.json.
An agent has:
- a model provider + model name (
openai/claude/google,gpt-4o-mini,claude-3-5-sonnet, …), - optional
roleandinstructions(system-prompt scaffolding), - optional structured output — a JSON Schema the runtime enforces on the model's reply,
- a
statefulflag: whentrue, runtime calls (via the SDK) remember the conversation per signed-in user across reloads and device switches.
Typical loop: create the agent → run it once from the CLI to sanity-check → call it from the browser with vibe.executeAgent(name, input).
facilio vibe agent create <name>#
Create a new agent. Fails if <name> already exists in the app.
# Minimal — free-form chat agentfacilio vibe agent create hello-agent \ --model-provider openai \ --model-name gpt-4o-mini \ --role "You are a warm greeter." \ --instructions "Greet the user by name if given, otherwise say hello. One short sentence."
# Stateful — persistent per-user thread at runtimefacilio vibe agent create hello-agent-stateful \ --model-provider openai \ --model-name gpt-4o-mini \ --instructions "Greet warmly. If the user told you their name in an earlier turn, use it." \ --stateful
# Structured output — reply is validated against a JSON schemafacilio vibe agent create sentiment-agent \ --model-provider openai \ --model-name gpt-4o-mini \ --instructions "Classify the input's sentiment. Reply strictly as JSON matching the schema." \ --output-schema-file agent-schemas/sentiment.json| Flag | Required | Notes |
|---|---|---|
--model-provider <name> | yes | openai, claude, google, … |
--model-name <name> | yes | Provider model id, e.g. gpt-4o-mini, claude-3-5-sonnet. |
--model-params <json> | no | Provider-specific model params as a JSON string. |
--api-key <key> | no | Provider API key when the agent uses a caller-supplied key rather than the org's shared key. |
--role <text> | no | Short role scaffolding prepended to the system prompt. |
--instructions <text> | no | System instructions the agent runs with. |
--description <text> | no | Human-readable description; shown in list / get. |
--output-schema <json> | no | Structured output schema as an inline JSON string. Mutually exclusive with --output-schema-file. |
--output-schema-file <path> | no | Structured output schema loaded from a file (JSON). |
--stateful | no | Maintain a per-user persistent thread on runtime runs. Default: stateless. |
--hidden | no | Hide the agent from external agent-catalog listings. |
--app <linkName> | no | Override the app target from vibe.json. |
Output schema requirements#
The schema is a standard JSON Schema object, with two Facilio-specific rules:
titleis mandatory and must match[A-Za-z0-9_-]{1,64}— it becomes the provider's response schema name. Missing → the server returns400 output schema title is mandatory and must match [A-Za-z0-9_-]{1,64}.- Numeric bounds (
minimum/maximum) are not preserved on the server round-trip.type,enum,items,properties,required,description, andadditionalPropertiesall survive. If you need a hard numeric range, put it in the instructions ("confidence between 0 and 1") — the model honors that in practice.
Minimal schema shape:
{ "title": "SentimentResult", "type": "object", "properties": { "sentiment": { "type": "string", "enum": ["positive", "negative", "neutral"] }, "confidence": { "type": "number" }, "keywords": { "type": "array", "items": { "type": "string" } }, "summary": { "type": "string" } }, "required": ["sentiment", "confidence", "keywords", "summary"], "additionalProperties": false}facilio vibe agent update <name>#
Update an existing agent. Fails if <name> does not exist. Accepts the same flags as create — every field is mutable except --stateful.
facilio vibe agent update hello-agent \ --model-name gpt-4o \ --instructions "Be even warmer. Two short sentences max."--stateful is permanent
The stateful flag is fixed at creation and cannot be toggled with update. To switch a stateless agent to stateful (or vice-versa), delete the agent and create a new one with the flag you want. The logical name can be reused, so calls to vibe.executeAgent('<name>', input) in your UI keep working.
facilio vibe agent list (alias ls)#
List every agent in the app.
facilio vibe agent listNAME AGENTID STATEFUL LINKNAME-------------------- ------- -------- -----------------------------------------------------hello-agent 5811 no hello-agent_55bc5734508041e38d1d99ec233ec6dfhello-agent-stateful 5812 yes hello-agent-stateful_55bc5734508041e38d1d99ec233ec6dfsentiment-agent 5813 no sentiment-agent_55bc5734508041e38d1d99ec233ec6dffacilio vibe agent get <name> (alias show)#
Print the agent's full configuration: provider, model, role, instructions, and — if set — the output_schema (returned verbatim, including your title, required, additionalProperties, and per-property enum / items / description).
facilio vibe agent get sentiment-agentUse this to confirm your schema was accepted as-is.
facilio vibe agent run <name> (alias exec)#
Run the agent once at author time and print the response. This is a developer harness, not the runtime path — the browser calls the agent via vibe.executeAgent.
# One-shot callfacilio vibe agent run hello-agent --input "Say hi to Vishnu"
# Continue an existing conversation — carries context across callsfacilio vibe agent run hello-agent-stateful \ --input "What's my name?" \ --thread-id 31541| Flag | Required | Notes |
|---|---|---|
--input <text> | yes | Prompt / input for the agent. |
--thread-id <id> | no | Continue a specific conversation from an earlier run (author-time only). Omit → a fresh conversation is started. |
--app <linkName> | no | Override the app target from vibe.json. |
Response shape — the CLI prints the run result as JSON. Key fields:
{ "status": "completed", "response": { "content": "Hello, Vishnu!", "thread_id": 31541, "id": 123682, "role": "system" }, "run_id": 41788, "thread_id": 31541}For structured-output agents, response.content is a JSON string (not a nested object) — parse it with JSON.parse(...) on the client. See the executeAgent SDK reference for the runtime equivalent.
run is stateless by default, even for stateful agents
The --stateful flag applies to runtime calls made via the SDK — that path uses the current user's identity to resolve or mint a persistent thread. facilio vibe agent run is a CLI dev harness and opens a new thread on every invocation unless you pass --thread-id. Chain calls with --thread-id <last_thread_id> to observe multi-turn memory from the CLI.
File attachments are runtime-only
facilio vibe agent run takes text only. To have an agent read an image or a document, upload the file from the browser with vibe.uploadFile and pass its id: vibe.executeAgent(name, input, { fileIds: [id] }). Images go to the model's vision input, other content types are extracted into its context, and a single run is capped at 10 files. See attachments.
facilio vibe agent delete <name> (alias rm)#
Delete the agent — the app can no longer invoke it after this call.
facilio vibe agent delete hello-agentCommon rejection reasons#
| Message | Cause |
|---|---|
output schema title is mandatory and must match [A-Za-z0-9_-]{1,64} (400) | Schema is missing title, or title has disallowed characters. Add "title": "SomeName" at the schema root. |
link_name cannot be updated. It is immutable after creation. (403) | You tried to change --stateful on an existing agent. Delete + recreate. |
agent '<name>' already exists in app <linkName> (409) | Agent name isn't unique per app. Pick a different name or update the existing one. |
Connection provider — facilio vibe connection (alias conn)#
Every Vibe app can expose its own outbound connection — a first-class Facilio Connection built out of the app's own functions. Once enabled, each of the app's function handlers can be registered as an action on that connection, discoverable and callable via the standard facilio connections surface (search / schemas / execute), by other apps, and by AI agents. Under the hood the CLI wraps the four author verbs on vibe-server's CliConnectionController.
All commands are app-scoped — resolved from --app or vibe.json.
Lifecycle: enable the provider → register actions from built handlers → update / toggle → publish.
facilio vibe connection enable#
Turn on the connection-provider addon for the current app. Creates the connection on connections-server and stores its slug in the app's addon config. Idempotent — re-running is a no-op that returns the existing config.
facilio vibe connection enable✔ Connection provider enabled. Slug : my-dashboard Id : 42 Base URL : https://my-dashboard.vibe.facilio.com| Flag | Notes |
|---|---|
--app <linkName> | Override the app target from vibe.json. |
facilio vibe connection update#
Partial update of the connection's metadata. Pass only the fields you want to change — passing none is rejected.
facilio vibe connection update --display-name "My Dashboard" --description "Ops dashboard connection"facilio vibe connection update --active false| Flag | Notes |
|---|---|
--display-name <text> | Human-readable name shown in the Connections catalog. |
--description <text> | Update the description. |
--base-url <url> | Override the base URL connections-server calls back to. |
--active <true\|false> | Toggle active status. |
--app <linkName> | Override the app target from vibe.json. |
facilio vibe connection actions list (alias ls)#
Table of actions registered on the app's connection.
facilio vibe connection actions listfacilio vibe connection actions list --draft --q workorderSLUG NAME TYPE ACTIVE DRAFT------------------ ------------------ ----- ------ -----list_workorders List work orders READ true noclose_workorder Close a work order WRITE true yes| Flag | Notes |
|---|---|
--draft | Include draft rows. |
--q <text> | Filter by name / slug substring. |
--app <linkName> | Override the app target from vibe.json. |
facilio vibe connection actions get <slug> (alias show)#
Full detail on one action — type, active/draft state, description, and the derived request template plus input / output JSON Schemas.
facilio vibe connection actions get list_workordersfacilio vibe connection actions get list_workorders --draft| Flag | Notes |
|---|---|
--draft | Read the draft copy instead of the live one. |
--app <linkName> | Override the app target from vibe.json. |
facilio vibe connection actions create <displayName>#
Register a function-handler pair as an action on the connection. The server derives the request_template from --function + --handler — you never hand-craft it. The function must already be built (facilio vibe function build <name>).
facilio vibe connection actions create "List work orders" \ --function workorderlist --handler list --type read \ --description "Return open work orders (optionally capped by limit)" \ --input-schema '{"type":"object","properties":{"limit":{"type":"number"}}}'| Flag | Required | Notes |
|---|---|---|
--function <name> | yes | Vibe function this action invokes. |
--handler <name> | yes | Handler entry point inside the function. |
--type <read\|write\|destructive> | yes | Action semantics — drives the LLM safety hints in the Connections catalog. |
--slug <slug> | no | Custom slug (defaults to a server-derived slug from the display name). |
--description <text> | no | Optional description surfaced in facilio connections search. |
--input-schema <json> | no | JSON Schema for input args, as a JSON string. |
--output-schema <json> | no | JSON Schema for the handler's return payload. |
--active <true\|false> | no | Set active on creation. |
--app <linkName> | no | Override the app target from vibe.json. |
Actions are written as draft — publish the connection from the platform UI to promote them.
facilio vibe connection actions update <slug>#
Partial update of an action. Writes to the draft copy. Immutable field: slug. To change the underlying handler, pass --function and --handler together — the server rewrites the request_template from that pair.
facilio vibe connection actions update list_workorders --description "Return open work orders for this tenant"facilio vibe connection actions update list_workorders --function workorderlist --handler listOpenfacilio vibe connection actions update list_workorders --active false| Flag | Notes |
|---|---|
--display-name <text> | Change the display name. |
--function <name> | Re-target — must be passed together with --handler. |
--handler <name> | Re-target — must be passed together with --function. |
--type <read\|write\|destructive> | Change action semantics. |
--description <text> | Update the description. |
--input-schema <json> | Replace the input JSON Schema. |
--output-schema <json> | Replace the output JSON Schema. |
--active <true\|false> | Toggle active status. |
--app <linkName> | Override the app target from vibe.json. |
Passing no updatable flags is rejected. Passing --function without --handler (or vice versa) is rejected.
2. Connections commands#
Facilio Connections is Facilio's integration layer: a catalog of 1000+ external apps (Xero, Salesforce, HubSpot, Slack, …) and 5000+ actions across them, all callable through one authenticated interface. From the CLI, you can discover actions, authorize accounts, and run those actions directly — using the same facilio login session you use for Vibe.
Every Connections command lives under facilio connections <cmd>. The typical loop is:
search— find actions by natural-language description ("create xero invoice").schemas— look up the input/output JSON Schemas for the actions you found.link— authorize the account (once per connection per user).execute— run the action with a JSON payload.
list, unlink, and wait are used less frequently to inspect and manage connected accounts.
Global flags#
These flags apply to every facilio connections <cmd> invocation:
| Flag | Notes |
|---|---|
--app <slug> | Scope every call to one connection. Faster than searching across all 1000+ apps when you already know which one you're targeting. |
--json | Print raw JSON payloads. Use in scripts and agents. |
facilio connections search#
Find actions by use case. The query is free-form natural language; the server ranks matching actions across every connection.
facilio connections search create xero invoicefacilio connections search list open workorders --app facilio-cmmsfacilio connections search send slack message --jsonReturns action slugs of the form <connection>.<action> (e.g. xero.create_invoice). Use those slugs with schemas and execute.
facilio connections schemas <action_slugs...>#
Show the input (and optionally output) JSON Schemas for one or more actions.
facilio connections schemas xero.create_invoicefacilio connections schemas xero.create_invoice salesforce.create_lead --with-output| Flag | Notes |
|---|---|
--with-output | Also fetch the output schema so you know how to parse the response. |
Feed the input schema to an LLM or read it yourself to build the --params payload for execute.
facilio connections list <connections...> (alias ls)#
Show your connected accounts for one or more connections. Use to check whether you've already authorized an app and to look up the account_slug for execute --account.
facilio connections list xerofacilio connections list xero salesforce hubspotfacilio connections link <connection>#
Authorize a connection. Opens the OAuth URL for the target app in your browser (or prints it, so you can complete the auth on another device).
facilio connections link xerofacilio connections link salesforce --wait --timeout 120facilio connections link hubspot --no-open # prints the URL only| Flag | Notes |
|---|---|
--no-open | Don't auto-open the browser. Useful on headless machines. |
--wait | Block until the connection becomes ACTIVE. |
--timeout <seconds> | With --wait, give up after this many seconds. |
facilio connections unlink <connection>#
Remove your authorization for a connection. Prompts to confirm.
facilio connections unlink xerofacilio connections unlink xero --yes # skip confirmationfacilio connections wait <connections...>#
Poll until one or all of the named connections become ACTIVE. Handy after link --no-open when the OAuth handshake happens on a different device.
facilio connections wait xerofacilio connections wait xero salesforce --mode all --timeout 300| Flag | Notes |
|---|---|
--mode <mode> | any (default — exit as soon as one becomes active) or all (wait for every named connection). |
--timeout <seconds> | Give up after this many seconds. |
facilio connections execute [action_slugs...] (alias exec)#
Run one or more actions. Pass the JSON payload with --params — repeat --params once per slug to run several actions in parallel.
# Run one actionfacilio connections execute xero.create_invoice --params '{"amount":100,"contactId":"abc"}'
# Read the payload from stdin (safer for secrets and long payloads)cat payload.json | facilio connections execute xero.create_invoice --params -
# Target a specific connected account (from `connections list`)facilio connections execute xero.create_invoice --params '{"amount":100}' --account xero-us
# Validate without executing — prints the request that would be sentfacilio connections execute xero.create_invoice --params '{"amount":100}' --dry-run
# Just show the input schema and exit (equivalent to `connections schemas`)facilio connections execute xero.create_invoice --get-schema
# Batch mode — read {action_slug, arguments, account_slug?} tuples from a JSON filefacilio connections execute --file batch.json
# Run multiple actions in parallel — one --params per slug, in orderfacilio connections execute xero.create_invoice salesforce.create_lead \ --params '{"amount":100}' \ --params '{"name":"Acme"}'| Flag | Notes |
|---|---|
--params <json> | Action arguments as a JSON object. Use - to read from stdin. Repeat once per slug to run several actions in parallel. |
--account <slug> | Target a specific connected account (from facilio connections list). Only relevant when you have multiple accounts for the same connection. |
--file <path> | Batch mode. Reads a JSON array of {action_slug, arguments, account_slug?} objects. |
--dry-run | Validate arguments against the action schema and print the request without executing. |
--get-schema | Print the input schema for the given action slugs and exit — no execution. |
Common errors#
| Error | Cause | Fix |
|---|---|---|
Not logged in | No token in the OS keychain or file fallback | Run facilio login |
vibe.json not found | Running facilio vibe deploy outside the project root | cd into the project root, or run facilio vibe app create first |
index.html missing in dist/ | Build didn't emit an entry point | Check your bundler config; ensure the output dir matches build.publish |
EACCES on npm install -g | Node was installed via system package manager | Reconfigure npm prefix (see Install) — do not use sudo |
App with linkName already exists | Re-running facilio vibe app create for an app that already exists | Skip this step; subsequent deploys use vibe.json |
Env vars#
All env vars use the FACILIO_ prefix. Legacy VIBE_ names from the old @facilio/vibe-cli are still honored as silent fallbacks so existing setups keep working.
| Variable | Purpose |
|---|---|
FACILIO_API_KEY | Log in via API key without the browser flow. Set once and every command authenticates from it. Preferred for CI. |
FACILIO_REGION | Region key for API-key logins (US, UK, AE, AU, …). Resolves the right server URLs. Defaults to US. |
FACILIO_NO_KEYCHAIN | Set to 1 / true / yes to skip the OS secret store and use ~/.facilio/credentials.json (0600) instead. Useful in CI without a session keyring. |
FACILIO_VIBE_SERVER_URL | Override the vibe-server API URL. Defaults to the server picked at login. |
FACILIO_IDENTITY_SERVER_URL | Override the identity-service base URL. Defaults are set per environment. |
FACILIO_CONNECTIONS_SERVER_URL | Override the connections-server endpoint. Defaults to the region default picked at login. |
FACILIO_CLIENT_ID | Override the OAuth client id. |
End-to-end script#
For an agent shipping an app on a fresh machine:
# 0. Bootstrap Node if missingcommand -v node >/dev/null 2>&1 || { curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash export NVM_DIR="$HOME/.nvm"; \. "$NVM_DIR/nvm.sh" nvm install --lts && nvm use --lts}
# 1. Scaffold the projectmkdir -p ~/vibe-apps && cd ~/vibe-appsnpm create vite@latest my-dashboard -- --template reactcd my-dashboardnpm installnpm install @facilio/vibe-sdk
# 2. Install the CLI (fall back to npx if global install fails)npm install -g @facilio/cli 2>/dev/null || echo "Falling back to npx"
# 3. Authenticate — ONLY step where a human clicks oncefacilio login
# 4. Create the appfacilio vibe app create
# 5. Edit src/ to use createVibe(), executeAction(), etc.
# 6. Build and deploynpm run build && facilio vibe deploySee Getting Started for the walkthrough with code, and Building with AI Agents for the full agent recipe.