Loading...
Loading...
Complete guide for building Moldable apps. Use this skill when creating new apps with scaffoldApp, modifying existing apps, implementing workspace-aware storage, integrating with the Moldable desktop via postMessage APIs (moldable:show-in-folder, moldable:set-chat-input, moldable:set-chat-instructions, moldable:save-file), configuring workspaces, managing skills/MCPs, or troubleshooting app issues. Essential for any Moldable app development task.
npx skill4agent add moldable-ai/skills moldable| Resource | Path |
|---|---|
| App source code | |
| App runtime data | |
| Workspace config | |
| MCP config | |
| Skills | |
| Environment | |
@moldable-ai/storagescaffoldAppscaffoldApp({
appId: "expense-tracker", // lowercase, hyphens only
name: "Expense Tracker", // Display name
icon: "💰", // Emoji icon
description: "Track expenses and generate reports",
widgetSize: "medium", // small, medium, or large
extraDependencies: { // Optional npm packages
"zod": "^3.0.0"
}
})src/app/page.tsxsrc/app/widget/page.tsxsrc/app/api/src/components/@moldable-ai/ui// Import components from @moldable-ai/ui (NOT from shadcn directly)
import {
Button, Card, Input, Dialog, Select, Tabs,
ThemeProvider, WorkspaceProvider, useTheme,
Markdown, CodeBlock, WidgetLayout,
downloadFile, sendToMoldable
} from '@moldable-ai/ui'
// For rich text editing
import { MarkdownEditor } from '@moldable-ai/editor'// ✅ Correct
<div className="bg-background text-foreground border-border" />
<Button className="bg-primary text-primary-foreground" />
// ❌ Wrong - raw colors don't adapt to theme
<div className="bg-white text-gray-900" />// Client - use workspaceId in query keys
const { workspaceId, fetchWithWorkspace } = useWorkspace()
const { data } = useQuery({
queryKey: ['items', workspaceId], // ← Include workspace!
queryFn: () => fetchWithWorkspace('/api/items').then(r => r.json())
})
// Server - extract workspace from request
import { getWorkspaceFromRequest, getAppDataDir } from '@moldable-ai/storage'
export async function GET(request: Request) {
const workspaceId = getWorkspaceFromRequest(request)
const dataDir = getAppDataDir(workspaceId)
// Read/write files in dataDir
}// Open external URL
window.parent.postMessage({ type: 'moldable:open-url', url: 'https://...' }, '*')
// Show file in Finder
window.parent.postMessage({ type: 'moldable:show-in-folder', path: '/path/to/file' }, '*')
// Pre-populate chat input
window.parent.postMessage({ type: 'moldable:set-chat-input', text: 'Help me...' }, '*')
// Provide context to AI
window.parent.postMessage({
type: 'moldable:set-chat-instructions',
text: 'User is viewing meeting #123...'
}, '*')// src/app/layout.tsx
import { ThemeProvider, WorkspaceProvider } from '@moldable-ai/ui'
import { QueryProvider } from '@/lib/query-provider'
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider>
<WorkspaceProvider>
<QueryProvider>{children}</QueryProvider>
</WorkspaceProvider>
</ThemeProvider>
</body>
</html>
)
}sandbox: falseawait runCommand({
command: 'cd ~/.moldable/shared/apps/my-app && pnpm add zod',
sandbox: false // Required for network access
})| Tool | Purpose | Reversible |
|---|---|---|
| Create new app | — |
| Check which workspaces use an app | — |
| Remove from current workspace only | ✅ Re-add later |
| Delete app's data (keep installed) | ❌ Data lost |
| Permanently delete from ALL workspaces | ❌ Everything lost |
~/.moldable/
├── shared/
│ ├── apps/{app-id}/ # App source code
│ │ ├── moldable.json # App manifest
│ │ ├── package.json
│ │ └── src/
│ ├── skills/{repo}/{skill}/ # Skills library
│ ├── mcps/{mcp-name}/ # Custom MCP servers
│ └── config/mcp.json # Shared MCP config
│
└── workspaces/{workspace-id}/
├── config.json # Registered apps
├── .env # Workspace env overrides
├── apps/{app-id}/data/ # App runtime data
└── conversations/ # Chat historyscaffoldAppgetAppDataDir()bg-backgroundbg-gray-100sandbox: false~/.moldable/shared/apps/