Skip to content

CLI reference

Terminal window
pip install linkworld
linkworld --help

Scaffold a new app in ./<slug>. Generates the manifest, server-side handler stub, Dockerfile, deploy workflow, and a web/ subdir with a working frontend bundle (HTML/CSS/JS) wired to the postMessage bridge.

FlagDefaultDescription
--langpythonpython or typescript
--namederived from slugDisplay name
--descriptionemptyOne-line description
--github-owneryour-orgUsed in the GHCR image URL
--directory./<slug>Override target dir
Terminal window
linkworld init tax-bot --lang python --github-owner you
linkworld init tax-bot-ts --lang typescript --github-owner you

The scaffolded web/index.html already shows the bridge protocol (init listener, tool call, resize). Edit web/styles.css and web/app.js to build your real UI; see Building app UIs.

Open a browser to GitHub OAuth. The CLI receives a JWT and stores it at ~/.linkworld/config.toml.

Terminal window
# OAuth flow
linkworld login
# Fallback: paste an admin-issued JWT
linkworld login --token eyJhbGc...

Print the current dev account (email, github_login, publish_allowed, tenant context). Useful in CI to verify the right token is in LINKWORLD_TOKEN before a release deploys to production.

Terminal window
$ linkworld whoami
[email protected] (publish_allowed=true)

Exit codes: 0 if authenticated, 1 if not.

Forget the locally-stored token by rewriting ~/.linkworld/config.toml without the [auth] section. Server-side the token stays valid until its TTL expires — for revocation, contact a platform admin.

Terminal window
linkworld logout

linkworld apps list / linkworld apps show <slug>

Section titled “linkworld apps list / linkworld apps show <slug>”

Inspect what you’ve published.

Terminal window
$ linkworld apps list
slug name status current version
my-bot My Bot active 1.2.0
news-feed News Feed draft
$ linkworld apps show my-bot
my-bot My Bot
status: active
current version: 1.2.0
versions:
1.2.0 published success 2026-04-12 ghcr.io/you/my-bot:1.2.0
1.1.0 published success 2026-03-30 ghcr.io/you/my-bot:1.1.0

apps show is the fastest way to confirm a deploy actually landed. The status column reflects the platform’s view (draft = registered but no current_version set; active = at least one version is current; archived = explicitly retired by the dev).

Force tenant installs of an app to its current published version. Bypasses the per-install upgrade_policy — used in dev to push a freshly-published fix to test tenants without waiting for auto-upgrade.

FlagDefaultDescription
--target-versioncurrent_versionPin to a specific published version.
--tenant <UUID>all installsRestrict to a single tenant.
--force-grant-new-scopesoffBypass the scope-expansion-needs-consent guard. Tenants normally re-consent in the UI when a new scope appears; this flag is for dev-tenant pushes only.
Terminal window
# Push the current version to every tenant that has an old one
linkworld upgrade my-bot
# Or just to a specific test tenant
linkworld upgrade my-bot --tenant 11111111-...

Diagnose the local environment. Runs the env-setup checks linkworld deploy would do anyway — but with actionable fix-hints when something’s off, so you don’t waste a deploy round-trip.

Five checks:

CheckWhat it verifies
login~/.linkworld/config.toml has a token AND the platform accepts it (/api/dev/me returns 200). Reports publish_allowed=false as a warn.
dockerdocker is on PATH and the daemon responds. Required unless you always deploy with --skip-build.
ghcr~/.docker/config.json shows a credential for ghcr.io (or a credHelper). Best-effort — false negatives possible with helper-based auth.
sdklinkworld-sdk is importable in this Python env (used by linkworld eval).
projectlinkworld.app.yaml exists in the current directory.

Exit code: 1 if any check failed, 0 if all-pass or warnings only. Run from the project root for the full picture.

Run the same preflight linkworld deploy runs, without deploying. Catches manifest errors, scope-existence problems, and cross-file inconsistency (e.g. ctx.tools.call("email_search", ...) when the manifest doesn’t declare mail.read) before you spend a build cycle on a version the platform would reject.

FlagDefaultDescription
--manifestlinkworld.app.yamlOverride the manifest path
Terminal window
$ linkworld lint
Running preflight checks…
preflight passed
# Or with errors:
$ linkworld lint
error linkworld.app.yaml required_scopes references unknown scope 'mail.send_attachments'
fix: Run `linkworld scopes` (or call `mcp.list_skills`) to see the catalog. Common typos: 'mail.send_attachments' 'mail.send'.

Exit codes: 0 on pass, 1 on any error-severity issue, 2 if the preflight request itself fails (network/auth).

