> ## Documentation Index
> Fetch the complete documentation index at: https://developers.arg.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Apps inside Arg

> Use @arg-ai/sdk from an .html, .tsx or .jsx app opened in Arg on web, desktop, iOS or Android: no install, no token, and the viewer's own permissions.

An app opened in Arg is a file in the workspace - an `.html` page, or a `.tsx` / `.jsx` component - rendered in an isolated preview. Inside that preview, `@arg-ai/sdk` resolves without a package install: HTML hosts inject an import map, and the React preview compiler resolves the modules locally. Your source never receives an API key and never chooses which workspace it talks to. The host does, and the viewer decides what it may touch. The earlier `@arg/sdk` spelling remains an alias for saved apps.

```html theme={null}
<script type="module">
  import { arg, fs } from "@arg-ai/sdk";

  await arg.ready;
  const notes = await fs.readJSON("./notes.json");
  render(notes);

  const stop = fs.watch("./notes.json", async () => {
    render(await fs.readJSON("./notes.json", { fresh: true }));
  });
  window.addEventListener("pagehide", stop);
</script>
```

```tsx theme={null}
import { useEffect, useState } from "react";
import { fs } from "@arg-ai/sdk";

export default function Notes() {
  const [notes, setNotes] = useState<string[]>([]);
  useEffect(() => {
    let live = true;
    fs.readJSON<string[]>("./notes.json").then((value) => live && setNotes(value));
    const stop = fs.watch("./notes.json", async () => {
      const next = await fs.readJSON<string[]>("./notes.json", { fresh: true });
      if (live) setNotes(next);
    });
    return () => {
      live = false;
      stop();
    };
  }, []);
  return (
    <ul>
      {notes.map((note) => (
        <li key={note}>{note}</li>
      ))}
    </ul>
  );
}
```

`import { arg } from "@arg-ai/sdk"` gives you every namespace on one object plus `arg.ready`, a promise that settles once the host has delivered the app's context. Named imports are lazy, so awaiting `arg.ready` is only required before reading `arg.context` (the workspace id, the app's path and its folder).

<Note>
  Static imports of workspace files are bundled when a React preview builds, and a changed file
  restarts the app. Read data that changes at runtime through `fs`, and subscribe with `fs.watch`,
  so the app keeps its state.
</Note>

## Permissions

Every preview starts with **Workspace access** off. The viewer switches it on from the preview's **Permissions** menu (on iOS and Android, from the **...** file menu), and picks two things:

| Choice      | Options                   | What it decides                                                                  |
| ----------- | ------------------------- | -------------------------------------------------------------------------------- |
| **Scope**   | This folder, or Workspace | Whether paths may leave the app's own folder; chats and discovery need Workspace |
| **Access**  | Read, or Read and write   | Whether `fs.write`, `db.exec`, moves and removals are allowed                    |
| **Actions** | Off, or on after a review | Whether `actions.*`, `agents.run` and chat sends may run as the viewer           |

Arg reads the app's source and suggests the grant it appears to need, so a viewer sees a matching prompt on first open. The prompt is informational only; the bridge enforces every call, whatever the scan said. A refused call rejects with:

* `permission_denied` when the path or the namespace is outside the granted scope,
* `read_only` when the grant is read-only and the call would change something,
* `disabled` when the call needs the **Actions** switch and it is off.

The grant can be remembered for the file. Revoking it, or a change of account, restarts the preview so nothing in memory survives a permission the viewer took back.

## Files, folders and ids

Relative paths resolve beside the app file; `/` is the workspace root. Under **This folder** scope a path that escapes the folder is refused, and `fs.list` and `fs.glob` are clamped to it. Cloud workspaces give every file a stable id: `fs.getId(path)` and `fs.resolveId(id)` convert between the two, and each read method has a `ById` twin for links that must survive a rename. The [files guide](/guides/sdk/files) covers every method.

`fs.assetUrl(path)` returns a signed URL you can put in an `<img>` or `<video>` tag. It expires; do not store it as a share link. `fs.open(path)` asks Arg to open the file in its own editor.

## Embedded SQLite

`db` runs SQL against a `.sqlite` or `.db` file in the workspace, inside the preview:

```ts theme={null}
import { db } from "@arg-ai/sdk";

const rows = await db.query("./crm.sqlite", "SELECT * FROM leads WHERE stage = ?", ["won"]);
await db.exec("./crm.sqlite", "UPDATE leads SET stage = 'lost' WHERE id = ?", [id]);
console.log(await db.tables("./crm.sqlite"), await db.schema("./crm.sqlite", "leads"));
```

The file is the database. There is no managed multi-user database behind it: two viewers editing the same file at once last-write-win, and `db.exec` needs **Read and write**. Servers and agent scripts do not serve `db`; it is an embedded capability only.

## Identity and team

```ts theme={null}
import { auth, users } from "@arg-ai/sdk";

const me = await auth.principal(); // { kind: "user", id, name, email }
const members = await users.list(); // everyone with access to this workspace
```

`auth.principal()` is the signed-in viewer. `users.list()` returns the workspace's members with their role, so an app can assign work or show avatars without its own directory.

## Chats and Actions

With **Workspace** scope and **Actions** on, an app can run Actions, start agent runs and open chats as the viewer:

```ts theme={null}
import { actions, agents, chat } from "@arg-ai/sdk";

const summary = await agents.run("Summarize ./notes.json", {
  readOnly: true,
  waitForCompletion: true,
});
const turn = await chat.start("Turn these notes into a plan", { openPanel: true });
await actions.run("file_read", { path: "notes.json" });
```

`openPanel: true` reveals the conversation beside the app: the docked chat panel on web and desktop, the native chat on iOS and Android. See [chats](/guides/sdk/chat) and [Actions and agents](/guides/sdk/actions).

## App chrome and navigation

`ui` registers the toolbar, inspector and bottom-bar descriptors an app may contribute to the surface around it, the same contract as [`@arg/ui`](/guides/apps/ui-sdk) without the React components:

```ts theme={null}
import { ui } from "@arg-ai/sdk";

if (ui.available) {
  const chrome = ui.register(
    { leftToolbar: { groups: [{ id: "data", tools: [{ id: "refresh", label: "Refresh" }] }] } },
    { onTool: (event) => event.toolId === "refresh" && reload() },
  );
  // Later: chrome.update({ ...registration }); chrome.dispose();
}
```

`host.open(path)` opens a workspace file in Arg's editor. Both are host capabilities: outside an Arg application host they report `unsupported_capability`, and `ui.available` is `false`.

## Where it works

Web, the desktop app, iOS and Android all provide this bridge for HTML and React apps, with the same permissions menu. A folder the desktop app has opened straight from disk is a local workspace: `fs` reads the disk, stable ids and `db` are unavailable, and the only chats are the desktop's own Claude Code and Codex sessions. The [desktop harness section](/guides/sdk/chat#claude-code-and-codex-on-the-desktop) explains that case.

Existing apps written against `window.arg` keep working unchanged; the two are the same objects. `window.arg.chat` and `window.arg.workspace` are new on every platform, so an HTML app can adopt them without switching to imports.
