🧱 App Starter
Every screen an app user can reach, already built, on @fleetless/sdk and the
Nuxt UI dashboard. Clone it, point it at an app, and the robots a role grants
appear.
Sign-in by password or emailed code, two-factor (the challenge, recovery codes, the setup an app can require, and turning it on or off under Account › Security), registration, verification, invitations, password reset, single sign-on and the MCP consent screen; the robots the signed-in person reaches; and on each robot every datapoint, action, service, publisher, camera, run history and asset their role grants. It is MIT, at github.com/fleetless/app-starter, and it is a GitHub template repository.
What a screen shows is decided by the person’s role in the console, re-read on every call. A role that grants nothing sees a polite empty page; a role that grants everything sees it all. The app does not decide, it draws.
Run it
Node 22 and pnpm (corepack enable), then three commands:
pnpm install
cp .env.example .env # fill in NUXT_PUBLIC_APP_IDENTIFIER
pnpm dev
NUXT_PUBLIC_APP_IDENTIFIER is the app’s identifier from the console,
NUXT_PUBLIC_API_URL is the cloud it talks to, and NUXT_DEV_ALLOWED_HOSTS
is the comma-separated list of DNS names the dev server may answer to. The
shipped NUXT_PUBLIC_API_URL points at a local dev stack, so set it to
https://api.fleetless.dev unless you run one — a cloud that answers nothing
fails the way a missing origin does, as a network error with no status.
Leave NUXT_DEV_ALLOWED_HOSTS empty unless you reach the dev server by a
DNS name, through a tunnel or from another machine on the network. Vite
refuses a host header it was not told about, which is what stops a
DNS-rebinding attack on your dev server; localhost and plain IP addresses
need no entry.
Set it up in the console
An app in the console is what this template draws; see Create an App for what an app is. Five settings, in this order. Each names the refusal you see without it, so you can tell a missing setting from a broken app.
-
Create an app on the Apps page. Its identifier is lowercase letters, digits and underscores, starting with a letter, and it goes into
.envasNUXT_PUBLIC_APP_IDENTIFIER. Without it, register, password reset and the provider list answer404 not_found, and sign-in answersinvalid_credentialslike every other miss. -
Registration page,
allowed_origins: the origin the app runs on, bare — scheme, host and port, no path, no trailing slash. Without it, every call fails as a network error with no status and no body, because the browser refuses the answer before your code sees it.Optional, once the app runs somewhere the people you invite can reach: on the app’s Sign-in page, under Pages, switch Invitation, Email verification, Password reset and MCP sign-in to Your app with these URLs. Until then Fleetless’s hosted pages answer every mailed link and the MCP sign-in (see Create an App).
Setting Value verify_url<origin>/auth/verify/{token}reset_url<origin>/auth/reset/{token}invite_url<origin>/auth/invite/{token}mcp_login_url<origin>/mcp/{interaction}, withmcp_enabledon -
Settings page,
default_role_id: the role a self-registered account gets. A new app has none. Without it, registration answers409 target_state_conflictnaming the field, and a federated callback answersno_access. -
Roles page: tick the datapoints, actions, services, publishers and cameras the role grants, plus the
action_historyandassetscapabilities if the Activity and Assets tabs should exist. A new role grants nothing. Without a grant, sign-in works and the robot list is empty. -
Attach a robot to the app on its page, then create or invite an app user on the Users page. That user signs in here with a password or an emailed code, whichever the app’s Sign-in page turns on (password by default). If it requires two-factor, the starter sets it up at the first sign-in. Your own console login will not, and answers
invalid_credentialswhen you try: it is a Fleetless user, a different identity space — see Manage Users and Roles.
Then sign in. The robot appears; click it.
Or let an agent do it
The five steps above are also ten tool calls, eleven when the mailed links
should open in the app, one more per extra robot. Paste the prompt below
into an MCP client connected to the central endpoint,
https://mcp.fleetless.dev, signed in as a Fleetless user of your
organisation. Anything that speaks MCP will do.
The block is the starter’s own AGENT-SETUP.md prompt, with item ©'s
default origin spelled out in words rather than as a literal address. Step 4
grants presence, a switch nothing consults yet and harmless to set.
You are setting up a Fleetless app for the app-starter template using the Fleetless MCP tools on the central endpoint. Work through the steps in order, call the tools rather than describing them, ask me only the questions in step 1, and invent no values. If a tool refuses, stop, quote the code and the sentence it returned, and ask me how to continue.
1. Ask me for, in one message:
(a) the app name;
(b) the app identifier — lowercase letters, digits and underscores, starting with a letter;
(c) the origin the app runs on, defaulting to the one the dev server prints when it starts;
(d) which robots to attach — call console_robots_list first and show me their names;
(e) the email address of the first app user;
(f) whether Fleetless should mail the invitation, or hand me the link;
(g) whether mailed links and the MCP sign-in should open in this app instead of on Fleetless's hosted pages — default no; yes only once the app runs at an origin the people I invite can reach.
2. Create the app with console_app_create: name, identifier and the chosen robot_ids. Keep the app id.
3. Read the auth configuration with console_app_auth_config_get, to see the current self_registration, allowed_domains and app_url. Then write each slice, carrying every field that slice takes:
- console_app_auth_config_registration_put: self_registration and allowed_domains exactly as read, allowed_origins set to the origin from (c), bare — scheme, host and port, no path, no trailing slash
- console_app_auth_config_mcp_put: mcp_enabled true
- only if I said yes in (g), console_app_auth_config_urls_put: app_url exactly as read, invite_url <origin>/auth/invite/{token}, verify_url <origin>/auth/verify/{token}, reset_url <origin>/auth/reset/{token}, mcp_login_url <origin>/mcp/{interaction}; otherwise leave the URLs unset, and Fleetless's hosted pages answer those links
4. Create a role named "Operator" with console_role_create and keep its id. For every attached robot call console_robot_exposures and collect its slugs. Then call console_role_permissions_put once, with one grant per robot carrying all of that robot's slugs, and the capabilities action_history, presence and assets all true. A robot with no exposures gets no grant; note it for the report.
5. Make that role the app's default with console_app_update, default_role_id.
6. Invite the first user with console_app_user_invite: the email from (e), role_id of the Operator role, send_mail true if I chose mail in (f), otherwise false.
7. Report, and nothing else:
- the line for .env: NUXT_PUBLIC_APP_IDENTIFIER=<identifier>
- the invitation: "mailed to <email>", or the accept_url exactly as returned
- whether the mailed links open in this app or on Fleetless's hosted pages
- every robot that was attached and what it was granted, and any robot that exposes nothing
- anything that was refused, with the code and the sentence
The agent asks seven things once and does the rest, then reports the .env
line and the invitation. It cannot choose a password, because the
invitation is where the person sets one when the app signs in by password,
and it cannot grant what a robot does not expose. A robot whose
configuration has no datapoints, actions, services, publishers or cameras
is attached, granted nothing, and named in the report.
Make it yours
app/app.config.ts— the app’s name, its description, the four brand files and the primary colour.public/brand/— four files, replaced in place; nothing reads them by another name.icon-light.svgandlogo-light.svgare dark ink for a light ground,icon-dark.svgandlogo-dark.svglight ink for a dark one, and the page picks by theme because an image takes no colour from the document around it. The favicon is the dark-ground icon for both tab strips.- A page added under
app/pages/is covered by the auth guard already. A tab on the robot page comes from extendingtabsForinapp/utils/datasheet.ts. - The robot page’s 3D tab renders the robot’s synced URDF, textures and
.dae-embedded images included, withclient.assets.prepareUrdfSceneand three.js withurdf-loader. It moves the joints from thejoint_statesdatapoint when the role grants it, and a robot without a synced URDF shows a notice instead. The component isModelView.client.vue.
What is where
| Path | What |
|---|---|
app/plugins/fleetless.client.ts |
The one SDK client, with a localStorage token store. |
app/composables/useSession.ts |
Who is signed in, and where an expired session goes. |
app/middleware/auth.global.ts |
Everything but /auth/* and /mcp/* needs a session. |
app/pages/auth/* |
Login, register, verify, forgot, reset, invite, the OIDC callback. |
app/composables/useSignInResult.ts |
Where every sign-in result goes: signed in, or on to the second factor. |
app/pages/auth/two-factor/* |
The second factor: the code or a recovery code, and the setup an app can require. |
app/pages/account/security.vue |
Two-factor on or off, from the user menu’s Security. |
app/pages/mcp/[interaction].vue |
The consent screen an AI tool sends its user to. |
app/pages/robots/* |
The list, and the robot page whose tabs come from the datasheet. |
app/components/robot/ParameterForm.vue |
A form built from a datasheet’s input_schema. |
app/utils/errors.ts |
Every refusal’s one sentence. |
pnpm lint && pnpm typecheck && pnpm test && pnpm build is the check set, and
pnpm generate writes a static dist/ any web server serves. The cloud is the
only backend. The SDK reference documents everything this
app calls.