Build your first habit in 5 minutes (Habit-as-Code)
This guide walks you through creating a multi-module workflow that generates text with OpenAI (Activepieces), converts it to speech with ElevenLabs (n8n), and uploads the audio to S3 (script)
Quick Start
Already packed and ready to go! Download example.zip and extract it to get started immediately.
Environment Setup Checklist
Install Node Version Manager (nvm)
- [ ] macOS/Linux: Install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash - [ ] Windows: Use nvm-windows or download Node.js directly from nodejs.org
- [ ] Restart terminal or run:
source ~/.bashrc(or~/.zshrc) - [ ] Verify nvm:
nvm --version
Install Node.js 24
- [ ] Install Node.js 24:
nvm install 24 - [ ] Set as default:
nvm alias default 24 - [ ] Verify Node.js:
node --version(should show v24.x.x)
Code-First Approach (HaC) Checklist
- [ ] Directory contains exactly one
stack.yamlfile - [ ] Directory contains at least one habit file (
.yamlor.json) - [ ]
.envfile exists in same directory - [ ] Run:
npx habits@latest cortex --config ./stack.yaml - [ ] Access UI at
http://localhost:3000 - [ ] For @next version:
npx habits@nextinstead ofnpx habits@latest
Workflow
The workflow chains three nodes across different modules: an Activepieces node for text generation, an n8n node for text-to-speech, and a script node for saving locally. Each node references the previous node's output via {{<id>}}.
id: text-to-voice-to-s3
name: text-to-voice-to-s3
nodes:
- id: generate-text
type: activepieces
data:
framework: activepieces
module: '@activepieces/piece-openai'
operation: ask_chatgpt
source: npm
credentials:
apiKey: '{{habits.env.OPENAI_API_KEY}}'
params:
prompt: 'Write a short 2-sentence motivational quote related to: {{habits.input.prompt}}'
model: gpt-4o-mini
- id: text-to-speech
type: n8n
data:
framework: n8n
module: n8n-nodes-elevenlabs
operation: text-to-speech
source: npm
credentials:
elevenLabsApi:
xiApiKey: '{{habits.env.ELEVENLABS_API_KEY}}'
params:
resource: speech
text: '{{generate-text}}'
voice_id: 21m00Tcm4TlvDq8ikWAM
- id: save-locally
type: script
data:
label: Save Audio Locally
framework: script
source: inline
params:
audio: '{{text-to-speech.result.0.base64}}'
language: deno
script: |
export async function main(audio: string | ArrayBuffer) {
const outputDir = '/tmp/habits-audio';
await Deno.mkdir(outputDir, { recursive: true });
const filename = `audio-${Date.now()}.mp3`;
const filepath = `${outputDir}/${filename}`;
// Use Buffer for base64 decoding (works in Node.js environment)
const buffer = typeof audio === 'string'
? Buffer.from(audio, 'base64')
: new Uint8Array(audio);
await Deno.writeFile(filepath, buffer);
const stats = await Deno.stat(filepath);
return {
success: true,
filepath,
filename,
size: stats.size,
modifiedAt: stats.mtime?.toISOString()
};
}
edges:
- source: generate-text
target: text-to-speech
- source: text-to-speech
target: save-locallyConfiguration
Create stack.yaml to define the server settings and workflow paths:
version: "1.0"
workflows:
- id: text-to-voice-to-s3
path: ./habit.yaml
enabled: true
server:
port: 13000
host: "0.0.0.0"
frontend: ./frontend
logging:
level: info # trace, debug, info, warn, error, fatal, none
outputs: [console] # console, file, json
format: text # text or json
colorize: trueEnvironment Variables
Create a .env file for secrets:
HABITS_OPENAPI_ENABLED=true
HABITS_MANAGE_ENABLED=true
OPENAI_API_KEY=sk-proj-key12-here34-makesuretocopyitall56
ELEVENLABS_API_KEY=Run the Habit
cd path/to/habit
npx @ha-bits/cortex@latest server --config ./stack.yamlEnabling Features
- Swagger API: Set Env var
HABITS_OPENAPI_ENABLED=true→ access athttp://localhost:3000/api/docs - Management Portal: Set Env var
HABITS_MANAGE_ENABLED=trueto enable the built-in workflow management UI - Frontend: Set
"frontend": "frontend"in the server block in stack.yaml → served at root/
Swagger API Explorer
When HABITS_OPENAPI_ENABLED=true, the Swagger UI is available at /api/docs. It provides interactive documentation for all workflow endpoints, allowing you to test triggers and inspect request/response schemas directly from the browser.

