internal-comms
ANanthropics/skills181k1,119 new stars in the last 7 days
Build and modify tinyjs desktop apps — tiny macOS (and beta Windows/Linux) apps with a txiki.js JavaScript backend and a native webview window. Use when working in a project with a tinyjs.json, when the user mentions tinyjs, tiny.api, or tinyjs dev/build, or when porting an Electron app to tinyjs.
tinyjs (https://tinyjs.app, repo tarwin/tinyjsapp) makes ~6 MB desktop apps: a txiki.js backend (full system access: files, sockets, processes, FFI)
tiny.api (which the tinyjs.json "api" gate can
narrow), which is why anything interpolated into innerHTML must be
escaped. Two launcher-side paths skip that crossing, both limited to the
app's own pages and the origins "api" trusts: tiny.proxyURL's
tiny-media:// proxy (cross-origin http(s) reads, macOS + Linux;
media.proxy) and mic/camera (media.microphone / media.camera).Current release: 0.50.1. App floors: macOS 15+ — a default build opens only
on the build Mac's CPU (build --arch arm64|x86_64 or --universal for the
other); Windows 10/11 (WebView2); Linux glibc 2.35+ (Ubuntu 22.04 / Debian 12
/ Mint 21 and up).
| doing | read |
|---|---|
| any API beyond the basics below (windows, menus, tray, clipboard, audio, permissions, system…) | references/api.md |
| per-OS support, Windows/Linux quirks, capability gating | references/platforms.md |
| starting a tray app, desktop pet/overlay, media app, document app, site wrapper, agent app | references/recipes.md |
| shipping: build/sign/notarize, per-OS packaging, auto-update manifests | references/release.md |
| porting from Electron (API map, what won't work) | references/electron-migration.md |
| deciding where code runs, wire/binary payloads, throttling, memory | references/performance.md |
tinyjs new <dir> # scaffold (zero dependencies)
tinyjs new <dir> --template react-ts|vue-ts|svelte-ts|solid-ts|preact-ts|lit-ts|alpine-ts|vanilla-ts|…
# create-vite + tinyjs overlay: HMR dev server in the
# native window, esbuild-bundled TS backend (npm pkgs ok).
# No name = asks framework + language. --pm npm|pnpm|
# yarn|bun|vp[:pnpm] (else asks; npm off a terminal),
# --no-install. Agents: pass both flags — no prompts.
# vp needs Vite+ ≥ 1.0. alpine = vanilla + Alpine.js.
tinyjs wrap <url> # site wrapper: the site IS the app, origin-gated API
# (--origins subdomains, --menubar [--panel], --top,
# --external a.com, --ua, --force re-wraps in place)
tinyjs dev # run with hot reload (frontend edits swap in place;
# backend edits restart the process)
tinyjs build # dist/<name> binary + dist/<Name>.app (codesigned)
# --dmg installer image; --cli [name] terminal shim
# --arch arm64|x86_64: macOS .app for that CPU (any Mac;
# 0.42+); --universal: one .app for both (needs the
# Command Line Tools' lipo). Bare dist/<name> = host CPU
tinyjs publish # build + dist/publish/<name>-<ver>.zip|tarball + manifest
tinyjs notarize # macOS: notarytool submit + staple (--dmg re-makes dmg)
# publish/notarize take --arch too — repeat the build's
tinyjs update # update tinyjs itself (--check); also: uninstall, version
TINYJS_DEBUG=1 tinyjs dev # trace every bridge message
tinyjs.json { name, title, size, id, version, icon?,
minTinyjsVersion?, // refuse older tinyjs with a
// real message; `tinyjs new`
// stamps its own version — raise
// it for newer APIs you adopt
update?: { url: "https://…/manifest.json", auto? },
urlScheme?: "myapp", fileExtensions?: ["md"],
openFolders?: true, readAccess?: true | "/path",
userAgent?: "…", activation?: "accessory",
mainWindow?: false, // no main window/WebView (tray agents)
offscreenRescue?: false, windowPlacement?: true,
contextMenu?: false, audioTap?: "app" | "system",
audioTapReason?: "why", about?: "menu",
attribution?: "…", // About panel credit line (macOS);
// a project-root Credits.html wins
debug?: true | "open", browserAccelerators?: true,
permissions?: { microphone?: "why", camera?: "why",
speechRecognition?: "why" },
chrome?: { frame, windowControls, windowControlsPos,
transparent, vibrancy, squareCorners,
acceptsFirstMouse, menu },
signIdentity?, notarize?: { profile },
backend?: "backend/main.ts", // .ts → esbuild bundle;
// default src/main.{js,ts} or backend/main.{js,ts}
frontend?: { dir?, build, dist, dev, devUrl },
// dir: plain page folder (default src/frontend);
// build/dev need the packages installed first
// wrapping a hosted site (recipes.md) — "url" replaces
// the local frontend, "api" gates what that origin may
// call. NEVER wrap a site you don't control without it.
url?: "https://app.example.com",
inject?: "src/shim.js", // document-start, every page
downloads?: "auto" | "ask" | "deny",
popups?: "external" | "window" | "deny",
api?: "wrapper" | { disable?, enable?, origins? },
macos?/windows?/linux?: { …merged on top per OS } }
icon.png 1024×1024 app icon
src/main.js backend
src/frontend/ index.html + assets — served as real files (file://),
so relative paths just work
export const api = {
// page calls tiny.api.call('readNotes', { dir }) — return resolves the
// page's promise, throwing rejects it; meta.window = calling window id
readNotes: async ({ dir }, app, meta) => { ... },
};
export function init(app) {
app.push('event-name', data); // page: tiny.api.on('event-name')
// app mostly mirrors the page: app.window(id).*, app.openWindow,
// app.tray.*, app.audio.sampler.*, app.clipboard.*, app.store.*,
// app.paths (plain object), notify, quit, … — but some names differ
// (app.setMenu, app.setChrome, …): name map in references/api.md
}
// other exports the scaffold wires, each (info, app): onMenu, onTray (id
// null = bare icon click), onContextMenu, onHotkey, onWindowState,
// onWindowClosed, onOpenUrl, onOpenFiles, onMediaKey, onNotificationClick,
// onNotificationAction, onUpdateAvailable, onClipboardChange, onLocale,
// onAudioTap, onNavigate, onDownload, onWindowOpen — except
// onSystem(kind, value, app)
Runtime is txiki.js (tjs global): tjs.readFile/writeFile/readDir/stat,
tjs.spawn, tjs.watch, fetch, WebSocket, tjs:sqlite, FFI. It is
NOT Node (no require, no Node builtins, no native npm modules) and it has
no JIT — compute-heavy work belongs in the page
(references/performance.md). Streams need getReader() (no for await);
tjs.cwd is a property; no Intl (format in the page). Every tjs.* fs
call is async — await it — while tjs:sqlite is fully sync (never await;
statements have run/all/finalize only, no get(), run() returns void).
await tiny.api.call('method', { params }); // -> backend api.<method>
const off = tiny.api.on('event', fn); // <- app.push; returns unsubscribe
audio.src = tiny.fileURL(path); // ALWAYS this, never 'file://' + path
// (breaks on Windows drive letters)
const r = await tiny.fetch(url, opts); // backend-proxied, no CORS/CSP
tiny.win.open('settings', { page: 'settings.html', size: '420x300',
chrome: { frame: false } });
// chrome/x/y/minSize in open() (or tinyjs.json "chrome" for main) apply
// BEFORE first paint — a late setChrome flashes the default window.
Everything else — windows/chrome/state events, menus (in every window),
dialogs, tray, notifications, clipboard, hotkeys, audio (sampler / filters /
audioTap / proxyURL), store/secrets, permissions, deep links, auto-update,
tiny.macos.* — is in references/api.md with signatures and gotchas.
capabilities() lists only the
EXCEPTIONS: test caps.x !== false, never if (caps.x) (the truthy form
reports "unsupported" on the OS that has it). Query calls resolve null
where unsupported, capability calls reject with the reason,
fire-and-forget ones no-op — but tiny.macos.* off macOS REJECTS.
caps.sampler is a string ('native'|'page') — informational, don't
branch.app.paths / tiny.app.paths() — never hardcode ~/Library or
%APPDATA%; join with '/' (works everywhere).tiny.system.os()/isMacOS()/isWindows()/isLinux() are synchronous;
architecture() must be awaited (navigator lies on Apple Silicon).tiny.system.promptMissing([ids]) puts the fix in front of the user.{ title: 'Edit' } — macOS already has one, so
the bar shows two. Use { role: 'edit', items: [...] }; for the SAME
menu on all three OSes, place the stock items yourself:
items: [{ role: 'standard' }, { separator: true }, ...yours]
(details in references/api.md → Menus).api methods; keep the frontend thin — but
keep COMPUTE in the page and bytes off the wire (pass paths, not
ArrayBuffers; references/performance.md).innerHTML — the page holds an RPC
channel to full system access.chrome identifier in frontend code:
window.chrome is a non-configurable global on WebView2, so a top-level
const chrome is a PARSE-time SyntaxError that kills the whole script.ctx.destination (it crackles —
measured, unfixable page-side). SFX → tiny.audio.sampler; EQ →
tiny.audio.filters; details in references/platforms.md.TINYJS_HTML=/abs/page.html tinyjs dev. The bundled test/smoke.html
only fits an UNMODIFIED zero-dependency scaffold (it calls the template's
sysinfo/listDir and waits for its tick push).tinyjs: explanation and exits 3 ("launcher exited before connecting") —
the environment, not your code; there is no headless mode.dist/<Name>.app is the distributable; bare dist/<name> is local-only.ANanthropics/skills181k1,119 new stars in the last 7 days
GAgarrytan/gstack136k883 new stars in the last 7 days
ADaddyosmani/agent-skills106k3,628 new stars in the last 7 days
A pre-launch checklist covering monitoring, staged rollouts and rollback plans.
ADaddyosmani/agent-skills106k3,628 new stars in the last 7 days
PApaperclipai/paperclip101k2,948 new stars in the last 7 days
RUruvnet/RuView98k968 new stars in the last 7 days