Core Concepts
App (bundle)
An app = an entry file + optional additional sources/resources + metadata. Internally it is called a bundle. Apps have a lifecycle: In Development → Under Review → Published → Installed. The tabs in the app manager map to these stages.
Entry file and metadata header
Every app has one entry file (e.g. main.js / main.ts) whose top // ==UserScript== comment block declares the metadata: the name (@name), where it runs (@match), and which built-ins it uses (@require). See "Metadata Header & Directives" for the full list.
- The entry must be a
.js/.mjs/.ts/.tsx/.jsxfile;.vuefiles can only be imported, never used as the entry - Plugin builds only honour the entry file's directives. A
// @requirein a non-entry file's header only applies when yourun <that file>directly in the terminal; the editor shows a Warning on such lines, and other files cannot borrow it - When importing from local files, the script named
main/index/entryis picked as the entry by default (whether or not it has a metadata header), falling back to the first script. You can change it in the import dialog, and later switch it from the IDE's entry dropdown or withentryFile set <path>in the terminal - After switching the entry, the app's name, match scope and built-ins all come from the new entry's header. If the new entry has no metadata header, the next compile registers it on
<all_urls>with no built-ins. Deleting the entry file automatically picks the currently open script or the first script as the new entry. In both cases the registered artifact only changes after you click "Compile" - npm dependency versions are read only from the
package.jsonat the workspace root, wherever the entry file lives
App name and @name
The project name and the entry's @name stay in sync both ways: edit @name and save to rename the project; rename the project in the app list (or let the AI name it from your first prompt) and the entry's @name is rewritten to match. Deleting @name does not clear the project name — compiles keep using the project name.
Two app shapes
Every app, plain JS included, is bundled into a single artifact by the in-browser compiler before it is injected. The table below is only the label shown at import time; it does not change the compile pipeline or how directives behave:
| Shape | Detection | Compilation |
|---|---|---|
| Plain JS app | Single .js entry, no ESM imports | Compiled and bundled in the browser, then injected |
| Compiled source app | Contains .vue / .ts / .tsx / .jsx / .mjs or ESM imports | Compiled and bundled in the browser, then injected |
Both shapes share the same metadata header. Wire local modules and npm packages with standard import; keep @require for built-in runtime libraries only (a local file fails the compile).
Built-in libraries
The extension runtime provides eight libraries directly, so they add nothing to your app's size. Enable each with @require <id> (except LFRuntime, which is always available):
| Library | Global | Purpose |
|---|---|---|
| runtime | LFRuntime | App context: metadata, hot reload, resources, styles (always available) |
| dom | LFDOM | DOM operations, mounting, locators, semantic snapshots |
| store | LFStore | App-scoped, cross-site key/value storage |
| http | LFHttp | Background-proxied HTTP / streaming / WebSocket |
| model | LFModel | PluginX AI gateway (chat / streaming / images) |
| user | LFUser | Plugin login state and current user info |
| network | LFNetwork | Inspect requests/responses of the current tab |
| agent | LFAgent | Spawn AI agent instances running in detached IDE sessions (plugincmd handlers only) |
Execution environment
Saved apps are injected as chrome.userScripts USER_SCRIPT-world scripts: isolated from page JS but sharing the DOM, and injected into the top-level page only (never into iframes). During development, liveDebug injections set LFRuntime.liveDebug to true, but that unlocks no extra capabilities.
The workspace bridge (LFRuntime.readFile / writeFile / execTerminalCommand) can only be called directly from temporary terminal run scripts (run / run -c / run --tab / run --target background). In app source — liveDebug, enabled, installed or background apps alike — a direct call fails the compile. Register a command with LFRuntime.cli.register({ workspace: ['read' | 'write' | 'exec'] }) and use ctx.workspace.readFile / writeFile / exec inside its handler; the lease only lasts for that plugincmd call.