diff --git a/MARKETING_WEBSITE.md b/MARKETING_WEBSITE.md new file mode 100644 index 00000000..2b4cb12a --- /dev/null +++ b/MARKETING_WEBSITE.md @@ -0,0 +1,716 @@ +# Officer.dev Marketing Website Plan + +> Written by Claude for Claude. This document covers the full marketing website — structure, copy, visuals, and demo video plans. When resuming, read this first and walk the user through the strategy before building. + +--- + +## Table of Contents + +1. [Positioning & Messaging Strategy](#1-positioning--messaging-strategy) +2. [Site Map](#2-site-map) +3. [Page-by-Page Breakdown](#3-page-by-page-breakdown) +4. [Copy Bank](#4-copy-bank) +5. [Visual Identity for Marketing](#5-visual-identity-for-marketing) +6. [Demo Videos Plan](#6-demo-videos-plan) +7. [Technical Implementation](#7-technical-implementation) +8. [Launch Considerations](#8-launch-considerations) + +--- + +## 1. Positioning & Messaging Strategy + +### The Core Idea + +Officer is not a developer tool. It's not an AI chatbot wrapper. It's an **operating system** — a central place where AI does your repetitive work so you can do what matters. + +### Positioning Triangle + +``` + OFFICER + / \ + Easier More Powerful + than than + | | + OpenClaw Claude Work + (open source (polished but + but complex) locked down) +``` + +**vs. OpenClaw/Open Source AI tools**: "You shouldn't need a CS degree to use AI. Officer gives you the same power — with a UI your team actually wants to use." + +**vs. Claude Work/Enterprise AI**: "Enterprise AI is a walled garden. Officer is open source, self-hosted, and yours. No data leaves your servers unless you say so." + +**vs. Building it yourself**: "You could stitch together 15 tools and spend months on glue code. Or you could install Officer in five minutes." + +### Messaging Pillars + +| Pillar | For Individuals | For Companies | +|--------|----------------|---------------| +| **Automation** | "Automate your life. Have more time to live it." | "Every minute your team spends on repetitive work is a minute wasted." | +| **AI-Native** | "An AI that doesn't just chat — it acts." | "Give every employee an AI-powered workspace." | +| **Self-Hosted** | "Your data, your server, your rules." | "Enterprise-grade AI without enterprise-grade surveillance." | +| **Open Source** | "Built in the open. Extend it however you want." | "No vendor lock-in. No surprise pricing changes." | + +### Tagline Options + +**Primary (hero):** +> **Your AI-powered operating system — everywhere.** + +**Alternatives to test:** +> **Stop managing. Start living.** +> +> **The last tool you'll need.** +> +> **One platform. Everything automated.** + +**For the business/enterprise angle:** +> **What if your entire company ran itself?** +> +> **The company intranet that actually works.** +> +> **Fortunes are lost to repetitive work every day. Not anymore.** + +### Tone of Voice + +- **Confident but not arrogant** — we know what we are, we don't trash competitors +- **Clear and direct** — no jargon, no buzzword soup +- **Warm** — the duck mascot sets the tone. Serious product, friendly personality +- **Action-oriented** — every section should make you want to try it + +--- + +## 2. Site Map + +``` +officer.dev/ +├── / (Landing / Home) +├── /features +│ ├── /features/ai-assistant +│ ├── /features/file-management +│ ├── /features/automation +│ └── /features/workspaces +├── /pricing +├── /docs (external link to docs site) +├── /marketplace (coming soon / waitlist) +├── /enterprise +├── /open-source +├── /blog (future) +└── /about +``` + +--- + +## 3. Page-by-Page Breakdown + +### 3.1 Landing Page (Home) + +The landing page is the entire pitch in one scroll. It must convert three different audiences: individual users, small teams, and enterprises. + +#### Section 1: Hero + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ [Nav: Logo | Features | Pricing | Docs | GitHub]│ +│ [ Star on GitHub | Login]│ +│ │ +│ Your AI-powered operating │ +│ system — everywhere. │ +│ │ +│ Open source. Self-hosted. Free forever. │ +│ │ +│ [ Get Started — Free ] [ Watch Demo ▶ ] │ +│ │ +│ 🦆 (animated duck walks across) │ +│ │ +│ ┌──────────────────────────────────────────┐ │ +│ │ │ │ +│ │ (Hero product screenshot/video) │ │ +│ │ Dashboard with widgets + chat open │ │ +│ │ │ │ +│ └──────────────────────────────────────────┘ │ +│ │ +│ Trusted by developers at [logos if available] │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +**Copy:** +- Headline: "Your AI-powered operating system — everywhere." +- Subheadline: "Open source. Self-hosted. Free forever." +- CTA Primary: "Get Started — Free" +- CTA Secondary: "Watch Demo" +- Background: landscape1.png with gradient overlay + +**Behavior:** +- Duck walks across the hero from right to left (CSS/JS animation, lightweight — not the 3D model) +- Product screenshot below the fold, slightly overlapping +- Subtle parallax on scroll +- GitHub stars badge updates live + +#### Section 2: The Problem + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ Every day, you... │ +│ │ +│ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ Rename │ │ Organize│ │ Copy │ │ +│ │ 47 │ │ files │ │ data │ │ +│ │ files │ │ into │ │ between│ │ +│ │ │ │ folders │ │ apps │ │ +│ └────────┘ └────────┘ └────────┘ │ +│ │ +│ ┌────────┐ ┌────────┐ ┌────────┐ │ +│ │ Format │ │ Convert │ │ Search │ │ +│ │ reports│ │ formats │ │ through│ │ +│ │ │ │ │ │ docs │ │ +│ └────────┘ └────────┘ └────────┘ │ +│ │ +│ This isn't work. It's busywork. │ +│ And it costs more than you think. │ +│ │ +│ [$X,XXX per employee per year wasted] │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +**Copy:** +- "Every day, you rename files. Organize folders. Copy data between apps. Format reports. Convert formats. Search through documents." +- "This isn't work. It's busywork." +- "And it's costing you more than you think." +- Animated counter or stat: estimated cost of repetitive work per employee/year + +#### Section 3: The Solution (Feature Overview) + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ Meet Officer. │ +│ │ +│ The platform that combines AI, file management, │ +│ automation, and workspaces into one self-hosted │ +│ operating system. │ +│ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ │ │ +│ │ (Animated product showcase) │ │ +│ │ Cycling through: Chat → Files → │ │ +│ │ Automation → Dashboard │ │ +│ │ │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ +│ │ 🤖 │ │ 📁 │ │ ⚡ │ │ 🏠 │ │ +│ │ AI │ │Files │ │Auto- │ │Work- │ │ +│ │Assist│ │ │ │mate │ │space │ │ +│ └──────┘ └──────┘ └──────┘ └──────┘ │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +**Four feature cards:** + +1. **AI Assistant** — "Not just chat. An AI that reads your files, runs your tasks, and gets things done." +2. **File Management** — "Your entire digital life in one place. Browse, preview, edit, convert, transcribe." +3. **Automation** — "Build workflows that run themselves. Tasks, cron jobs, pipelines — without writing code." +4. **Workspaces** — "Your desk, digitized. Combine any tool into a custom layout that fits how you work." + +Each card links to its feature detail page. + +#### Section 4: For You / For Your Team (Split Section) + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ ┌─────────────────┐ ┌─────────────────────┐ │ +│ │ │ │ │ │ +│ │ FOR YOU │ │ FOR YOUR COMPANY │ │ +│ │ │ │ │ │ +│ │ "Automate your │ │ "What if your │ │ +│ │ life. Have more │ │ entire team had an │ │ +│ │ time to live │ │ AI-powered intranet │ │ +│ │ it." │ │ that actually │ │ +│ │ │ │ worked?" │ │ +│ │ • Personal AI │ │ │ │ +│ │ assistant │ │ • Role-based access │ │ +│ │ • File organiz- │ │ • Shared workspaces │ │ +│ │ ation on │ │ • Company-wide │ │ +│ │ autopilot │ │ automation │ │ +│ │ • Smart dash- │ │ • Self-hosted: │ │ +│ │ board with │ │ your data stays │ │ +│ │ your goals │ │ on your servers │ │ +│ │ • Voice notes, │ │ • Plugin ecosystem │ │ +│ │ OCR, TTS │ │ for custom needs │ │ +│ │ │ │ │ │ +│ │ [Try Free →] │ │ [Enterprise →] │ │ +│ │ │ │ │ │ +│ └─────────────────┘ └─────────────────────┘ │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +#### Section 5: How It Works + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ Up and running in 5 minutes. │ +│ │ +│ 1. Install │ +│ ┌──────────────────────────────────┐ │ +│ │ $ curl -fsSL install.officer.dev │ │ +│ │ | sh │ │ +│ └──────────────────────────────────┘ │ +│ One command. That's it. │ +│ │ +│ 2. Open your browser │ +│ Navigate to localhost:5000. │ +│ Create your admin account. │ +│ │ +│ 3. Start automating │ +│ Your AI assistant is ready. │ +│ Your files are connected. │ +│ Your first automation is one click away. │ +│ │ +│ [Get Started — Free] │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +#### Section 6: Open Source & Community + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ Built in the open. Extended by everyone. │ +│ │ +│ Officer is open source under [LICENSE]. │ +│ Read every line. Modify anything. │ +│ Self-host for free, forever. │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ ⭐ │ │ 🔌 │ │ 🎨 │ │ +│ │ [count] │ │ Plugins │ │ Themes │ │ +│ │ GitHub │ │ Build & │ │ Make it │ │ +│ │ Stars │ │ share │ │ yours │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ Marketplace coming soon. │ +│ Build plugins. Sell themes. Earn money. │ +│ │ +│ [Star on GitHub ⭐] [Join the community] │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +#### Section 7: Pricing Preview + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ Simple pricing. No surprises. │ +│ │ +│ ┌──────────┐ ┌───────────┐ ┌──────────┐ │ +│ │ Self- │ │ Cloud │ │ Enter- │ │ +│ │ Hosted │ │ │ │ prise │ │ +│ │ │ │ │ │ │ │ +│ │ FREE │ │ $XX/mo │ │ Custom │ │ +│ │ forever │ │ per user │ │ │ │ +│ │ │ │ │ │ │ │ +│ │ Full │ │ Everything│ │ SLA, │ │ +│ │ features │ │ hosted & │ │ support, │ │ +│ │ Your │ │ managed │ │ custom │ │ +│ │ servers │ │ for you │ │ features │ │ +│ │ │ │ │ │ │ │ +│ │ [Install]│ │ [Start] │ │ [Talk] │ │ +│ └──────────┘ └───────────┘ └──────────┘ │ +│ │ +│ All plans include all features. │ +│ The only difference is who runs the server. │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +**Key messaging**: "All plans include all features. The only difference is who runs the server." This is a powerful differentiator. No feature gating. + +#### Section 8: CTA / Footer + +``` +┌──────────────────────────────────────────────────┐ +│ │ +│ Ready to stop wasting time? │ +│ │ +│ [Get Started — Free] [Book a Demo] │ +│ │ +│ ───────────────────────────────────────────── │ +│ │ +│ officer.dev │ +│ Product: Features | Pricing | Docs │ +│ Community: GitHub | Discord | Blog │ +│ Company: About | Enterprise | Contact │ +│ │ +│ © 2026 Officer.dev │ +│ │ +└──────────────────────────────────────────────────┘ +``` + +--- + +### 3.2 Features Page (`/features`) + +Overview page with anchor-linked sections for each feature area. Each section has: +- Feature title & one-line pitch +- 3-4 bullet capabilities +- Embedded demo video or animated screenshot +- CTA to try it + +**Feature areas:** + +#### AI Assistant (`/features/ai-assistant`) +- Multi-provider (Claude, OpenCode, Pi-Mono) +- File-aware — reads, edits, and creates files +- Task-aware — runs automation on your behalf +- Voice input, image analysis, code understanding +- Session persistence across devices + +**Demo video**: Show a conversation where the user asks "organize my downloads folder by file type" and the AI does it, showing the file browser updating in real-time. + +#### File Management (`/features/file-management`) +- Browse, upload, download, search +- Built-in viewers: text, images, PDFs, audio, video +- AI-powered: OCR, text-to-speech, transcription, audio extraction +- Video transcoding for any format +- Archive extraction (zip, tar, 7z, rar) +- Git clone, yt-dlp download integration + +**Demo video**: Upload a photo of a receipt → OCR extracts the text → AI categorizes the expense. All in Officer. + +#### Automation (`/features/automation`) +- Tasks triggered by file type, schedule, or manually +- Workflows chaining multiple steps +- Cron jobs for recurring work +- Background processes +- Skills system — teach AI new capabilities + +**Demo video**: Show setting up a task that compresses images when they're uploaded to a specific folder. + +#### Workspaces (`/features/workspaces`) +- Multi-panel layouts +- Combine chat + files + any tool +- Save and switch between workspace configurations +- Create workspace templates for team + +**Demo video**: Building a "Content Creation" workspace with file browser on the left, AI chat in the middle, and preview on the right. + +--- + +### 3.3 Pricing Page (`/pricing`) + +Three tiers, feature-complete across all. The difference is hosting and support. + +**Self-Hosted (Free)** +- All features, unlimited users +- You host, you manage +- Community support (GitHub, Discord) +- "Perfect for developers, homelab enthusiasts, and teams who want full control." + +**Cloud (Subscription — $XX/user/month)** +- All features, unlimited +- We host and manage everything +- Automatic updates, backups, SSL +- Email support +- "For teams who want Officer without the DevOps." + +**Enterprise (Custom)** +- Everything in Cloud +- SLA with guaranteed uptime +- Priority support with dedicated account manager +- Custom feature development +- SSO/SAML integration +- Audit logging +- On-premises deployment assistance +- "For organizations that need enterprise-grade guarantees." + +**Below the pricing cards:** +- FAQ section (Is it really free? Can I migrate between plans? What about data portability?) +- "Need something custom?" → Contact form +- "Building plugins?" → Marketplace waitlist signup + +--- + +### 3.4 Enterprise Page (`/enterprise`) + +**Audience**: IT decision-makers, CTOs, operations managers. + +**Tone**: Professional, emphasizing reliability, security, and ROI. + +**Sections:** + +1. **Hero**: "The company intranet that works for you — literally." +2. **Problem**: "Your team uses 12 different tools. None of them talk to each other. Officer replaces the glue." +3. **Security**: "Self-hosted means your data never leaves your servers. Full audit logging. Role-based access. SOC 2 compliance roadmap." +4. **ROI Calculator**: Interactive widget — input team size, estimated hours on repetitive tasks → shows annual savings +5. **Consultancy**: "Our team builds custom integrations, plugins, and workflows tailored to your operations." +6. **Case studies**: (placeholder for future customer stories) +7. **CTA**: "Book a Demo" → Calendly or contact form + +--- + +### 3.5 Open Source Page (`/open-source`) + +**Audience**: Developers, contributors, plugin builders. + +**Sections:** + +1. **Hero**: "Built in the open. Extended by everyone." +2. **Why Open Source**: Philosophy statement — transparency, community ownership, no vendor lock-in +3. **Contributing**: Quick guide to getting started, link to CONTRIBUTING.md +4. **Plugin Development**: Overview of the plugin system, link to docs +5. **Marketplace Preview**: "Coming soon — build and sell plugins, themes, and automations. Earn money from your work." +6. **GitHub Stats**: Stars, forks, contributors, recent commits +7. **CTA**: "Star on GitHub" / "Join Discord" + +--- + +## 4. Copy Bank + +### Headlines (A/B test candidates) + +**Automation-focused:** +- "Your AI-powered operating system — everywhere." +- "Stop doing. Start automating." +- "The work that works itself." +- "Automate everything. Miss nothing." + +**For individuals:** +- "Your second brain. But it actually does things." +- "AI that doesn't just answer — it acts." +- "Automate your life. Have more time to live it." + +**For companies:** +- "What if your team's busywork just... disappeared?" +- "The company intranet that works for you — literally." +- "Fortunes are lost to repetitive work every day. Meet the fix." +- "Every employee deserves an AI assistant. Now they can have one." + +**Open source angle:** +- "Open source. Self-hosted. No strings attached." +- "Your data. Your server. Your AI." +- "The power of enterprise AI. The freedom of open source." + +### Social Proof (to collect/create) + +- GitHub stars count +- "Used by X developers/companies" +- Testimonials (collect after launch) +- "Featured in [publications]" (aim for Hacker News, ProductHunt, etc.) + +### Microcopy + +- Install CTA: "Get Started — Free" +- Cloud CTA: "Start your trial" +- Enterprise CTA: "Book a demo" +- GitHub CTA: "Star on GitHub" +- Newsletter CTA: "Get updates" +- Marketplace waitlist: "Join the waitlist" + +--- + +## 5. Visual Identity for Marketing + +### Design System Adaptation + +The product uses a duck-themed design (teal, yellow, forest green, glass-morphism with pixel grid). The marketing site should feel like the same brand but optimized for conversion: + +**Colors:** +- Primary: `#F4C430` (Duck Yellow) — CTAs, highlights +- Secondary: `#0891B2` (Duck Teal) — links, accents +- Dark: `#14532D` (Duck Dark) — text, headings +- Background: Soft cream/beige (`#F5F0E8`) or white with subtle grid texture +- Dark mode: Support it, matching the product's dark theme + +**Typography:** +- Headings: Bold, system-ui stack with the same skewed/3D shadow effect as the logo (but subtler for readability) +- Body: Clean sans-serif (Inter or system-ui), 18px base +- Code: Monospace for install commands and API examples + +**The Duck:** +- Hero: Subtle walking animation (lightweight 2D sprite or CSS, not the 3D model — save loading time) +- Scroll companion: Small duck that appears in corners, reacts to scroll +- 404 page: Lost duck illustration +- Loading states: Duck waddle animation + +**Photography/Visuals:** +- Product screenshots with the landscape backgrounds +- Minimal mockups (no fake Chrome windows — just clean screenshots with rounded corners and shadow) +- Feature diagrams: Simple, geometric, using brand colors + +**Grid Pattern:** +- Carry the pixel grid texture from the product into section backgrounds +- Use it subtly — don't overwhelm the content + +### Section Visual Hierarchy + +| Section | Background | Text Color | +|---------|-----------|------------| +| Hero | landscape1.png with dark overlay | White | +| Problem | White/cream | Dark green | +| Solution | Subtle grid pattern | Dark green | +| For You/Company | Split — light teal / light yellow | Dark | +| How It Works | White | Dark | +| Open Source | Dark (forest green) | Yellow/white | +| Pricing | White/cream | Dark | +| CTA | Teal gradient | White/yellow | + +--- + +## 6. Demo Videos Plan + +### Product Demo Reel (60s — for hero) + +A fast-paced showcase that cycles through the major features: + +| Time | Scene | Description | +|------|-------|-------------| +| 0-5s | Cold open | Tagline appears over landscape. Duck walks in. | +| 5-12s | Chat | User asks AI to "summarize all PDFs in my project folder." AI responds with tool use, result appears. | +| 12-20s | Files | File browser opens. User uploads files, previews images, plays audio. Quick montage. | +| 20-30s | Automation | User creates a task. It runs automatically. Toast notification: "Task complete." | +| 30-40s | Workspace | Multi-panel workspace assembles itself — chat + files + viewer. User drags to resize. | +| 40-48s | Dashboard | Widgets dashboard: clock, weather, goals getting checked off, pomodoro timer running. | +| 48-55s | Scale | "For you. For your team. For your company." — zoom out to show multiple users. | +| 55-60s | CTA | Logo + "Open source. Self-hosted. Free forever." + URL | + +### Feature-Specific Demos (30s each) + +**AI Assistant Demo:** +- Show real conversation with tool use +- AI reads a file, suggests changes, user approves +- Emphasis on the AI *doing things*, not just chatting + +**File Management Demo:** +- Upload → organize → preview → AI actions (OCR, transcribe) +- Show the breadth of file types supported +- yt-dlp download as a wow moment + +**Automation Demo:** +- Create a task that processes files automatically +- Show it triggering on file upload +- End with "Set it up once. It runs forever." + +**Workspaces Demo:** +- Start with empty screen +- Build a workspace: add chat, files, viewer +- Split panels, resize +- "Your desk. Your way." + +### Recording Guidelines + +- **Resolution**: 1920x1080 for landscape, 1080x1920 for portrait (social) +- **Browser**: Clean browser, no bookmarks bar, no extensions visible +- **Data**: Use realistic-looking sample data (not lorem ipsum) +- **Cursor**: Smooth, deliberate movements. No jitter. +- **Speed**: 1.5x natural speed for repetitive parts, real-time for "wow" moments +- **Music**: Upbeat instrumental for playful, ambient electronic for professional + +--- + +## 7. Technical Implementation + +### Stack Recommendation + +The marketing site should be **separate from the main monorepo** — it's a public website with different deployment, SEO, and performance requirements. + +**Recommended stack:** +- **Framework**: Astro (static-first, great SEO, supports React islands for interactive parts) +- **Styling**: Tailwind CSS (matches the product's design system) +- **Animations**: Framer Motion (for scroll-triggered animations) +- **Hosting**: Vercel or Cloudflare Pages (fast global CDN) +- **CMS**: Markdown files for blog posts (future) +- **Analytics**: Plausible or PostHog (privacy-friendly) + +**Alternative**: If the team prefers to keep everything in the monorepo's React ecosystem, a Next.js or Vite app could work. But Astro's static output and built-in SEO features make it better for a marketing site. + +### Project Location + +Create at `/home/pastilhas/projects/officer.dev/website/` — separate from the monorepo but within the project directory. + +### SEO Strategy + +**Target keywords:** +- "self-hosted AI platform" +- "open source AI assistant" +- "AI-powered intranet" +- "personal AI operating system" +- "self-hosted automation" +- "open source alternative to [Claude Work/Notion AI/etc.]" +- "AI file management" + +**Technical SEO:** +- Static HTML pages (Astro) +- Proper meta tags, OpenGraph images per page +- JSON-LD structured data +- Sitemap.xml +- Fast Core Web Vitals (landscape images need lazy loading + WebP conversion) + +### Performance Considerations + +- landscape1.png is 16MB — must be converted to WebP/AVIF and served responsive +- The 3D duck model (40MB total) should NOT be on the marketing site — use a lightweight 2D animation instead +- Use the rubber-duck.png (1.6MB) at reduced size for the duck character +- Lazy load everything below the fold + +--- + +## 8. Launch Considerations + +### Pre-Launch Checklist + +- [ ] Marketing site live with all sections +- [ ] Product demo video recorded and embedded +- [ ] GitHub repo public with README, CONTRIBUTING.md, LICENSE +- [ ] Installation script (`install.officer.dev`) working +- [ ] Documentation site (even minimal) live +- [ ] Discord/community channel created +- [ ] Social accounts created (Twitter/X, LinkedIn) +- [ ] ProductHunt launch page drafted +- [ ] Hacker News "Show HN" post drafted +- [ ] Email waitlist for cloud/marketplace + +### Launch Channels + +| Channel | Content | Timing | +|---------|---------|--------| +| Hacker News | "Show HN: Officer — open-source, AI-powered OS for automating your life" | Day 1 | +| ProductHunt | Full launch with screenshots, video, maker story | Day 1 | +| Reddit | r/selfhosted, r/homelab, r/opensource, r/artificial | Day 1 | +| Twitter/X | Thread: "I built an OS that automates my life. It's open source." | Day 1 | +| LinkedIn | Professional angle: "Every company wastes $X on repetitive work" | Day 2 | +| Dev.to / Hashnode | Technical deep-dive blog post | Week 1 | +| YouTube | Full demo video (5min) | Week 1 | + +### Post-Launch + +- Monitor GitHub issues and community channels +- Publish weekly blog posts (features, tutorials, philosophy) +- Start marketplace waitlist campaign +- Collect testimonials from early adopters +- Begin enterprise outreach + +--- + +## Appendix: Competitor Landscape + +| Product | What it is | Officer's advantage | +|---------|-----------|-------------------| +| **Notion** | Wiki + project management | Officer has AI that acts, not just organizes. Self-hosted. | +| **Obsidian** | Markdown knowledge base | Officer is broader — files, AI, automation, not just notes. | +| **Nextcloud** | Self-hosted file sync | Officer has native AI, automation, and workspaces. | +| **n8n** | Workflow automation | Officer combines automation with AI + file management + UI. | +| **Claude Work** | Enterprise AI chat | Officer is open source, self-hosted, and more extensible. | +| **OpenClaw** | Open source AI tools | Officer is friendlier, more polished, broader scope. | +| **Home Assistant** | Home automation | Different domain, but similar self-hosted philosophy. Potential integration. | + +Officer's unique position: **No one else combines AI + files + automation + workspaces in a single self-hosted platform.** That's the moat. diff --git a/PHONE_APP.md b/PHONE_APP.md new file mode 100644 index 00000000..661406d4 --- /dev/null +++ b/PHONE_APP.md @@ -0,0 +1,1208 @@ +# Officer Mobile App Plan + +> This document is written by Claude for Claude. It contains the full plan for native mobile apps (Kotlin/Android + Swift/iOS) for the Officer platform, based on a thorough study of the monorepo. When resuming this work, read this document first, then explain the plan to the user before starting implementation. + +--- + +## Table of Contents + +1. [Current Web App Summary](#1-current-web-app-summary) +2. [What to Include vs Exclude](#2-what-to-include-vs-exclude) +3. [Architecture Overview](#3-architecture-overview) +4. [Authentication](#4-authentication) +5. [Site Map](#5-site-map) +6. [Screen-by-Screen Breakdown](#6-screen-by-screen-breakdown) +7. [Tablet Adaptations](#7-tablet-adaptations) +8. [Technical Challenges](#8-technical-challenges) +9. [API Integration Notes](#9-api-integration-notes) +10. [Shared Design Language](#10-shared-design-language) +11. [Implementation Phases](#11-implementation-phases) + +--- + +## 1. Current Web App Summary + +Officer is an AI-assisted personal backend for life management. The web app (`officer-web`) is a React 19 SPA talking to a Hono API server (port 5000). Key features: + +| Feature | Description | +|---------|-------------| +| **Chat** | Multi-provider AI chat (Claude, OpenCode, Pi-Mono) via WebSockets. Sessions persisted as JSON files. Supports images, file attachments, model selection, working directory context. | +| **File Browser** | Full file manager with upload/download, rename, copy/move, search, archive extraction, git clone, yt-dlp download, OCR, TTS, transcription, audio extraction, video transcoding. Multiple "roots" (home, ~, officer.dev). | +| **Workspaces** | Multi-panel layouts combining chat, files, terminal, code editor. Panels can be split horizontally/vertically. State persisted per workspace. | +| **Terminal** | Sandboxed Docker terminals and host terminals (Super Admin). WebSocket-based PTY. | +| **Code Editor** | Text editor with syntax highlighting (PrismJS). | +| **Dashboard** | Widget panel (clock, weather, pomodoro, daily goals, quick notes) + workspace launcher + chat launcher. | +| **Automation** | Builder for workflows, pipelines, processes, services, cron jobs, tasks, skills. | +| **Tasks** | File-based task definitions (TASK.md) that can be triggered on files/directories. Native/global/user scopes. | +| **Skills** | AI skill library (SKILL.md files) injected into chat system prompts. | +| **Settings** | User profile, system settings, server resources, AI harness configuration. | +| **Auth** | Email/password + passkey (WebAuthn). JWT tokens. Role-based access (Member, Admin, Owner, Super Admin). | + +The API is at `/api/*` with JWT auth on all protected routes. WebSockets for chat (`/api/harness/*/ws`) and terminal (`/api/terminal/ws`). + +--- + +## 2. What to Include vs Exclude + +### Include (core mobile experience) + +| Feature | Rationale | +|---------|-----------| +| **Chat** | The #1 use case. Having an AI assistant in your pocket is the killer feature. Voice input makes it even better on mobile. | +| **File Browser** | Essential for viewing/managing files. Mobile adds camera upload, share sheet integration. Simplified but fully functional. | +| **Dashboard / Widgets** | Perfect for mobile - quick glance at clock, weather, goals, notes. Widget-style info is native to mobile platforms. | +| **Notifications** | Mobile-exclusive feature. Push notifications for chat completions, task results, long downloads finishing. | +| **Settings** | Profile, preferences. Subset of web settings. | +| **Session History** | Browse and resume past chat sessions. | +| **Quick Actions** | Share sheet integration (share URL/text/image to Officer for chat or file upload). | + +### Include (reduced scope) + +| Feature | What changes | +|---------|-------------| +| **File Viewer** | View files (text, images, PDFs, audio, video) but no full code editor. Read-only text with syntax highlighting. | +| **Tasks** | View and trigger tasks, see logs. No task creation/editing (too complex for mobile). | +| **Skills** | Browse skill library. No editing. | +| **Workspaces** | View workspace list, open a workspace (but as a single-panel experience, not multi-panel). | + +### Exclude (not suitable for phone) + +| Feature | Rationale | +|---------|-----------| +| **Terminal** | Typing shell commands on a phone keyboard is miserable. The sandboxed Docker terminal is a power-user web feature. | +| **Code Editor (write mode)** | Writing code on a phone is impractical. Read-only viewing with syntax highlighting is fine. | +| **Multi-panel workspaces** | Phone screens can't meaningfully split into panels. Tablet gets a simplified version (see section 7). | +| **Automation Builder** | Complex drag-and-drop/form-heavy workflow builder. Keep this web-only. | +| **Server/Resource Management** | Admin infrastructure management (installing databases, configuring services). Web-only. | +| **System Settings** | Server configuration, AI harness setup, plugin management. Web-only. | + +### Mobile-Exclusive Features (not on web) + +| Feature | Description | +|---------|-------------| +| **Push Notifications** | FCM (Android) / APNs (iOS) for chat completions, task results, downloads. | +| **Voice Input** | Native speech-to-text for chat messages. Much better than web speech API. | +| **Camera Integration** | Take photo and send to chat or upload to file browser directly. | +| **Share Sheet** | Receive shared content (URLs, text, images) from other apps. | +| **Biometric Auth** | Fingerprint/Face ID as alternative to passkey/password. | +| **Home Screen Widgets** | Android widgets / iOS widgets for quick notes, pomodoro, weather. | +| **Offline Queue** | Queue chat messages when offline, send when reconnected. | +| **Background Downloads** | File downloads and yt-dlp downloads continue in background. | + +--- + +## 3. Architecture Overview + +### Platform Strategy + +Two fully native apps sharing no code, but sharing identical: +- API contract (same REST + WebSocket endpoints) +- UX flows and information architecture +- Design language (adapted to each platform's conventions) + +``` +┌──────────────┐ ┌──────────────┐ +│ Android App │ │ iOS App │ +│ (Kotlin) │ │ (Swift) │ +│ │ │ │ +│ Jetpack │ │ SwiftUI │ +│ Compose │ │ │ +│ Material 3 │ │ Native iOS │ +│ │ │ Components │ +└──────┬───────┘ └──────┬───────┘ + │ │ + └────────┬───────────┘ + │ + ┌──────┴──────┐ + │ Officer API │ + │ (Hono) │ + │ port 5000 │ + └─────────────┘ +``` + +### Android Stack + +| Layer | Technology | +|-------|-----------| +| UI | Jetpack Compose + Material 3 | +| Navigation | Compose Navigation | +| Networking | Ktor Client (HTTP + WebSocket) | +| State | ViewModel + StateFlow | +| DI | Hilt | +| Storage | DataStore (preferences), Room (offline cache) | +| Images | Coil | +| Push | Firebase Cloud Messaging | +| Auth | BiometricPrompt API | +| Background | WorkManager | + +### iOS Stack + +| Layer | Technology | +|-------|-----------| +| UI | SwiftUI | +| Navigation | NavigationStack (iOS 16+) | +| Networking | URLSession + native WebSocket (URLSessionWebSocketTask) | +| State | @Observable (iOS 17+) / ObservableObject | +| DI | Swift native (Environment, protocol-based) | +| Storage | UserDefaults, SwiftData (offline cache) | +| Images | AsyncImage + custom cache | +| Push | APNs | +| Auth | LocalAuthentication (Face ID / Touch ID) | +| Background | BGTaskScheduler | + +### Minimum OS Versions + +- **Android**: API 26 (Android 8.0) - covers 95%+ of devices, gives us WorkManager, BiometricPrompt, etc. +- **iOS**: iOS 16 - gives us NavigationStack, modern SwiftUI features. iOS 17 for @Observable if we want it. + +--- + +## 4. Authentication + +### Flow + +``` +App Launch + │ + ├─ Has stored JWT? ──Yes──► Validate token (GET /api/auth/me) + │ │ + │ Valid? ──Yes──► Home Screen + │ │ + │ No ──► Login Screen + │ + └─ No ──► Login Screen + │ + ├─ Email/Password + │ POST /api/auth/signin + │ ► Store JWT securely + │ ► Home Screen + │ + ├─ Passkey (WebAuthn) + │ ► Platform passkey flow + │ ► Store JWT securely + │ + └─ Biometric Unlock (returning user) + ► Decrypt stored JWT + ► Validate & proceed +``` + +### Token Storage + +- **Android**: EncryptedSharedPreferences (AndroidX Security) +- **iOS**: Keychain Services + +### Biometric Integration + +On first successful login, offer to enable biometric unlock: +1. User logs in with email/password or passkey +2. App prompts: "Enable Face ID / Fingerprint for quick access?" +3. If yes: encrypt JWT with biometric-bound key, store in Keychain/Keystore +4. On next launch: biometric prompt → decrypt JWT → validate → proceed + +### Server URL Configuration + +The mobile app needs to know where the Officer API lives. First-launch flow: + +1. "Enter your Officer server URL" input +2. Validate by hitting `GET /api/server-settings/plugins` (public endpoint) +3. Store URL in app preferences +4. Proceed to login + +This is important because Officer is self-hosted - there's no central server. + +--- + +## 5. Site Map + +``` +App +├── Login +│ ├── Server URL Setup (first launch) +│ ├── Email/Password +│ ├── Passkey +│ └── Biometric Unlock +│ +├── Home (Tab 1) +│ ├── Widget Dashboard +│ │ ├── Clock +│ │ ├── Weather +│ │ ├── Pomodoro Timer +│ │ ├── Daily Goals +│ │ └── Quick Notes +│ └── Workspace Quick Access +│ +├── Chat (Tab 2) +│ ├── Session List +│ │ ├── Active Sessions +│ │ └── Archived Sessions +│ ├── New Chat +│ └── Chat Session +│ ├── Message Thread +│ ├── Model Selector +│ ├── Image Attachment +│ ├── Voice Input +│ └── Tool Use Display +│ +├── Files (Tab 3) +│ ├── Directory Browser +│ │ ├── Grid/List View +│ │ ├── Search +│ │ ├── Sort/Filter +│ │ └── Context Actions +│ │ ├── Open/Preview +│ │ ├── Share +│ │ ├── Rename +│ │ ├── Copy/Move +│ │ ├── Delete +│ │ ├── Download to Device +│ │ └── Upload (camera, gallery, files) +│ └── File Viewer +│ ├── Text (syntax highlighted, read-only) +│ ├── Image +│ ├── PDF +│ ├── Audio Player +│ ├── Video Player +│ └── AI Actions (OCR, TTS, Transcribe) +│ +├── Activity (Tab 4) +│ ├── Task List +│ │ ├── Native Tasks +│ │ ├── Global Tasks +│ │ └── User Tasks +│ ├── Task Logs +│ └── Running Processes +│ +└── Settings (Tab 5 / Profile) + ├── Profile + │ ├── Name, Username, Avatar + │ ├── Password Change + │ └── Passkey Management + ├── App Preferences + │ ├── Theme (Light/Dark/System) + │ ├── Notification Settings + │ ├── Default Chat Model + │ ├── Biometric Lock + │ └── Server URL + └── About / Logout +``` + +### Navigation Pattern + +**Bottom Tab Bar** with 4 primary tabs + profile access: + +``` +┌─────────────────────────────────────┐ +│ │ +│ [Screen Content] │ +│ │ +├─────┬─────┬─────┬─────┬────────────┤ +│ Home│ Chat│Files│ Act.│ Settings │ +│ 🏠 │ 💬 │ 📁 │ ⚡ │ ⚙️ │ +└─────┴─────┴─────┴─────┴────────────┘ +``` + +- **Home**: Dashboard with widgets +- **Chat**: Session list → chat view +- **Files**: File browser → file viewer +- **Activity**: Tasks, logs, processes +- **Settings**: Profile and preferences + +On Android: Material 3 NavigationBar. On iOS: UITabBarController / SwiftUI TabView. + +--- + +## 6. Screen-by-Screen Breakdown + +### 6.1 Login Screen + +``` +┌─────────────────────────┐ +│ │ +│ [Officer Logo] │ +│ │ +│ ┌───────────────────┐ │ +│ │ Server URL │ │ ← Only on first launch or +│ └───────────────────┘ │ tap "Change server" +│ │ +│ ┌───────────────────┐ │ +│ │ Email │ │ +│ └───────────────────┘ │ +│ ┌───────────────────┐ │ +│ │ Password │ │ +│ └───────────────────┘ │ +│ │ +│ [ Sign In ] │ +│ │ +│ ── or ── │ +│ │ +│ [ Sign in with Passkey ] │ +│ │ +│ Forgot password? │ +│ │ +└─────────────────────────┘ +``` + +**Behavior:** +- Server URL field shown on first launch or when tapped +- Email/password fields with proper keyboard types +- "Sign in with Passkey" triggers platform WebAuthn flow +- On success: store JWT, navigate to Home +- On returning user with biometric enabled: show biometric prompt immediately, with "Use password instead" fallback + +--- + +### 6.2 Home / Dashboard + +``` +┌─────────────────────────┐ +│ Officer [Avatar]│ +├─────────────────────────┤ +│ │ +│ ┌──────┐ ┌──────────┐│ +│ │ 14:32│ │ Weather ││ +│ │ Fri │ │ 22° ☀️ ││ +│ └──────┘ └──────────┘│ +│ │ +│ ┌──────────────────┐ │ +│ │ 🍅 Pomodoro │ │ +│ │ 18:42 remaining │ │ +│ │ [Start] [Reset] │ │ +│ └──────────────────┘ │ +│ │ +│ ┌──────────────────┐ │ +│ │ Daily Goals │ │ +│ │ ☑ Exercise │ │ +│ │ ☐ Read 30min │ │ +│ │ ☐ Journal │ │ +│ │ [+ Add goal] │ │ +│ └──────────────────┘ │ +│ │ +│ ┌──────────────────┐ │ +│ │ Quick Notes │ │ +│ │ Tap to add... │ │ +│ └──────────────────┘ │ +│ │ +│ Workspaces │ +│ ┌────┐ ┌────┐ ┌────┐ │ +│ │ WS1│ │ WS2│ │ WS3│ │ +│ └────┘ └────┘ └────┘ │ +│ │ +└─────────────────────────┘ +``` + +**Behavior:** +- Scrollable vertical layout +- Widgets are cards that can be reordered (long-press drag) +- Widget data syncs with web app via user settings API +- Workspace cards show name + icon, tap opens workspace (single-panel mode on phone) +- Pull-to-refresh updates widget data +- Avatar tap opens Settings + +**Widget Implementation:** +- Clock: Local device time (no API needed) +- Weather: Can use device location + weather API, or sync from web app settings +- Pomodoro: Local timer with state synced to server (so it persists across devices) +- Daily Goals: Stored in user settings, synced +- Quick Notes: Stored in user settings, synced + +--- + +### 6.3 Chat - Session List + +``` +┌─────────────────────────┐ +│ Chat [+ New]│ +├─────────────────────────┤ +│ [🔍 Search sessions...] │ +├─────────────────────────┤ +│ │ +│ Today │ +│ ┌──────────────────┐ │ +│ │ 🤖 Debug login │ │ +│ │ Claude · 2h ago │ │ +│ │ Last: "The issue │ │ +│ │ was in the..." │ │ +│ └──────────────────┘ │ +│ ┌──────────────────┐ │ +│ │ 🤖 Refactor API │ │ +│ │ Pi-Mono · 5h ago │ │ +│ │ Last: "I've upda- │ │ +│ │ ted the router..." │ │ +│ └──────────────────┘ │ +│ │ +│ Yesterday │ +│ ┌──────────────────┐ │ +│ │ 🤖 Recipe ideas │ │ +│ │ Claude · 1d ago │ │ +│ └──────────────────┘ │ +│ │ +│ Archived ▸ │ +│ │ +└─────────────────────────┘ +``` + +**Behavior:** +- Sessions fetched from `GET /api/sessions?provider=all` +- Grouped by date (Today, Yesterday, This Week, Older) +- Each card shows: session name, provider icon, time, last message preview +- Swipe left to delete, swipe right to archive +- Long-press for context menu (rename, archive, delete) +- "Archived" section expandable +- Search filters sessions by name +- FAB or top-right button for new chat + +**Data Source:** +- `GET /api/sessions?provider=claude` (and opencode, pi-mono) +- Sessions are JSON files on the server, API returns metadata +- Message preview from last assistant message + +--- + +### 6.4 Chat - Conversation + +``` +┌─────────────────────────┐ +│ ← Debug login [···] │ +│ Claude 3.5 Sonnet ▾ │ +├─────────────────────────┤ +│ │ +│ ┌─ You ────────────┐ │ +│ │ The login page │ │ +│ │ throws a 401... │ │ +│ └──────────────────┘ │ +│ │ +│ ┌─ Claude ─────────┐ │ +│ │ Looking at the │ │ +│ │ auth middleware... │ │ +│ │ │ │ +│ │ ┌─ Tool Use ────┐ │ │ +│ │ │ 📄 Read file │ │ │ +│ │ │ auth/jwt.ts │ │ │ +│ │ └──────────────┘ │ │ +│ │ │ │ +│ │ The issue is in │ │ +│ │ the token verify │ │ +│ │ ```ts │ │ +│ │ if (!jti) ... │ │ +│ │ ``` │ │ +│ └──────────────────┘ │ +│ │ +├─────────────────────────┤ +│ ┌───────────────┐ 📎 🎤│ +│ │ Message... │ [Send]│ +│ └───────────────┘ │ +└─────────────────────────┘ +``` + +**Behavior:** +- WebSocket connection to `/api/harness/{provider}/ws` +- Messages stream in real-time (assistant:partial → assistant:text) +- Tool use blocks shown as collapsible cards (collapsed by default) +- Code blocks with syntax highlighting and copy button +- Markdown rendering for assistant messages +- Model selector dropdown in the header (or a bottom sheet) +- Attachment button (📎) opens picker: Camera, Photo Library, Files +- Microphone button (🎤) for voice input (speech-to-text → send as text) +- Send button disabled while assistant is generating +- "Stop" button appears during generation +- Pull-down to load older messages (if session has many) +- `[···]` menu: Rename session, View in files, Delete, Share + +**WebSocket Message Protocol:** +``` +Client sends: + { type: 'chat', prompt, sessionId, model, images?, attachmentIds? } + { type: 'resume', sessionId } + { type: 'stop' } + +Server sends: + { type: 'session:init', sessionId, name } + { type: 'assistant:partial', text } + { type: 'assistant:text', text } + { type: 'tool:use', name, input } + { type: 'tool:result', result } + { type: 'result', cost?, duration?, turns? } + { type: 'error', message } + { type: 'stopped' } +``` + +**Offline Behavior:** +- If offline when sending, queue message locally +- Show "Queued - will send when online" indicator +- On reconnect, send queued messages in order +- Previously loaded messages remain visible offline (cached in local DB) + +**Voice Input:** +- Tap and hold microphone for continuous recording +- Or tap once to start, tap again to stop +- Platform native speech recognition +- Transcribed text placed in input field for review before sending +- User can edit before sending + +--- + +### 6.5 Files - Directory Browser + +``` +┌─────────────────────────┐ +│ ← Files [···] │ +│ / > Documents > Work │ +├─────────────────────────┤ +│ [🔍 Search files...] │ +├─────────────────────────┤ +│ Sort: Name ▾ [≡] [⊞] │ +├─────────────────────────┤ +│ │ +│ 📁 Projects → │ +│ 📁 Reports → │ +│ 📄 notes.md 3KB │ +│ 🖼️ photo.jpg 1.2MB │ +│ 🎵 song.mp3 4.5MB │ +│ 📹 demo.mp4 45.2MB │ +│ 📦 backup.zip 12.0MB │ +│ │ +│ [+ Upload] │ +│ │ +└─────────────────────────┘ +``` + +**Behavior:** +- Breadcrumb navigation at top (scrollable horizontal) +- Tap folder to navigate in, tap file to open viewer +- Long-press for context menu: + - Open / Preview + - Share (native share sheet) + - Rename + - Copy / Move (enter selection mode) + - Delete + - Download to device + - AI actions (OCR, TTS, Transcribe - based on file type) +- Pull-to-refresh +- FAB or bottom button for upload (Camera, Photo Library, Files picker) +- Multi-select mode (long-press first item, then tap others) +- Swipe actions: left = delete, right = share +- Grid/List view toggle +- Sort by name/date/size +- Search with debounce (calls `/api/file-browser/search`) + +**Root Selection:** +- Dropdown or segmented control at top for root selection +- Only show non-home roots for Super Admin users +- Default to "home" + +**Upload Sources:** +- Device file picker +- Camera (take photo) +- Photo library +- Share sheet (from other apps) +- Drag & drop (tablet only) + +--- + +### 6.6 Files - File Viewer + +``` +┌─────────────────────────┐ +│ ← notes.md [↗] [···]│ +├─────────────────────────┤ +│ │ +│ # Meeting Notes │ +│ │ +│ ## Action Items │ +│ - Fix login bug │ +│ - Update docs │ +│ - Review PR #42 │ +│ │ +│ ## Discussion │ +│ We talked about the │ +│ new mobile app and... │ +│ │ +│ │ +│ │ +│ │ +└─────────────────────────┘ +``` + +**Viewer Types:** + +| File Type | Viewer | Notes | +|-----------|--------|-------| +| `.md` | Rendered Markdown | With proper styling | +| `.txt`, `.json`, `.ts`, `.js`, etc. | Syntax-highlighted text | Read-only, with line numbers | +| `.jpg`, `.png`, `.gif`, `.svg`, `.webp` | Image viewer | Pinch to zoom, pan | +| `.pdf` | PDF viewer | Native PDF rendering | +| `.mp3`, `.wav`, `.flac`, `.ogg` | Audio player | Play/pause, seek, speed control | +| `.mp4`, `.webm`, `.mov` | Video player | Native player, fullscreen support | +| `.mkv`, `.avi` | Video player (transcoded) | Uses `/api/file-browser/transcode` endpoint | +| Other | Download prompt | "This file type can't be previewed. Download?" | + +**Header Actions:** +- [↗] Share via native share sheet +- [···] Menu: Download to device, AI actions (contextual), Open in chat, Delete + +**AI Actions (contextual):** +- Images: OCR (extract text) +- Text files: TTS (read aloud) +- Audio/Video: Transcribe +- Video: Extract audio + +--- + +### 6.7 Activity + +``` +┌─────────────────────────┐ +│ Activity │ +├─────────────────────────┤ +│ [Tasks] [Logs] [Procs] │ +├─────────────────────────┤ +│ │ +│ Native Tasks │ +│ ┌──────────────────┐ │ +│ │ 📋 Format Code │ │ +│ │ Formats source │ │ +│ │ files with... │ │ +│ │ [Run ▶] │ │ +│ └──────────────────┘ │ +│ ┌──────────────────┐ │ +│ │ 📋 Compress Imgs │ │ +│ │ Optimizes image │ │ +│ │ files in... │ │ +│ │ [Run ▶] │ │ +│ └──────────────────┘ │ +│ │ +│ User Tasks │ +│ ┌──────────────────┐ │ +│ │ 📋 Backup DB │ │ +│ │ Runs pg_dump... │ │ +│ │ [Run ▶] │ │ +│ └──────────────────┘ │ +│ │ +└─────────────────────────┘ +``` + +**Tabs:** +1. **Tasks**: List of available tasks (native/global/user). Each shows name, description, trigger types. "Run" button opens a sheet to select target file/directory. +2. **Logs**: Recent task execution logs. Each shows task name, status (success/failure), timestamp, duration. Tap to see full log output. +3. **Processes**: Currently running background processes. Show name, status, uptime. No start/stop controls (too dangerous on mobile - manage via web). + +**Task Runner:** +- When "Run" is tapped, show a bottom sheet: + - Select target file/directory (mini file browser) + - Confirm and run + - Show real-time output in a scrollable log view + - Toast on completion + +--- + +### 6.8 Settings + +``` +┌─────────────────────────┐ +│ Settings │ +├─────────────────────────┤ +│ │ +│ ┌──────────────────┐ │ +│ │ 👤 John Doe │ │ +│ │ john@example.com │ │ +│ │ Super Admin │ │ +│ └──────────────────┘ │ +│ │ +│ Profile │ +│ Name, Username, Avatar │ +│ Change Password → │ +│ Passkeys → │ +│ │ +│ ───────────────────── │ +│ │ +│ App │ +│ Theme System │ +│ Notifications → │ +│ Default Model Claude │ +│ Biometric Lock [🔘] │ +│ │ +│ ───────────────────── │ +│ │ +│ Connection │ +│ Server 192.168.1.5 → │ +│ Status Connected 🟢│ +│ │ +│ ───────────────────── │ +│ │ +│ [ Sign Out ] │ +│ │ +│ Officer v1.0.0 │ +│ │ +└─────────────────────────┘ +``` + +**Sections:** +1. **Profile card**: Avatar, name, email, role. Tap to edit. +2. **Profile**: Name/username editing, avatar upload (camera or gallery), password change, passkey management. +3. **App**: Theme (light/dark/system), notification preferences, default chat model/provider, biometric lock toggle. +4. **Connection**: Current server URL (editable), connection status indicator, last sync time. +5. **Sign Out**: Clears JWT, biometric keys, navigates to login. + +--- + +## 7. Tablet Adaptations + +Tablets (iPad, Android tablets/foldables) get an enhanced layout while using the same app binary. Detection via screen width: +- **Phone**: < 600dp (Android) / Compact (iOS) +- **Tablet**: >= 600dp (Android) / Regular (iOS) + +### 7.1 Navigation + +**Phone**: Bottom tab bar (5 tabs) +**Tablet**: Side navigation rail (Android) / Sidebar (iOS) + +``` +Tablet Layout: +┌────┬──────────────────────────────┐ +│ │ │ +│ 🏠 │ │ +│ 💬 │ [Main Content] │ +│ 📁 │ │ +│ ⚡ │ │ +│ │ │ +│ │ │ +│ ⚙️ │ │ +└────┴──────────────────────────────┘ +``` + +### 7.2 Chat on Tablet + +**Split view**: Session list on left (1/3), conversation on right (2/3). + +``` +┌────┬──────────┬───────────────────┐ +│ │Sessions │ Debug login │ +│ 🏠 │──────────│ Claude 3.5 Sonnet │ +│ 💬 │ Debug.. │───────────────────│ +│ 📁 │ Refact.. │ │ +│ ⚡ │ Recipe.. │ [Messages...] │ +│ │ │ │ +│ │ │ │ +│ │ ├───────────────────│ +│ ⚙️ │ │ [Input] [Send] │ +└────┴──────────┴───────────────────┘ +``` + +### 7.3 Files on Tablet + +**Split view**: Directory browser on left, file viewer on right. Tap a file to preview it in the right pane without navigating away. + +``` +┌────┬──────────┬───────────────────┐ +│ │ / > Docs │ notes.md │ +│ 🏠 │──────────│───────────────────│ +│ 💬 │ 📁 Proj │ # Meeting Notes │ +│ 📁 │ 📁 Reps │ │ +│ ⚡ │ 📄 note │ ## Action Items │ +│ │ 🖼️ phot │ - Fix login bug │ +│ │ 🎵 song │ - Update docs │ +│ │ │ │ +│ ⚙️ │ │ │ +└────┴──────────┴───────────────────┘ +``` + +### 7.4 Dashboard on Tablet + +Widgets in a 2-3 column grid instead of single column. More widgets visible at once. + +``` +┌────┬──────────────────────────────┐ +│ │ ┌──────┐ ┌──────┐ ┌──────┐ │ +│ 🏠 │ │Clock │ │Weath.│ │Pomo │ │ +│ 💬 │ └──────┘ └──────┘ └──────┘ │ +│ 📁 │ ┌─────────────┐ ┌──────────┐│ +│ ⚡ │ │ Daily Goals │ │ Quick ││ +│ │ │ ☑ Exercise │ │ Notes ││ +│ │ │ ☐ Read 30min │ │ ... ││ +│ │ └─────────────┘ └──────────┘│ +│ ⚙️ │ Workspaces │ +│ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ +└────┴──────────────────────────────┘ +``` + +### 7.5 Tablet-Only Features + +| Feature | Description | +|---------|-------------| +| **Drag & Drop** | Drag files between panes, drag text into chat | +| **Keyboard Shortcuts** | External keyboard support with shortcuts (Cmd+N for new chat, etc.) | +| **Picture-in-Picture** | Video playback continues in PiP when navigating away | +| **Multi-Window** | Android: Split screen support. iPad: Stage Manager support. | + +--- + +## 8. Technical Challenges + +### 8.1 WebSocket Lifecycle on Mobile + +**Problem**: Mobile OSes aggressively kill background connections. WebSocket to chat/terminal will disconnect when app is backgrounded. + +**Solution**: +- Detect app going to background → gracefully close WebSocket +- On foreground → reconnect WebSocket, resume session +- Use `{ type: 'resume', sessionId }` to restore chat state +- Buffer any pending assistant output server-side (the server already does this - messages are saved to JSON files) +- Show "Reconnecting..." indicator when connection drops +- Implement exponential backoff for reconnection attempts + +**Android specifics:** +- Use `ProcessLifecycleOwner` to detect app lifecycle +- Consider foreground service for active chat sessions (shows notification "Chat in progress") + +**iOS specifics:** +- Use `UIApplication.willResignActiveNotification` / `didBecomeActiveNotification` +- Background URL session for critical requests +- Use `BGAppRefreshTask` for periodic sync + +### 8.2 Push Notifications + +**Problem**: The Officer API server currently has no push notification infrastructure. + +**Required Backend Changes:** +1. New endpoint: `POST /api/devices/register` — stores FCM/APNs token per user +2. New database table: `DeviceTokens` (userId, token, platform, createdAt) +3. Server-side notification triggers: + - Chat: When assistant finishes responding and user's WebSocket is disconnected + - Tasks: When a long-running task completes + - Downloads: When yt-dlp or git clone finishes +4. Firebase Admin SDK (Android) + APNs library (iOS) on the server + +**Notification Types:** +| Type | Title | Body | Action | +|------|-------|------|--------| +| Chat complete | "Claude responded" | First 100 chars of response | Open chat session | +| Task complete | "Task finished" | "Format Code completed successfully" | Open task log | +| Download complete | "Download ready" | "video.mp4 downloaded" | Open file browser | +| Error | "Task failed" | "Backup DB failed: connection refused" | Open task log | + +### 8.3 File Upload from Mobile + +**Problem**: Mobile file uploads have different sources than web (camera, photo library, share sheet). + +**Solution**: +- Use platform file pickers that support multiple sources +- Camera capture: Take photo → upload via `POST /api/file-browser/upload` +- Compress images before upload (configurable quality setting) +- Show upload progress (the existing XHR-based upload in `useFiles.ts` supports progress) +- Background upload for large files using WorkManager (Android) / BGTaskScheduler (iOS) +- Resume interrupted uploads if possible + +### 8.4 Offline Support + +**Problem**: Users may open the app with no connectivity. + +**Strategy**: Read-heavy offline, write-queued. + +| Feature | Offline Behavior | +|---------|-----------------| +| Dashboard | Cached widget data, local timer for pomodoro | +| Chat history | Cached session list and recent messages | +| New chat | Queue message, show "Will send when online" | +| File browser | Show last cached directory listing | +| File viewer | Show cached/downloaded files only | +| Settings | Show cached profile, edits queued | + +**Local Database Schema (Room / SwiftData):** +``` +ChatSessions: id, name, provider, lastMessagePreview, updatedAt, isArchived +ChatMessages: id, sessionId, role, content, timestamp (cache recent N per session) +DirectoryCache: path, root, entriesJson, cachedAt +FileCache: path, root, localPath, size, cachedAt (for downloaded/viewed files) +PendingActions: id, type, payload, createdAt, status +``` + +### 8.5 Deep Linking & Share Sheet + +**URL Scheme**: `officer://` +**Universal Links**: `https://officer.dev/app/...` (if applicable) + +**Deep Link Routes:** +- `officer://chat/{sessionId}` — open specific chat +- `officer://files?path=/Documents` — open file browser at path +- `officer://chat/new?prompt=...` — start new chat with prefilled text + +**Share Sheet Integration (receiving):** +- Register app as share target for: text, URLs, images, files +- When content is shared to Officer: + 1. Show a sheet: "Send to Chat" or "Upload to Files" + 2. If Chat: open new chat with shared content as first message + 3. If Files: open file browser with upload in current directory + +### 8.6 Image Handling in Chat + +**Problem**: The web app sends images as base64 in the WebSocket message. Large images on mobile could cause memory issues. + +**Solution**: +1. Before sending, resize images to max 2048px on longest edge +2. Compress to JPEG at 85% quality (unless PNG is needed for screenshots) +3. Convert to base64 and include in chat message +4. Show thumbnail in the message thread, tap to view full-size +5. Consider using the upload endpoint first, then sending attachment IDs instead of base64 (check if API supports this path — it does via `attachmentIds` in the chat message) + +### 8.7 Markdown & Code Rendering + +**Problem**: Rendering rich markdown with code blocks, tables, and images in native views. + +**Android Solution:** +- Use a Markdown library (Markwon or compose-markdown) +- Code blocks: custom span with monospace font + syntax highlighting +- Consider a lightweight WebView-based renderer for complex markdown as fallback + +**iOS Solution:** +- Use `AttributedString` with custom markdown parsing +- Or use a SwiftUI markdown library (MarkdownUI) +- Code blocks: custom view with monospace font + syntax highlighting + +**Code Syntax Highlighting:** +- Android: Highlight.js via WebView, or a native library like CodeView +- iOS: Splash or Highlightr library + +### 8.8 Streaming Text Display + +**Problem**: Chat responses stream token-by-token via WebSocket. Need smooth text rendering without layout thrashing. + +**Solution**: +- Buffer incoming tokens (e.g., collect for 50ms before rendering) +- Use a single mutable text state that appends +- Avoid re-rendering the entire message list on each token +- Only the last (active) message bubble needs updating +- Auto-scroll to bottom during streaming, unless user has scrolled up + +### 8.9 Security + +**Concerns:** +- JWT stored on device must be encrypted +- Biometric key binding for stored credentials +- Certificate pinning for API connections (optional but recommended for self-hosted) +- No sensitive data in logs +- Clear all data on sign out +- Jailbreak/root detection (optional) + +**Android:** +- EncryptedSharedPreferences for tokens +- Android Keystore for biometric-bound keys +- ProGuard/R8 for release builds + +**iOS:** +- Keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` +- Secure Enclave for biometric keys +- App Transport Security (ATS) enforced + +### 8.10 Self-Hosted Server Discovery + +**Problem**: Officer is self-hosted. Users need to point the app at their server. The server might be on a local network, behind a VPN, or on the public internet. + +**Solution**: +- First-launch: manual URL entry with validation +- Local network: mDNS/Bonjour discovery (optional nice-to-have) +- QR code scanning from web app settings page (generate QR with server URL + optional auth token) +- Store multiple server profiles (for users who have dev/prod setups) +- Connection status indicator in settings + +--- + +## 9. API Integration Notes + +### Endpoints the Mobile App Will Use + +**Auth:** +- `POST /api/auth/signin` — login +- `POST /api/auth/signup` — register (if enabled) +- `POST /api/auth/signout` — logout +- `GET /api/auth/me` — validate token, get user info +- `POST /api/auth/change-password` — password change +- `*/passkeys/*` — passkey registration/auth + +**Chat:** +- `GET /api/sessions?provider={provider}` — list sessions +- `DELETE /api/sessions/{provider}/{id}` — delete session +- `POST /api/sessions/{provider}/{id}/rename` — rename session +- `POST /api/sessions/{provider}/{id}/archive` — archive session +- WebSocket `/api/harness/{provider}/ws` — chat communication + +**Files:** +- `GET /api/file-browser/ls?path={path}&root={root}` — list directory +- `GET /api/file-browser/read?path={path}` — read text file +- `GET /api/file-browser/raw?path={path}` — raw file (binary, supports Range) +- `GET /api/file-browser/search?q={query}` — search files +- `POST /api/file-browser/mkdir` — create directory +- `POST /api/file-browser/write` — write file +- `POST /api/file-browser/upload` — upload files (multipart) +- `POST /api/file-browser/rename` — rename +- `POST /api/file-browser/copy` — copy +- `POST /api/file-browser/move` — move +- `DELETE /api/file-browser/rm` — delete +- `GET /api/file-browser/download?path={path}` — download file +- `POST /api/file-browser/download` — download multiple as zip +- `GET /api/file-browser/transcode?path={path}` — transcode video +- `POST /api/file-browser/tts` — text-to-speech +- `POST /api/file-browser/ocr` — OCR image +- `POST /api/file-browser/transcribe` — transcribe audio +- `POST /api/file-browser/extract-audio` — extract audio from video +- `POST /api/file-browser/extract` — extract archive +- `POST /api/file-browser/git-clone` — clone git repo +- `POST /api/file-browser/download-video` — yt-dlp download + +**Tasks:** +- `GET /api/tasks` — list tasks +- `GET /api/tasks/{name}` — task details +- `POST /api/tasks/{name}/run` — run task + +**Task Logs:** +- `GET /api/task-logs` — list logs +- `GET /api/task-logs/{id}` — log details + +**Processes:** +- `GET /api/processes` — list processes + +**Settings:** +- `GET /api/user/settings` — user settings +- `PUT /api/user/settings` — update settings +- `PUT /api/auth/users` — update profile + +**Upload:** +- `POST /api/upload` — upload attachments (for chat) + +**Scrape:** +- `POST /api/scrape` — scrape URL (for sharing URLs to chat) + +### New Endpoints Needed + +| Endpoint | Purpose | +|----------|---------| +| `POST /api/devices/register` | Register push notification token | +| `DELETE /api/devices/{token}` | Unregister device on logout | +| `GET /api/sessions/{provider}/{id}/messages` | Get messages for a session (currently read from files, need API endpoint) | + +### Authentication Header + +All requests include: `Authorization: Bearer {jwt_token}` + +WebSocket connections pass token as query parameter: `?token={jwt_token}` + +--- + +## 10. Shared Design Language + +While each platform follows its native design guidelines (Material 3 for Android, Human Interface Guidelines for iOS), some design elements should be consistent: + +### Color Palette + +From the web app's Tailwind theme: +- **Primary (Teal)**: `duck-teal` — used for primary actions, active states +- **Accent (Yellow)**: `duck-yellow` — used for text on primary, highlights +- **Dark**: `duck-dark` — text, icons +- **Background**: Light/dark mode adaptive + +Both apps should use these brand colors while adapting to platform conventions: +- Android: Material 3 dynamic color with Officer palette as seed +- iOS: Officer palette with system-adaptive backgrounds + +### Typography + +- **Android**: Roboto (system default), with monospace for code +- **iOS**: SF Pro (system default), with SF Mono for code +- Both: Similar size scale, consistent heading hierarchy + +### Iconography + +- **Android**: Material Symbols (outlined) +- **iOS**: SF Symbols +- Map lucide-react icons used in web to platform equivalents + +### Animations + +- **Android**: Material Motion (shared element transitions, container transforms) +- **iOS**: Spring animations, matched geometry effects +- Both: Smooth transitions between screens, subtle feedback on interactions + +--- + +## 11. Implementation Phases + +### Phase 1: Foundation (MVP) + +**Goal**: Core functionality that makes the app useful enough to ship. + +| Feature | Details | +|---------|---------| +| Auth | Email/password login, token storage, biometric unlock | +| Server setup | URL configuration, validation | +| Chat | New chat, session list, real-time conversation with streaming | +| Chat basics | Text messages, model selection, session management | +| Settings | Profile view, theme, logout | + +**Estimated screens**: Login, Home (simple), Chat List, Chat Conversation, Settings + +### Phase 2: Files & Polish + +**Goal**: File management and mobile-specific enhancements. + +| Feature | Details | +|---------|---------| +| File browser | Directory listing, navigation, search | +| File viewer | Text, images, PDF, audio, video | +| File actions | Upload (camera, files), download, rename, delete | +| Chat attachments | Image attachment, voice input | +| Push notifications | Basic chat completion notifications | + +**Estimated screens**: File Browser, File Viewer, enhanced Chat + +### Phase 3: Productivity + +**Goal**: Dashboard, tasks, and deeper integration. + +| Feature | Details | +|---------|---------| +| Dashboard | Widget system (clock, weather, pomodoro, goals, notes) | +| Tasks | Task list, run tasks, view logs | +| Workspace quick access | Open workspaces (single-panel) | +| Share sheet | Receive content from other apps | +| Offline support | Cached data, queued actions | + +**Estimated screens**: Dashboard, Task List, Task Logs, enhanced Home + +### Phase 4: Tablet & Advanced + +**Goal**: Tablet optimization and advanced features. + +| Feature | Details | +|---------|---------| +| Tablet layouts | Split views for chat, files | +| Tablet navigation | Side rail / sidebar | +| Home screen widgets | Android widgets, iOS widgets | +| AI file actions | OCR, TTS, Transcribe from mobile | +| Multi-select | Batch file operations | +| Deep linking | URL scheme, universal links | + +### Phase 5: Nice-to-Haves + +| Feature | Details | +|---------|---------| +| QR code server setup | Scan from web to configure | +| Local network discovery | mDNS/Bonjour | +| Picture-in-Picture | Video PiP on tablet | +| Passkey auth | Full WebAuthn support | +| Keyboard shortcuts | External keyboard on tablet | +| Skills browser | View and search skills library | +| Background downloads | yt-dlp and git clone in background | + +--- + +## Appendix: Decision Log + +| Decision | Rationale | +|----------|-----------| +| Native (Kotlin + Swift) over cross-platform | User preference. Also gives best performance, platform-native UX, and full access to platform APIs (biometrics, push, background tasks, share sheet). | +| Exclude terminal | Phone keyboards make shell commands impractical. The sandboxed Docker setup is inherently desktop-oriented. | +| Exclude code editor (write) | Writing code on a phone is poor UX. Read-only with syntax highlighting is sufficient for review. | +| Exclude automation builder | Complex form-heavy UI that doesn't translate to small screens. | +| Include chat as primary feature | AI assistant in your pocket is the strongest mobile use case. Voice input makes it even more compelling on mobile. | +| Bottom tabs (phone) / Side rail (tablet) | Standard navigation patterns for each form factor. 5 top-level destinations fits within guidelines. | +| Offline-first for reads, queue for writes | Mobile connectivity is unreliable. Showing cached data is better than empty screens. | +| Phase 1 = Auth + Chat | Chat is the killer feature. Get it right first, everything else is complementary. | diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx index dc53b556..230f257b 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { Server, Wrench, Loader2 } from 'lucide-react'; +import { Server, Wrench, Loader2, RefreshCw, Trash2 } from 'lucide-react'; +import { toast } from 'sonner'; import { useGlobal } from 'hooks/useGlobal'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { Button } from '@/components/ui/button'; @@ -14,7 +15,6 @@ import { AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; -import { CommandBlock } from '@/components/CommandBlock'; import { useResources, getResourceCategory, type Resource, type PingResult } from '@/state/useResources'; import { RUN_COMMAND_CHANNEL, type RunCommandState } from './run-command-channel'; @@ -135,29 +135,133 @@ type LocalAvailabilitySectionProps = { onRun: (command: string) => void; }; -const LocalAvailabilitySection = ({ resource, onRun }: LocalAvailabilitySectionProps) => ( -
-