Run preflight, build the Docker image, push to GHCR, register the version with the platform, and (by default) promote it to current_version.

FlagDefaultDescription
--versionderived from manifestOverride the version published
--skip-buildoffDon’t build/push; just register an existing image
--no-preflightoffSkip the preflight check. Use only when the platform is unreachable.
--no-promoteoffRegister the version but don’t make it current
--manifestlinkworld.app.yamlOverride the manifest path
--bundleauto-detectPath to the frontend bundle dir to upload. Auto-detects ./dist, then ./web. Pass --bundle '' to skip.

Deploy lifecycle:

  1. Preflight — POST manifest + source files to /api/dev/preflight. Manifest validation, lint (cross-file scope ↔ tool consistency), and scope-existence check against the running platform’s scope registry. If any error: print issues + fix-hints, exit 1.
  2. Image build + pushdocker build + docker push against the manifest’s runtime.image. Skipped with --skip-build.
  3. Version registrationPOST /api/dev/apps/<slug>/versions.
  4. Bundle upload — if ./dist or ./web exists (or --bundle is set), tarball + upload as the iframe content for /apps/<slug>.
Terminal window
# Common case: preflight + build + push + register + upload bundle
linkworld deploy
# CI flow: image already in GHCR, preflight + register
linkworld deploy --skip-build --version 1.2.3
# Skip bundle upload (headless app — no /apps/<slug> UI)
linkworld deploy --bundle ''
# Platform unreachable — bypass preflight (rarely needed)
linkworld deploy --no-preflight

Bundle upload is immutable per version. Re-deploying the same version with a different bundle is rejected — bump to a new version to publish updated UI. Tenants stay frozen on whatever version they installed until they explicitly upgrade.

Run a local dev loop for your frontend bundle. Spins up two HTTP servers — a static-file server with hot-reload for the bundle, and a “fake parent” that replicates the tenant shell’s postMessage bridge — so you can iterate on HTML/CSS/JS without a linkworld deploy round-trip.

FlagDefaultDescription
--dirauto-detectBundle directory. Defaults to ./dist, then ./web.
--app-idfrom manifestApp slug used in the init payload.
--mockslinkworld.dev.jsonJSON file mapping tool names → mock responses.
--bundle-port5173Port for the bundle’s static server.
--parent-port5174Port for the fake-parent shell.
--no-openoffDon’t auto-open a browser.
Terminal window
$ linkworld dev
linkworld dev
app_id: your-app
bundle: /path/to/web
mocks: /path/to/linkworld.dev.json (using defaults)
open at: http://localhost:5174/
bundle on: http://localhost:5173/
Edit files in the bundle dir; the iframe reloads automatically.
Ctrl+C to stop.

The fake parent ships a light/dark toggle in its chrome so you can verify the theme.changed flow without needing a real tenant shell. It also logs every bridge message to the chrome bar — handy when something stops working.

Drop this file next to your manifest to control what platform tool calls return:

{
"email_send": {
"ok": false,
"error": {
"decision": "scope_denied",
"message": "App didn't request mail.send.",
"neededScopes": ["mail.send"]
}
},
"echo": {
"ok": true,
"result": { "echoed": "args reflected back" }
}
}

If a tool is called that isn’t in the file, the parent returns { decision: 'unmocked', message: ... } so the absence is loud. The file is re-read every few seconds — edits pick up without restarting the server.

  • Not a real-platform integration test. Real tools, scopes, and audit logs only happen against api.linkworld.ai (or your local backend). For that, deploy to staging or run the full Linkworld monorepo on localhost:3000.
  • Not security-equivalent. CSP, sandbox, and origin gates the production CDN enforces are skipped here so you can iterate without fighting policy. The production behaviour kicks in as soon as you linkworld deploy.

Manage dev-default secrets visible to every tenant install of an app. Tenant-specific overrides live in the tenant’s app-settings UI.

Terminal window
linkworld secrets set OPENAI_KEY=sk-real-... --app tax-bot --description "LLM key"
linkworld secrets list --app tax-bot
linkworld secrets get OPENAI_KEY --app tax-bot # metadata only, never the value
linkworld secrets delete OPENAI_KEY --app tax-bot

Key format: ^[A-Z][A-Z0-9_]{0,63}$ (uppercase, digits, underscore; first char letter). Values are encrypted at rest with AES-256-GCM and never displayed after creation.

CodeMeaning
0Success
1Bad input (invalid slug, missing required flag)
2Platform error (4xx/5xx, network)
3Auth error (no token, expired, suspended)