diff --git a/group_vars/all.yml b/group_vars/all.yml index dc189e5b..663e9646 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -53,6 +53,7 @@ default_roles: - tmate - tmux - sesh + - vibe-kanban - zoxide - zsh @@ -93,3 +94,35 @@ go: # NOTE: termshark has dependency issues with v2.4.0 # - package: github.com/stillmatic/chat@latest # cmd: chat + +# Vibe Kanban - AI Coding Agent Orchestration +# https://vibekanban.com +vibe_kanban: + theme: "DARK" + executor: + type: "CLAUDE_CODE" + variant: "OPUS" + editor: + type: "CUSTOM" + custom_command: "nvim" + # For remote hosting, set these: + # remote_ssh_host: "your-server.example.com" + # remote_ssh_user: "techdufus" + git: + branch_prefix: "vk" + github: + default_pr_base: "main" + auto_description_enabled: true + notifications: + sound_enabled: true + push_enabled: true + sound_file: "ABSTRACT_SOUND3" + analytics_enabled: false + # Server configuration for remote hosting + server: + # Set to true to run as a systemd/launchd service + enabled: false + # Port for web UI + port: 8080 + # Host binding (0.0.0.0 for remote access) + host: "127.0.0.1" diff --git a/roles/claude/tasks/main.yml b/roles/claude/tasks/main.yml index bad415fc..8131be04 100644 --- a/roles/claude/tasks/main.yml +++ b/roles/claude/tasks/main.yml @@ -124,3 +124,18 @@ dest: "{{ ansible_facts['env']['HOME'] }}/.claude/skills" state: link force: true + +# MCP Server Configuration +# Use `claude mcp add` to idempotently configure MCP servers +- name: "{{ role_name }} | Check if vibe-kanban MCP server is configured" + ansible.builtin.command: claude mcp get vibe_kanban + register: mcp_vibe_kanban + changed_when: false + failed_when: false + +- name: "{{ role_name }} | Add vibe-kanban MCP server" + ansible.builtin.command: > + claude mcp add vibe_kanban + --transport stdio + -- bunx vibe-kanban@latest --mcp + when: mcp_vibe_kanban.rc != 0 diff --git a/roles/vibe-kanban/defaults/main.yml b/roles/vibe-kanban/defaults/main.yml new file mode 100644 index 00000000..6bbdf113 --- /dev/null +++ b/roles/vibe-kanban/defaults/main.yml @@ -0,0 +1,59 @@ +--- +# Vibe Kanban Configuration Defaults +# These can be overridden in group_vars/all.yml + +vibe_kanban: + # UI theme: "DARK" or "LIGHT" + theme: "DARK" + + # Default coding agent executor + executor: + # Options: CLAUDE_CODE, GEMINI, CODEX, CURSOR_AGENT, AMP, OPENCODE, QWEN_CODE, COPILOT + type: "CLAUDE_CODE" + # Variant depends on executor (e.g., OPUS, SONNET, DEFAULT, PLAN, ROUTER) + variant: "OPUS" + + # Editor configuration + editor: + # Options: VSCODE, CURSOR, WINDSURF, NEOVIM, EMACS, SUBLIME_TEXT, CUSTOM + type: "CUSTOM" + # Required when type is CUSTOM + custom_command: "nvim" + # Remote SSH settings (for accessing from other machines) + remote_ssh_host: null + remote_ssh_user: null + + # Git configuration + git: + # Prefix for auto-generated branch names (e.g., "vk" -> "vk/task-name") + branch_prefix: "vk" + + # GitHub integration + github: + # Default base branch for PRs + default_pr_base: "main" + # Auto-generate PR descriptions + auto_description_enabled: true + auto_description_prompt: null + + # Notifications + notifications: + sound_enabled: true + push_enabled: true + # Sound options: ABSTRACT_SOUND1, ABSTRACT_SOUND2, ABSTRACT_SOUND3, etc. + sound_file: "ABSTRACT_SOUND3" + + # Privacy + analytics_enabled: false + + # Language: "BROWSER" uses system locale + language: "BROWSER" + + # Server configuration (for remote hosting) + server: + # Set to true to run as a service + enabled: false + # Port for web UI (0 = auto-assign) + port: 0 + # Host binding (127.0.0.1 = local only, 0.0.0.0 = all interfaces) + host: "127.0.0.1" diff --git a/roles/vibe-kanban/tasks/MacOSX.yml b/roles/vibe-kanban/tasks/MacOSX.yml new file mode 100644 index 00000000..18553596 --- /dev/null +++ b/roles/vibe-kanban/tasks/MacOSX.yml @@ -0,0 +1,43 @@ +--- +# macOS-specific tasks for Vibe Kanban + +# Vibe-kanban is distributed as an npm package that self-extracts binaries +# It requires Node.js/npm or Bun to run + +- name: "{{ role_name }} | Check if bun is installed" + ansible.builtin.command: which bun + register: bun_check + changed_when: false + failed_when: false + +- name: "{{ role_name }} | Set bun path" + ansible.builtin.set_fact: + bun_path: "{{ bun_check.stdout }}" + when: bun_check.rc == 0 + +- name: "{{ role_name }} | Warn if bun not found" + ansible.builtin.debug: + msg: "Bun not found. Vibe Kanban MCP server requires bun or npx to run." + when: bun_check.rc != 0 + +# Note: macOS uses launchd instead of systemd +# For remote hosting on macOS, use launchd plist +- name: "{{ role_name }} | Deploy launchd service" + when: vibe_kanban.server.enabled | default(false) + block: + - name: "{{ role_name }} | Create LaunchAgents directory" + ansible.builtin.file: + path: "{{ ansible_facts['env']['HOME'] }}/Library/LaunchAgents" + state: directory + mode: '0755' + + - name: "{{ role_name }} | Create launchd plist" + ansible.builtin.template: + src: com.vibekanban.plist.j2 + dest: "{{ ansible_facts['env']['HOME'] }}/Library/LaunchAgents/com.vibekanban.plist" + mode: '0644' + + - name: "{{ role_name }} | Load launchd service" + ansible.builtin.command: launchctl load {{ ansible_facts['env']['HOME'] }}/Library/LaunchAgents/com.vibekanban.plist + changed_when: true + failed_when: false diff --git a/roles/vibe-kanban/tasks/Ubuntu.yml b/roles/vibe-kanban/tasks/Ubuntu.yml new file mode 100644 index 00000000..57088b25 --- /dev/null +++ b/roles/vibe-kanban/tasks/Ubuntu.yml @@ -0,0 +1,43 @@ +--- +# Ubuntu-specific tasks for Vibe Kanban + +# Vibe-kanban is distributed as an npm package that self-extracts binaries +# It requires Node.js/npm or Bun to run + +- name: "{{ role_name }} | Check if bun is installed" + ansible.builtin.command: which bun + register: bun_check + changed_when: false + failed_when: false + +- name: "{{ role_name }} | Set bun path" + ansible.builtin.set_fact: + bun_path: "{{ bun_check.stdout }}" + when: bun_check.rc == 0 + +- name: "{{ role_name }} | Warn if bun not found" + ansible.builtin.debug: + msg: "Bun not found. Vibe Kanban MCP server requires bun or npx to run." + when: bun_check.rc != 0 + +# Optional: Deploy systemd service for remote hosting +- name: "{{ role_name }} | Deploy systemd service" + when: vibe_kanban.server.enabled | default(false) + block: + - name: "{{ role_name }} | Create systemd service file" + ansible.builtin.template: + src: vibe-kanban.service.j2 + dest: "{{ ansible_facts['env']['HOME'] }}/.config/systemd/user/vibe-kanban.service" + mode: '0644' + + - name: "{{ role_name }} | Reload systemd daemon" + ansible.builtin.systemd: + daemon_reload: true + scope: user + + - name: "{{ role_name }} | Enable and start vibe-kanban service" + ansible.builtin.systemd: + name: vibe-kanban + enabled: true + state: started + scope: user diff --git a/roles/vibe-kanban/tasks/main.yml b/roles/vibe-kanban/tasks/main.yml new file mode 100644 index 00000000..b60c55dc --- /dev/null +++ b/roles/vibe-kanban/tasks/main.yml @@ -0,0 +1,85 @@ +--- +# Vibe Kanban - AI Coding Agent Orchestration Tool +# https://github.com/BloopAI/vibe-kanban + +# Check for OS-specific configuration +- name: "{{ role_name }} | Checking for Distribution Config: {{ ansible_facts['distribution'] }}" + ansible.builtin.stat: + path: "{{ role_path }}/tasks/{{ ansible_facts['distribution'] }}.yml" + register: distribution_config + +- name: "{{ role_name }} | Run Tasks: {{ ansible_facts['distribution'] }}" + ansible.builtin.include_tasks: "{{ ansible_facts['distribution'] }}.yml" + when: distribution_config.stat.exists + +# Check for macOS-specific configuration +- name: "{{ role_name }} | Checking for macOS Config" + ansible.builtin.stat: + path: "{{ role_path }}/tasks/MacOSX.yml" + register: macos_config + when: ansible_facts['os_family'] == 'Darwin' + +- name: "{{ role_name }} | Run Tasks: macOS" + ansible.builtin.include_tasks: "MacOSX.yml" + when: + - ansible_facts['os_family'] == 'Darwin' + - macos_config.stat.exists + +# Common configuration for all platforms +- name: "{{ role_name }} | Ensure data directory exists" + ansible.builtin.file: + path: "{{ ansible_facts['env']['HOME'] }}/.local/share/vibe-kanban" + state: directory + mode: '0755' + +- name: "{{ role_name }} | Check if config.json exists" + ansible.builtin.stat: + path: "{{ ansible_facts['env']['HOME'] }}/.local/share/vibe-kanban/config.json" + register: vibe_kanban_config + +- name: "{{ role_name }} | Deploy configuration" + ansible.builtin.template: + src: config.json.j2 + dest: "{{ ansible_facts['env']['HOME'] }}/.local/share/vibe-kanban/config.json" + mode: '0644' + backup: true + when: not vibe_kanban_config.stat.exists or vibe_kanban_force_config | default(false) + +- name: "{{ role_name }} | Update existing config (preserve user data)" + when: vibe_kanban_config.stat.exists and not (vibe_kanban_force_config | default(false)) + block: + - name: "{{ role_name }} | Read existing config" + ansible.builtin.slurp: + src: "{{ ansible_facts['env']['HOME'] }}/.local/share/vibe-kanban/config.json" + register: existing_config_raw + + - name: "{{ role_name }} | Parse existing config" + ansible.builtin.set_fact: + existing_config: "{{ existing_config_raw.content | b64decode | from_json }}" + + - name: "{{ role_name }} | Merge configuration settings" + ansible.builtin.copy: + content: | + {{ existing_config | combine({ + 'theme': vibe_kanban.theme | default(existing_config.theme), + 'executor_profile': { + 'executor': vibe_kanban.executor.type | default(existing_config.executor_profile.executor), + 'variant': vibe_kanban.executor.variant | default(existing_config.executor_profile.variant) + }, + 'editor': { + 'editor_type': vibe_kanban.editor.type | default(existing_config.editor.editor_type), + 'custom_command': vibe_kanban.editor.custom_command | default(existing_config.editor.custom_command), + 'remote_ssh_host': vibe_kanban.editor.remote_ssh_host | default(existing_config.editor.remote_ssh_host), + 'remote_ssh_user': vibe_kanban.editor.remote_ssh_user | default(existing_config.editor.remote_ssh_user) + }, + 'git_branch_prefix': vibe_kanban.git.branch_prefix | default(existing_config.git_branch_prefix), + 'analytics_enabled': vibe_kanban.analytics_enabled | default(existing_config.analytics_enabled), + 'notifications': { + 'sound_enabled': vibe_kanban.notifications.sound_enabled | default(existing_config.notifications.sound_enabled), + 'push_enabled': vibe_kanban.notifications.push_enabled | default(existing_config.notifications.push_enabled), + 'sound_file': vibe_kanban.notifications.sound_file | default(existing_config.notifications.sound_file) + } + }, recursive=true) | to_nice_json }} + dest: "{{ ansible_facts['env']['HOME'] }}/.local/share/vibe-kanban/config.json" + mode: '0644' + backup: true diff --git a/roles/vibe-kanban/templates/com.vibekanban.plist.j2 b/roles/vibe-kanban/templates/com.vibekanban.plist.j2 new file mode 100644 index 00000000..facc1c6a --- /dev/null +++ b/roles/vibe-kanban/templates/com.vibekanban.plist.j2 @@ -0,0 +1,32 @@ + + + + + Label + com.vibekanban + ProgramArguments + + {{ bun_path | default('/opt/homebrew/bin/bunx') }} + vibe-kanban@latest + + EnvironmentVariables + + PORT + {{ vibe_kanban.server.port | default(8080) }} + HOST + {{ vibe_kanban.server.host | default('0.0.0.0') }} + HOME + {{ ansible_facts['env']['HOME'] }} + + WorkingDirectory + {{ ansible_facts['env']['HOME'] }} + RunAtLoad + + KeepAlive + + StandardOutPath + {{ ansible_facts['env']['HOME'] }}/Library/Logs/vibe-kanban.log + StandardErrorPath + {{ ansible_facts['env']['HOME'] }}/Library/Logs/vibe-kanban.error.log + + diff --git a/roles/vibe-kanban/templates/config.json.j2 b/roles/vibe-kanban/templates/config.json.j2 new file mode 100644 index 00000000..7c840b4e --- /dev/null +++ b/roles/vibe-kanban/templates/config.json.j2 @@ -0,0 +1,55 @@ +{ + "config_version": "v8", + "theme": "{{ vibe_kanban.theme | default('DARK') }}", + "executor_profile": { + "executor": "{{ vibe_kanban.executor.type | default('CLAUDE_CODE') }}", + "variant": "{{ vibe_kanban.executor.variant | default('OPUS') }}" + }, + "disclaimer_acknowledged": true, + "onboarding_acknowledged": true, + "notifications": { + "sound_enabled": {{ vibe_kanban.notifications.sound_enabled | default(true) | lower }}, + "push_enabled": {{ vibe_kanban.notifications.push_enabled | default(true) | lower }}, + "sound_file": "{{ vibe_kanban.notifications.sound_file | default('ABSTRACT_SOUND3') }}" + }, + "editor": { + "editor_type": "{{ vibe_kanban.editor.type | default('CUSTOM') }}", +{% if vibe_kanban.editor.custom_command is defined and vibe_kanban.editor.custom_command %} + "custom_command": "{{ vibe_kanban.editor.custom_command }}", +{% else %} + "custom_command": null, +{% endif %} +{% if vibe_kanban.editor.remote_ssh_host is defined and vibe_kanban.editor.remote_ssh_host %} + "remote_ssh_host": "{{ vibe_kanban.editor.remote_ssh_host }}", +{% else %} + "remote_ssh_host": null, +{% endif %} +{% if vibe_kanban.editor.remote_ssh_user is defined and vibe_kanban.editor.remote_ssh_user %} + "remote_ssh_user": "{{ vibe_kanban.editor.remote_ssh_user }}" +{% else %} + "remote_ssh_user": null +{% endif %} + }, + "github": { + "pat": null, + "oauth_token": null, + "username": null, + "primary_email": null, + "default_pr_base": "{{ vibe_kanban.github.default_pr_base | default('main') }}" + }, + "analytics_enabled": {{ vibe_kanban.analytics_enabled | default(false) | lower }}, + "workspace_dir": null, + "last_app_version": null, + "show_release_notes": false, + "language": "{{ vibe_kanban.language | default('BROWSER') }}", + "git_branch_prefix": "{{ vibe_kanban.git.branch_prefix | default('vk') }}", + "showcases": { + "seen_features": [] + }, + "pr_auto_description_enabled": {{ vibe_kanban.github.auto_description_enabled | default(true) | lower }}, +{% if vibe_kanban.github.auto_description_prompt is defined and vibe_kanban.github.auto_description_prompt %} + "pr_auto_description_prompt": "{{ vibe_kanban.github.auto_description_prompt }}" +{% else %} + "pr_auto_description_prompt": null +{% endif %} +} diff --git a/roles/vibe-kanban/templates/vibe-kanban.service.j2 b/roles/vibe-kanban/templates/vibe-kanban.service.j2 new file mode 100644 index 00000000..f914bee1 --- /dev/null +++ b/roles/vibe-kanban/templates/vibe-kanban.service.j2 @@ -0,0 +1,35 @@ +[Unit] +Description=Vibe Kanban - AI Coding Agent Orchestration +Documentation=https://vibekanban.com/docs +After=network.target + +[Service] +Type=simple +User={{ ansible_facts['env']['USER'] }} +Group={{ ansible_facts['env']['USER'] }} +WorkingDirectory={{ ansible_facts['env']['HOME'] }} + +# Environment variables for server configuration +Environment="PORT={{ vibe_kanban.server.port | default(8080) }}" +Environment="HOST={{ vibe_kanban.server.host | default('0.0.0.0') }}" +Environment="HOME={{ ansible_facts['env']['HOME'] }}" +Environment="XDG_DATA_HOME={{ ansible_facts['env']['HOME'] }}/.local/share" + +# Use bun to run vibe-kanban +ExecStart={{ bun_path | default('/home/' + ansible_facts['env']['USER'] + '/.bun/bin/bunx') }} vibe-kanban@latest + +# Restart configuration +Restart=on-failure +RestartSec=10 + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=vibe-kanban + +[Install] +WantedBy=multi-user.target