This page walks through the full lifecycle of a webAI app: setting up your project, developing locally, bundling for production, and uploading to the webAI shell.
Setting up your project
Start by scaffolding a new project with Vite and installing the single-file bundler plugin:
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm install --save-dev vite-plugin-singlefile
npm create vite@latest my-app -- --template vue
cd my-app
npm install
npm install --save-dev vite-plugin-singlefile
Update your vite.config.js to use vite-plugin-singlefile. This inlines all JavaScript, CSS, and assets into a single index.html at build time:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; // or vue() for Vue projects
import { viteSingleFile } from 'vite-plugin-singlefile';
export default defineConfig({
plugins: [react(), viteSingleFile()],
build: { outDir: 'dist' },
});
The vite-plugin-singlefile plugin is required for webAI compatibility. Without it, your build will produce separate JS and CSS files that can’t be loaded inside the shell.
Create the integration module
Create src/webai.js as your single integration point with the shell APIs:
export const getShellAPI = (name) =>
window[name] ?? window.parent?.[name] ?? null;
export const getOasisHost = () => getShellAPI('OasisHost');
export const getApogeeShell = () => getShellAPI('ApogeeShell');
export const getCollaborationManager = () => getShellAPI('CollaborationManager');
export const getUserIdentityManager = () => getShellAPI('UserIdentityManager');
export const getE2ECrypto = () => getShellAPI('E2ECrypto');
export function getOasisState() {
const host = getOasisHost();
if (!host?.getStatus) return 'waiting';
const s = host.getStatus();
if (s?.lastModel) return 'ready';
if (s?.loadingModel || s?.isGenerating) return 'loading';
return 'waiting';
}
export async function streamCompletion(prompt, systemPrompt, onToken) {
const host = getOasisHost();
if (!host) throw new Error('Oasis AI is not available in this environment.');
const release = await host.acquire({ warmRuntime: true });
try {
return await host.request(prompt, {
systemPrompt: systemPrompt ?? '',
maxTokens: 2048,
temperature: 0.7,
onToken,
});
} finally {
if (release) release();
}
}
Local development
Run the Vite dev server to iterate on your app:
Your app opens in a normal browser tab. Shell APIs (OasisHost, CollaborationManager, etc.) will be null — this is expected. Build your UI and logic around graceful fallbacks for these APIs so you can develop comfortably without the full shell running.
Building for production
When you’re ready to deploy, build the single-file output:
This produces dist/index.html — a single self-contained HTML file with all JavaScript, CSS, and assets inlined.
Check the file size of your build output. Apps under 1MB load quickly. If your build exceeds 5MB, consider optimizing — large apps may be slow to load inside the shell.
Uploading to the shell
webAI apps are stored in the browser’s localStorage under the key apogee-uploaded-apps. The shell reads this on startup and registers each entry as an app in the launcher. There are two ways to upload.
Option 1: Direct install (desktop app)
If you’re running the webAI desktop app (Tauri), the app exposes a local install server at http://127.0.0.1:44280/install. You can POST your app directly:
The upload script detects whether the Tauri app is running and installs directly. If it’s not running, it falls back to Option 2.
Option 2: Browser console script
Generate a paste-able script and run it in the browser console:
- Build your app:
npm run build
- Run the upload generator:
node scripts/upload.js
- Copy the output script
- Open your webAI shell in Chrome
- Open DevTools (
F12 or Cmd+Option+I)
- Paste the script into the Console and press Enter
- Refresh the launcher — your app appears
Creating the upload script
Add scripts/upload.js to your project:
#!/usr/bin/env node
import { readFileSync } from 'fs';
import { resolve } from 'path';
const htmlPath = resolve('./dist/index.html');
let html;
try {
html = readFileSync(htmlPath, 'utf8');
} catch {
console.error('dist/index.html not found. Run `npm run build` first.');
process.exit(1);
}
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
const appId = pkg.name;
const displayName = pkg.description || pkg.name;
const uploadScript = `
(async function uploadToApogee() {
const htmlContent = ${JSON.stringify(html)};
const appId = ${JSON.stringify(appId)};
const displayName = ${JSON.stringify(displayName)};
const hashBuffer = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(htmlContent.replace(/\\s+/g, ' ').replace(/<!--[\\s\\S]*?-->/g, ''))
);
const sourceId = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0')).join('');
const stored = JSON.parse(localStorage.getItem('apogee-uploaded-apps') || '[]');
const filtered = stored.filter(app => app.appId !== appId);
filtered.push({ appId, displayName, htmlContent, sourceId, uploadedAt: Date.now(), version: 1 });
localStorage.setItem('apogee-uploaded-apps', JSON.stringify(filtered));
console.log('[webAI] Uploaded: ' + displayName + ' (' + appId + ')');
console.log('[webAI] Refresh the Apogee launcher to see your app.');
})();
`.trim();
console.log('=== Paste this in your browser console on the Apogee shell page ===\\n');
console.log(uploadScript);
console.log('\\n=== End of script ===');
And add the corresponding npm script to package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"upload": "node scripts/upload.js"
}
}
How versioning works
Each uploaded app includes a sourceId — a SHA-256 hash of the HTML content. The shell uses this for:
- Deduplication: re-uploading the same content overwrites the existing entry
- Cross-device sync: the
sourceId matches the approach used by the shell’s internal ApogeeShellManager
- Version tracking: each upload increments the version field
The appId is derived from your package.json name field. The displayName is derived from the description field. Keep these meaningful — they’re what users see in the launcher.
Full workflow summary
Scaffold
Create a new Vite project with React or Vue and install vite-plugin-singlefile.
Develop
Run npm run dev and build your app. Shell APIs are null during local dev — design around it.
Integrate
Import from src/webai.js to use AI, navigation, collaboration, or identity features.
Build
Run npm run build to produce a single dist/index.html.
Upload
Run npm run upload and follow the instructions — either direct install or browser console paste.
Share
Open your app in a space and share it with others. They receive it automatically.
Next steps