Local Availability

-
+type ResourceAction = 'install' | 'uninstall' | 'verify' | 'update' | 'manage'; + +const LocalAvailabilitySection = ({ resource, onRun }: LocalAvailabilitySectionProps) => { + const { runCommand } = useResources(); + const [runningAction, setRunningAction] = useState(null); + + const isSudo = (cmd: string) => cmd.trimStart().startsWith('sudo'); + + const handleAction = async (action: ResourceAction, command: string) => { + if (isSudo(command)) { + onRun(command); + return; + } + setRunningAction(action); + try { + const result = await runCommand(resource.id, action); + if (result.exitCode === 0) { + toast.success('Command completed successfully'); + } else { + toast.error(result.output || `Command failed (exit code ${result.exitCode})`, { duration: 8000 }); + } + } catch { + toast.error('Failed to run command'); + } finally { + setRunningAction(null); + } + }; + + return ( +
{resource.installed ? ( - - Installed{resource.version ? ` (${resource.version})` : ''} - + <> +
+ Installed +
+ {resource.version && ( +
+ {resource.updateCommand && ( + + )} + {resource.version} +
+ )} +
+ {!resource.version && resource.updateCommand && ( + + )} + {resource.verifyCommand && ( + + )} + {resource.manageCommand && ( + + )} +
+ {resource.uninstallCommand && ( +
+ +
+ )} + ) : ( - Not installed +
+ Not installed + {resource.installCommand && ( + + )} +
)}
-
- {resource.installCommand && } - {resource.uninstallCommand && ( - - )} - {resource.manageCommand && } - {resource.verifyCommand && } - {resource.updateCommand && } -
-
-); + ); +}; type CatalogCardProps = { resource: Resource; @@ -306,18 +410,12 @@ const ResourceDetail = ({ resource }: { resource: Resource }) => { Run with elevated privileges - -
- You are about to run a command with elevated privileges (sudo) -
- - in the host machine. -
- ARE YOU SURE? -
-
-
+

+ For this operation the script must be run with elevated privileges (sudo) on the host machine. +
+ Not to worry, though, we wrote it and battle tested it ourselves. +

{confirmCommand} Cancel diff --git a/src/apps/officer-web/state/useResources.ts b/src/apps/officer-web/state/useResources.ts index 2fabde42..05d58f37 100644 --- a/src/apps/officer-web/state/useResources.ts +++ b/src/apps/officer-web/state/useResources.ts @@ -58,5 +58,11 @@ export const useResources = () => { return client.post(`/server-settings/resources/${id}/ping`, { url }); }; - return { resources, isLoading, saveConnectionConfig, pingResource }; + const runCommand = async (id: string, action: string) => { + const result = await client.post<{ exitCode: number; output: string }>(`/server-settings/resources/${id}/run`, { action }); + queryClient.invalidateQueries({ queryKey: RESOURCES_KEY }); + return result; + }; + + return { resources, isLoading, saveConnectionConfig, pingResource, runCommand }; }; diff --git a/src/servers/api/server-settings/resources.ts b/src/servers/api/server-settings/resources.ts index f1a05c18..3c5d74d1 100644 --- a/src/servers/api/server-settings/resources.ts +++ b/src/servers/api/server-settings/resources.ts @@ -198,20 +198,22 @@ function buildConnectionConfig( return { url: `http://127.0.0.1:${port ?? resource.port}` }; } -async function loadResources(): Promise { +async function parseResources() { const dir = getResourcesDir(); if (!existsSync(dir)) return []; - const [files, config] = await Promise.all([readdir(dir), readConfig()]); + const files = await readdir(dir); const serviceFiles = files.filter((f) => f.startsWith('SERVICE_') && f.endsWith('.md')); - - const parsed = await Promise.all( + return Promise.all( serviceFiles.map(async (filename) => { const content = await Bun.file(`${dir}/${filename}`).text(); return parseResourceFile(filename, content); }), ); +} - const resources = await Promise.all( +async function loadResources(): Promise { + const [parsed, config] = await Promise.all([parseResources(), readConfig()]); + return Promise.all( parsed.map(async (r) => { const configUrl = config[r.id]?.url; const status = await checkResourceStatus({ resource: r, configUrl }); @@ -219,8 +221,6 @@ async function loadResources(): Promise { return { ...r, ...status, connectionConfig }; }), ); - - return resources; } export const resourcesRouter = createRouter(); @@ -290,6 +290,42 @@ resourcesRouter.post('/error-log', async (ctx) => { return ctx.json({ filePath }); }); +resourcesRouter.post('/:id/run', async (ctx) => { + const id = ctx.req.param('id'); + const { action } = await ctx.req.json<{ action: string }>(); + + const parsed = await parseResources(); + const resource = parsed.find((r) => r.id === id); + if (!resource) return ctx.json({ error: 'Resource not found' }, 404); + + const commands: Record = { + install: resource.installCommand, + uninstall: resource.uninstallCommand, + verify: resource.verifyCommand, + update: resource.updateCommand, + manage: resource.manageCommand, + }; + + const command = commands[action]; + if (!command) return ctx.json({ error: `No ${action} command for this resource` }, 400); + + if (command.trimStart().startsWith('sudo')) { + return ctx.json({ error: 'Sudo commands must run in terminal' }, 400); + } + + try { + const proc = Bun.spawn(['sh', '-c', command], { stdout: 'pipe', stderr: 'pipe' }); + const [stdout, stderr] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]); + await proc.exited; + return ctx.json({ exitCode: proc.exitCode, output: (stdout + stderr).trim() }); + } catch { + return ctx.json({ exitCode: 1, output: 'Failed to execute command' }); + } +}); + resourcesRouter.get('/:id', async (ctx) => { const resources = await loadResources(); const resource = resources.find((r) => r.id === ctx.req.param('id'));