@valetos/app
v0.4.0
Published
Build third-party apps for Valet: a typed window.valet client (useValet) + a crafted UI kit, so a sandboxed app looks native.
Maintainers
Readme
@valetos/app
The client for building Valet apps: dual-operated apps that a human opens and an agent drives.
A Valet app runs sandboxed. Its only door out is the broker, exposed to the page as window.valet. This package types that bridge and adds React hooks, plus a small crafted UI kit so a sandboxed app looks native without importing Valet's whole design system.
Install
npm install @valetos/app reactreact (>=18) is a peer dependency.
The model: manifest = identity, interface = code
manifest.json holds only the app's deterministic identity (id, name, icon, group, description, version, and the static flags netHosts / fsLocal / storage). The capability surface, what the app uses and provides, is declared in code. valet pack extracts it into the packed manifest, so you declare it once and it never drifts.
Two directions, plus autonomy:
valet.uses.*— inbound: what the app calls (skills, connections, MCP, channels).valet.provides.*— outbound: what the agent can drive (actions, resources, prompts).valet.on.*— autonomy: when the app runs on its own.
valet.uses — inbound (declare + call by handle)
import { valet } from "@valetos/app"
// Declare once, get a typed handle. `valet pack` extracts the dependency.
const voicebox = valet.uses.mcp("voicebox", ["voicebox.speak"])
const notion = valet.uses.connection("notion")
await voicebox.call("voicebox.speak", { text: "Ready to merge." })
const results = await notion.call("notion_search", { query: "roadmap" })
valet.uses.skill("some_other_app_action") // another app's action
valet.uses.channel("telegram") // reachabilityvalet.provides — outbound (what the agent can drive)
import { valet } from "@valetos/app"
// ACTION — the agent can DO it. `net` = declarative (runs headless);
// `run` = live (your code, in the sandbox).
valet.provides.action("cat_fact", {
description: "Get a random cat fact.",
net: { host: "catfact.ninja", path: "/fact", field: "fact" },
})
valet.provides.action("save", {
description: "Save a note.",
input: { text: "string" },
run: async ({ text }) => store.add(text),
})
// RESOURCE — the agent can READ it for grounding.
valet.provides.resource("notes", { description: "Saved notes.", read: () => store.notes })
// PROMPT — an invokable template.
valet.provides.prompt("review", {
description: "Weekly review.",
args: { tone: "string?" },
build: ({ tone }) => `Review the notes. Tone: ${tone ?? "plain"}.`,
})valet.on — autonomy
valet.on.schedule("every 30m", async () => { /* runs on its own */ })
valet.on.message("telegram", async () => { /* runs on an incoming message */ })Client (net / fs / storage / theme)
import { useValet, useTheme } from "@valetos/app"
function Main() {
const valet = useValet() // getValet() outside React
const theme = useTheme() // "light" | "dark"
async function load() {
const f = await valet.net("catfact.ninja").json<{ fact: string }>("/fact")
await valet.storage.set("last", f.fact)
}
return <button onClick={load}>Fetch</button>
}valet.net(host).json(path)/.text(path)— GET a declared host, broker-gated.valet.fs— the app's own sandboxed folder (list/read/write/mkdir/remove).valet.storage— per-app key/value (get/set).valet.theme/valet.available— current theme, and whether a real sandbox is present.
Outside a sandbox (a plain-browser dev preview), broker calls reject with a clear message instead of crashing on a missing global.
UI kit
import { App, Panel, Row, Button, Note } from "@valetos/app/ui"
export default function Dice() {
return (
<App eyebrow="Valet app" title="Dice">
<Panel>Roll a die.</Panel>
<Row>
<Button onClick={() => {}}>Roll</Button>
<Note>Theme-aware, self-contained.</Note>
</Row>
</App>
)
}Styles are inline (the sandbox CSP blocks external CSS) and follow Valet's current light/dark theme, so the app blends in.
Docs
- Build an app end to end:
Valet/docs/build-a-valet-app.md - The app contract and runtime:
Valet/docs/valet-apps-and-sdk.md,Valet/docs/valet-app-runtime.md
MIT.