Frontend UI
Set the frontend config option to serve a simple web interface at the root path. This is ideal for building custom dashboards or trigger forms that interact with your workflows via the REST API.

Management Portal
Enable HABITS_MANAGE_ENABLED=true to access the built-in management portal at /manage. This UI lets you view registered workflows, monitor execution status, and inspect node configurations without touching the JSON files.

Habits Stack Preparation Checklist
Basic Stack Requirements
- [ ] Stack has a
namefield instack.yaml - [ ] Each habit has a unique
namefield - [ ] Each habit has at least one node
- [ ] All API keys stored in
.envfile (not in habit files) - [ ]
.envis in.gitignoreif you have a version control - [ ]
.env.exampleexists with required variable names
If using Server-Side or Full-Stack Logic
- [ ] All habits have clear
inputsdefined - [ ] All habits have clear
outputsdefined - [ ] UI points to correct backend endpoints
- [ ] CORS configured if frontend/backend on different origins
Troubleshooting: Cannot Find stack.yaml Error
- [ ] Verify
stack.yamlexists in current directory - [ ] Or provide full path:
--config /path/to/stack.yaml
Troubleshooting: Missing Environment Variable Error
- [ ] Verify
.envfile exists - [ ] Check variable names match references in habits (e.g.,
${OPENAI_API_KEY}) - [ ] Ensure
.envis in same directory asstack.yaml
Exporting for Production
Note: We are deprecating support for
capacitor,cordova, andelectron. Future releases will rely exclusively ontaurifor desktop and mobile exports. Please migrate any existing projects to usetaurifor best support and compatibility.
If exporting Server-Side or Full-Stack (Recommended: Docker)
- [ ] Stack tested locally and working
- [ ] Export via Base UI: Export → Docker
- [ ] Download
{name}-docker.zip - [ ] Unzip and run:
docker-compose up -d
If exporting Server-Side (Alternative: Single Executable)
- [ ] Stack tested locally and working
- [ ] Export via Base UI → Export tab → Binary
- [ ] Download binary for target platform
- [ ] Run executable on target machine
If exporting Desktop App (Experimental)
- [ ] Stack tested locally and working
- [ ] Backend URL configured (where app will connect)
- [ ] Choose framework:
tauri(recommended) orelectron - [ ] Choose platform:
windows,mac,linux, orall - [ ] Check build tools:
curl http://localhost:3000/habits/base/api/export/binary/supportor in UI - [ ] For Tauri: Rust, Cargo installed
- [ ] Export via Base UI → Export tab → Desktop
- [ ] If first time: Download scaffold (buildBinary: false)
- [ ] If ready for binary: Enable buildBinary: true
- [ ] Download and test on target platform
If exporting Mobile App (Experimental)
- [ ] Stack tested locally and working
- [ ] Backend URL configured (must be accessible from mobile device)
- [ ] Choose framework:
tauri(recommended) - [ ] Choose target:
ios,android, orboth - [ ] Check build tools:
curl http://localhost:3000/habits/base/api/export/binary/support- [ ] For Android: Java, Gradle, Android SDK installed
- [ ] For iOS: macOS with Xcode installed (iOS builds only work on macOS)
- [ ] Set environment variables:
- [ ]
ANDROID_HOMEorANDROID_SDK_ROOTfor Android - [ ] Export via Base UI → Export tab → Mobile
- [ ] If first time: Download scaffold (buildBinary: false)
- [ ] If ready for binary: Enable buildBinary: true
- [ ] Download APK (Android) or IPA (iOS)
- [ ] Test on real device or emulator
Troubleshooting: Desktop/Mobile Build Fails
- [ ] Check
mobileordesktopsection for missing tools - [ ] Install missing dependencies
- [ ] Try scaffold export first (buildBinary: false) to verify config
- [ ] Check logs for specific error messages
- [ ] Via API, Run:
curl http://localhost:3000/habits/base/api/export/binary/support
Troubleshooting: iOS Build Fails
- [ ] Verify you're on macOS (iOS builds require macOS)
- [ ] Verify Xcode is installed:
xcodebuild -version - [ ] Open Xcode at least once to accept license agreements
Troubleshooting: Android Build Fails
- [ ] Verify
ANDROID_HOME/ANDROID_SDK_ROOTis set - [ ] Verify Java and Gradle versions are compatible
- [ ] Check compatibility in support endpoint response
- [ ] Install Android SDK build tools if missing
Next Steps
Ready to explore more? Check out the Examples section for real-world use cases including:
- Email Classification - AI-powered email categorization
- Minimal Blog - Full CMS backend with authentication
- Marketing Campaign - Multi-channel content generation
Deployment Options
- Binary Export - Package your habit as a standalone executable file.
