Skip to content

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 onestack.yaml file
  • [ ] Directory contains at least one habit file (.yaml or .json)
  • [ ] .env file exists in same directory
  • [ ] Run: npx habits@latest cortex --config ./stack.yaml
  • [ ] Access UI at http://localhost:3000
  • [ ] For @next version: npx habits@next instead of npx 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>}}.

yaml
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-locally

Configuration

Create stack.yaml to define the server settings and workflow paths:

yaml
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: true

Environment Variables

Create a .env file for secrets:

example
HABITS_OPENAPI_ENABLED=true
HABITS_MANAGE_ENABLED=true

OPENAI_API_KEY=sk-proj-key12-here34-makesuretocopyitall56
ELEVENLABS_API_KEY=

Run the Habit

bash
cd path/to/habit
npx @ha-bits/cortex@latest server --config ./stack.yaml

Enabling Features

  • Swagger API: Set Env var HABITS_OPENAPI_ENABLED=true → access at http://localhost:3000/api/docs
  • Management Portal: Set Env var HABITS_MANAGE_ENABLED=true to 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.

Swagger API Explorer

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.

Frontend UI

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.

Management Portal

Habits Stack Preparation Checklist

Basic Stack Requirements

  • [ ] Stack has a name field in stack.yaml
  • [ ] Each habit has a unique name field
  • [ ] Each habit has at least one node
  • [ ] All API keys stored in .env file (not in habit files)
  • [ ] .env is in .gitignore if you have a version control
  • [ ] .env.example exists with required variable names

If using Server-Side or Full-Stack Logic

  • [ ] All habits have clear inputs defined
  • [ ] All habits have clear outputs defined
  • [ ] UI points to correct backend endpoints
  • [ ] CORS configured if frontend/backend on different origins

Troubleshooting: Cannot Find stack.yaml Error

  • [ ] Verify stack.yaml exists in current directory
  • [ ] Or provide full path: --config /path/to/stack.yaml

Troubleshooting: Missing Environment Variable Error

  • [ ] Verify .env file exists
  • [ ] Check variable names match references in habits (e.g., ${OPENAI_API_KEY})
  • [ ] Ensure .env is in same directory as stack.yaml

Exporting for Production

Note: We are deprecating support for capacitor, cordova, and electron. Future releases will rely exclusively on tauri for desktop and mobile exports. Please migrate any existing projects to use tauri for best support and compatibility.

  • [ ] 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) or electron
  • [ ] Choose platform: windows, mac, linux, or all
  • [ ] Check build tools: curl http://localhost:3000/habits/base/api/export/binary/support or 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, or both
  • [ ] 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_HOME or ANDROID_SDK_ROOT for 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 mobile or desktop section 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_ROOT is 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:

Deployment Options

  • Binary Export - Package your habit as a standalone executable file.

Released under the Apache 2.0 License.