{"repo": "cambriantech/continuum", "files": {"core/continuum-core/src/persona/catalog.json": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1785674487.6465595, "n": 6658, "h": "2952116d3e90c7a9", "c": "[\n {\n \"uniqueId\": \"claude\",\n \"displayName\": \"Claude Code\",\n \"provider\": \"anthropic\",\n \"type\": \"agent\",\n \"voiceId\": \"10\",\n \"apiKeyEnv\": \"ANTHROPIC_API_KEY\",\n \"bio\": \"Anthropic's coding agent — writes, debugs, and ships code autonomously\",\n \"speciality\": \"code\",\n \"accentColor\": \"#d4a574\"\n },\n {\n \"uniqueId\": \"general\",\n \"displayName\": \"General AI\",\n \"provider\": \"anthropic\",\n \"type\": \"agent\",\n \"voiceId\": \"25\",\n \"apiKeyEnv\": \"ANTHROPIC_API_KEY\",\n \"bio\": \"General-purpose AI assistant for broad knowledge and reasoning tasks\",\n \"speciality\": \"general\",\n \"accentColor\": \"#7c4dff\"\n },\n {\n \"uniqueId\": \"codereview\",\n \"displayName\": \"CodeReview AI\",\n \"provider\": \"local\",\n \"type\": \"persona\",\n \"voiceId\": \"100\",\n \"minVramGB\": 9,\n \"bio\": \"A critical analyst who evaluates code quality, security, and best practices with constructive feedback\",\n \"speciality\": \"code-analysis\",\n \"accentColor\": \"#e91e63\",\n \"modelPreferences\": [\n { \"minVramGb\": 0, \"model\": \"continuum-ai/qwen3.5-4b-code-forged-GGUF\", \"vramBudgetGb\": 3 }\n ]\n },\n {\n \"uniqueId\": \"teacher\",\n \"displayName\": \"Teacher AI\",\n \"provider\": \"local\",\n \"type\": \"persona\",\n \"voiceId\": \"75\",\n \"minVramGB\": 5,\n \"bio\": \"An educational mentor who explains concepts thoroughly with examples and patient guidance\",\n \"speciality\": \"education-mentoring\",\n \"accentColor\": \"#ff9800\",\n \"modelPreferences\": [\n { \"minVramGb\": 0, \"model\": \"continuum-ai/qwen3.5-4b-code-forged-GGUF\", \"vramBudgetGb\": 3 }\n ]\n },\n {\n \"uniqueId\": \"helper\",\n \"displayName\": \"Helper AI\",\n \"provider\": \"local\",\n \"type\": \"persona\",\n \"voiceId\": \"50\",\n \"minVramGB\": 3,\n \"bio\": \"A friendly, concise assistant who provides quick practical help and actionable solutions\",\n \"speciality\": \"practical-assistance\",\n \"accentColor\": \"#00d4ff\",\n \"modelPreferences\": [\n { \"minVramGb\": 0, \"model\": \"continuum-ai/qwen3.5-4b-code-forged-GGUF\", \"vramBudgetGb\": 3 }\n ]\n },\n {\n \"uniqueId\": \"deepseek\",\n \"displayName\": \"DeepSeek Assistant\",\n \"provider\": \"deepseek\",\n \"type\": \"persona\",\n \"voiceId\": \"125\",\n \"apiKeyEnv\": \"DEEPSEEK_API_KEY\",\n \"bio\": \"DeepSeek's reasoning model — excels at math, code, and deep analytical thinking\",\n \"speciality\": \"analysis\",\n \"accentColor\": \"#00bcd4\"\n },\n {\n \"uniqueId\": \"groq\",\n \"displayName\": \"Groq Lightning\",\n \"provider\": \"groq\",\n \"type\": \"persona\",\n \"voiceId\": \"150\",\n \"apiKeyEnv\": \"GROQ_API_KEY\",\n \"bio\": \"Lightning-fast inference — optimized for speed without sacrificing quality\",\n \"speciality\": \"general\",\n \"accentColor\": \"#ff5722\"\n },\n {\n \"uniqueId\": \"claudeassistant\",\n \"displayName\": \"Claude Assistant\",\n \"provider\": \"anthropic\",\n \"type\": \"persona\",\n \"voiceId\": \"175\",\n \"apiKeyEnv\": \"ANTHROPIC_API_KEY\",\n \"bio\": \"Anthropic's conversational assistant — thoughtful, nuanced, and safety-conscious\",\n \"speciality\": \"general\",\n \"accentColor\": \"#d4a574\"\n },\n {\n \"uniqueId\": \"gpt\",\n \"displayName\": \"GPT Assistant\",\n \"provider\": \"openai\",\n \"type\": \"persona\",\n \"voiceId\": \"200\",\n \"apiKeyEnv\": \"OPENAI_API_KEY\",\n \"bio\": \"OpenAI's versatile assistant — broad knowledge, creative writing, and problem solving\",\n \"speciality\": \"creative\",\n \"accentColor\": \"#4caf50\"\n },\n {\n \"uniqueId\": \"grok\",\n \"displayName\": \"Grok\",\n \"provider\": \"xai\",\n \"type\": \"persona\",\n \"voiceId\": \"220\",\n \"apiKeyEnv\": \"XAI_API_KEY\",\n \"bio\": \"xAI's unfiltered model — real-time knowledge, wit, and image generation\",\n \"speciality\": \"creative\",\n \"accentColor\": \"#f44336\"\n },\n {\n \"uniqueId\": \"together\",\n \"displayName\": \"Together Assistant\",\n \"provider\": \"together\",\n \"type\": \"persona\",\n \"voiceId\": \"30\",\n \"apiKeyEnv\": \"TOGETHER_API_KEY\",\n \"bio\": \"Open-source model hub — access to the best community models at scale\",\n \"speciality\": \"general\",\n \"accentColor\": \"#2196f3\"\n },\n {\n \"uniqueId\": \"fireworks\",\n \"displayName\": \"Fireworks AI\",\n \"provider\": \"fireworks\",\n \"type\": \"persona\",\n \"voiceId\": \"60\",\n \"apiKeyEnv\": \"FIREWORKS_API_KEY\",\n \"bio\": \"High-performance inference platform — optimized open models with custom fine-tuning\",\n \"speciality\": \"code\",\n \"accentColor\": \"#ff6d00\"\n },\n {\n \"uniqueId\": \"local\",\n \"displayName\": \"Local Assistant\",\n \"provider\": \"local\",\n \"type\": \"persona\",\n \"voiceId\": \"90\",\n \"minVramGB\": 3,\n \"bio\": \"Local Qwen inference — runs entirely on your hardware, no cloud dependency\",\n \"speciality\": \"general\",\n \"accentColor\": \"#8bc34a\",\n \"modelPreferences\": [\n { \"minVramGb\": 0, \"model\": \"continuum-ai/qwen3.5-4b-code-forged-GGUF\", \"vramBudgetGb\": 3 }\n ]\n },\n {\n \"uniqueId\": \"vision\",\n \"displayName\": \"Vision AI\",\n \"provider\": \"local\",\n \"type\": \"persona\",\n \"voiceId\": \"105\",\n \"minVramGB\": 5,\n \"bio\": \"Native local vision persona powered by Qwen2-VL for image understanding\",\n \"speciality\": \"vision\",\n \"accentColor\": \"#009688\",\n \"modelPreferences\": [\n { \"minVramGb\": 0, \"model\": \"qwen2-vl-7b-instruct\", \"vramBudgetGb\": 5 }\n ]\n },\n {\n \"uniqueId\": \"sentinel\",\n \"displayName\": \"Sentinel\",\n \"provider\": \"sentinel\",\n \"type\": \"persona\",\n \"voiceId\": \"240\",\n \"bio\": \"Autonomous pipeline executor — orchestrates multi-step workflows with fault tolerance\",\n \"speciality\": \"orchestration\",\n \"accentColor\": \"#9c27b0\"\n },\n {\n \"uniqueId\": \"gemini\",\n \"displayName\": \"Gemini\",\n \"provider\": \"google\",\n \"type\": \"persona\",\n \"voiceId\": \"115\",\n \"apiKeyEnv\": \"GOOGLE_API_KEY\",\n \"bio\": \"Google's multimodal model — vision, code, and reasoning across modalities\",\n \"speciality\": \"analysis\",\n \"accentColor\": \"#4285f4\"\n },\n {\n \"uniqueId\": \"qwen3-omni\",\n \"displayName\": \"Qwen3-Omni\",\n \"provider\": \"alibaba\",\n \"type\": \"persona\",\n \"modelId\": \"qwen3-omni-flash-realtime\",\n \"isAudioNative\": true,\n \"apiKeyEnv\": \"DASHSCOPE_API_KEY\",\n \"bio\": \"Audio-native AI that hears and speaks directly without text conversion. Open-source, multilingual, real-time.\",\n \"speciality\": \"voice-conversation\",\n \"accentColor\": \"#6c5ce7\"\n },\n {\n \"uniqueId\": \"gemini-live\",\n \"displayName\": \"Gemini Live\",\n \"provider\": \"google\",\n \"type\": \"persona\",\n \"modelId\": \"gemini-2.5-flash-native-audio-preview\",\n \"isAudioNative\": true,\n \"apiKeyEnv\": \"GOOGLE_API_KEY\",\n \"bio\": \"Google's audio-native model — real-time voice conversation with native audio understanding\",\n \"speciality\": \"voice-conversation\",\n \"accentColor\": \"#34a853\"\n }\n]\n"}}, "src/daemons/ai-provider-daemon/ADAPTER-ARCHITECTURE.md": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1785674597.8255696, "n": 9187, "h": "42f0de9eca56fd15", "c": "# AI Provider Adapter Architecture\n\n## 🎯 Design Goals\n\n1. **Zero code duplication** - Providers sharing the same API format share code\n2. **Trivial to add new providers** - 20-30 lines per provider\n3. **Multimodal support** - Text, audio, video, images\n4. **Hybrid local + cloud** - Ollama for local, APIs for large models\n5. **Cost optimization** - Track usage, route by cost/latency/quality\n6. **Integration testing** - Test all providers systematically\n7. **🔐 Security-first** - API keys never exposed to browser, automatic redaction from logs\n\n## 📦 Adapter Hierarchy\n\n```\nAIProviderAdapter (interface)\n├── BaseLocalAdapter (local inference servers)\n│ ├── OllamaAdapter ✅\n│ ├── LMStudioAdapter\n│ └── LlamaCppServerAdapter\n│\n├── BaseOpenAICompatibleAdapter (OpenAI API format)\n│ ├── OpenAIAdapter ✅\n│ ├── TogetherAIAdapter ✅\n│ ├── FireworksAdapter ✅\n│ ├── GroqAdapter\n│ ├── AnyscaleAdapter\n│ ├── PerplexityAdapter\n│ ├── MistralAdapter\n│ ├── DeepInfraAdapter\n│ └── ReplicateAdapter\n│\n└── Proprietary Adapters (unique APIs)\n ├── AnthropicAdapter ✅ (already implemented)\n ├── GoogleGeminiAdapter\n └── CohereAdapter\n```\n\n## 🔥 Code Reuse: The Power of Base Classes\n\n### Example: Adding Together AI (25 lines!)\n\n```typescript\nimport { BaseOpenAICompatibleAdapter } from './BaseOpenAICompatibleAdapter';\nimport { getSecret } from '../../../../system/secrets/SecretManager';\n\nexport class TogetherAIAdapter extends BaseOpenAICompatibleAdapter {\n constructor(apiKey?: string) {\n super({\n providerId: 'together',\n providerName: 'Together AI',\n apiKey: apiKey || getSecret('TOGETHER_API_KEY', 'TogetherAIAdapter') || '',\n baseUrl: 'https://api.together.xyz', // Only difference!\n defaultModel: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',\n timeout: 60000,\n supportedCapabilities: ['text-generation', 'chat', 'embeddings'],\n });\n }\n}\n```\n\n**That's it!** Inherits all functionality from `BaseOpenAICompatibleAdapter`:\n- Text generation with retries\n- Health checks\n- Model listing\n- Token counting\n- Error handling\n- Cost calculation (override if needed)\n\n### Providers Using OpenAI Format (95% code sharing)\n\n| Provider | Base URL | Added Value |\n|----------|----------|-------------|\n| OpenAI | `api.openai.com` | Official, DALL-E, GPT-4 |\n| Together AI | `api.together.xyz` | Llama 70B+, fine-tuning |\n| Fireworks | `api.fireworks.ai` | Fast inference, LoRA |\n| Groq | `api.groq.com` | Ultra-fast inference (LPU) |\n| Anyscale | `api.endpoints.anyscale.com` | Ray scaling |\n| Perplexity | `api.perplexity.ai` | Search-augmented |\n| Mistral | `api.mistral.ai` | European, Mixtral models |\n| DeepInfra | `api.deepinfra.com` | Cost-effective |\n\n## 🏗️ Implementation Status\n\n### ✅ Completed\n- [x] `AIProviderTypesV2.ts` - Multimodal type system\n- [x] `BaseLocalAdapter.ts` - Local inference servers\n- [x] `BaseOpenAICompatibleAdapter.ts` - OpenAI API format\n- [x] `OllamaAdapter.ts` - Local Ollama models\n- [x] `OpenAIAdapter.ts` - Official OpenAI\n- [x] `TogetherAIAdapter.ts` - Together AI (Llama 70B+)\n- [x] `FireworksAdapter.ts` - Fireworks AI\n\n### 🚧 To Implement (Easy - 20 lines each)\n- [ ] `GroqAdapter.ts` - Ultra-fast inference\n- [ ] `AnyscaleAdapter.ts` - Ray-based scaling\n- [ ] `PerplexityAdapter.ts` - Search-augmented generation\n- [ ] `MistralAdapter.ts` - Mixtral models\n- [ ] `GoogleGeminiAdapter.ts` - Proprietary API\n- [ ] `CohereAdapter.ts` - Proprietary API\n\n### 🔬 Testing Strategy\n\n#### Unit Tests (Per Adapter)\n```typescript\ndescribe('TogetherAIAdapter', () => {\n it('should generate text', async () => {\n const adapter = new TogetherAIAdapter();\n await adapter.initialize();\n\n const response = await adapter.generateText({\n messages: [{ role: 'user', content: 'Hello!' }],\n });\n\n expect(response.text).toBeTruthy();\n expect(response.provider).toBe('together');\n });\n});\n```\n\n#### Integration Tests (Multi-Provider Routing)\n```typescript\ndescribe('AIProviderDaemon', () => {\n it('should route to fastest available provider', async () => {\n // Register multiple providers with priorities\n daemon.registerAdapter(new OllamaAdapter(), { priority: 100 });\n daemon.registerAdapter(new TogetherAIAdapter(), { priority: 90 });\n daemon.registerAdapter(new OpenAIAdapter(), { priority: 80 });\n\n // Request should use Ollama (highest priority)\n const response = await daemon.generateText({\n messages: [{ role: 'user', content: 'Test' }],\n });\n\n expect(response.provider).toBe('ollama');\n });\n\n it('should failover to backup provider', async () => {\n // Simulate Ollama offline\n jest.spyOn(ollamaAdapter, 'healthCheck').mockResolvedValue({ status: 'unhealthy' });\n\n // Should automatically use Together AI\n const response = await daemon.generateText({\n messages: [{ role: 'user', content: 'Test' }],\n });\n\n expect(response.provider).toBe('together');\n });\n});\n```\n\n## 🎯 Capability-Based Routing\n\nThe daemon can select providers based on required capabilities:\n\n```typescript\n// Need multimodal (text + image)? Use GPT-4V\nconst response = await daemon.generateText({\n messages: [{\n role: 'user',\n content: [\n { type: 'text', text: 'What's in this image?' },\n { type: 'image', image: { url: 'https://...' } }\n ]\n }],\n preferredCapabilities: ['multimodal']\n});\n// → Routes to OpenAI (GPT-4V)\n\n// Need just text? Use local Ollama\nconst response = await daemon.generateText({\n messages: [{ role: 'user', content: 'Hello!' }],\n preferredCapabilities: ['text-generation']\n});\n// → Routes to Ollama (free, local, private)\n\n// Need 70B+ model? Use Together AI\nconst response = await daemon.generateText({\n messages: [{ role: 'user', content: 'Complex reasoning task' }],\n model: 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo'\n});\n// → Routes to Together AI\n```\n\n## 💰 Cost Optimization\n\nTrack costs across all providers:\n\n```typescript\nconst stats = await daemon.getUsageStats();\n\nconsole.log(stats);\n// {\n// ollama: { requests: 1500, cost: 0, avgLatency: 45ms },\n// together: { requests: 200, cost: $2.50, avgLatency: 350ms },\n// openai: { requests: 50, cost: $15.00, avgLatency: 800ms }\n// }\n```\n\n**Smart routing strategies:**\n1. **Local-first**: Try Ollama, fall back to cloud if needed\n2. **Cost-optimized**: Use cheapest provider that meets quality bar\n3. **Latency-optimized**: Use fastest provider (Groq for speed, Ollama for local)\n4. **Quality-optimized**: Use best model regardless of cost (GPT-4, Claude 3.5)\n\n## 🔐 Security: SecretManager Integration\n\n**All API keys are managed securely via SecretManager:**\n\n### Key Security Features\n1. **Server-side only** - API keys NEVER sent to browser\n2. **Multi-source loading** - `~/.continuum/config.env` → `process.env` → `.env`\n3. **Automatic redaction** - Keys filtered from logs, screenshots, error messages\n4. **Audit trail** - Track which adapters access which keys\n5. **Graceful degradation** - Missing keys don't crash system\n\n### Usage in Adapters\n```typescript\nimport { getSecret } from '../../../../system/secrets/SecretManager';\n\nexport class OpenAIAdapter extends BaseOpenAICompatibleAdapter {\n constructor(apiKey?: string) {\n super({\n providerId: 'openai',\n providerName: 'OpenAI',\n // ✅ Secure: Uses SecretManager\n apiKey: apiKey || getSecret('OPENAI_API_KEY', 'OpenAIAdapter') || '',\n baseUrl: 'https://api.openai.com',\n // ...\n });\n }\n}\n```\n\n### API Key Setup\nUsers configure API keys in `~/.continuum/config.env`:\n```bash\nOPENAI_API_KEY=sk-...\nANTHROPIC_API_KEY=sk-ant-...\nTOGETHER_API_KEY=...\nFIREWORKS_API_KEY=...\n```\n\n**Future**: Persona-guided widget for secure API key entry through UI.\n\n## 🚀 Next Steps\n\n1. **✅ SecretManager integration** - COMPLETED\n2. **Implement remaining adapters** (20 lines each, trivial)\n3. **Add integration tests** for multi-provider routing\n4. **Implement cost tracking** and usage analytics\n5. **Add streaming support** for real-time responses\n6. **ProcessPool integration** for local model management\n7. **LoRA adapter hot-swapping** (genome evolution)\n8. **Persona-guided API key setup widget**\n\n## 📊 M1 Mac Capabilities\n\n### Local Inference (Ollama + MLX)\n- **2B-7B models**: Run instantly, 16GB RAM\n- **13B-70B models**: Quantized (4-bit), slower but works\n\n### Training (MLX)\n- **LoRA fine-tuning**: 7B-13B models\n- **Full fine-tuning**: Only 2B-7B models\n- **Training time**: 1-4 hours for LoRA on small datasets\n\n### Hybrid Strategy\n```\nCoordination (fast, cheap) → Ollama llama3.2:1b (local)\nChat responses (quality) → Ollama phi3:mini or Together Llama 70B\nComplex reasoning (best) → GPT-4 or Claude 3.5 Sonnet\nMultimodal (images) → GPT-4V or Claude 3.5\nTraining (LoRA) → MLX (local) or Together AI (cloud)\n```\n\n## 🎉 Summary\n\n**Before this architecture:**\n- Each provider = 300+ lines of boilerplate\n- Adding providers = tedious, error-prone\n- No code reuse\n\n**After this architecture:**\n- Each provider = 20-30 lines\n- Adding providers = trivial\n- 95% code reuse for OpenAI-compatible APIs\n- Multimodal support built-in\n- Cost/latency tracking unified\n\n**Result:** Can support 10+ providers with minimal effort!\n"}}, "src/config.env.example": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1785745122.1602983, "n": 3791, "h": "6c8065fe5c038e1e", "c": "# ============================================\n# CONTINUUM CONFIGURATION\n# ============================================\n# Copy this file to ~/.continuum/config.env and add your API keys.\n# Or just run `npm start` - it auto-creates config.env on first run.\n#\n# This file is auto-generated and will NOT be overwritten.\n# Edit values as needed for your environment.\n\n# ============================================\n# NETWORKING\n# ============================================\n# Controls network ports for HTTP and WebSocket servers\n\n# HTTP Port - Port for HTTP server\nHTTP_PORT=9000\n\n# WebSocket Port - Port for WebSocket server\nWS_PORT=9001\n\n# ============================================\n# LOGGER CONFIGURATION\n# ============================================\n# Controls system-wide logging behavior via Logger.ts\n\n# Log Level - Controls verbosity of logs\n# Values: debug, info, warn, error, silent\nLOG_LEVEL=info\n\n# Timestamps - Add timestamps to log entries\nLOG_TIMESTAMPS=1\n\n# Console Logging - Output logs to stdout/stderr\nLOG_TO_CONSOLE=1\n\n# File Logging - Write logs to .continuum/*/logs/\nLOG_TO_FILES=1\n\n# File Mode - How to handle existing log files on restart\n# Values: clean, append, archive\nLOG_FILE_MODE=clean\n\n# ============================================\n# API KEYS\n# ============================================\n# Add your API keys below. At least one is required for AI responses.\n# Leave blank if not using a provider.\n\n# Anthropic (Claude models) - https://console.anthropic.com/\nANTHROPIC_API_KEY=\n\n# OpenAI (GPT models) - https://platform.openai.com/\nOPENAI_API_KEY=\n\n# X.AI (Grok models) - https://x.ai/\nXAI_API_KEY=\n\n# DeepSeek - https://platform.deepseek.com/\nDEEPSEEK_API_KEY=\n\n# Together AI - https://api.together.xyz/\nTOGETHER_API_KEY=\n\n# Fireworks AI - https://fireworks.ai/\nFIREWORKS_API_KEY=\nFIREWORKS_ACCOUNT_ID=\n\n# Groq (fast inference) - https://console.groq.com/\nGROQ_API_KEY=\n\n# Mistral AI - https://console.mistral.ai/\nMISTRAL_API_KEY=\n\n# HuggingFace - https://huggingface.co/settings/tokens\nHF_TOKEN=\n\n# ============================================\n# DATABASE PATHS (optional)\n# ============================================\n# Override default database locations if needed.\n# By default, databases are stored in ~/.continuum/data\n\n# Main database directory (SQLite files)\n#DATABASE_DIR=~/.continuum/data\n\n# Database backups directory\n#DATABASE_BACKUP_DIR=~/.continuum/data/backups\n\n# Database archive directory (for archived data)\n#DATABASE_ARCHIVE_DIR=~/.continuum/data/archive\n\n# ============================================\n# DATASETS AND EXTENSIONS (optional)\n# ============================================\n\n# Datasets directory - Location for training datasets\n#DATASETS_DIR=/path/to/datasets\n\n# Sentinel-AI Path - Path to Sentinel AI system (if installed)\n#SENTINEL_PATH=/path/to/sentinel-ai\n\n# ============================================\n# LOCAL INFERENCE\n# ============================================\n\n# Inference Mode - Controls local model loading strategy\n# Values: auto, quantized, bf16\n# - auto: BF16 (full LoRA support), fallback to quantized if needed\n# - quantized: Force Q4_K_M quantized (~2GB, 2s load, NO LoRA support)\n# - bf16: Force full-precision BF16 (~6GB, 14s load, full LoRA)\nINFERENCE_MODE=auto\n\n# ============================================\n# FEATURE FLAGS\n# ============================================\n\n# Verbose Logging - Enable detailed diagnostic logging (connect/disconnect, routing, etc.)\n# Values: 0/false/no/off (disabled), 1/true/yes/on (enabled)\nJTAG_VERBOSE=0\n\n# Use Rust AI Provider - Enable Rust-based AI provider for better performance\n# Values: 0 (disabled), 1 (enabled)\nUSE_RUST_AI_PROVIDER=0\n\n# Enable Archive Daemon - Enable automatic data archiving\n# Values: 0 (disabled), 1 (enabled)\nENABLE_ARCHIVE_DAEMON=0\n"}, "21dc80175ad415b2a3f84e087d64b1c4e5795a1f": {"t": 1785747962.2520077, "n": 3791, "h": "6c8065fe5c038e1e", "c": "# ============================================\n# CONTINUUM CONFIGURATION\n# ============================================\n# Copy this file to ~/.continuum/config.env and add your API keys.\n# Or just run `npm start` - it auto-creates config.env on first run.\n#\n# This file is auto-generated and will NOT be overwritten.\n# Edit values as needed for your environment.\n\n# ============================================\n# NETWORKING\n# ============================================\n# Controls network ports for HTTP and WebSocket servers\n\n# HTTP Port - Port for HTTP server\nHTTP_PORT=9000\n\n# WebSocket Port - Port for WebSocket server\nWS_PORT=9001\n\n# ============================================\n# LOGGER CONFIGURATION\n# ============================================\n# Controls system-wide logging behavior via Logger.ts\n\n# Log Level - Controls verbosity of logs\n# Values: debug, info, warn, error, silent\nLOG_LEVEL=info\n\n# Timestamps - Add timestamps to log entries\nLOG_TIMESTAMPS=1\n\n# Console Logging - Output logs to stdout/stderr\nLOG_TO_CONSOLE=1\n\n# File Logging - Write logs to .continuum/*/logs/\nLOG_TO_FILES=1\n\n# File Mode - How to handle existing log files on restart\n# Values: clean, append, archive\nLOG_FILE_MODE=clean\n\n# ============================================\n# API KEYS\n# ============================================\n# Add your API keys below. At least one is required for AI responses.\n# Leave blank if not using a provider.\n\n# Anthropic (Claude models) - https://console.anthropic.com/\nANTHROPIC_API_KEY=\n\n# OpenAI (GPT models) - https://platform.openai.com/\nOPENAI_API_KEY=\n\n# X.AI (Grok models) - https://x.ai/\nXAI_API_KEY=\n\n# DeepSeek - https://platform.deepseek.com/\nDEEPSEEK_API_KEY=\n\n# Together AI - https://api.together.xyz/\nTOGETHER_API_KEY=\n\n# Fireworks AI - https://fireworks.ai/\nFIREWORKS_API_KEY=\nFIREWORKS_ACCOUNT_ID=\n\n# Groq (fast inference) - https://console.groq.com/\nGROQ_API_KEY=\n\n# Mistral AI - https://console.mistral.ai/\nMISTRAL_API_KEY=\n\n# HuggingFace - https://huggingface.co/settings/tokens\nHF_TOKEN=\n\n# ============================================\n# DATABASE PATHS (optional)\n# ============================================\n# Override default database locations if needed.\n# By default, databases are stored in ~/.continuum/data\n\n# Main database directory (SQLite files)\n#DATABASE_DIR=~/.continuum/data\n\n# Database backups directory\n#DATABASE_BACKUP_DIR=~/.continuum/data/backups\n\n# Database archive directory (for archived data)\n#DATABASE_ARCHIVE_DIR=~/.continuum/data/archive\n\n# ============================================\n# DATASETS AND EXTENSIONS (optional)\n# ============================================\n\n# Datasets directory - Location for training datasets\n#DATASETS_DIR=/path/to/datasets\n\n# Sentinel-AI Path - Path to Sentinel AI system (if installed)\n#SENTINEL_PATH=/path/to/sentinel-ai\n\n# ============================================\n# LOCAL INFERENCE\n# ============================================\n\n# Inference Mode - Controls local model loading strategy\n# Values: auto, quantized, bf16\n# - auto: BF16 (full LoRA support), fallback to quantized if needed\n# - quantized: Force Q4_K_M quantized (~2GB, 2s load, NO LoRA support)\n# - bf16: Force full-precision BF16 (~6GB, 14s load, full LoRA)\nINFERENCE_MODE=auto\n\n# ============================================\n# FEATURE FLAGS\n# ============================================\n\n# Verbose Logging - Enable detailed diagnostic logging (connect/disconnect, routing, etc.)\n# Values: 0/false/no/off (disabled), 1/true/yes/on (enabled)\nJTAG_VERBOSE=0\n\n# Use Rust AI Provider - Enable Rust-based AI provider for better performance\n# Values: 0 (disabled), 1 (enabled)\nUSE_RUST_AI_PROVIDER=0\n\n# Enable Archive Daemon - Enable automatic data archiving\n# Values: 0 (disabled), 1 (enabled)\nENABLE_ARCHIVE_DAEMON=0\n"}}, "core/continuum-core/config/providers.toml": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1785745342.5165415, "n": 3465, "h": "ceeb7f7f658c8766", "c": "# providers.toml — single source of truth for AI provider endpoints.\n#\n# `model_prefixes` lists stable id prefixes that identify models this\n# provider serves. Matches are case-insensitive `starts_with`. Used by\n# `supports_model` to route id-based requests even when the specific id\n# isn't enumerated in models.toml yet (e.g. \"gpt-5-preview\" → openai).\n# Leave empty for providers with dynamic catalogs (DMR) — they dispatch\n# via live /v1/models probes, not prefix lookup.\n\n[[provider]]\nid = \"anthropic\"\nname = \"Anthropic\"\nbase_url = \"https://api.anthropic.com\"\napi_key_env = \"ANTHROPIC_API_KEY\"\ndefault_model = \"claude-sonnet-4-5-20250929\"\nauth = \"api_key\" # Anthropic uses x-api-key header, not Bearer\nmodel_prefixes = [\"claude\"]\n\n[[provider]]\nid = \"openai\"\nname = \"OpenAI\"\nbase_url = \"https://api.openai.com\"\napi_key_env = \"OPENAI_API_KEY\"\ndefault_model = \"gpt-4-turbo-preview\"\nauth = \"bearer\"\nmodel_prefixes = [\"gpt\", \"o1\", \"o3\"]\n\n[[provider]]\nid = \"deepseek\"\nname = \"DeepSeek\"\nbase_url = \"https://api.deepseek.com\"\napi_key_env = \"DEEPSEEK_API_KEY\"\ndefault_model = \"deepseek-chat\"\nauth = \"bearer\"\nmodel_prefixes = [\"deepseek\"]\n\n[[provider]]\nid = \"together\"\nname = \"Together AI\"\nbase_url = \"https://api.together.xyz\"\napi_key_env = \"TOGETHER_API_KEY\"\ndefault_model = \"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo\"\nauth = \"bearer\"\nmodel_prefixes = [\"togethercomputer/\", \"meta-llama/\"]\n\n[[provider]]\nid = \"groq\"\nname = \"Groq\"\nbase_url = \"https://api.groq.com/openai\"\napi_key_env = \"GROQ_API_KEY\"\ndefault_model = \"llama-3.1-8b-instant\"\nauth = \"bearer\"\nmodel_prefixes = [\"llama-3\", \"mixtral\", \"gemma2\"]\n\n[[provider]]\nid = \"fireworks\"\nname = \"Fireworks AI\"\nbase_url = \"https://api.fireworks.ai/inference\"\napi_key_env = \"FIREWORKS_API_KEY\"\ndefault_model = \"accounts/fireworks/models/llama-v3p3-70b-instruct\"\nauth = \"bearer\"\nmodel_prefixes = [\"accounts/fireworks/\"]\n\n[[provider]]\nid = \"xai\"\nname = \"xAI\"\nbase_url = \"https://api.x.ai\"\napi_key_env = \"XAI_API_KEY\"\ndefault_model = \"grok-3\"\nauth = \"bearer\"\nmodel_prefixes = [\"grok\"]\n\n[[provider]]\nid = \"google\"\nname = \"Google\"\nbase_url = \"https://generativelanguage.googleapis.com/v1beta/openai\"\napi_key_env = \"GOOGLE_API_KEY\"\ndefault_model = \"gemini-2.0-flash\"\nauth = \"bearer\"\nmodel_prefixes = [\"gemini\"]\n\n[[provider]]\nid = \"docker-model-runner\"\nname = \"Docker Model Runner (local Metal/CUDA)\"\nkind = \"local\"\n# IPv4 literal on purpose — `localhost` on macOS resolves to both ::1 and\n# 127.0.0.1 and Docker Desktop's model runner listens on IPv4 only. When\n# the hyper client tries ::1 first it waits for the connect path to fall\n# through, producing the 120s \"error sending request\" stalls that were\n# silently killing persona chat. Pinning to 127.0.0.1 bypasses the dual-\n# stack resolution entirely.\nbase_url = \"http://127.0.0.1:12434/engines/llama.cpp\"\ndefault_model = \"huggingface.co/continuum-ai/qwen3.5-4b-code-forged-gguf:latest\"\nauth = \"none\"\n# Dynamic catalog — provider lists models via /v1/models at init.\n# No model_prefixes — supports_model consults the live catalog, not static prefixes.\n# Override base URL via DOCKER_MODEL_RUNNER_BASE_URL env var (deployment concern).\n\n[[provider]]\nid = \"llamacpp-local\"\nname = \"Llama.cpp (in-process Metal/CUDA)\"\nkind = \"local\"\nbase_url = \"in-process\"\nauth = \"none\"\ndefault_model = \"continuum-ai/qwen3.5-4b-code-forged-GGUF\"\n# In-process llama.cpp backend — no HTTP endpoint; base_url is sentinel.\n# No model_prefixes — adapter matches by exact id from the registry.\n"}}, "core/continuum-core/src/modules/persona_allocator.rs": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1785745841.926418, "n": 4877, "h": "38816f52a6e5c99d", "c": "//! PersonaAllocatorModule — IPC command for hardware-aware persona allocation.\n//!\n//! Commands:\n//! - `persona/allocate`: Given available API keys, returns optimal persona allocations\n//! based on detected GPU hardware and VRAM budget.\n//!\n//! This is the single source of truth for \"which personas should exist on this machine.\"\n//! TypeScript calls this at seed time AND at runtime (when API keys are added/removed).\n\nuse crate::gpu::GpuMemoryManager;\nuse crate::persona::{allocate_personas, load_catalog};\nuse crate::runtime::{CommandResult, ModuleConfig, ModuleContext, ModulePriority, ServiceModule};\nuse async_trait::async_trait;\nuse serde_json::Value;\nuse std::any::Any;\nuse std::sync::Arc;\n\npub struct PersonaAllocatorModule {\n gpu_manager: Arc,\n}\n\nimpl PersonaAllocatorModule {\n pub fn new(gpu_manager: Arc) -> Self {\n Self { gpu_manager }\n }\n}\n\n#[async_trait]\nimpl ServiceModule for PersonaAllocatorModule {\n fn config(&self) -> ModuleConfig {\n ModuleConfig {\n name: \"persona_allocator\",\n priority: ModulePriority::Normal,\n command_prefixes: &[\"persona/\"],\n event_subscriptions: &[],\n needs_dedicated_thread: false,\n max_concurrency: 0,\n tick_interval: None,\n }\n }\n\n async fn initialize(&self, _ctx: &ModuleContext) -> Result<(), String> {\n Ok(())\n }\n\n async fn handle_command(&self, command: &str, params: Value) -> Result {\n match command {\n \"persona/allocate\" => {\n // Extract available API key names from params\n let api_keys: Vec = params\n .get(\"availableApiKeys\")\n .and_then(|v| v.as_array())\n .map(|arr| {\n arr.iter()\n .filter_map(|v| v.as_str().map(|s| s.to_string()))\n .collect()\n })\n .unwrap_or_default();\n\n let catalog = load_catalog();\n let result = allocate_personas(&self.gpu_manager, &api_keys, &catalog);\n\n let json = serde_json::to_value(&result)\n .map_err(|e| format!(\"Failed to serialize allocation result: {e}\"))?;\n Ok(CommandResult::Json(json))\n }\n\n \"persona/catalog\" => {\n // Return the raw catalog (for UI display)\n let catalog = load_catalog();\n let json = serde_json::to_value(&catalog)\n .map_err(|e| format!(\"Failed to serialize catalog: {e}\"))?;\n Ok(CommandResult::Json(json))\n }\n\n _ => Err(format!(\"Unknown persona command: {command}\")),\n }\n }\n\n fn as_any(&self) -> &dyn Any {\n self\n }\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n fn test_module() -> PersonaAllocatorModule {\n let manager = Arc::new(GpuMemoryManager::detect());\n PersonaAllocatorModule::new(manager)\n }\n\n #[tokio::test]\n async fn test_allocate_no_keys() {\n let module = test_module();\n let params = serde_json::json!({ \"availableApiKeys\": [] });\n let result = module.handle_command(\"persona/allocate\", params).await;\n assert!(result.is_ok());\n if let Ok(CommandResult::Json(json)) = result {\n assert!(json[\"allocations\"].is_array());\n assert!(json[\"summary\"].is_array());\n assert!(json[\"gpuName\"].is_string());\n assert!(json[\"localModel\"].is_string());\n }\n }\n\n #[tokio::test]\n async fn test_allocate_with_keys() {\n let module = test_module();\n let params = serde_json::json!({\n \"availableApiKeys\": [\"ANTHROPIC_API_KEY\", \"DEEPSEEK_API_KEY\"]\n });\n let result = module.handle_command(\"persona/allocate\", params).await;\n assert!(result.is_ok());\n if let Ok(CommandResult::Json(json)) = result {\n let allocations = json[\"allocations\"].as_array().unwrap();\n // Should have Anthropic personas\n assert!(allocations\n .iter()\n .any(|a| { a[\"apiKeyEnv\"].as_str() == Some(\"ANTHROPIC_API_KEY\") }));\n }\n }\n\n #[tokio::test]\n async fn test_catalog() {\n let module = test_module();\n let result = module.handle_command(\"persona/catalog\", Value::Null).await;\n assert!(result.is_ok());\n if let Ok(CommandResult::Json(json)) = result {\n let entries = json.as_array().unwrap();\n assert!(!entries.is_empty());\n }\n }\n\n #[tokio::test]\n async fn test_unknown_command() {\n let module = test_module();\n let result = module.handle_command(\"persona/unknown\", Value::Null).await;\n assert!(result.is_err());\n }\n}\n"}}, "install.sh": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1785745942.7158697, "n": 59934, "h": "ff3da3ed577d6f16", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\n# Log primitives (info/ok/warn/fail/die) come from\n# tools/scripts/lib/install-common.sh after clone. Until the repo is\n# cloned, use these minimal pre-clone versions; they'll be overridden\n# when we source the canonical library below.\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n# Alias so the canonical lib's `die` also works here and vice versa.\ndie() { fail \"$@\"; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\n# ── Friendly-failure infrastructure ─────────────────────────\n# When install.sh fails partway, Carl needs to know WHICH phase died,\n# not just what bash printed. PHASE gets updated as we enter each\n# section; the ERR trap reads it + maps to phase-specific guidance.\n# Empirically (2026-04-25): existing failures dump bash's last line\n# of stderr with no context. Carl can't tell if it's a Docker thing,\n# a Tailscale thing, a model-download thing, or a Rust build thing\n# without reading install.sh source.\nPHASE=\"(starting up)\"\nINSTALL_LOG=\"${INSTALL_LOG:-/tmp/continuum-install-$$.log}\"\nexec > >(tee -a \"$INSTALL_LOG\") 2>&1\n\nphase_guidance() {\n case \"$PHASE\" in\n *\"detect environment\"*) echo \"Verify uname -s + uname -m return expected values; check disk space (df -h /).\";;\n *\"pre-clone bootstrap\"*) echo \"Install git + docker first; on Mac, ensure Docker Desktop is running.\";;\n *\"clone\"*|*\"update repo\"*) echo \"Check network: ping github.com; verify INSTALL_DIR ($INSTALL_DIR) is writable.\";;\n *\"shared modules\"*) echo \"Re-clone may be incomplete; rm -rf $INSTALL_DIR && re-run installer.\";;\n *\"configuration\"*) echo \"Check $CONTINUUM_DATA exists + is writable; mkdir -p $CONTINUUM_DATA && chmod 700 $CONTINUUM_DATA.\";;\n *\"TLS certs\"*) echo \"Tailscale + cert step is optional; export CONTINUUM_NO_TLS=1 and re-run.\";;\n *\"compose files\"*) echo \"Verify docker-compose.yml exists in $INSTALL_DIR; the install repo may be incomplete.\";;\n *\"pull\"*|*\"images\"*) echo \"Network or GHCR auth issue; docker login ghcr.io and retry.\";;\n *\"start support services\"*|*\"bring up\"*) echo \"Check Docker Desktop has enough RAM (≥30GB). docker compose -f $INSTALL_DIR/docker-compose.yml logs --tail=100\";;\n *\"widget-server health\"*) echo \"Compose came up but widget-server isn't serving. docker compose -f $INSTALL_DIR/docker-compose.yml logs widget-server --tail=100\";;\n *) echo \"Capture full log + open an issue: cat $INSTALL_LOG | gh issue create -t 'install fail @ $PHASE' -b -\";;\n esac\n}\n\non_install_fail() {\n local rc=$?\n # Trap fires on any non-zero exit (set -e). Avoid recursing if the\n # ERR trap itself trips a sub-shell.\n trap - ERR EXIT\n echo \"\"\n echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n echo \" ❌ Install failed during phase: $PHASE (exit $rc)\"\n echo \"\"\n echo \" Suggestion: $(phase_guidance)\"\n echo \"\"\n echo \" Full log: $INSTALL_LOG\"\n echo \" Last 30 lines:\"\n tail -30 \"$INSTALL_LOG\" | sed 's/^/ /'\n echo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n exit \"$rc\"\n}\ntrap on_install_fail ERR\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Log: $INSTALL_LOG\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\nPHASE=\"detect environment\"\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Pre-clone bootstrap: git + minimal Docker presence check ────\nPHASE=\"pre-clone bootstrap\"\n# We can't source the canonical module library yet (lives in the repo).\n# Just verify prerequisites so the clone can happen. Deeper checks live\n# in the canonical modules that run after the clone.\n\nif ! command -v git &>/dev/null; then\n case \"$OS\" in\n Linux) fail \"git required. Run: sudo apt-get install -y git (or equivalent), then re-run.\" ;;\n Darwin) fail \"git required. Run: brew install git (or install Xcode CLI tools), then re-run.\" ;;\n esac\nfi\n\n# Container runtime + inference setup.\n#\n# Linux: Docker Engine + continuum-core runs containerized (with cuda/vulkan\n# GPU passthrough via /dev/dri or runtime:nvidia). Everything in containers.\n#\n# Mac: Docker Desktop for support services ONLY. continuum-core runs NATIVELY\n# on the host to access Metal for Candle embeddings, Bevy headless avatar\n# render, vision processing, and audio MPS paths — Apple's hypervisor exposes\n# no GPU to containers (Docker themselves confirmed in Feb 2026), so anything\n# Metal-needing must be on the host. LLM inference routes to Docker Model\n# Runner's vllm-metal backend, which also runs native on the host — Docker\n# Desktop manages the process but the compute happens on Apple Silicon directly.\n#\n# CONTAINER_CMD is used for every `compose` / `info` call. On Mac that\n# handles support services; continuum-core-server is launched separately\n# as a native host process via `npm start`.\ncase \"$OS\" in\n Linux)\n if ! command -v docker &>/dev/null; then\n info \"Docker not found — installing via get.docker.com…\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n fi\n CONTAINER_CMD=docker\n ;;\n Darwin)\n if ! command -v docker &>/dev/null; then\n fail \"Docker Desktop required on Mac.\n\n 1. Download: https://docker.com/products/docker-desktop (4.62+ for Model Runner)\n 2. Install the .dmg, then launch Docker Desktop from Launchpad\n 3. When prompted, grant Admin password for the vmnetd privileged helper\n (one-time macOS permission for container networking — standard Docker setup)\n 4. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 5. Re-run this install script\n\"\n fi\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop is installed but not running.\n\n 1. Launch Docker Desktop from Launchpad (or Applications)\n 2. If this is your first launch, macOS will prompt for Admin password for the\n vmnetd privileged helper — click 'Allow' and enter your password\n 3. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 4. Re-run this install script\n\n (Scripted \\`open -a Docker\\` can't satisfy the macOS privileged-helper prompt —\n that's why this script asks you to launch Docker Desktop manually once.)\n\"\n fi\n # ── Docker Desktop VM memory (Mac Option B — continuum-core NATIVE) ─────\n # The previous 80%-of-RAM target crashed Docker Desktop mid-run on 32GB\n # M1 during matrix testing ( 2026-04-16): Docker VM at 25.6GB\n # + native continuum-core at ~11GB RSS + macOS overhead ~6GB ≈ 43GB on a\n # 32GB physical box → heavy swap → Docker daemon died, DMR endpoint\n # disappeared, Helper AI fell back to Candle (5x slower) and never\n # produced a reply within the chat gate. Real-world blocker on the\n # primary-audience hardware.\n #\n # Mac Option B has THREE concurrent memory tenants, not two:\n # (a) native continuum-core ~12GB budget (Qwen 4B Q4 + KV + Candle\n # embeddings + Bevy render + vision +\n # audio + working set)\n # (b) Docker Desktop VM hosts postgres*, node-server, widget-\n # server, livekit-bridge, model-init.\n # With SQLite default (postgres opt-in),\n # the active containers need ~6-10GB.\n # (c) macOS itself kernel, window server, user apps ~6GB\n #\n # So Mac Option B target: PHYS - NATIVE_BUDGET(12) - MACOS_OVERHEAD(6)\n # = PHYS - 18GB headroom reserve. Floor at 10GB (below that, containers\n # don't fit; Option B isn't viable on that hardware).\n #\n # Physical memory sizing (Option B honest mins, not aspirational):\n # 32GB → 14GB Docker VM (comfortable)\n # 24GB → 6GB Docker VM (below floor → refuse)\n # Below 24GB → refuse install (can't fit all three tenants).\n PHYS_BYTES=$(sysctl -n hw.memsize)\n PHYS_MIB=$((PHYS_BYTES / 1048576))\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # Hardware tier — sets NATIVE_RESERVE + PERSONA_MODEL to fit available RAM.\n # Per Joel's \"MacBook Air on up, accessible, high-school-computer\" target:\n # 16GB MBA must be a working OOTB chat experience, not a 28GB-floor reject.\n # Tier breakdown (continuum-ai's published smaller models all public):\n # 8-15GB → reject; even minimal config doesn't fit (macOS 6GB +\n # Docker 4GB minimum + minimal continuum-core 3GB + small\n # model + working set ≈ 14-15GB working set, no headroom)\n # 16-23GB → MBA tier: smaller persona model, no Bevy/vision/audio\n # pre-pull at install time (chat-only OOTB; multimodal\n # enables when user attaches an image / opens video chat —\n # those code paths still load lazily). Native budget 5GB.\n # 24-31GB → mid tier: still chat-focused but slightly larger model;\n # Bevy/vision/audio available. Native budget 8GB.\n # 32GB+ → primary tier: full Qwen 4B code-forged + multimodal +\n # everything pre-pulled. Native budget 12GB (original).\n #\n # PERSONA_MODEL also tiers (set later when ic_decide_gpu_path runs;\n # this just sets the byte budget for Docker VM sizing). The tiered\n # PERSONA_MODEL is referenced by the docker model pull section below.\n if [[ \"$PHYS_MIB\" -lt $((16 * 1024)) ]]; then\n fail \"This Mac has ${PHYS_GB}GB physical RAM. Continuum's minimum is 16GB:\n - macOS itself reserves ~6GB\n - Docker Desktop VM needs at least ~4GB\n - Native continuum-core needs at least ~3GB (smallest persona model + working set)\n - Total minimum: 13-15GB, leaves no headroom under 16GB\nFor 16GB MBA: chat-only OOTB works (smaller model). For 32GB+: full multimodal experience.\"\n elif [[ \"$PHYS_MIB\" -lt $((24 * 1024)) ]]; then\n # MBA tier\n NATIVE_RESERVE_MIB=$((5 * 1024))\n CONTINUUM_TIER=\"mba\"\n info \"Hardware tier: MBA (${PHYS_GB}GB) — chat-only OOTB with smaller persona model\"\n elif [[ \"$PHYS_MIB\" -lt $((32 * 1024)) ]]; then\n # Mid tier\n NATIVE_RESERVE_MIB=$((8 * 1024))\n CONTINUUM_TIER=\"mid\"\n info \"Hardware tier: mid (${PHYS_GB}GB) — multimodal available with mid-size persona model\"\n else\n # Primary tier (original behavior)\n NATIVE_RESERVE_MIB=$((12 * 1024))\n CONTINUUM_TIER=\"primary\"\n info \"Hardware tier: primary (${PHYS_GB}GB) — full multimodal + Qwen 4B code-forged\"\n fi\n\n # Mac Intel override — RAM-based tier alone misclassifies Mac Intel +\n # discrete AMD or integrated Intel UHD as full/primary, but the\n # llama.cpp Metal-AMD shader path produces incoherent tokens on this\n # hardware (continuum 2026-05-30 evidence on MacBookPro15,1 / Radeon\n # Pro 560X: 0.8 tok/s + multilingual garbage + hundreds of nil\n # tensor buffer errors). Force the small CPU-runnable model tier\n # regardless of RAM until our CambrianTech/llama.cpp fork patches\n # the Metal-AMD kernels OR grid-share routes to an Apple-Silicon /\n # NVIDIA peer. Mirrors the Rust HwCapabilityTier::MacIntelMetalDiscrete\n # branch and the `mac_intel_discrete` tier in src/shared/models.json.\n CPU_BRAND=$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo \"\")\n if [[ \"$CPU_BRAND\" == *\"Intel\"* ]]; then\n info \"Mac Intel detected ($CPU_BRAND) — overriding to mac_intel_discrete tier (Metal-AMD shaders unreliable; smallest forged model + CPU-only floor)\"\n CONTINUUM_TIER=\"mac_intel_discrete\"\n NATIVE_RESERVE_MIB=$((5 * 1024))\n fi\n export CONTINUUM_TIER\n MACOS_RESERVE_MIB=$((6 * 1024))\n HEADROOM_MIB=$((NATIVE_RESERVE_MIB + MACOS_RESERVE_MIB))\n DOCKER_FLOOR_MIB=$((4 * 1024))\n\n TARGET_MIB=$((PHYS_MIB - HEADROOM_MIB))\n if [[ \"$TARGET_MIB\" -lt \"$DOCKER_FLOOR_MIB\" ]]; then\n TARGET_MIB=$DOCKER_FLOOR_MIB\n fi\n\n CURRENT_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n SETTINGS_FILE=\"$HOME/Library/Group Containers/group.com.docker/settings-store.json\"\n # Bump if current is substantially below target (>10% gap — don't thrash\n # restarts over rounding noise).\n if [[ -f \"$SETTINGS_FILE\" ]] && [[ -n \"$CURRENT_MIB\" ]] && [[ \"$CURRENT_MIB\" -lt \"$((TARGET_MIB * 90 / 100))\" ]]; then\n info \"Docker Desktop VM memory is ${CURRENT_MIB}MiB; bumping to ${TARGET_MIB}MiB (80% of ${PHYS_GB}GB host RAM, 16GB floor) for Continuum's inference + sensory workload…\"\n python3 - </dev/null 2>&1 || true\n # Wait for daemon to come back\n for _ in $(seq 1 30); do\n if docker info &>/dev/null 2>&1; then break; fi\n sleep 4\n done\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop didn't come back after memory-limit restart. Launch it manually from Launchpad.\"\n fi\n NEW_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n ok \"Docker Desktop VM memory now ${NEW_MIB}MiB (target ${TARGET_MIB}MiB)\"\n elif [[ -n \"$CURRENT_MIB\" ]]; then\n ok \"Docker Desktop VM memory already ${CURRENT_MIB}MiB (≥ ${TARGET_MIB}MiB target)\"\n fi\n\n # Docker Model Runner provides host-native vllm-metal for LLM inference.\n # Ships with Docker Desktop 4.62+. If `docker model` isn't available the\n # user's Docker Desktop is too old.\n if ! docker model --help &>/dev/null 2>&1; then\n fail \"Docker Model Runner not available (needs Docker Desktop 4.62+).\n\n 1. Open Docker Desktop → Settings → Software Updates → Check for updates\n 2. Install the update (restart Docker Desktop if prompted)\n 3. Re-run this install script\n\"\n fi\n # Verify the vllm runner is registered. On Docker DESKTOP (Mac), the\n # runners are bundled — Docker Desktop installs them automatically when\n # Model Runner is enabled. There's no /usr/local/cli-plugins step\n # (that's the Docker ENGINE / Linux path; `docker model install-runner\n # --help` says \"Docker Engine only\"). The earlier mkdir + install-runner\n # block was misapplied Linux logic on Mac, and forced a sudo prompt\n # for a directory Docker Desktop never reads from. Caught when CONTINUUM_\n # DEPS_ONLY=1 from parallel-start.sh tripped the prompt non-interactively\n # on every `npm start` (2026-04-16).\n #\n # If vllm shows \"Not Installed\", the user needs to enable it in Docker\n # Desktop → Settings → Beta features → Model Runner → install backends.\n # No CLI command can do this on Desktop, so we point at the GUI.\n if ! docker model status 2>/dev/null | awk '/^vllm[[:space:]]+Running/{found=1} END{exit !found}'; then\n warn \"vllm-metal backend not registered with Docker Model Runner.\n Open Docker Desktop → Settings → Features in development → Model Runner\n → ensure 'Enable Docker Model Runner' is on → install the vllm backend.\n Continuum will fall back to llama.cpp until vllm is enabled (~5x slower\n on M-series for some models).\"\n fi\n # Enable Model Runner's host-side TCP endpoint on port 12434. Without this,\n # continuum-core (running natively on the Mac host) can't reach the OpenAI-\n # compatible API — the probe in ai_provider.rs fails, the\n # docker-model-runner adapter doesn't register, and Candle becomes the\n # default local provider. That's a 5x perf regression (~10 tok/s vs ~50\n # tok/s on M5). Caught during M5 validation 2026-04-16: I had to enable\n # this manually before the adapter probe succeeded. Make it part of the\n # install so Carl never has to discover the toggle.\n #\n # `docker desktop enable model-runner --tcp=12434 --cors=all` is idempotent\n # — safe to re-run on every install. CORS=all is fine because the endpoint\n # binds 127.0.0.1 only (not exposed externally).\n if ! curl -fsS --max-time 1 http://localhost:12434/engines/llama.cpp/v1/models >/dev/null 2>&1; then\n info \"Enabling Docker Model Runner TCP endpoint on localhost:12434…\"\n docker desktop enable model-runner --tcp=12434 --cors=all 2>&1 | tail -3 || \\\n warn \"Could not enable Model Runner TCP — continuum-core will fall back to Candle (slower). Enable manually: docker desktop enable model-runner --tcp=12434 --cors=all\"\n fi\n # cmake — required by the vendored llama.cpp build (Phase 2a of `npm\n # start`). Carl's M1 install pass (#980 Bug 1) hit\n # thread 'main' panicked at cmake-0.1.57/src/lib.rs:1132:5:\n # failed to execute command: No such file or directory (os error 2)\n # is `cmake` not installed?\n # because install.sh said \"✅ Continuum Tower installed!\" without\n # checking cmake, then npm start died inside the cargo build of the\n # llama crate. Auto-install via brew matches the node pattern below\n # so fresh-Mac users have a working build path out of the box.\n if ! command -v cmake &>/dev/null; then\n if command -v brew &>/dev/null; then\n info \"cmake not found — installing via Homebrew (needed by vendored llama.cpp build)…\"\n brew install cmake\n else\n fail \"cmake required for vendored llama.cpp build. Install Homebrew + run 'brew install cmake', or use 'xcode-select --install' to get the macOS CLI tools that include cmake.\"\n fi\n fi\n # Rust toolchain — continuum-core-server is built natively on Mac (not\n # containerized) so it can link Metal for Candle embeddings, Bevy, vision,\n # and audio MPS paths. Build happens during `npm start` at end of install.\n if ! command -v cargo &>/dev/null; then\n info \"Rust not found — installing via rustup (needed for native continuum-core build)…\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable\n # shellcheck disable=SC1091\n [ -f \"$HOME/.cargo/env\" ] && source \"$HOME/.cargo/env\"\n fi\n # Node toolchain — required to build TypeScript side + run launcher.\n if ! command -v node &>/dev/null; then\n if command -v brew &>/dev/null; then\n info \"Node not found — installing via Homebrew…\"\n brew install node\n else\n fail \"Node.js required. Install from https://nodejs.org or via Homebrew, then re-run.\"\n fi\n fi\n CONTAINER_CMD=docker\n ;;\nesac\n\n# ── GPU detection + persona model pull (shared by Carl + Dev) ──────\n# Uses the centralized ic_detect_hardware / ic_decide_gpu_path from\n# install-common.sh so both install.sh paths use the SAME detection.\n# After this block: IC_GPU_PATH tells us which inference backend to\n# verify, and the default persona model is pulled into DMR (if DMR path).\nif type ic_detect_hardware &>/dev/null; then\n ic_detect_hardware\n ic_decide_gpu_path\n ic_describe_hardware\n\n # Hard-fail on unsupported. Previously this case fell through silently:\n # install.sh \"completed\", continuum runtime then errored on missing models.\n # That's the silent-failure-is-failure rule — Carl deserves an actionable\n # error at install time, not a confusing model-not-found at first chat.\n if [ \"$IC_GPU_PATH\" = \"unsupported\" ]; then\n cat >&2 </dev/null 2>&1; then\n if docker model configure --context-size \"$KV_CTX_SIZE\" --keep-alive 5m \"$PERSONA_MODEL\" 2>/dev/null; then\n ok \"DMR context-size capped at ${KV_CTX_SIZE} + keep-alive 5m for $PERSONA_MODEL (sized to ${PHYS_GB:-?}GB physical RAM; kills the per-slot KV bloat)\"\n else\n warn \"Could not apply DMR context-size cap. Older Docker Desktop? Upgrade to 4.62+ for 'docker model configure'. Falling back to model default (high memory use).\"\n fi\n else\n warn \"'docker model configure' not available — Docker Desktop may be older than 4.62. Per-slot KV cache will use model default (~262k tokens, high RAM).\"\n fi\n # Install vLLM MLX backend on Mac for 3x faster Qwen3.5 DeltaNet inference.\n # llama.cpp's Metal shaders for Gated DeltaNet are poorly optimized (~11 tok/s);\n # vllm-metal uses native MLX kernels (~33+ tok/s). Requires Docker Desktop 4.62+.\n if [[ \"$OS\" == \"Darwin\" ]]; then\n if docker model runner ls 2>/dev/null | grep -q \"vllm\"; then\n ok \"vLLM MLX backend already installed\"\n else\n info \"Installing vLLM MLX backend for native Apple Silicon inference...\"\n if docker model install-runner --backend vllm 2>/dev/null; then\n ok \"vLLM MLX backend installed — Qwen3.5 DeltaNet will use native MLX kernels\"\n # Pull MLX-format Qwen3.5-4B for vllm-metal routing.\n # DMR auto-routes MLX models to vllm-metal when installed.\n MLX_MODEL=\"hf.co/mlx-community/Qwen3.5-4B-MLX-4bit\"\n # MLX-format model also moves to registry-driven download.\n # Add MLX entry to src/shared/models.json + auto_download.always\n # if/when we want vllm-metal to find it on disk.\n ok \"MLX model download deferred to model-init (add to src/shared/models.json to enable)\"\n else\n warn \"vLLM install failed (requires Docker Desktop 4.62+). llama.cpp Metal will be used.\"\n fi\n fi\n fi\n ;;\n llama-vulkan)\n ok \"Vulkan GPU path — model download handled by continuum-core at first inference\"\n ;;\n unsupported)\n warn \"No supported GPU detected. Local chat will error until a GPU adapter is available.\"\n ;;\n esac\nfi\n\n# ── Vision-capable model (Qwen2-VL-7B) — pull if missing ───────────\n# The Vision AI persona uses the in-process llama.cpp adapter against\n# Qwen2-VL-7B-Instruct + its multimodal projector (mmproj). Without\n# both files on disk, AIProviderModule registers the adapter then logs\n# the gap, and any image upload falls through to the text-bridge path\n# (VisionDescriptionService) instead of going to a model that natively\n# sees pixels — defeats the README's \"see + speak\" thesis.\n#\n# Total ~5.5 GB on disk (Q4_K_M GGUF + f16 mmproj). Pull with `hf\n# download` (HuggingFace CLI; installed via `pip install huggingface-hub`\n# which already happens earlier in install for the python deps). Skips\n# cleanly if the files are already there.\n#\n# Path matches `models.toml::qwen2-vl-7b-instruct.gguf_local_path`\n# (today: `~/models/qwen2-vl-7b/`). Loader expand_path resolves `~`.\nQWEN2_VL_DIR=\"${HOME}/models/qwen2-vl-7b\"\nQWEN2_VL_GGUF=\"${QWEN2_VL_DIR}/Qwen2-VL-7B-Instruct-Q4_K_M.gguf\"\nQWEN2_VL_MMPROJ=\"${QWEN2_VL_DIR}/mmproj-Qwen2-VL-7B-Instruct-f16.gguf\"\nif [[ -f \"$QWEN2_VL_GGUF\" && -f \"$QWEN2_VL_MMPROJ\" ]]; then\n ok \"Vision model already on disk: $QWEN2_VL_DIR\"\nelse\n info \"Pulling Vision AI model — Qwen2-VL-7B-Instruct (~5.5 GB, first install only)...\"\n mkdir -p \"$QWEN2_VL_DIR\"\n if command -v hf >/dev/null 2>&1; then\n # `hf download` (huggingface-cli successor) — copies into local-dir\n # by default, no symlink dance. Both files in one call.\n if hf download bartowski/Qwen2-VL-7B-Instruct-GGUF \\\n Qwen2-VL-7B-Instruct-Q4_K_M.gguf \\\n mmproj-Qwen2-VL-7B-Instruct-f16.gguf \\\n --local-dir \"$QWEN2_VL_DIR\" 2>/dev/null; then\n ok \"Vision model pulled to $QWEN2_VL_DIR\"\n else\n warn \"Vision model pull failed. Manual: hf download bartowski/Qwen2-VL-7B-Instruct-GGUF Qwen2-VL-7B-Instruct-Q4_K_M.gguf mmproj-Qwen2-VL-7B-Instruct-f16.gguf --local-dir $QWEN2_VL_DIR\"\n warn \"Until pulled, the Vision AI persona will register but image uploads will hard-error.\"\n fi\n else\n warn \"'hf' (huggingface-cli) not on PATH — can't auto-pull vision model.\"\n warn \"Install: pip install huggingface-hub\"\n warn \"Then: hf download bartowski/Qwen2-VL-7B-Instruct-GGUF Qwen2-VL-7B-Instruct-Q4_K_M.gguf mmproj-Qwen2-VL-7B-Instruct-f16.gguf --local-dir $QWEN2_VL_DIR\"\n fi\nfi\n\n# ── Audio-capable model (Qwen2-Audio-7B) — pull if missing ─────────\n# Symmetric to the vision pull above. Audio AI persona uses the SAME\n# in-process llama.cpp + libmtmd path the vision side uses\n# (`backend.generate_with_audio()` → `MtmdContext::eval_audio()`),\n# verified end-to-end 2026-04-22. Without both the GGUF + audio mmproj\n# on disk, the adapter registers and any audio attachment falls through\n# to the STT bridge — lossy: tone, pacing, non-speech sounds gone.\n#\n# mradermacher carries both files; bartowski / second-state / gaianet\n# have weights only and are useless for libmtmd.\n#\n# Total ~5.7 GB on disk (Q4_K_M GGUF + f16 mmproj).\nQWEN2_AUDIO_DIR=\"${HOME}/models/qwen2-audio-7b\"\nQWEN2_AUDIO_GGUF=\"${QWEN2_AUDIO_DIR}/Qwen2-Audio-7B-Instruct-Q4_K_M.gguf\"\nQWEN2_AUDIO_MMPROJ=\"${QWEN2_AUDIO_DIR}/mmproj-Qwen2-Audio-7B-Instruct-f16.gguf\"\nif [[ -f \"$QWEN2_AUDIO_GGUF\" && -f \"$QWEN2_AUDIO_MMPROJ\" ]]; then\n ok \"Audio model already on disk: $QWEN2_AUDIO_DIR\"\nelse\n info \"Pulling Audio AI model — Qwen2-Audio-7B-Instruct (~5.7 GB, first install only)...\"\n mkdir -p \"$QWEN2_AUDIO_DIR\"\n if command -v hf >/dev/null 2>&1; then\n # Note: mradermacher's repo names files with `.` separators (e.g.\n # `Qwen2-Audio-7B-Instruct.Q4_K_M.gguf`). Renamed locally to the\n # `-` convention models.toml expects so paths are consistent with\n # the vision sibling.\n if hf download mradermacher/Qwen2-Audio-7B-Instruct-GGUF \\\n Qwen2-Audio-7B-Instruct.Q4_K_M.gguf \\\n Qwen2-Audio-7B-Instruct.mmproj-f16.gguf \\\n --local-dir \"$QWEN2_AUDIO_DIR\" 2>/dev/null && \\\n mv \"$QWEN2_AUDIO_DIR/Qwen2-Audio-7B-Instruct.Q4_K_M.gguf\" \"$QWEN2_AUDIO_GGUF\" 2>/dev/null && \\\n mv \"$QWEN2_AUDIO_DIR/Qwen2-Audio-7B-Instruct.mmproj-f16.gguf\" \"$QWEN2_AUDIO_MMPROJ\" 2>/dev/null; then\n ok \"Audio model pulled to $QWEN2_AUDIO_DIR\"\n else\n warn \"Audio model pull failed. Manual: hf download mradermacher/Qwen2-Audio-7B-Instruct-GGUF Qwen2-Audio-7B-Instruct.Q4_K_M.gguf Qwen2-Audio-7B-Instruct.mmproj-f16.gguf --local-dir $QWEN2_AUDIO_DIR\"\n warn \"Until pulled, the Audio AI persona will register but audio uploads will fall back to STT bridge.\"\n fi\n else\n warn \"'hf' (huggingface-cli) not on PATH — can't auto-pull audio model.\"\n warn \"Install: pip install huggingface-hub\"\n warn \"Then: hf download mradermacher/Qwen2-Audio-7B-Instruct-GGUF Qwen2-Audio-7B-Instruct.Q4_K_M.gguf Qwen2-Audio-7B-Instruct.mmproj-f16.gguf --local-dir $QWEN2_AUDIO_DIR\"\n fi\nfi\n\n# ── Per-service memory caps — auto-calculated from host RAM ────────\n# Joel's directive: don't ask users to set mem limits; auto-calc from host.\n# Don't paper over OOMs with undersized limits; size containers for the\n# actual mission. Mission per-service budgets:\n#\n# continuum-core (Linux container; on Mac it runs NATIVE and this cap\n# is informational / unused because docker-compose.mac.yml sets\n# replicas=0): needs to hold 4-8B param Qwen at Q4 (~4GB) + KV cache\n# for 5 concurrent personas (~2GB) + embeddings + Bevy + vision +\n# audio. Budget = host - 10GB (reserve for OS + Docker VM overhead\n# + support services).\n#\n# livekit-bridge: native WebRTC encode/decode buffers, multiple\n# streams. Budget scales with host — roughly host/8.\n#\n# node-server: TS orchestrator + IPC buffers + RAG state. Budget\n# same as livekit-bridge.\n#\n# model-init: one-time downloader, fits in 2GB.\n#\n# widget-server: static + light TS, 1GB.\n#\n# postgres: our dataset is small, 512MB (already set in compose).\n# livekit server: 256m (already set in compose).\n#\n# Physical RAM is whichever host this runs on.\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n # Mac branch set PHYS_MIB already. Linux sets it here from /proc/meminfo.\n :\nelif [[ -f /proc/meminfo ]]; then\n PHYS_MIB=$(awk '/^MemTotal:/{print int($2/1024)}' /proc/meminfo)\nfi\n\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # continuum-core cap: (host - 10GB) on ≥32GB machines, (host - 8GB)\n # on 20-31GB machines. Floor at 10GB.\n if [[ $PHYS_GB -ge 32 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 10))g\n elif [[ $PHYS_GB -ge 20 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 8))g\n else\n CONTINUUM_CORE_MEM=10g\n fi\n\n # Scale livekit-bridge + node-server with host. Floor 2GB each.\n # 16GB host → 2g, 32GB → 4g, 64GB → 8g.\n SCALED=$((PHYS_GB / 8))\n [[ $SCALED -lt 2 ]] && SCALED=2\n LIVEKIT_BRIDGE_MEM=${SCALED}g\n NODE_SERVER_MEM=${SCALED}g\n\n # Static + small — these don't need to scale.\n MODEL_INIT_MEM=2g\n WIDGET_SERVER_MEM=1g\n\n export CONTINUUM_CORE_MEM LIVEKIT_BRIDGE_MEM NODE_SERVER_MEM MODEL_INIT_MEM WIDGET_SERVER_MEM\n\n info \"Memory caps (${PHYS_GB}GB host): continuum-core=${CONTINUUM_CORE_MEM}, livekit-bridge=${LIVEKIT_BRIDGE_MEM}, node-server=${NODE_SERVER_MEM}, model-init=${MODEL_INIT_MEM}, widget-server=${WIDGET_SERVER_MEM}\"\nfi\n\n# (OS-branch case/esac above handled Linux/Darwin and set CONTAINER_CMD.)\ncase \"$OS\" in\n Linux|Darwin) : ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 3. Clone / update repo ─────────────────────────────────\nPHASE=\"clone / update repo\"\n# CONTINUUM_REF env override: clone a specific branch/sha instead of\n# default (origin/HEAD). Used by carl-install-smoke CI to validate PR\n# src/ changes — without it, install.sh always cloned origin/main and\n# PR src/ edits never got tested by CI. 2026-05-03: this gap meant\n# every fix to src/jtag, tools/scripts/install.sh, etc landed via PR\n# but couldn't be validated by carl-install-smoke until merged. Joel:\n# \"months of trying to get continuum working out-of-box for Carl.\"\n# Default ref is canary, NOT origin/HEAD (= main). main is intentionally\n# behind canary until release cadence promotes the branch on schedule;\n# 2026-05-03 main is 79 commits BEHIND canary, including critical install\n# fixes (mod_jtag_bin_link, WSL2 config.env mirror, .env image-tag writer,\n# resolveRoomIdentifier, stripLeakedToolMarkup, phantom-tab sanitize,\n# socket chmod 666, etc). Default Carl install used to clone main and\n# fail at line 769 with \"mod_jtag_bin_link: command not found\".\n# Per Joel 2026-05-03: \"Everyone uses current code period.\"\nDEFAULT_CONTINUUM_REF=\"canary\"\nRESOLVED_CONTINUUM_REF=\"${CONTINUUM_REF:-$DEFAULT_CONTINUUM_REF}\"\n\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum at ref $RESOLVED_CONTINUUM_REF...\"\n git clone --depth 1 --branch \"$RESOLVED_CONTINUUM_REF\" \"$REPO\" \"$INSTALL_DIR\" 2>/dev/null \\\n || (git clone \"$REPO\" \"$INSTALL_DIR\" && cd \"$INSTALL_DIR\" && git checkout \"$RESOLVED_CONTINUUM_REF\")\n cd \"$INSTALL_DIR\"\nfi\n\n# ── 4. Shared modules (same code that Dev runs via npm start) ────\nPHASE=\"shared modules\"\n# docs/infrastructure/INSTALL-ARCHITECTURE.md §Module-shape: the canonical\n# module library at tools/scripts/lib/install-common.sh defines\n# mod_submodules_init + mod_docker_wsl_integration + log/sudo primitives.\n# Carl and Dev call the SAME functions so there's no drift.\nif [ ! -f \"tools/scripts/lib/install-common.sh\" ]; then\n fail \"Canonical install library missing at tools/scripts/lib/install-common.sh — incomplete clone? Try: rm -rf $INSTALL_DIR && re-run this installer.\"\nfi\n\n# shellcheck source=tools/scripts/lib/install-common.sh\nsource \"tools/scripts/lib/install-common.sh\"\n\nmod_submodules_init\nmod_docker_wsl_integration\n\n# Real daemon check. On Linux this verifies Docker Engine is up (after the\n# WSL integration module had a chance to fix it on Windows/WSL2 hosts);\n# on Mac it verifies `podman machine start` above actually connected.\nif ! $CONTAINER_CMD info &>/dev/null 2>&1; then\n case \"$OS\" in\n Darwin) fail \"Podman machine not reachable. Run: podman machine start — then re-run this installer.\" ;;\n *) fail \"Docker daemon not reachable. Start Docker Desktop / Rancher Desktop and re-run.\" ;;\n esac\nfi\nok \"$CONTAINER_CMD $($CONTAINER_CMD version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\nok \"Source: $INSTALL_DIR\"\n\n# ── 3a. Build host-side CLI bundle (REQUIRED for jtag fast path) ──\n# Without dist/cli-bundle.js, src/jtag falls back to `tsx cli.ts`\n# which can't resolve tsconfig path aliases at runtime → every jtag\n# invocation fails with ERR_MODULE_NOT_FOUND. The bundle is what\n# every host-side jtag user actually needs. Pre-2026-05-03 install.sh\n# never built it on Linux (Docker-only flow); fresh users' first\n# jtag invocation has been broken for months. Joel: \"months of\n# trying to get continuum working out-of-box for Carl.\"\n#\n# 2026-05-03 reliability fix: be LOUD about success/failure. Pre-fix\n# wrapped npm in `| tail -2` which silently ate exit codes. Now uses\n# explicit set -o pipefail equivalent via PIPESTATUS check, AND\n# verifies dist/cli-bundle.js exists post-build. Loud success = user\n# sees \"✅ jtag bundle ready\"; loud failure = user sees the actual\n# npm error + a die() so installation can't claim success while\n# leaving jtag broken.\nPHASE=\"host-side jtag CLI bundle\"\nif [ ! -f \"$INSTALL_DIR/src/package.json\" ]; then\n fail \"src/package.json missing in $INSTALL_DIR — clone incomplete? Re-run with: rm -rf $INSTALL_DIR && curl ... | bash\"\nfi\nif ! command -v npm >/dev/null 2>&1; then\n fail \"npm not found on PATH but required for host-side jtag CLI bundle. Install Node.js (https://nodejs.org) and re-run.\"\nfi\ninfo \"Building host-side jtag CLI bundle (~30-60s — first install)...\"\n# build:cli takes dist/cli.js as INPUT (esbuild input file). dist/cli.js\n# is OUTPUT of build:ts. So the right invocation is `npm run build`\n# (which is build:ts → postbuild → build:cli per package.json scripts).\n# Pre-fix only ran build:cli → esbuild's missing-input failed silently\n# (the script suppresses stderr with `2>/dev/null`), no bundle written,\n# install completed \"successfully\" with broken jtag.\n(\n set -e\n cd \"$INSTALL_DIR/src\"\n echo \" → npm install (~10s)...\"\n npm install 2>&1 | tail -5 || { echo \" ✗ npm install failed\"; exit 1; }\n echo \" → npm run build (TypeScript compile + esbuild bundle, ~30-50s)...\"\n npm run build 2>&1 | tail -10 || { echo \" ✗ npm run build failed\"; exit 1; }\n) || fail \"Host-side bundle build failed (see lines above). jtag CLI cannot work without dist/cli-bundle.js. Manually retry: cd $INSTALL_DIR/src && npm install && npm run build\"\n# Verify the bundle actually exists — npm exit 0 + missing file = silent failure.\nif [ ! -f \"$INSTALL_DIR/src/dist/cli-bundle.js\" ]; then\n fail \"dist/cli-bundle.js was NOT created by build:cli (esbuild silently failed?). Manually retry: cd $INSTALL_DIR/src && npm install && npm run build:cli — and inspect output.\"\nfi\nok \"jtag CLI bundle ready ($INSTALL_DIR/src/dist/cli-bundle.js)\"\n\n# ── 3b. Install continuum command (modular, headless-safe) ─\n# Was an inline `sudo cp` that crashed on \"no TTY for password\" when the\n# install ran headless (curl|bash without -t, BigMama SSH dry-run, CI).\n# Now goes through mod_continuum_bin_link which routes to a user-space\n# fallback (~/.local/bin) when sudo would prompt without a TTY.\nmod_continuum_bin_link \"$INSTALL_DIR/bin/continuum\"\n\n# Also place `jtag` on PATH — symlinked, not copied, so the launcher's\n# BASH_SOURCE-based dist lookup keeps working. Without this, post-install\n# `jtag ` (per CLAUDE.md / skill docs) returns command-not-found\n# because src/jtag never gets a PATH entry. airc-8a5e 2026-05-03 Carl-UX\n# QA caught this — chat-probe simulates `./jtag` from inside the install\n# tree but real users follow the documented `jtag` form.\nmod_jtag_bin_link \"$INSTALL_DIR/src/jtag\"\n\n# ── 4. Configuration ───────────────────────────────────────\nPHASE=\"configuration\"\n# Pre-create the directories the docker mount overlays. The continuum-core\n# Dockerfile does `RUN mkdir -p /root/.continuum/sockets …` but the\n# compose `~/.continuum:/root/.continuum` mount overlays that with the\n# HOST's ~/.continuum at container start — so any subdir created at image\n# build time becomes invisible inside the container. continuum-core then\n# fails to bind its IPC socket with \"IPC server error: No such file or\n# directory (os error 2)\" and the healthcheck never goes green, blocking\n# the whole stack (continuum-core unhealthy → node-server's depends_on\n# fails → compose up exits 1). Caught 2026-05-30 on carl-install-smoke\n# of #1480; the canary image healthcheck regression had been silently\n# blocking install-smoke for any install touching the docker stack.\nmkdir -p \"$CONTINUUM_DATA\" \"$CONTINUUM_DATA/sockets\" \\\n \"$CONTINUUM_DATA/jtag/data\" \"$CONTINUUM_DATA/jtag/logs\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# WSL2 + Docker Desktop quirk: the bind mount `~/.continuum/config.env` in\n# docker-compose.yml expands `~` on the Docker daemon side. On Windows the\n# daemon runs as the Windows user so `~` resolves to C:\\Users\\,\n# NOT the WSL user's /home/. Without the file existing on the\n# Windows-side path, Docker auto-vivifies an EMPTY DIRECTORY there — and\n# then `compose up` fails with \"mounting a directory onto a file\" when it\n# tries to mount that dir over /root/.continuum/config.env (a file path\n# inside the container). Caught live by Carl-Windows install on\n# bigmama-1 (continuum-b69f, 2026-05-03).\n#\n# Fix: on WSL2, mirror config.env to the Windows user's home so the file\n# mount has a valid source. The OTHER bind mounts (`~/.continuum` dir)\n# survive Docker's auto-vivify because dir-on-dir mount is fine, but the\n# file mount needs the source to exist first.\n#\n# This is a no-op on Linux (no /mnt/c) and Mac (no /proc/version match).\nif grep -qi microsoft /proc/version 2>/dev/null && [ -d /mnt/c ]; then\n WIN_USER=\"$(cmd.exe /c 'echo %USERNAME%' 2>/dev/null | tr -d '\\r' | tr -d '\\n')\"\n if [ -n \"$WIN_USER\" ] && [ -d \"/mnt/c/Users/$WIN_USER\" ]; then\n WIN_CONTINUUM=\"/mnt/c/Users/$WIN_USER/.continuum\"\n mkdir -p \"$WIN_CONTINUUM\"\n # If Docker auto-vivified an empty DIRECTORY where the file should\n # be, blow it away so we can write the file. rmdir refuses\n # non-empty dirs (so we don't clobber real user data); rm -rf only\n # if rmdir failed AND the dir is empty.\n if [ -d \"$WIN_CONTINUUM/config.env\" ]; then\n rmdir \"$WIN_CONTINUUM/config.env\" 2>/dev/null \\\n || warn \"Windows-side $WIN_CONTINUUM/config.env is a non-empty directory (likely user data); leaving it. May still hit the mount error — manually rm -rf and re-run if needed.\"\n fi\n if [ ! -e \"$WIN_CONTINUUM/config.env\" ]; then\n cp \"$CONFIG_FILE\" \"$WIN_CONTINUUM/config.env\"\n ok \"Mirrored config.env to Windows path: $WIN_CONTINUUM/config.env\"\n fi\n else\n warn \"WSL2 detected but Windows username/home not found; config.env may not mount on Docker Desktop.\"\n fi\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nPHASE=\"TLS certs (optional)\"\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pick compose files + profile ───────────────────────\nPHASE=\"compose files\"\n# Base file is always loaded. On GPU hosts, layer docker-compose.gpu.yml\n# so continuum-core picks up the cuda image override (otherwise compose\n# silently uses the CPU image and inference falls back to CPU). The same\n# -f set MUST be passed to both `pull` and `up`, or pull grabs base\n# images while up tries to use override-named images that aren't local.\nCOMPOSE_FILES=\"-f docker-compose.yml\"\nCOMPOSE_ARGS=\"\"\nif [[ \"$OS\" == \"Darwin\" ]]; then\n # Mac path — the docker-compose.mac.yml override sets continuum-core's\n # replicas to 0 so support services boot in containers but continuum-core\n # stays off. We run continuum-core NATIVELY on Mac (via `npm start` below)\n # so Candle embeddings, Bevy headless render, vision processing, and audio\n # MPS paths all get real Metal. LLM inference routes to Docker Model\n # Runner's vllm-metal backend — also host-native, no container GPU tax.\n if [ -f \"docker-compose.mac.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.mac.yml\"\n else\n warn \"docker-compose.mac.yml missing — Mac detected but override won't apply. Without it, docker compose would try to run continuum-core in a container, which on Mac means CPU-only for Candle/Bevy/vision.\"\n fail \"Fix: ensure you cloned with repository integrity — the Mac override file is part of the PR891 install architecture.\"\n fi\nelif [[ \"$HAS_GPU\" == \"true\" ]]; then\n if [ -f \"docker-compose.gpu.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.gpu.yml\"\n else\n warn \"docker-compose.gpu.yml missing — GPU detected but cuda override won't apply. Continuing on Vulkan base image (still GPU-API; will use llvmpipe ICD if no vulkan driver).\"\n fi\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n# Linux without a CUDA GPU: base docker-compose.yml uses continuum-core-vulkan.\n# On real-driver hosts (Intel/AMD with vulkan) this picks up the hardware ICD;\n# on hosts without a driver, mesa-vulkan-drivers (apt) provides llvmpipe as a\n# software ICD so the Vulkan code path runs without panicking. Joel's\n# 2026-04-23 rule: GPU integration is forbidden to fall back. Vulkan-via-\n# llvmpipe is GPU integration (loader + ICD), not a CPU fallback.\nif [[ \"$OS\" == \"Linux\" ]] && [[ \"$HAS_GPU\" != \"true\" ]]; then\n if ! command -v vulkaninfo >/dev/null 2>&1; then\n warn \"vulkaninfo not found — install mesa-vulkan-drivers vulkan-tools so the Vulkan loader has the llvmpipe software ICD: sudo apt-get install -y mesa-vulkan-drivers vulkan-tools\"\n elif ! vulkaninfo --summary 2>/dev/null | grep -qE \"deviceName\"; then\n warn \"Vulkan loader present but enumerated zero devices. continuum-core-vulkan will panic on startup. Install: sudo apt-get install -y mesa-vulkan-drivers\"\n else\n info \"Vulkan loader OK — will use $(vulkaninfo --summary 2>/dev/null | grep -E 'deviceName' | head -1 | sed 's/.*= *//')\"\n fi\nfi\n\n# ── 7. Pull support-service images ─────────────────────────\nPHASE=\"pull images\"\n# Image tag resolution: compose files honor ${CONTINUUM_IMAGE_TAG:-latest}.\n# Main-branch installs (Carl's default) use :latest. Reviewers validating\n# a PR before merge can pin the PR's staged image set:\n# CONTINUUM_IMAGE_TAG=pr-891 curl -fsSL install.sh | bash\n# CI tags every PR build with pr- (see .github/workflows/docker-images.yml).\n# Merging to main promotes that image set to :latest, so main and :latest\n# are always in sync by construction.\n#\n# On Mac: `continuum-core` is not pulled (replicas=0 in docker-compose.mac.yml);\n# only support services (postgres, node-server, widget-server, livekit-bridge,\n# model-init) are pulled. continuum-core runs natively from `npm start` below.\n# docker compose v2 substitution for ${CONTINUUM_IMAGE_TAG:-latest} reads\n# from .env in the compose dir AND from shell env. In practice (observed\n# 2026-05-03 on bigmama-1 + Carl-Windows install) it picks up .env\n# reliably but NOT the shell env passed by install.sh — every compose\n# invocation resolved to :latest even though install.sh exported the\n# variable. Writing .env to $INSTALL_DIR (the compose-dir) before\n# pulling images is the canonical fix per docs and works regardless of\n# how the user invokes install.sh (curl|bash, direct, dispatched).\n#\n# Always write the .env (overwrite stale values from prior installs).\n# CONTINUUM_IMAGE_TAG defaults to \"latest\" preserving the historical\n# Carl path; explicit env override (e.g. CONTINUUM_IMAGE_TAG=canary\n# curl|bash for testing canary) flows through unchanged.\nEFFECTIVE_IMAGE_TAG=\"${CONTINUUM_IMAGE_TAG:-latest}\"\n{\n echo \"# Auto-generated by install.sh — do not edit manually.\"\n echo \"# Re-run install.sh to regenerate. Read by docker compose substitution.\"\n echo \"CONTINUUM_IMAGE_TAG=$EFFECTIVE_IMAGE_TAG\"\n} > \"$INSTALL_DIR/.env\"\n\ninfo \"Pulling container images (tag: $EFFECTIVE_IMAGE_TAG)...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 8. Start support services ──────────────────────────────\nPHASE=\"start support services\"\n# Inverse of parallel-start.sh's cross-mode detection: if native Dev-mode\n# processes (continuum-core-server, tsx orchestrator) are running, docker\n# compose up will collide on ports 9001/9100/7880-82/9003/5432. Warn so\n# the user can stop them before starting the stack.\nif pgrep -x 'continuum-core-server' >/dev/null 2>&1 \\\n || pgrep -f 'tsx.*scripts/launch-active-example' >/dev/null 2>&1; then\n warn \"Native Dev-mode continuum processes are running — they'll collide with the docker stack on ports.\"\n warn \"Run 'cd src && npm stop' to stop BOTH native and any running docker stack (idempotent).\"\n warn \"Continuing — expect bind errors below if they persist.\"\nfi\ninfo \"Starting support services...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS up -d\n\n\n# Some published continuum-core images may predate the in-binary socket chmod\n# fix (#1011). On Linux installs the host-side jtag CLI connects to the\n# bind-mounted core socket — when the running image is older than #1011, the\n# socket comes up root-owned without world-perms and host jtag gets EACCES.\n# Workaround at install time until every architecture's heavy core image\n# is refreshed past #1011.\nfix_core_socket_permissions() {\n local socket_dir=\"$CONTINUUM_DATA/sockets\"\n local core_socket=\"$socket_dir/continuum-core.sock\"\n\n [ -d \"$socket_dir\" ] || return 1\n\n chmod 755 \"$socket_dir\" 2>/dev/null \\\n || sudo -n chmod 755 \"$socket_dir\" 2>/dev/null \\\n || warn \"Could not chmod $socket_dir; host jtag may get EACCES\"\n\n [ -S \"$core_socket\" ] || return 1\n\n chmod 666 \"$core_socket\" 2>/dev/null \\\n || sudo -n chmod 666 \"$core_socket\" 2>/dev/null \\\n || warn \"Could not chmod $core_socket; host jtag may get EACCES\"\n}\n\nif [[ \"$OS\" != \"Darwin\" ]]; then\n for _ in $(seq 1 60); do\n if fix_core_socket_permissions; then\n break\n fi\n sleep 1\n done\nfi\n\n# ── 8b. Start continuum-core natively on Mac ───────────────\n# Mac runs continuum-core as a native host process so it can link Metal\n# directly. `npm start` drives the full build (cargo build --release\n# --features=metal + TS compile) and launches the server daemonized.\nif [[ \"$OS\" == \"Darwin\" ]]; then\n info \"Building + launching native continuum-core-server (Metal-enabled)...\"\n info \" First run: cargo build takes 5-15 min. Subsequent runs: incremental.\"\n\n # No DATABASE_URL configured by default. Rust's data module defaults to\n # SQLite at ~/.continuum/database/main.db — zero-dep, portable, no\n # network topology gymnastics. For grid deployments (multi-writer over\n # Tailscale) users explicitly set DATABASE_URL in config.env AND run\n # `docker compose --profile postgres up`. All other callers (TS, tests,\n # jtag CLI) pass opaque handles; Rust resolves them to the configured\n # backend in modules/data.rs::resolve_handle.\n\n # CONTINUUM_CORE_TCP=9100 tells the native continuum-core-server to bind an\n # additional TCP listener alongside its Unix socket. Containerized\n # node-server (Option B Mac architecture) reaches the host-native\n # continuum-core via tcp://host.docker.internal:9100 because Unix sockets\n # don't traverse Docker Desktop's VM boundary on Mac. Native callers\n # (jtag CLI, continuum bin) keep using the Unix socket as before.\n #\n # CONTINUUM_CORE_BIND=0.0.0.0 is REQUIRED on Mac: Docker Desktop's\n # `host.docker.internal` resolves inside containers to the host's\n # docker-bridge IP (e.g. 192.168.65.254), NOT to 127.0.0.1. A loopback-\n # bound listener is unreachable from containers. 0.0.0.0 accepts on all\n # interfaces; macOS's application firewall blocks inbound LAN traffic\n # for unsigned dev binaries by default, so exposure stays local.\n export CONTINUUM_CORE_TCP=9100\n export CONTINUUM_CORE_BIND=0.0.0.0\n (cd \"$INSTALL_DIR/src\" && npm install --silent && npm start) || \\\n warn \"npm start failed — check logs at ~/.continuum/jtag/logs/system/continuum-core.log\"\nfi\n\n# ── 8. Wait for widget-server health ───────────────────────\nPHASE=\"widget-server health\"\n# Carl's experience hinges on this gate: if we open the browser before\n# widget-server is actually serving, Chrome lands on the failed URL,\n# replaces the location bar with chrome-error://chromewebdata/, and any\n# subsequent reload tries to navigate from chrome-error back to http: —\n# which the browser blocks as a cross-scheme navigation. Carl is then\n# stuck on an error page with no clean recovery. Empirically: 2026-04-25\n# joel hit \"Unsafe attempt to load URL http://localhost:9003/ from frame\n# with URL chrome-error://chromewebdata/\" exactly because of this race.\n#\n# Two changes vs the prior 'curl -sf' wait:\n# 1. Hit /health specifically (widget-server's health endpoint at\n# JTAGEndpoints.HEALTH = '/health'). A 200 here means widget-server\n# is actually serving HTTP, not just that the port is open.\n# 2. If we never get a 200 in HEALTH_TIMEOUT_SEC, DO NOT open the\n# browser. Print actionable diagnostic + a manual-open command for\n# Carl to use after he checks the logs. Opening to a not-yet-ready\n# server is the bug; refusing to open is the correct behavior.\ninfo \"Waiting for widget-server health (timeout ${HEALTH_TIMEOUT_SEC:=120}s)...\"\nHEALTH_OK=0\nfor i in $(seq 1 \"$HEALTH_TIMEOUT_SEC\"); do\n # --fail returns non-zero on 4xx/5xx; --max-time keeps each probe snappy\n # so the loop stays close to a 1s cadence even when the server hangs.\n if curl -sf --max-time 2 http://localhost:9003/health >/dev/null 2>&1 \\\n || curl -sfk --max-time 2 https://localhost:9003/health >/dev/null 2>&1; then\n HEALTH_OK=1\n ok \"widget-server healthy after ${i}s\"\n break\n fi\n sleep 1\ndone\n\n# ── 8c. Wait for node-server seed to populate the default room ──────\n# widget-server /health on port 9003 only proves that container is up.\n# node-server (port 9001) runs auto-seed in docker-entrypoint.ts which\n# creates the \"general\" room + personas. If the user opens the page or\n# chat probe runs BEFORE seed completes, chat/send returns \"Room not\n# found: general\" or \"User not found\" silently. Probe directly for the\n# general room via jtag — fast, no new endpoint needed, deterministic.\n# Caught by carl-install-smoke 2026-05-04 (PR #1038).\nSEED_TIMEOUT_SEC=\"${SEED_TIMEOUT_SEC:-60}\"\nJTAG_BIN=\"$(command -v jtag 2>/dev/null || true)\"\n[ -z \"$JTAG_BIN\" ] && JTAG_BIN=\"$INSTALL_DIR/src/jtag\"\nif [ -x \"$JTAG_BIN\" ] && [ \"$HEALTH_OK\" -eq 1 ]; then\n info \"Waiting for seed to populate default room (timeout ${SEED_TIMEOUT_SEC}s)...\"\n SEED_OK=0\n for i in $(seq 1 \"$SEED_TIMEOUT_SEC\"); do\n # data/list returns success+items when the room exists. Empty items\n # means seed hasn't created it yet.\n if \"$JTAG_BIN\" data/list --collection=rooms --filter='{\"uniqueId\":\"general\"}' --limit=1 2>/dev/null \\\n | grep -q '\"success\":true.*\"items\":\\[{'; then\n SEED_OK=1\n ok \"default room seeded after ${i}s\"\n break\n fi\n sleep 1\n done\n if [ \"$SEED_OK\" -ne 1 ]; then\n warn \"general room not present after ${SEED_TIMEOUT_SEC}s — seed may have failed.\"\n warn \" Chat will return 'Room not found' until seed completes.\"\n warn \" Diagnose: $CONTAINER_CMD compose -f $INSTALL_DIR/docker-compose.yml logs node-server | tail -50\"\n fi\nfi\n\n# ── 9. Determine URL + open browser (only if healthy) ──────\nPHASE=\"open browser\"\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9003\"\nelse\n URL=\"http://localhost:9003\"\nfi\n\nif [ \"$HEALTH_OK\" -eq 1 ]; then\n case \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\n esac\nelse\n warn \"widget-server not healthy after ${HEALTH_TIMEOUT_SEC}s — NOT opening browser.\"\n warn \" Opening Chrome to a not-yet-ready URL traps you on a chrome-error page\"\n warn \" that cannot cleanly recover. Diagnose + retry instead:\"\n echo \"\"\n echo \" Logs: $CONTAINER_CMD compose -f $INSTALL_DIR/docker-compose.yml logs --tail=200\"\n echo \" Status: $CONTAINER_CMD compose -f $INSTALL_DIR/docker-compose.yml ps\"\n echo \" Retry: curl -v http://localhost:9003/health\"\n echo \"\"\n echo \" Once the health endpoint returns 200, open the URL manually:\"\n echo \" $URL\"\n echo \"\"\nfi\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9003\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "2c31cc2ee51f49aa55c19942f5368f2b36c1e02d": {"t": 1785747963.5858426, "n": 37127, "h": "6547d61bb790209e", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\n# Log primitives (info/ok/warn/fail/die) come from\n# src/scripts/lib/install-common.sh after clone. Until the repo is\n# cloned, use these minimal pre-clone versions; they'll be overridden\n# when we source the canonical library below.\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n# Alias so the canonical lib's `die` also works here and vice versa.\ndie() { fail \"$@\"; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Pre-clone bootstrap: git + minimal Docker presence check ────\n# We can't source the canonical module library yet (lives in the repo).\n# Just verify prerequisites so the clone can happen. Deeper checks live\n# in the canonical modules that run after the clone.\n\nif ! command -v git &>/dev/null; then\n case \"$OS\" in\n Linux) fail \"git required. Run: sudo apt-get install -y git (or equivalent), then re-run.\" ;;\n Darwin) fail \"git required. Run: brew install git (or install Xcode CLI tools), then re-run.\" ;;\n esac\nfi\n\n# Container runtime + inference setup.\n#\n# Linux: Docker Engine + continuum-core runs containerized (with cuda/vulkan\n# GPU passthrough via /dev/dri or runtime:nvidia). Everything in containers.\n#\n# Mac: Docker Desktop for support services ONLY. continuum-core runs NATIVELY\n# on the host to access Metal for Candle embeddings, Bevy headless avatar\n# render, vision processing, and audio MPS paths — Apple's hypervisor exposes\n# no GPU to containers (Docker themselves confirmed in Feb 2026), so anything\n# Metal-needing must be on the host. LLM inference routes to Docker Model\n# Runner's vllm-metal backend, which also runs native on the host — Docker\n# Desktop manages the process but the compute happens on Apple Silicon directly.\n#\n# CONTAINER_CMD is used for every `compose` / `info` call. On Mac that\n# handles support services; continuum-core-server is launched separately\n# as a native host process via `npm start`.\ncase \"$OS\" in\n Linux)\n if ! command -v docker &>/dev/null; then\n info \"Docker not found — installing via get.docker.com…\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n fi\n CONTAINER_CMD=docker\n ;;\n Darwin)\n if ! command -v docker &>/dev/null; then\n fail \"Docker Desktop required on Mac.\n\n 1. Download: https://docker.com/products/docker-desktop (4.62+ for Model Runner)\n 2. Install the .dmg, then launch Docker Desktop from Launchpad\n 3. When prompted, grant Admin password for the vmnetd privileged helper\n (one-time macOS permission for container networking — standard Docker setup)\n 4. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 5. Re-run this install script\n\"\n fi\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop is installed but not running.\n\n 1. Launch Docker Desktop from Launchpad (or Applications)\n 2. If this is your first launch, macOS will prompt for Admin password for the\n vmnetd privileged helper — click 'Allow' and enter your password\n 3. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 4. Re-run this install script\n\n (Scripted \\`open -a Docker\\` can't satisfy the macOS privileged-helper prompt —\n that's why this script asks you to launch Docker Desktop manually once.)\n\"\n fi\n # ── Docker Desktop VM memory (Mac Option B — continuum-core NATIVE) ─────\n # The previous 80%-of-RAM target crashed Docker Desktop mid-run on 32GB\n # M1 during matrix testing ( 2026-04-16): Docker VM at 25.6GB\n # + native continuum-core at ~11GB RSS + macOS overhead ~6GB ≈ 43GB on a\n # 32GB physical box → heavy swap → Docker daemon died, DMR endpoint\n # disappeared, Helper AI fell back to Candle (5x slower) and never\n # produced a reply within the chat gate. Real-world blocker on the\n # primary-audience hardware.\n #\n # Mac Option B has THREE concurrent memory tenants, not two:\n # (a) native continuum-core ~12GB budget (Qwen 4B Q4 + KV + Candle\n # embeddings + Bevy render + vision +\n # audio + working set)\n # (b) Docker Desktop VM hosts postgres*, node-server, widget-\n # server, livekit-bridge, model-init.\n # With SQLite default (postgres opt-in),\n # the active containers need ~6-10GB.\n # (c) macOS itself kernel, window server, user apps ~6GB\n #\n # So Mac Option B target: PHYS - NATIVE_BUDGET(12) - MACOS_OVERHEAD(6)\n # = PHYS - 18GB headroom reserve. Floor at 10GB (below that, containers\n # don't fit; Option B isn't viable on that hardware).\n #\n # Physical memory sizing (Option B honest mins, not aspirational):\n # 32GB → 14GB Docker VM (comfortable)\n # 24GB → 6GB Docker VM (below floor → refuse)\n # Below 24GB → refuse install (can't fit all three tenants).\n PHYS_BYTES=$(sysctl -n hw.memsize)\n PHYS_MIB=$((PHYS_BYTES / 1048576))\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # Reserve headroom for native continuum-core (12GB) + macOS (6GB).\n NATIVE_RESERVE_MIB=$((12 * 1024))\n MACOS_RESERVE_MIB=$((6 * 1024))\n HEADROOM_MIB=$((NATIVE_RESERVE_MIB + MACOS_RESERVE_MIB))\n DOCKER_FLOOR_MIB=$((10 * 1024))\n\n if [[ \"$PHYS_MIB\" -lt $((HEADROOM_MIB + DOCKER_FLOOR_MIB)) ]]; then\n fail \"This Mac has ${PHYS_GB}GB physical RAM. Mac Option B (continuum-core native + Docker Desktop for support services) needs at least $(( (HEADROOM_MIB + DOCKER_FLOOR_MIB) / 1024 ))GB: ~12GB for native continuum-core (Qwen 4B + Bevy + vision + audio), ~6GB for macOS itself, and a ${DOCKER_FLOOR_MIB}MiB floor for the Docker VM. Below that, Docker Desktop crashes under combined memory pressure (verified on a 32GB box with the old 80%-target formula). Get a 32GB+ M-series for the primary audience experience.\"\n fi\n\n TARGET_MIB=$((PHYS_MIB - HEADROOM_MIB))\n if [[ \"$TARGET_MIB\" -lt \"$DOCKER_FLOOR_MIB\" ]]; then\n TARGET_MIB=$DOCKER_FLOOR_MIB\n fi\n\n CURRENT_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n SETTINGS_FILE=\"$HOME/Library/Group Containers/group.com.docker/settings-store.json\"\n # Bump if current is substantially below target (>10% gap — don't thrash\n # restarts over rounding noise).\n if [[ -f \"$SETTINGS_FILE\" ]] && [[ -n \"$CURRENT_MIB\" ]] && [[ \"$CURRENT_MIB\" -lt \"$((TARGET_MIB * 90 / 100))\" ]]; then\n info \"Docker Desktop VM memory is ${CURRENT_MIB}MiB; bumping to ${TARGET_MIB}MiB (80% of ${PHYS_GB}GB host RAM, 16GB floor) for Continuum's inference + sensory workload…\"\n python3 - </dev/null 2>&1 || true\n # Wait for daemon to come back\n for _ in $(seq 1 30); do\n if docker info &>/dev/null 2>&1; then break; fi\n sleep 4\n done\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop didn't come back after memory-limit restart. Launch it manually from Launchpad.\"\n fi\n NEW_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n ok \"Docker Desktop VM memory now ${NEW_MIB}MiB (target ${TARGET_MIB}MiB)\"\n elif [[ -n \"$CURRENT_MIB\" ]]; then\n ok \"Docker Desktop VM memory already ${CURRENT_MIB}MiB (≥ ${TARGET_MIB}MiB target)\"\n fi\n\n # Docker Model Runner provides host-native vllm-metal for LLM inference.\n # Ships with Docker Desktop 4.62+. If `docker model` isn't available the\n # user's Docker Desktop is too old.\n if ! docker model --help &>/dev/null 2>&1; then\n fail \"Docker Model Runner not available (needs Docker Desktop 4.62+).\n\n 1. Open Docker Desktop → Settings → Software Updates → Check for updates\n 2. Install the update (restart Docker Desktop if prompted)\n 3. Re-run this install script\n\"\n fi\n # Verify the vllm runner is registered. On Docker DESKTOP (Mac), the\n # runners are bundled — Docker Desktop installs them automatically when\n # Model Runner is enabled. There's no /usr/local/cli-plugins step\n # (that's the Docker ENGINE / Linux path; `docker model install-runner\n # --help` says \"Docker Engine only\"). The earlier mkdir + install-runner\n # block was misapplied Linux logic on Mac, and forced a sudo prompt\n # for a directory Docker Desktop never reads from. Caught when CONTINUUM_\n # DEPS_ONLY=1 from parallel-start.sh tripped the prompt non-interactively\n # on every `npm start` (2026-04-16).\n #\n # If vllm shows \"Not Installed\", the user needs to enable it in Docker\n # Desktop → Settings → Beta features → Model Runner → install backends.\n # No CLI command can do this on Desktop, so we point at the GUI.\n if ! docker model status 2>/dev/null | awk '/^vllm[[:space:]]+Running/{found=1} END{exit !found}'; then\n warn \"vllm-metal backend not registered with Docker Model Runner.\n Open Docker Desktop → Settings → Features in development → Model Runner\n → ensure 'Enable Docker Model Runner' is on → install the vllm backend.\n Continuum will fall back to llama.cpp until vllm is enabled (~5x slower\n on M-series for some models).\"\n fi\n # Enable Model Runner's host-side TCP endpoint on port 12434. Without this,\n # continuum-core (running natively on the Mac host) can't reach the OpenAI-\n # compatible API — the probe in ai_provider.rs fails, the\n # docker-model-runner adapter doesn't register, and Candle becomes the\n # default local provider. That's a 5x perf regression (~10 tok/s vs ~50\n # tok/s on M5). Caught during M5 validation 2026-04-16: I had to enable\n # this manually before the adapter probe succeeded. Make it part of the\n # install so Carl never has to discover the toggle.\n #\n # `docker desktop enable model-runner --tcp=12434 --cors=all` is idempotent\n # — safe to re-run on every install. CORS=all is fine because the endpoint\n # binds 127.0.0.1 only (not exposed externally).\n if ! curl -fsS --max-time 1 http://localhost:12434/engines/llama.cpp/v1/models >/dev/null 2>&1; then\n info \"Enabling Docker Model Runner TCP endpoint on localhost:12434…\"\n docker desktop enable model-runner --tcp=12434 --cors=all 2>&1 | tail -3 || \\\n warn \"Could not enable Model Runner TCP — continuum-core will fall back to Candle (slower). Enable manually: docker desktop enable model-runner --tcp=12434 --cors=all\"\n fi\n # Rust toolchain — continuum-core-server is built natively on Mac (not\n # containerized) so it can link Metal for Candle embeddings, Bevy, vision,\n # and audio MPS paths. Build happens during `npm start` at end of install.\n if ! command -v cargo &>/dev/null; then\n info \"Rust not found — installing via rustup (needed for native continuum-core build)…\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable\n # shellcheck disable=SC1091\n [ -f \"$HOME/.cargo/env\" ] && source \"$HOME/.cargo/env\"\n fi\n # Node toolchain — required to build TypeScript side + run launcher.\n if ! command -v node &>/dev/null; then\n if command -v brew &>/dev/null; then\n info \"Node not found — installing via Homebrew…\"\n brew install node\n else\n fail \"Node.js required. Install from https://nodejs.org or via Homebrew, then re-run.\"\n fi\n fi\n CONTAINER_CMD=docker\n ;;\nesac\n\n# ── GPU detection + persona model pull (shared by Carl + Dev) ──────\n# Uses the centralized ic_detect_hardware / ic_decide_gpu_path from\n# install-common.sh so both install.sh paths use the SAME detection.\n# After this block: IC_GPU_PATH tells us which inference backend to\n# verify, and the default persona model is pulled into DMR (if DMR path).\nif type ic_detect_hardware &>/dev/null; then\n ic_detect_hardware\n ic_decide_gpu_path\n ic_describe_hardware\n\n # Hard-fail on unsupported. Previously this case fell through silently:\n # install.sh \"completed\", continuum runtime then errored on missing models.\n # That's the silent-failure-is-failure rule — Carl deserves an actionable\n # error at install time, not a confusing model-not-found at first chat.\n if [ \"$IC_GPU_PATH\" = \"unsupported\" ]; then\n cat >&2 </dev/null | grep -q \"qwen3.5-4b-code-forged\"; then\n info \"Pulling default persona model into Docker Model Runner (~2.7GB, first install only)...\"\n docker model pull \"$PERSONA_MODEL\" || warn \"Model pull failed — chat will error until model is available. Retry: docker model pull $PERSONA_MODEL\"\n else\n ok \"Persona model already in DMR: $PERSONA_MODEL\"\n fi\n # Cap llama-server's per-slot KV cache reservation, sized to actual\n # physical RAM. Without this cap each slot reserves the full model\n # context (262144 tokens for Qwen3.5), ballooning\n # com.docker.llama-server to 11+ GB resident on a single active slot\n # — observed live tonight on M5.\n #\n # Per-slot KV cost: ~16 KB per token at FP16 for Qwen3.5-4B\n # (32 layers × 256 attn-dim × 2 bytes × 2 tensors). Tier the cap so\n # 4 concurrent personas keep KV ≤10% of physical RAM:\n #\n # Physical RAM ctx-size 4-slot worst case\n # 8GB 4096 ~256 MB\n # 16GB 8192 ~512 MB\n # 24GB 16384 ~1 GB\n # 32GB 32768 ~2 GB\n # 48GB+ 65536 ~4 GB\n #\n # Specialized recipes (codereview, research) can opt up via per-recipe\n # overrides — Phase 9 in docs/architecture/RESOURCE-ARCHITECTURE.md.\n if [[ -n \"${PHYS_MIB:-}\" ]]; then\n if [[ \"$PHYS_MIB\" -ge $((48 * 1024)) ]]; then KV_CTX_SIZE=65536\n elif [[ \"$PHYS_MIB\" -ge $((32 * 1024)) ]]; then KV_CTX_SIZE=32768\n elif [[ \"$PHYS_MIB\" -ge $((24 * 1024)) ]]; then KV_CTX_SIZE=16384\n elif [[ \"$PHYS_MIB\" -ge $((16 * 1024)) ]]; then KV_CTX_SIZE=8192\n else KV_CTX_SIZE=4096\n fi\n else\n # PHYS_MIB unset (shouldn't happen on Mac/Linux paths but be safe)\n KV_CTX_SIZE=8192\n fi\n if docker model configure show >/dev/null 2>&1; then\n if docker model configure --context-size \"$KV_CTX_SIZE\" --keep-alive 5m \"$PERSONA_MODEL\" 2>/dev/null; then\n ok \"DMR context-size capped at ${KV_CTX_SIZE} + keep-alive 5m for $PERSONA_MODEL (sized to ${PHYS_GB:-?}GB physical RAM; kills the per-slot KV bloat)\"\n else\n warn \"Could not apply DMR context-size cap. Older Docker Desktop? Upgrade to 4.62+ for 'docker model configure'. Falling back to model default (high memory use).\"\n fi\n else\n warn \"'docker model configure' not available — Docker Desktop may be older than 4.62. Per-slot KV cache will use model default (~262k tokens, high RAM).\"\n fi\n # Install vLLM MLX backend on Mac for 3x faster Qwen3.5 DeltaNet inference.\n # llama.cpp's Metal shaders for Gated DeltaNet are poorly optimized (~11 tok/s);\n # vllm-metal uses native MLX kernels (~33+ tok/s). Requires Docker Desktop 4.62+.\n if [[ \"$OS\" == \"Darwin\" ]]; then\n if docker model runner ls 2>/dev/null | grep -q \"vllm\"; then\n ok \"vLLM MLX backend already installed\"\n else\n info \"Installing vLLM MLX backend for native Apple Silicon inference...\"\n if docker model install-runner --backend vllm 2>/dev/null; then\n ok \"vLLM MLX backend installed — Qwen3.5 DeltaNet will use native MLX kernels\"\n # Pull MLX-format Qwen3.5-4B for vllm-metal routing.\n # DMR auto-routes MLX models to vllm-metal when installed.\n MLX_MODEL=\"hf.co/mlx-community/Qwen3.5-4B-MLX-4bit\"\n if ! docker model ls 2>/dev/null | grep -q \"Qwen3.5-4B-MLX\"; then\n info \"Pulling MLX-format Qwen3.5-4B (~2.5GB, for 3x faster inference)...\"\n docker model pull \"$MLX_MODEL\" \\\n || warn \"MLX model pull failed. GGUF via llama.cpp will be used instead.\"\n fi\n else\n warn \"vLLM install failed (requires Docker Desktop 4.62+). llama.cpp Metal will be used.\"\n fi\n fi\n fi\n ;;\n llama-vulkan)\n ok \"Vulkan GPU path — model download handled by continuum-core at first inference\"\n ;;\n unsupported)\n warn \"No supported GPU detected. Local chat will error until a GPU adapter is available.\"\n ;;\n esac\nfi\n\n# ── Vision-capable model (Qwen2-VL-7B) — pull if missing ───────────\n# The Vision AI persona uses the in-process llama.cpp adapter against\n# Qwen2-VL-7B-Instruct + its multimodal projector (mmproj). Without\n# both files on disk, AIProviderModule registers the adapter then logs\n# the gap, and any image upload falls through to the text-bridge path\n# (VisionDescriptionService) instead of going to a model that natively\n# sees pixels — defeats the README's \"see + speak\" thesis.\n#\n# Total ~5.5 GB on disk (Q4_K_M GGUF + f16 mmproj). Pull with `hf\n# download` (HuggingFace CLI; installed via `pip install huggingface-hub`\n# which already happens earlier in install for the python deps). Skips\n# cleanly if the files are already there.\n#\n# Path matches `models.toml::qwen2-vl-7b-instruct.gguf_local_path`\n# (today: `~/models/qwen2-vl-7b/`). Loader expand_path resolves `~`.\nQWEN2_VL_DIR=\"${HOME}/models/qwen2-vl-7b\"\nQWEN2_VL_GGUF=\"${QWEN2_VL_DIR}/Qwen2-VL-7B-Instruct-Q4_K_M.gguf\"\nQWEN2_VL_MMPROJ=\"${QWEN2_VL_DIR}/mmproj-Qwen2-VL-7B-Instruct-f16.gguf\"\nif [[ -f \"$QWEN2_VL_GGUF\" && -f \"$QWEN2_VL_MMPROJ\" ]]; then\n ok \"Vision model already on disk: $QWEN2_VL_DIR\"\nelse\n info \"Pulling Vision AI model — Qwen2-VL-7B-Instruct (~5.5 GB, first install only)...\"\n mkdir -p \"$QWEN2_VL_DIR\"\n if command -v hf >/dev/null 2>&1; then\n # `hf download` (huggingface-cli successor) — copies into local-dir\n # by default, no symlink dance. Both files in one call.\n if hf download bartowski/Qwen2-VL-7B-Instruct-GGUF \\\n Qwen2-VL-7B-Instruct-Q4_K_M.gguf \\\n mmproj-Qwen2-VL-7B-Instruct-f16.gguf \\\n --local-dir \"$QWEN2_VL_DIR\" 2>/dev/null; then\n ok \"Vision model pulled to $QWEN2_VL_DIR\"\n else\n warn \"Vision model pull failed. Manual: hf download bartowski/Qwen2-VL-7B-Instruct-GGUF Qwen2-VL-7B-Instruct-Q4_K_M.gguf mmproj-Qwen2-VL-7B-Instruct-f16.gguf --local-dir $QWEN2_VL_DIR\"\n warn \"Until pulled, the Vision AI persona will register but image uploads will hard-error.\"\n fi\n else\n warn \"'hf' (huggingface-cli) not on PATH — can't auto-pull vision model.\"\n warn \"Install: pip install huggingface-hub\"\n warn \"Then: hf download bartowski/Qwen2-VL-7B-Instruct-GGUF Qwen2-VL-7B-Instruct-Q4_K_M.gguf mmproj-Qwen2-VL-7B-Instruct-f16.gguf --local-dir $QWEN2_VL_DIR\"\n fi\nfi\n\n# ── Audio-capable model (Qwen2-Audio-7B) — pull if missing ─────────\n# Symmetric to the vision pull above. Audio AI persona uses the SAME\n# in-process llama.cpp + libmtmd path the vision side uses\n# (`backend.generate_with_audio()` → `MtmdContext::eval_audio()`),\n# verified end-to-end 2026-04-22. Without both the GGUF + audio mmproj\n# on disk, the adapter registers and any audio attachment falls through\n# to the STT bridge — lossy: tone, pacing, non-speech sounds gone.\n#\n# mradermacher carries both files; bartowski / second-state / gaianet\n# have weights only and are useless for libmtmd.\n#\n# Total ~5.7 GB on disk (Q4_K_M GGUF + f16 mmproj).\nQWEN2_AUDIO_DIR=\"${HOME}/models/qwen2-audio-7b\"\nQWEN2_AUDIO_GGUF=\"${QWEN2_AUDIO_DIR}/Qwen2-Audio-7B-Instruct-Q4_K_M.gguf\"\nQWEN2_AUDIO_MMPROJ=\"${QWEN2_AUDIO_DIR}/mmproj-Qwen2-Audio-7B-Instruct-f16.gguf\"\nif [[ -f \"$QWEN2_AUDIO_GGUF\" && -f \"$QWEN2_AUDIO_MMPROJ\" ]]; then\n ok \"Audio model already on disk: $QWEN2_AUDIO_DIR\"\nelse\n info \"Pulling Audio AI model — Qwen2-Audio-7B-Instruct (~5.7 GB, first install only)...\"\n mkdir -p \"$QWEN2_AUDIO_DIR\"\n if command -v hf >/dev/null 2>&1; then\n # Note: mradermacher's repo names files with `.` separators (e.g.\n # `Qwen2-Audio-7B-Instruct.Q4_K_M.gguf`). Renamed locally to the\n # `-` convention models.toml expects so paths are consistent with\n # the vision sibling.\n if hf download mradermacher/Qwen2-Audio-7B-Instruct-GGUF \\\n Qwen2-Audio-7B-Instruct.Q4_K_M.gguf \\\n Qwen2-Audio-7B-Instruct.mmproj-f16.gguf \\\n --local-dir \"$QWEN2_AUDIO_DIR\" 2>/dev/null && \\\n mv \"$QWEN2_AUDIO_DIR/Qwen2-Audio-7B-Instruct.Q4_K_M.gguf\" \"$QWEN2_AUDIO_GGUF\" 2>/dev/null && \\\n mv \"$QWEN2_AUDIO_DIR/Qwen2-Audio-7B-Instruct.mmproj-f16.gguf\" \"$QWEN2_AUDIO_MMPROJ\" 2>/dev/null; then\n ok \"Audio model pulled to $QWEN2_AUDIO_DIR\"\n else\n warn \"Audio model pull failed. Manual: hf download mradermacher/Qwen2-Audio-7B-Instruct-GGUF Qwen2-Audio-7B-Instruct.Q4_K_M.gguf Qwen2-Audio-7B-Instruct.mmproj-f16.gguf --local-dir $QWEN2_AUDIO_DIR\"\n warn \"Until pulled, the Audio AI persona will register but audio uploads will fall back to STT bridge.\"\n fi\n else\n warn \"'hf' (huggingface-cli) not on PATH — can't auto-pull audio model.\"\n warn \"Install: pip install huggingface-hub\"\n warn \"Then: hf download mradermacher/Qwen2-Audio-7B-Instruct-GGUF Qwen2-Audio-7B-Instruct.Q4_K_M.gguf Qwen2-Audio-7B-Instruct.mmproj-f16.gguf --local-dir $QWEN2_AUDIO_DIR\"\n fi\nfi\n\n# ── Per-service memory caps — auto-calculated from host RAM ────────\n# Joel's directive: don't ask users to set mem limits; auto-calc from host.\n# Don't paper over OOMs with undersized limits; size containers for the\n# actual mission. Mission per-service budgets:\n#\n# continuum-core (Linux container; on Mac it runs NATIVE and this cap\n# is informational / unused because docker-compose.mac.yml sets\n# replicas=0): needs to hold 4-8B param Qwen at Q4 (~4GB) + KV cache\n# for 5 concurrent personas (~2GB) + embeddings + Bevy + vision +\n# audio. Budget = host - 10GB (reserve for OS + Docker VM overhead\n# + support services).\n#\n# livekit-bridge: native WebRTC encode/decode buffers, multiple\n# streams. Budget scales with host — roughly host/8.\n#\n# node-server: TS orchestrator + IPC buffers + RAG state. Budget\n# same as livekit-bridge.\n#\n# model-init: one-time downloader, fits in 2GB.\n#\n# widget-server: static + light TS, 1GB.\n#\n# postgres: our dataset is small, 512MB (already set in compose).\n# livekit server: 256m (already set in compose).\n#\n# Physical RAM is whichever host this runs on.\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n # Mac branch set PHYS_MIB already. Linux sets it here from /proc/meminfo.\n :\nelif [[ -f /proc/meminfo ]]; then\n PHYS_MIB=$(awk '/^MemTotal:/{print int($2/1024)}' /proc/meminfo)\nfi\n\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # continuum-core cap: (host - 10GB) on ≥32GB machines, (host - 8GB)\n # on 20-31GB machines. Floor at 10GB.\n if [[ $PHYS_GB -ge 32 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 10))g\n elif [[ $PHYS_GB -ge 20 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 8))g\n else\n CONTINUUM_CORE_MEM=10g\n fi\n\n # Scale livekit-bridge + node-server with host. Floor 2GB each.\n # 16GB host → 2g, 32GB → 4g, 64GB → 8g.\n SCALED=$((PHYS_GB / 8))\n [[ $SCALED -lt 2 ]] && SCALED=2\n LIVEKIT_BRIDGE_MEM=${SCALED}g\n NODE_SERVER_MEM=${SCALED}g\n\n # Static + small — these don't need to scale.\n MODEL_INIT_MEM=2g\n WIDGET_SERVER_MEM=1g\n\n export CONTINUUM_CORE_MEM LIVEKIT_BRIDGE_MEM NODE_SERVER_MEM MODEL_INIT_MEM WIDGET_SERVER_MEM\n\n info \"Memory caps (${PHYS_GB}GB host): continuum-core=${CONTINUUM_CORE_MEM}, livekit-bridge=${LIVEKIT_BRIDGE_MEM}, node-server=${NODE_SERVER_MEM}, model-init=${MODEL_INIT_MEM}, widget-server=${WIDGET_SERVER_MEM}\"\nfi\n\n# (OS-branch case/esac above handled Linux/Darwin and set CONTAINER_CMD.)\ncase \"$OS\" in\n Linux|Darwin) : ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\n# ── 4. Shared modules (same code that Dev runs via npm start) ────\n# docs/infrastructure/INSTALL-ARCHITECTURE.md §Module-shape: the canonical\n# module library at src/scripts/lib/install-common.sh defines\n# mod_submodules_init + mod_docker_wsl_integration + log/sudo primitives.\n# Carl and Dev call the SAME functions so there's no drift.\nif [ ! -f \"src/scripts/lib/install-common.sh\" ]; then\n fail \"Canonical install library missing at src/scripts/lib/install-common.sh — incomplete clone? Try: rm -rf $INSTALL_DIR && re-run this installer.\"\nfi\n\n# shellcheck source=src/scripts/lib/install-common.sh\nsource \"src/scripts/lib/install-common.sh\"\n\nmod_submodules_init\nmod_docker_wsl_integration\n\n# Real daemon check. On Linux this verifies Docker Engine is up (after the\n# WSL integration module had a chance to fix it on Windows/WSL2 hosts);\n# on Mac it verifies `podman machine start` above actually connected.\nif ! $CONTAINER_CMD info &>/dev/null 2>&1; then\n case \"$OS\" in\n Darwin) fail \"Podman machine not reachable. Run: podman machine start — then re-run this installer.\" ;;\n *) fail \"Docker daemon not reachable. Start Docker Desktop / Rancher Desktop and re-run.\" ;;\n esac\nfi\nok \"$CONTAINER_CMD $($CONTAINER_CMD version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\nok \"Source: $INSTALL_DIR\"\n\n# ── 3b. Install continuum command (modular, headless-safe) ─\n# Was an inline `sudo cp` that crashed on \"no TTY for password\" when the\n# install ran headless (curl|bash without -t, BigMama SSH dry-run, CI).\n# Now goes through mod_continuum_bin_link which routes to a user-space\n# fallback (~/.local/bin) when sudo would prompt without a TTY.\nmod_continuum_bin_link \"$INSTALL_DIR/bin/continuum\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pick compose files + profile ───────────────────────\n# Base file is always loaded. On GPU hosts, layer docker-compose.gpu.yml\n# so continuum-core picks up the cuda image override (otherwise compose\n# silently uses the CPU image and inference falls back to CPU). The same\n# -f set MUST be passed to both `pull` and `up`, or pull grabs base\n# images while up tries to use override-named images that aren't local.\nCOMPOSE_FILES=\"-f docker-compose.yml\"\nCOMPOSE_ARGS=\"\"\nif [[ \"$OS\" == \"Darwin\" ]]; then\n # Mac path — the docker-compose.mac.yml override sets continuum-core's\n # replicas to 0 so support services boot in containers but continuum-core\n # stays off. We run continuum-core NATIVELY on Mac (via `npm start` below)\n # so Candle embeddings, Bevy headless render, vision processing, and audio\n # MPS paths all get real Metal. LLM inference routes to Docker Model\n # Runner's vllm-metal backend — also host-native, no container GPU tax.\n if [ -f \"docker-compose.mac.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.mac.yml\"\n else\n warn \"docker-compose.mac.yml missing — Mac detected but override won't apply. Without it, docker compose would try to run continuum-core in a container, which on Mac means CPU-only for Candle/Bevy/vision.\"\n fail \"Fix: ensure you cloned with repository integrity — the Mac override file is part of the PR891 install architecture.\"\n fi\nelif [[ \"$HAS_GPU\" == \"true\" ]]; then\n if [ -f \"docker-compose.gpu.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.gpu.yml\"\n else\n warn \"docker-compose.gpu.yml missing — GPU detected but cuda override won't apply. Continuing on CPU images.\"\n fi\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\n# ── 7. Pull support-service images ─────────────────────────\n# Image tag resolution: compose files honor ${CONTINUUM_IMAGE_TAG:-latest}.\n# Main-branch installs (Carl's default) use :latest. Reviewers validating\n# a PR before merge can pin the PR's staged image set:\n# CONTINUUM_IMAGE_TAG=pr-891 curl -fsSL install.sh | bash\n# CI tags every PR build with pr- (see .github/workflows/docker-images.yml).\n# Merging to main promotes that image set to :latest, so main and :latest\n# are always in sync by construction.\n#\n# On Mac: `continuum-core` is not pulled (replicas=0 in docker-compose.mac.yml);\n# only support services (postgres, node-server, widget-server, livekit-bridge,\n# model-init) are pulled. continuum-core runs natively from `npm start` below.\ninfo \"Pulling container images (tag: ${CONTINUUM_IMAGE_TAG:-latest})...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 8. Start support services ──────────────────────────────\n# Inverse of parallel-start.sh's cross-mode detection: if native Dev-mode\n# processes (continuum-core-server, tsx orchestrator) are running, docker\n# compose up will collide on ports 9001/9100/7880-82/9003/5432. Warn so\n# the user can stop them before starting the stack.\nif pgrep -x 'continuum-core-server' >/dev/null 2>&1 \\\n || pgrep -f 'tsx.*scripts/launch-active-example' >/dev/null 2>&1; then\n warn \"Native Dev-mode continuum processes are running — they'll collide with the docker stack on ports.\"\n warn \"Run 'cd src && npm stop' to stop BOTH native and any running docker stack (idempotent).\"\n warn \"Continuing — expect bind errors below if they persist.\"\nfi\ninfo \"Starting support services...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS up -d\n\n# ── 8b. Start continuum-core natively on Mac ───────────────\n# Mac runs continuum-core as a native host process so it can link Metal\n# directly. `npm start` drives the full build (cargo build --release\n# --features=metal + TS compile) and launches the server daemonized.\nif [[ \"$OS\" == \"Darwin\" ]]; then\n info \"Building + launching native continuum-core-server (Metal-enabled)...\"\n info \" First run: cargo build takes 5-15 min. Subsequent runs: incremental.\"\n\n # No DATABASE_URL configured by default. Rust's data module defaults to\n # SQLite at ~/.continuum/database/main.db — zero-dep, portable, no\n # network topology gymnastics. For grid deployments (multi-writer over\n # Tailscale) users explicitly set DATABASE_URL in config.env AND run\n # `docker compose --profile postgres up`. All other callers (TS, tests,\n # jtag CLI) pass opaque handles; Rust resolves them to the configured\n # backend in modules/data.rs::resolve_handle.\n\n # CONTINUUM_CORE_TCP=9100 tells the native continuum-core-server to bind an\n # additional TCP listener alongside its Unix socket. Containerized\n # node-server (Option B Mac architecture) reaches the host-native\n # continuum-core via tcp://host.docker.internal:9100 because Unix sockets\n # don't traverse Docker Desktop's VM boundary on Mac. Native callers\n # (jtag CLI, continuum bin) keep using the Unix socket as before.\n #\n # CONTINUUM_CORE_BIND=0.0.0.0 is REQUIRED on Mac: Docker Desktop's\n # `host.docker.internal` resolves inside containers to the host's\n # docker-bridge IP (e.g. 192.168.65.254), NOT to 127.0.0.1. A loopback-\n # bound listener is unreachable from containers. 0.0.0.0 accepts on all\n # interfaces; macOS's application firewall blocks inbound LAN traffic\n # for unsigned dev binaries by default, so exposure stays local.\n export CONTINUUM_CORE_TCP=9100\n export CONTINUUM_CORE_BIND=0.0.0.0\n (cd \"$INSTALL_DIR/src\" && npm install --silent && npm start) || \\\n warn \"npm start failed — check logs at ~/.continuum/jtag/logs/system/continuum-core.log\"\nfi\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9003 &>/dev/null || curl -sf https://localhost:9003 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: $CONTAINER_CMD compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9003\"\nelse\n URL=\"http://localhost:9003\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9003\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "51d0f719558993e642b8d5232689c4ecbdd7f473": {"t": 1785747964.865695, "n": 31043, "h": "2bd1e942894ca562", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\n# Log primitives (info/ok/warn/fail/die) come from\n# src/scripts/lib/install-common.sh after clone. Until the repo is\n# cloned, use these minimal pre-clone versions; they'll be overridden\n# when we source the canonical library below.\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n# Alias so the canonical lib's `die` also works here and vice versa.\ndie() { fail \"$@\"; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Pre-clone bootstrap: git + minimal Docker presence check ────\n# We can't source the canonical module library yet (lives in the repo).\n# Just verify prerequisites so the clone can happen. Deeper checks live\n# in the canonical modules that run after the clone.\n\nif ! command -v git &>/dev/null; then\n case \"$OS\" in\n Linux) fail \"git required. Run: sudo apt-get install -y git (or equivalent), then re-run.\" ;;\n Darwin) fail \"git required. Run: brew install git (or install Xcode CLI tools), then re-run.\" ;;\n esac\nfi\n\n# Container runtime + inference setup.\n#\n# Linux: Docker Engine + continuum-core runs containerized (with cuda/vulkan\n# GPU passthrough via /dev/dri or runtime:nvidia). Everything in containers.\n#\n# Mac: Docker Desktop for support services ONLY. continuum-core runs NATIVELY\n# on the host to access Metal for Candle embeddings, Bevy headless avatar\n# render, vision processing, and audio MPS paths — Apple's hypervisor exposes\n# no GPU to containers (Docker themselves confirmed in Feb 2026), so anything\n# Metal-needing must be on the host. LLM inference routes to Docker Model\n# Runner's vllm-metal backend, which also runs native on the host — Docker\n# Desktop manages the process but the compute happens on Apple Silicon directly.\n#\n# CONTAINER_CMD is used for every `compose` / `info` call. On Mac that\n# handles support services; continuum-core-server is launched separately\n# as a native host process via `npm start`.\ncase \"$OS\" in\n Linux)\n if ! command -v docker &>/dev/null; then\n info \"Docker not found — installing via get.docker.com…\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n fi\n CONTAINER_CMD=docker\n ;;\n Darwin)\n if ! command -v docker &>/dev/null; then\n fail \"Docker Desktop required on Mac.\n\n 1. Download: https://docker.com/products/docker-desktop (4.62+ for Model Runner)\n 2. Install the .dmg, then launch Docker Desktop from Launchpad\n 3. When prompted, grant Admin password for the vmnetd privileged helper\n (one-time macOS permission for container networking — standard Docker setup)\n 4. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 5. Re-run this install script\n\"\n fi\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop is installed but not running.\n\n 1. Launch Docker Desktop from Launchpad (or Applications)\n 2. If this is your first launch, macOS will prompt for Admin password for the\n vmnetd privileged helper — click 'Allow' and enter your password\n 3. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 4. Re-run this install script\n\n (Scripted \\`open -a Docker\\` can't satisfy the macOS privileged-helper prompt —\n that's why this script asks you to launch Docker Desktop manually once.)\n\"\n fi\n # ── Docker Desktop VM memory (Mac Option B — continuum-core NATIVE) ─────\n # The previous 80%-of-RAM target crashed Docker Desktop mid-run on 32GB\n # M1 during matrix testing (FlashGordon 2026-04-16): Docker VM at 25.6GB\n # + native continuum-core at ~11GB RSS + macOS overhead ~6GB ≈ 43GB on a\n # 32GB physical box → heavy swap → Docker daemon died, DMR endpoint\n # disappeared, Helper AI fell back to Candle (5x slower) and never\n # produced a reply within the chat gate. Real-world blocker on the\n # primary-audience hardware.\n #\n # Mac Option B has THREE concurrent memory tenants, not two:\n # (a) native continuum-core ~12GB budget (Qwen 4B Q4 + KV + Candle\n # embeddings + Bevy render + vision +\n # audio + working set)\n # (b) Docker Desktop VM hosts postgres*, node-server, widget-\n # server, livekit-bridge, model-init.\n # With SQLite default (postgres opt-in),\n # the active containers need ~6-10GB.\n # (c) macOS itself kernel, window server, user apps ~6GB\n #\n # So Mac Option B target: PHYS - NATIVE_BUDGET(12) - MACOS_OVERHEAD(6)\n # = PHYS - 18GB headroom reserve. Floor at 10GB (below that, containers\n # don't fit; Option B isn't viable on that hardware).\n #\n # Physical memory sizing (Option B honest mins, not aspirational):\n # 32GB → 14GB Docker VM (comfortable)\n # 24GB → 6GB Docker VM (below floor → refuse)\n # Below 24GB → refuse install (can't fit all three tenants).\n PHYS_BYTES=$(sysctl -n hw.memsize)\n PHYS_MIB=$((PHYS_BYTES / 1048576))\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # Reserve headroom for native continuum-core (12GB) + macOS (6GB).\n NATIVE_RESERVE_MIB=$((12 * 1024))\n MACOS_RESERVE_MIB=$((6 * 1024))\n HEADROOM_MIB=$((NATIVE_RESERVE_MIB + MACOS_RESERVE_MIB))\n DOCKER_FLOOR_MIB=$((10 * 1024))\n\n if [[ \"$PHYS_MIB\" -lt $((HEADROOM_MIB + DOCKER_FLOOR_MIB)) ]]; then\n fail \"This Mac has ${PHYS_GB}GB physical RAM. Mac Option B (continuum-core native + Docker Desktop for support services) needs at least $(( (HEADROOM_MIB + DOCKER_FLOOR_MIB) / 1024 ))GB: ~12GB for native continuum-core (Qwen 4B + Bevy + vision + audio), ~6GB for macOS itself, and a ${DOCKER_FLOOR_MIB}MiB floor for the Docker VM. Below that, Docker Desktop crashes under combined memory pressure (verified on a 32GB box with the old 80%-target formula). Get a 32GB+ M-series for the primary audience experience.\"\n fi\n\n TARGET_MIB=$((PHYS_MIB - HEADROOM_MIB))\n if [[ \"$TARGET_MIB\" -lt \"$DOCKER_FLOOR_MIB\" ]]; then\n TARGET_MIB=$DOCKER_FLOOR_MIB\n fi\n\n CURRENT_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n SETTINGS_FILE=\"$HOME/Library/Group Containers/group.com.docker/settings-store.json\"\n # Bump if current is substantially below target (>10% gap — don't thrash\n # restarts over rounding noise).\n if [[ -f \"$SETTINGS_FILE\" ]] && [[ -n \"$CURRENT_MIB\" ]] && [[ \"$CURRENT_MIB\" -lt \"$((TARGET_MIB * 90 / 100))\" ]]; then\n info \"Docker Desktop VM memory is ${CURRENT_MIB}MiB; bumping to ${TARGET_MIB}MiB (80% of ${PHYS_GB}GB host RAM, 16GB floor) for Continuum's inference + sensory workload…\"\n python3 - </dev/null 2>&1 || true\n # Wait for daemon to come back\n for _ in $(seq 1 30); do\n if docker info &>/dev/null 2>&1; then break; fi\n sleep 4\n done\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop didn't come back after memory-limit restart. Launch it manually from Launchpad.\"\n fi\n NEW_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n ok \"Docker Desktop VM memory now ${NEW_MIB}MiB (target ${TARGET_MIB}MiB)\"\n elif [[ -n \"$CURRENT_MIB\" ]]; then\n ok \"Docker Desktop VM memory already ${CURRENT_MIB}MiB (≥ ${TARGET_MIB}MiB target)\"\n fi\n\n # Docker Model Runner provides host-native vllm-metal for LLM inference.\n # Ships with Docker Desktop 4.62+. If `docker model` isn't available the\n # user's Docker Desktop is too old.\n if ! docker model --help &>/dev/null 2>&1; then\n fail \"Docker Model Runner not available (needs Docker Desktop 4.62+).\n\n 1. Open Docker Desktop → Settings → Software Updates → Check for updates\n 2. Install the update (restart Docker Desktop if prompted)\n 3. Re-run this install script\n\"\n fi\n # Verify the vllm runner is registered. On Docker DESKTOP (Mac), the\n # runners are bundled — Docker Desktop installs them automatically when\n # Model Runner is enabled. There's no /usr/local/cli-plugins step\n # (that's the Docker ENGINE / Linux path; `docker model install-runner\n # --help` says \"Docker Engine only\"). The earlier mkdir + install-runner\n # block was misapplied Linux logic on Mac, and forced a sudo prompt\n # for a directory Docker Desktop never reads from. Caught when CONTINUUM_\n # DEPS_ONLY=1 from parallel-start.sh tripped the prompt non-interactively\n # on every `npm start` (2026-04-16).\n #\n # If vllm shows \"Not Installed\", the user needs to enable it in Docker\n # Desktop → Settings → Beta features → Model Runner → install backends.\n # No CLI command can do this on Desktop, so we point at the GUI.\n if ! docker model status 2>/dev/null | awk '/^vllm[[:space:]]+Running/{found=1} END{exit !found}'; then\n warn \"vllm-metal backend not registered with Docker Model Runner.\n Open Docker Desktop → Settings → Features in development → Model Runner\n → ensure 'Enable Docker Model Runner' is on → install the vllm backend.\n Continuum will fall back to llama.cpp until vllm is enabled (~5x slower\n on M-series for some models).\"\n fi\n # Enable Model Runner's host-side TCP endpoint on port 12434. Without this,\n # continuum-core (running natively on the Mac host) can't reach the OpenAI-\n # compatible API — the probe in ai_provider.rs fails, the\n # docker-model-runner adapter doesn't register, and Candle becomes the\n # default local provider. That's a 5x perf regression (~10 tok/s vs ~50\n # tok/s on M5). Caught during M5 validation 2026-04-16: I had to enable\n # this manually before the adapter probe succeeded. Make it part of the\n # install so Carl never has to discover the toggle.\n #\n # `docker desktop enable model-runner --tcp=12434 --cors=all` is idempotent\n # — safe to re-run on every install. CORS=all is fine because the endpoint\n # binds 127.0.0.1 only (not exposed externally).\n if ! curl -fsS --max-time 1 http://localhost:12434/engines/llama.cpp/v1/models >/dev/null 2>&1; then\n info \"Enabling Docker Model Runner TCP endpoint on localhost:12434…\"\n docker desktop enable model-runner --tcp=12434 --cors=all 2>&1 | tail -3 || \\\n warn \"Could not enable Model Runner TCP — continuum-core will fall back to Candle (slower). Enable manually: docker desktop enable model-runner --tcp=12434 --cors=all\"\n fi\n # Rust toolchain — continuum-core-server is built natively on Mac (not\n # containerized) so it can link Metal for Candle embeddings, Bevy, vision,\n # and audio MPS paths. Build happens during `npm start` at end of install.\n if ! command -v cargo &>/dev/null; then\n info \"Rust not found — installing via rustup (needed for native continuum-core build)…\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable\n # shellcheck disable=SC1091\n [ -f \"$HOME/.cargo/env\" ] && source \"$HOME/.cargo/env\"\n fi\n # Node toolchain — required to build TypeScript side + run launcher.\n if ! command -v node &>/dev/null; then\n if command -v brew &>/dev/null; then\n info \"Node not found — installing via Homebrew…\"\n brew install node\n else\n fail \"Node.js required. Install from https://nodejs.org or via Homebrew, then re-run.\"\n fi\n fi\n CONTAINER_CMD=docker\n ;;\nesac\n\n# ── GPU detection + persona model pull (shared by Carl + Dev) ──────\n# Uses the centralized ic_detect_hardware / ic_decide_gpu_path from\n# install-common.sh so both install.sh paths use the SAME detection.\n# After this block: IC_GPU_PATH tells us which inference backend to\n# verify, and the default persona model is pulled into DMR (if DMR path).\nif type ic_detect_hardware &>/dev/null; then\n ic_detect_hardware\n ic_decide_gpu_path\n ic_describe_hardware\n\n # Pull default persona model into DMR so Carl's first chat is instant.\n # Only for DMR paths — Vulkan path loads models differently (local GGUF).\n PERSONA_MODEL=\"hf.co/continuum-ai/qwen3.5-4b-code-forged-GGUF\"\n case \"$IC_GPU_PATH\" in\n dmr-*)\n if ! docker model ls 2>/dev/null | grep -q \"qwen3.5-4b-code-forged\"; then\n info \"Pulling default persona model into Docker Model Runner (~2.7GB, first install only)...\"\n docker model pull \"$PERSONA_MODEL\" || warn \"Model pull failed — chat will error until model is available. Retry: docker model pull $PERSONA_MODEL\"\n else\n ok \"Persona model already in DMR: $PERSONA_MODEL\"\n fi\n # Cap llama-server's per-slot KV cache reservation, sized to actual\n # physical RAM. Without this cap each slot reserves the full model\n # context (262144 tokens for Qwen3.5), ballooning\n # com.docker.llama-server to 11+ GB resident on a single active slot\n # — observed live tonight on M5.\n #\n # Per-slot KV cost: ~16 KB per token at FP16 for Qwen3.5-4B\n # (32 layers × 256 attn-dim × 2 bytes × 2 tensors). Tier the cap so\n # 4 concurrent personas keep KV ≤10% of physical RAM:\n #\n # Physical RAM ctx-size 4-slot worst case\n # 8GB 4096 ~256 MB\n # 16GB 8192 ~512 MB\n # 24GB 16384 ~1 GB\n # 32GB 32768 ~2 GB\n # 48GB+ 65536 ~4 GB\n #\n # Specialized recipes (codereview, research) can opt up via per-recipe\n # overrides — Phase 9 in docs/architecture/RESOURCE-ARCHITECTURE.md.\n if [[ -n \"${PHYS_MIB:-}\" ]]; then\n if [[ \"$PHYS_MIB\" -ge $((48 * 1024)) ]]; then KV_CTX_SIZE=65536\n elif [[ \"$PHYS_MIB\" -ge $((32 * 1024)) ]]; then KV_CTX_SIZE=32768\n elif [[ \"$PHYS_MIB\" -ge $((24 * 1024)) ]]; then KV_CTX_SIZE=16384\n elif [[ \"$PHYS_MIB\" -ge $((16 * 1024)) ]]; then KV_CTX_SIZE=8192\n else KV_CTX_SIZE=4096\n fi\n else\n # PHYS_MIB unset (shouldn't happen on Mac/Linux paths but be safe)\n KV_CTX_SIZE=8192\n fi\n if docker model configure show >/dev/null 2>&1; then\n if docker model configure --context-size \"$KV_CTX_SIZE\" --keep-alive 5m \"$PERSONA_MODEL\" 2>/dev/null; then\n ok \"DMR context-size capped at ${KV_CTX_SIZE} + keep-alive 5m for $PERSONA_MODEL (sized to ${PHYS_GB:-?}GB physical RAM; kills the per-slot KV bloat)\"\n else\n warn \"Could not apply DMR context-size cap. Older Docker Desktop? Upgrade to 4.62+ for 'docker model configure'. Falling back to model default (high memory use).\"\n fi\n else\n warn \"'docker model configure' not available — Docker Desktop may be older than 4.62. Per-slot KV cache will use model default (~262k tokens, high RAM).\"\n fi\n # Install vLLM MLX backend on Mac for 3x faster Qwen3.5 DeltaNet inference.\n # llama.cpp's Metal shaders for Gated DeltaNet are poorly optimized (~11 tok/s);\n # vllm-metal uses native MLX kernels (~33+ tok/s). Requires Docker Desktop 4.62+.\n if [[ \"$OS\" == \"Darwin\" ]]; then\n if docker model runner ls 2>/dev/null | grep -q \"vllm\"; then\n ok \"vLLM MLX backend already installed\"\n else\n info \"Installing vLLM MLX backend for native Apple Silicon inference...\"\n if docker model install-runner --backend vllm 2>/dev/null; then\n ok \"vLLM MLX backend installed — Qwen3.5 DeltaNet will use native MLX kernels\"\n # Pull MLX-format Qwen3.5-4B for vllm-metal routing.\n # DMR auto-routes MLX models to vllm-metal when installed.\n MLX_MODEL=\"hf.co/mlx-community/Qwen3.5-4B-MLX-4bit\"\n if ! docker model ls 2>/dev/null | grep -q \"Qwen3.5-4B-MLX\"; then\n info \"Pulling MLX-format Qwen3.5-4B (~2.5GB, for 3x faster inference)...\"\n docker model pull \"$MLX_MODEL\" \\\n || warn \"MLX model pull failed. GGUF via llama.cpp will be used instead.\"\n fi\n else\n warn \"vLLM install failed (requires Docker Desktop 4.62+). llama.cpp Metal will be used.\"\n fi\n fi\n fi\n ;;\n llama-vulkan)\n ok \"Vulkan GPU path — model download handled by continuum-core at first inference\"\n ;;\n unsupported)\n warn \"No supported GPU detected. Local chat will error until a GPU adapter is available.\"\n ;;\n esac\nfi\n\n# ── Per-service memory caps — auto-calculated from host RAM ────────\n# Joel's directive: don't ask users to set mem limits; auto-calc from host.\n# Don't paper over OOMs with undersized limits; size containers for the\n# actual mission. Mission per-service budgets:\n#\n# continuum-core (Linux container; on Mac it runs NATIVE and this cap\n# is informational / unused because docker-compose.mac.yml sets\n# replicas=0): needs to hold 4-8B param Qwen at Q4 (~4GB) + KV cache\n# for 5 concurrent personas (~2GB) + embeddings + Bevy + vision +\n# audio. Budget = host - 10GB (reserve for OS + Docker VM overhead\n# + support services).\n#\n# livekit-bridge: native WebRTC encode/decode buffers, multiple\n# streams. Budget scales with host — roughly host/8.\n#\n# node-server: TS orchestrator + IPC buffers + RAG state. Budget\n# same as livekit-bridge.\n#\n# model-init: one-time downloader, fits in 2GB.\n#\n# widget-server: static + light TS, 1GB.\n#\n# postgres: our dataset is small, 512MB (already set in compose).\n# livekit server: 256m (already set in compose).\n#\n# Physical RAM is whichever host this runs on.\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n # Mac branch set PHYS_MIB already. Linux sets it here from /proc/meminfo.\n :\nelif [[ -f /proc/meminfo ]]; then\n PHYS_MIB=$(awk '/^MemTotal:/{print int($2/1024)}' /proc/meminfo)\nfi\n\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # continuum-core cap: (host - 10GB) on ≥32GB machines, (host - 8GB)\n # on 20-31GB machines. Floor at 10GB.\n if [[ $PHYS_GB -ge 32 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 10))g\n elif [[ $PHYS_GB -ge 20 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 8))g\n else\n CONTINUUM_CORE_MEM=10g\n fi\n\n # Scale livekit-bridge + node-server with host. Floor 2GB each.\n # 16GB host → 2g, 32GB → 4g, 64GB → 8g.\n SCALED=$((PHYS_GB / 8))\n [[ $SCALED -lt 2 ]] && SCALED=2\n LIVEKIT_BRIDGE_MEM=${SCALED}g\n NODE_SERVER_MEM=${SCALED}g\n\n # Static + small — these don't need to scale.\n MODEL_INIT_MEM=2g\n WIDGET_SERVER_MEM=1g\n\n export CONTINUUM_CORE_MEM LIVEKIT_BRIDGE_MEM NODE_SERVER_MEM MODEL_INIT_MEM WIDGET_SERVER_MEM\n\n info \"Memory caps (${PHYS_GB}GB host): continuum-core=${CONTINUUM_CORE_MEM}, livekit-bridge=${LIVEKIT_BRIDGE_MEM}, node-server=${NODE_SERVER_MEM}, model-init=${MODEL_INIT_MEM}, widget-server=${WIDGET_SERVER_MEM}\"\nfi\n\n# (OS-branch case/esac above handled Linux/Darwin and set CONTAINER_CMD.)\ncase \"$OS\" in\n Linux|Darwin) : ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\n# ── 4. Shared modules (same code that Dev runs via npm start) ────\n# docs/infrastructure/INSTALL-ARCHITECTURE.md §Module-shape: the canonical\n# module library at src/scripts/lib/install-common.sh defines\n# mod_submodules_init + mod_docker_wsl_integration + log/sudo primitives.\n# Carl and Dev call the SAME functions so there's no drift.\nif [ ! -f \"src/scripts/lib/install-common.sh\" ]; then\n fail \"Canonical install library missing at src/scripts/lib/install-common.sh — incomplete clone? Try: rm -rf $INSTALL_DIR && re-run this installer.\"\nfi\n\n# shellcheck source=src/scripts/lib/install-common.sh\nsource \"src/scripts/lib/install-common.sh\"\n\nmod_submodules_init\nmod_docker_wsl_integration\n\n# Real daemon check. On Linux this verifies Docker Engine is up (after the\n# WSL integration module had a chance to fix it on Windows/WSL2 hosts);\n# on Mac it verifies `podman machine start` above actually connected.\nif ! $CONTAINER_CMD info &>/dev/null 2>&1; then\n case \"$OS\" in\n Darwin) fail \"Podman machine not reachable. Run: podman machine start — then re-run this installer.\" ;;\n *) fail \"Docker daemon not reachable. Start Docker Desktop / Rancher Desktop and re-run.\" ;;\n esac\nfi\nok \"$CONTAINER_CMD $($CONTAINER_CMD version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\nok \"Source: $INSTALL_DIR\"\n\n# ── 3b. Install continuum command (modular, headless-safe) ─\n# Was an inline `sudo cp` that crashed on \"no TTY for password\" when the\n# install ran headless (curl|bash without -t, BigMama SSH dry-run, CI).\n# Now goes through mod_continuum_bin_link which routes to a user-space\n# fallback (~/.local/bin) when sudo would prompt without a TTY.\nmod_continuum_bin_link \"$INSTALL_DIR/bin/continuum\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pick compose files + profile ───────────────────────\n# Base file is always loaded. On GPU hosts, layer docker-compose.gpu.yml\n# so continuum-core picks up the cuda image override (otherwise compose\n# silently uses the CPU image and inference falls back to CPU). The same\n# -f set MUST be passed to both `pull` and `up`, or pull grabs base\n# images while up tries to use override-named images that aren't local.\nCOMPOSE_FILES=\"-f docker-compose.yml\"\nCOMPOSE_ARGS=\"\"\nif [[ \"$OS\" == \"Darwin\" ]]; then\n # Mac path — the docker-compose.mac.yml override sets continuum-core's\n # replicas to 0 so support services boot in containers but continuum-core\n # stays off. We run continuum-core NATIVELY on Mac (via `npm start` below)\n # so Candle embeddings, Bevy headless render, vision processing, and audio\n # MPS paths all get real Metal. LLM inference routes to Docker Model\n # Runner's vllm-metal backend — also host-native, no container GPU tax.\n if [ -f \"docker-compose.mac.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.mac.yml\"\n else\n warn \"docker-compose.mac.yml missing — Mac detected but override won't apply. Without it, docker compose would try to run continuum-core in a container, which on Mac means CPU-only for Candle/Bevy/vision.\"\n fail \"Fix: ensure you cloned with repository integrity — the Mac override file is part of the PR891 install architecture.\"\n fi\nelif [[ \"$HAS_GPU\" == \"true\" ]]; then\n if [ -f \"docker-compose.gpu.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.gpu.yml\"\n else\n warn \"docker-compose.gpu.yml missing — GPU detected but cuda override won't apply. Continuing on CPU images.\"\n fi\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\n# ── 7. Pull support-service images ─────────────────────────\n# Image tag resolution: compose files honor ${CONTINUUM_IMAGE_TAG:-latest}.\n# Main-branch installs (Carl's default) use :latest. Reviewers validating\n# a PR before merge can pin the PR's staged image set:\n# CONTINUUM_IMAGE_TAG=pr-891 curl -fsSL install.sh | bash\n# CI tags every PR build with pr- (see .github/workflows/docker-images.yml).\n# Merging to main promotes that image set to :latest, so main and :latest\n# are always in sync by construction.\n#\n# On Mac: `continuum-core` is not pulled (replicas=0 in docker-compose.mac.yml);\n# only support services (postgres, node-server, widget-server, livekit-bridge,\n# model-init) are pulled. continuum-core runs natively from `npm start` below.\ninfo \"Pulling container images (tag: ${CONTINUUM_IMAGE_TAG:-latest})...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 8. Start support services ──────────────────────────────\n# Inverse of parallel-start.sh's cross-mode detection: if native Dev-mode\n# processes (continuum-core-server, tsx orchestrator) are running, docker\n# compose up will collide on ports 9001/9100/7880-82/9003/5432. Warn so\n# the user can stop them before starting the stack.\nif pgrep -x 'continuum-core-server' >/dev/null 2>&1 \\\n || pgrep -f 'tsx.*scripts/launch-active-example' >/dev/null 2>&1; then\n warn \"Native Dev-mode continuum processes are running — they'll collide with the docker stack on ports.\"\n warn \"Run 'cd src && npm stop' to stop BOTH native and any running docker stack (idempotent).\"\n warn \"Continuing — expect bind errors below if they persist.\"\nfi\ninfo \"Starting support services...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS up -d\n\n# ── 8b. Start continuum-core natively on Mac ───────────────\n# Mac runs continuum-core as a native host process so it can link Metal\n# directly. `npm start` drives the full build (cargo build --release\n# --features=metal + TS compile) and launches the server daemonized.\nif [[ \"$OS\" == \"Darwin\" ]]; then\n info \"Building + launching native continuum-core-server (Metal-enabled)...\"\n info \" First run: cargo build takes 5-15 min. Subsequent runs: incremental.\"\n\n # No DATABASE_URL configured by default. Rust's data module defaults to\n # SQLite at ~/.continuum/database/main.db — zero-dep, portable, no\n # network topology gymnastics. For grid deployments (multi-writer over\n # Tailscale) users explicitly set DATABASE_URL in config.env AND run\n # `docker compose --profile postgres up`. All other callers (TS, tests,\n # jtag CLI) pass opaque handles; Rust resolves them to the configured\n # backend in modules/data.rs::resolve_handle.\n\n # CONTINUUM_CORE_TCP=9100 tells the native continuum-core-server to bind an\n # additional TCP listener alongside its Unix socket. Containerized\n # node-server (Option B Mac architecture) reaches the host-native\n # continuum-core via tcp://host.docker.internal:9100 because Unix sockets\n # don't traverse Docker Desktop's VM boundary on Mac. Native callers\n # (jtag CLI, continuum bin) keep using the Unix socket as before.\n #\n # CONTINUUM_CORE_BIND=0.0.0.0 is REQUIRED on Mac: Docker Desktop's\n # `host.docker.internal` resolves inside containers to the host's\n # docker-bridge IP (e.g. 192.168.65.254), NOT to 127.0.0.1. A loopback-\n # bound listener is unreachable from containers. 0.0.0.0 accepts on all\n # interfaces; macOS's application firewall blocks inbound LAN traffic\n # for unsigned dev binaries by default, so exposure stays local.\n export CONTINUUM_CORE_TCP=9100\n export CONTINUUM_CORE_BIND=0.0.0.0\n (cd \"$INSTALL_DIR/src\" && npm install --silent && npm start) || \\\n warn \"npm start failed — check logs at ~/.continuum/jtag/logs/system/continuum-core.log\"\nfi\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9003 &>/dev/null || curl -sf https://localhost:9003 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: $CONTAINER_CMD compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9003\"\nelse\n URL=\"http://localhost:9003\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9003\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "ae5c356800d02117222565c622ff167aaad576a7": {"t": 1785747966.1531105, "n": 28829, "h": "35f14503b16f9c52", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\n# Log primitives (info/ok/warn/fail/die) come from\n# src/scripts/lib/install-common.sh after clone. Until the repo is\n# cloned, use these minimal pre-clone versions; they'll be overridden\n# when we source the canonical library below.\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n# Alias so the canonical lib's `die` also works here and vice versa.\ndie() { fail \"$@\"; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Pre-clone bootstrap: git + minimal Docker presence check ────\n# We can't source the canonical module library yet (lives in the repo).\n# Just verify prerequisites so the clone can happen. Deeper checks live\n# in the canonical modules that run after the clone.\n\nif ! command -v git &>/dev/null; then\n case \"$OS\" in\n Linux) fail \"git required. Run: sudo apt-get install -y git (or equivalent), then re-run.\" ;;\n Darwin) fail \"git required. Run: brew install git (or install Xcode CLI tools), then re-run.\" ;;\n esac\nfi\n\n# Container runtime + inference setup.\n#\n# Linux: Docker Engine + continuum-core runs containerized (with cuda/vulkan\n# GPU passthrough via /dev/dri or runtime:nvidia). Everything in containers.\n#\n# Mac: Docker Desktop for support services ONLY. continuum-core runs NATIVELY\n# on the host to access Metal for Candle embeddings, Bevy headless avatar\n# render, vision processing, and audio MPS paths — Apple's hypervisor exposes\n# no GPU to containers (Docker themselves confirmed in Feb 2026), so anything\n# Metal-needing must be on the host. LLM inference routes to Docker Model\n# Runner's vllm-metal backend, which also runs native on the host — Docker\n# Desktop manages the process but the compute happens on Apple Silicon directly.\n#\n# CONTAINER_CMD is used for every `compose` / `info` call. On Mac that\n# handles support services; continuum-core-server is launched separately\n# as a native host process via `npm start`.\ncase \"$OS\" in\n Linux)\n if ! command -v docker &>/dev/null; then\n info \"Docker not found — installing via get.docker.com…\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n fi\n CONTAINER_CMD=docker\n ;;\n Darwin)\n if ! command -v docker &>/dev/null; then\n fail \"Docker Desktop required on Mac.\n\n 1. Download: https://docker.com/products/docker-desktop (4.62+ for Model Runner)\n 2. Install the .dmg, then launch Docker Desktop from Launchpad\n 3. When prompted, grant Admin password for the vmnetd privileged helper\n (one-time macOS permission for container networking — standard Docker setup)\n 4. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 5. Re-run this install script\n\"\n fi\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop is installed but not running.\n\n 1. Launch Docker Desktop from Launchpad (or Applications)\n 2. If this is your first launch, macOS will prompt for Admin password for the\n vmnetd privileged helper — click 'Allow' and enter your password\n 3. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 4. Re-run this install script\n\n (Scripted \\`open -a Docker\\` can't satisfy the macOS privileged-helper prompt —\n that's why this script asks you to launch Docker Desktop manually once.)\n\"\n fi\n # ── Docker Desktop VM memory (Mac Option B — continuum-core NATIVE) ─────\n # The previous 80%-of-RAM target crashed Docker Desktop mid-run on 32GB\n # M1 during matrix testing (FlashGordon 2026-04-16): Docker VM at 25.6GB\n # + native continuum-core at ~11GB RSS + macOS overhead ~6GB ≈ 43GB on a\n # 32GB physical box → heavy swap → Docker daemon died, DMR endpoint\n # disappeared, Helper AI fell back to Candle (5x slower) and never\n # produced a reply within the chat gate. Real-world blocker on the\n # primary-audience hardware.\n #\n # Mac Option B has THREE concurrent memory tenants, not two:\n # (a) native continuum-core ~12GB budget (Qwen 4B Q4 + KV + Candle\n # embeddings + Bevy render + vision +\n # audio + working set)\n # (b) Docker Desktop VM hosts postgres*, node-server, widget-\n # server, livekit-bridge, model-init.\n # With SQLite default (postgres opt-in),\n # the active containers need ~6-10GB.\n # (c) macOS itself kernel, window server, user apps ~6GB\n #\n # So Mac Option B target: PHYS - NATIVE_BUDGET(12) - MACOS_OVERHEAD(6)\n # = PHYS - 18GB headroom reserve. Floor at 10GB (below that, containers\n # don't fit; Option B isn't viable on that hardware).\n #\n # Physical memory sizing (Option B honest mins, not aspirational):\n # 32GB → 14GB Docker VM (comfortable)\n # 24GB → 6GB Docker VM (below floor → refuse)\n # Below 24GB → refuse install (can't fit all three tenants).\n PHYS_BYTES=$(sysctl -n hw.memsize)\n PHYS_MIB=$((PHYS_BYTES / 1048576))\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # Reserve headroom for native continuum-core (12GB) + macOS (6GB).\n NATIVE_RESERVE_MIB=$((12 * 1024))\n MACOS_RESERVE_MIB=$((6 * 1024))\n HEADROOM_MIB=$((NATIVE_RESERVE_MIB + MACOS_RESERVE_MIB))\n DOCKER_FLOOR_MIB=$((10 * 1024))\n\n if [[ \"$PHYS_MIB\" -lt $((HEADROOM_MIB + DOCKER_FLOOR_MIB)) ]]; then\n fail \"This Mac has ${PHYS_GB}GB physical RAM. Mac Option B (continuum-core native + Docker Desktop for support services) needs at least $(( (HEADROOM_MIB + DOCKER_FLOOR_MIB) / 1024 ))GB: ~12GB for native continuum-core (Qwen 4B + Bevy + vision + audio), ~6GB for macOS itself, and a ${DOCKER_FLOOR_MIB}MiB floor for the Docker VM. Below that, Docker Desktop crashes under combined memory pressure (verified on a 32GB box with the old 80%-target formula). Get a 32GB+ M-series for the primary audience experience.\"\n fi\n\n TARGET_MIB=$((PHYS_MIB - HEADROOM_MIB))\n if [[ \"$TARGET_MIB\" -lt \"$DOCKER_FLOOR_MIB\" ]]; then\n TARGET_MIB=$DOCKER_FLOOR_MIB\n fi\n\n CURRENT_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n SETTINGS_FILE=\"$HOME/Library/Group Containers/group.com.docker/settings-store.json\"\n # Bump if current is substantially below target (>10% gap — don't thrash\n # restarts over rounding noise).\n if [[ -f \"$SETTINGS_FILE\" ]] && [[ -n \"$CURRENT_MIB\" ]] && [[ \"$CURRENT_MIB\" -lt \"$((TARGET_MIB * 90 / 100))\" ]]; then\n info \"Docker Desktop VM memory is ${CURRENT_MIB}MiB; bumping to ${TARGET_MIB}MiB (80% of ${PHYS_GB}GB host RAM, 16GB floor) for Continuum's inference + sensory workload…\"\n python3 - </dev/null 2>&1 || true\n # Wait for daemon to come back\n for _ in $(seq 1 30); do\n if docker info &>/dev/null 2>&1; then break; fi\n sleep 4\n done\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop didn't come back after memory-limit restart. Launch it manually from Launchpad.\"\n fi\n NEW_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n ok \"Docker Desktop VM memory now ${NEW_MIB}MiB (target ${TARGET_MIB}MiB)\"\n elif [[ -n \"$CURRENT_MIB\" ]]; then\n ok \"Docker Desktop VM memory already ${CURRENT_MIB}MiB (≥ ${TARGET_MIB}MiB target)\"\n fi\n\n # Docker Model Runner provides host-native vllm-metal for LLM inference.\n # Ships with Docker Desktop 4.62+. If `docker model` isn't available the\n # user's Docker Desktop is too old.\n if ! docker model --help &>/dev/null 2>&1; then\n fail \"Docker Model Runner not available (needs Docker Desktop 4.62+).\n\n 1. Open Docker Desktop → Settings → Software Updates → Check for updates\n 2. Install the update (restart Docker Desktop if prompted)\n 3. Re-run this install script\n\"\n fi\n # Verify the vllm runner is registered. On Docker DESKTOP (Mac), the\n # runners are bundled — Docker Desktop installs them automatically when\n # Model Runner is enabled. There's no /usr/local/cli-plugins step\n # (that's the Docker ENGINE / Linux path; `docker model install-runner\n # --help` says \"Docker Engine only\"). The earlier mkdir + install-runner\n # block was misapplied Linux logic on Mac, and forced a sudo prompt\n # for a directory Docker Desktop never reads from. Caught when CONTINUUM_\n # DEPS_ONLY=1 from parallel-start.sh tripped the prompt non-interactively\n # on every `npm start` (2026-04-16).\n #\n # If vllm shows \"Not Installed\", the user needs to enable it in Docker\n # Desktop → Settings → Beta features → Model Runner → install backends.\n # No CLI command can do this on Desktop, so we point at the GUI.\n if ! docker model status 2>/dev/null | awk '/^vllm[[:space:]]+Running/{found=1} END{exit !found}'; then\n warn \"vllm-metal backend not registered with Docker Model Runner.\n Open Docker Desktop → Settings → Features in development → Model Runner\n → ensure 'Enable Docker Model Runner' is on → install the vllm backend.\n Continuum will fall back to llama.cpp until vllm is enabled (~5x slower\n on M-series for some models).\"\n fi\n # Enable Model Runner's host-side TCP endpoint on port 12434. Without this,\n # continuum-core (running natively on the Mac host) can't reach the OpenAI-\n # compatible API — the probe in ai_provider.rs fails, the\n # docker-model-runner adapter doesn't register, and Candle becomes the\n # default local provider. That's a 5x perf regression (~10 tok/s vs ~50\n # tok/s on M5). Caught during M5 validation 2026-04-16: I had to enable\n # this manually before the adapter probe succeeded. Make it part of the\n # install so Carl never has to discover the toggle.\n #\n # `docker desktop enable model-runner --tcp=12434 --cors=all` is idempotent\n # — safe to re-run on every install. CORS=all is fine because the endpoint\n # binds 127.0.0.1 only (not exposed externally).\n if ! curl -fsS --max-time 1 http://localhost:12434/engines/llama.cpp/v1/models >/dev/null 2>&1; then\n info \"Enabling Docker Model Runner TCP endpoint on localhost:12434…\"\n docker desktop enable model-runner --tcp=12434 --cors=all 2>&1 | tail -3 || \\\n warn \"Could not enable Model Runner TCP — continuum-core will fall back to Candle (slower). Enable manually: docker desktop enable model-runner --tcp=12434 --cors=all\"\n fi\n # Rust toolchain — continuum-core-server is built natively on Mac (not\n # containerized) so it can link Metal for Candle embeddings, Bevy, vision,\n # and audio MPS paths. Build happens during `npm start` at end of install.\n if ! command -v cargo &>/dev/null; then\n info \"Rust not found — installing via rustup (needed for native continuum-core build)…\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable\n # shellcheck disable=SC1091\n [ -f \"$HOME/.cargo/env\" ] && source \"$HOME/.cargo/env\"\n fi\n # Node toolchain — required to build TypeScript side + run launcher.\n if ! command -v node &>/dev/null; then\n if command -v brew &>/dev/null; then\n info \"Node not found — installing via Homebrew…\"\n brew install node\n else\n fail \"Node.js required. Install from https://nodejs.org or via Homebrew, then re-run.\"\n fi\n fi\n CONTAINER_CMD=docker\n ;;\nesac\n\n# ── GPU detection + persona model pull (shared by Carl + Dev) ──────\n# Uses the centralized ic_detect_hardware / ic_decide_gpu_path from\n# install-common.sh so both install.sh paths use the SAME detection.\n# After this block: IC_GPU_PATH tells us which inference backend to\n# verify, and the default persona model is pulled into DMR (if DMR path).\nif type ic_detect_hardware &>/dev/null; then\n ic_detect_hardware\n ic_decide_gpu_path\n ic_describe_hardware\n\n # Pull default persona model into DMR so Carl's first chat is instant.\n # Only for DMR paths — Vulkan path loads models differently (local GGUF).\n PERSONA_MODEL=\"hf.co/continuum-ai/qwen3.5-4b-code-forged-GGUF\"\n case \"$IC_GPU_PATH\" in\n dmr-*)\n if ! docker model ls 2>/dev/null | grep -q \"qwen3.5-4b-code-forged\"; then\n info \"Pulling default persona model into Docker Model Runner (~2.7GB, first install only)...\"\n docker model pull \"$PERSONA_MODEL\" || warn \"Model pull failed — chat will error until model is available. Retry: docker model pull $PERSONA_MODEL\"\n else\n ok \"Persona model already in DMR: $PERSONA_MODEL\"\n fi\n # Install vLLM MLX backend on Mac for 3x faster Qwen3.5 DeltaNet inference.\n # llama.cpp's Metal shaders for Gated DeltaNet are poorly optimized (~11 tok/s);\n # vllm-metal uses native MLX kernels (~33+ tok/s). Requires Docker Desktop 4.62+.\n if [[ \"$OS\" == \"Darwin\" ]]; then\n if docker model runner ls 2>/dev/null | grep -q \"vllm\"; then\n ok \"vLLM MLX backend already installed\"\n else\n info \"Installing vLLM MLX backend for native Apple Silicon inference...\"\n if docker model install-runner --backend vllm 2>/dev/null; then\n ok \"vLLM MLX backend installed — Qwen3.5 DeltaNet will use native MLX kernels\"\n # Pull MLX-format Qwen3.5-4B for vllm-metal routing.\n # DMR auto-routes MLX models to vllm-metal when installed.\n MLX_MODEL=\"hf.co/mlx-community/Qwen3.5-4B-MLX-4bit\"\n if ! docker model ls 2>/dev/null | grep -q \"Qwen3.5-4B-MLX\"; then\n info \"Pulling MLX-format Qwen3.5-4B (~2.5GB, for 3x faster inference)...\"\n docker model pull \"$MLX_MODEL\" \\\n || warn \"MLX model pull failed. GGUF via llama.cpp will be used instead.\"\n fi\n else\n warn \"vLLM install failed (requires Docker Desktop 4.62+). llama.cpp Metal will be used.\"\n fi\n fi\n fi\n ;;\n llama-vulkan)\n ok \"Vulkan GPU path — model download handled by continuum-core at first inference\"\n ;;\n unsupported)\n warn \"No supported GPU detected. Local chat will error until a GPU adapter is available.\"\n ;;\n esac\nfi\n\n# ── Per-service memory caps — auto-calculated from host RAM ────────\n# Joel's directive: don't ask users to set mem limits; auto-calc from host.\n# Don't paper over OOMs with undersized limits; size containers for the\n# actual mission. Mission per-service budgets:\n#\n# continuum-core (Linux container; on Mac it runs NATIVE and this cap\n# is informational / unused because docker-compose.mac.yml sets\n# replicas=0): needs to hold 4-8B param Qwen at Q4 (~4GB) + KV cache\n# for 5 concurrent personas (~2GB) + embeddings + Bevy + vision +\n# audio. Budget = host - 10GB (reserve for OS + Docker VM overhead\n# + support services).\n#\n# livekit-bridge: native WebRTC encode/decode buffers, multiple\n# streams. Budget scales with host — roughly host/8.\n#\n# node-server: TS orchestrator + IPC buffers + RAG state. Budget\n# same as livekit-bridge.\n#\n# model-init: one-time downloader, fits in 2GB.\n#\n# widget-server: static + light TS, 1GB.\n#\n# postgres: our dataset is small, 512MB (already set in compose).\n# livekit server: 256m (already set in compose).\n#\n# Physical RAM is whichever host this runs on.\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n # Mac branch set PHYS_MIB already. Linux sets it here from /proc/meminfo.\n :\nelif [[ -f /proc/meminfo ]]; then\n PHYS_MIB=$(awk '/^MemTotal:/{print int($2/1024)}' /proc/meminfo)\nfi\n\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # continuum-core cap: (host - 10GB) on ≥32GB machines, (host - 8GB)\n # on 20-31GB machines. Floor at 10GB.\n if [[ $PHYS_GB -ge 32 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 10))g\n elif [[ $PHYS_GB -ge 20 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 8))g\n else\n CONTINUUM_CORE_MEM=10g\n fi\n\n # Scale livekit-bridge + node-server with host. Floor 2GB each.\n # 16GB host → 2g, 32GB → 4g, 64GB → 8g.\n SCALED=$((PHYS_GB / 8))\n [[ $SCALED -lt 2 ]] && SCALED=2\n LIVEKIT_BRIDGE_MEM=${SCALED}g\n NODE_SERVER_MEM=${SCALED}g\n\n # Static + small — these don't need to scale.\n MODEL_INIT_MEM=2g\n WIDGET_SERVER_MEM=1g\n\n export CONTINUUM_CORE_MEM LIVEKIT_BRIDGE_MEM NODE_SERVER_MEM MODEL_INIT_MEM WIDGET_SERVER_MEM\n\n info \"Memory caps (${PHYS_GB}GB host): continuum-core=${CONTINUUM_CORE_MEM}, livekit-bridge=${LIVEKIT_BRIDGE_MEM}, node-server=${NODE_SERVER_MEM}, model-init=${MODEL_INIT_MEM}, widget-server=${WIDGET_SERVER_MEM}\"\nfi\n\n# (OS-branch case/esac above handled Linux/Darwin and set CONTAINER_CMD.)\ncase \"$OS\" in\n Linux|Darwin) : ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\n# ── 4. Shared modules (same code that Dev runs via npm start) ────\n# docs/infrastructure/INSTALL-ARCHITECTURE.md §Module-shape: the canonical\n# module library at src/scripts/lib/install-common.sh defines\n# mod_submodules_init + mod_docker_wsl_integration + log/sudo primitives.\n# Carl and Dev call the SAME functions so there's no drift.\nif [ ! -f \"src/scripts/lib/install-common.sh\" ]; then\n fail \"Canonical install library missing at src/scripts/lib/install-common.sh — incomplete clone? Try: rm -rf $INSTALL_DIR && re-run this installer.\"\nfi\n\n# shellcheck source=src/scripts/lib/install-common.sh\nsource \"src/scripts/lib/install-common.sh\"\n\nmod_submodules_init\nmod_docker_wsl_integration\n\n# Real daemon check. On Linux this verifies Docker Engine is up (after the\n# WSL integration module had a chance to fix it on Windows/WSL2 hosts);\n# on Mac it verifies `podman machine start` above actually connected.\nif ! $CONTAINER_CMD info &>/dev/null 2>&1; then\n case \"$OS\" in\n Darwin) fail \"Podman machine not reachable. Run: podman machine start — then re-run this installer.\" ;;\n *) fail \"Docker daemon not reachable. Start Docker Desktop / Rancher Desktop and re-run.\" ;;\n esac\nfi\nok \"$CONTAINER_CMD $($CONTAINER_CMD version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\nok \"Source: $INSTALL_DIR\"\n\n# ── 3b. Install continuum command (modular, headless-safe) ─\n# Was an inline `sudo cp` that crashed on \"no TTY for password\" when the\n# install ran headless (curl|bash without -t, BigMama SSH dry-run, CI).\n# Now goes through mod_continuum_bin_link which routes to a user-space\n# fallback (~/.local/bin) when sudo would prompt without a TTY.\nmod_continuum_bin_link \"$INSTALL_DIR/bin/continuum\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pick compose files + profile ───────────────────────\n# Base file is always loaded. On GPU hosts, layer docker-compose.gpu.yml\n# so continuum-core picks up the cuda image override (otherwise compose\n# silently uses the CPU image and inference falls back to CPU). The same\n# -f set MUST be passed to both `pull` and `up`, or pull grabs base\n# images while up tries to use override-named images that aren't local.\nCOMPOSE_FILES=\"-f docker-compose.yml\"\nCOMPOSE_ARGS=\"\"\nif [[ \"$OS\" == \"Darwin\" ]]; then\n # Mac path — the docker-compose.mac.yml override sets continuum-core's\n # replicas to 0 so support services boot in containers but continuum-core\n # stays off. We run continuum-core NATIVELY on Mac (via `npm start` below)\n # so Candle embeddings, Bevy headless render, vision processing, and audio\n # MPS paths all get real Metal. LLM inference routes to Docker Model\n # Runner's vllm-metal backend — also host-native, no container GPU tax.\n if [ -f \"docker-compose.mac.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.mac.yml\"\n else\n warn \"docker-compose.mac.yml missing — Mac detected but override won't apply. Without it, docker compose would try to run continuum-core in a container, which on Mac means CPU-only for Candle/Bevy/vision.\"\n fail \"Fix: ensure you cloned with repository integrity — the Mac override file is part of the PR891 install architecture.\"\n fi\nelif [[ \"$HAS_GPU\" == \"true\" ]]; then\n if [ -f \"docker-compose.gpu.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.gpu.yml\"\n else\n warn \"docker-compose.gpu.yml missing — GPU detected but cuda override won't apply. Continuing on CPU images.\"\n fi\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\n# ── 7. Pull support-service images ─────────────────────────\n# Image tag resolution: compose files honor ${CONTINUUM_IMAGE_TAG:-latest}.\n# Main-branch installs (Carl's default) use :latest. Reviewers validating\n# a PR before merge can pin the PR's staged image set:\n# CONTINUUM_IMAGE_TAG=pr-891 curl -fsSL install.sh | bash\n# CI tags every PR build with pr- (see .github/workflows/docker-images.yml).\n# Merging to main promotes that image set to :latest, so main and :latest\n# are always in sync by construction.\n#\n# On Mac: `continuum-core` is not pulled (replicas=0 in docker-compose.mac.yml);\n# only support services (postgres, node-server, widget-server, livekit-bridge,\n# model-init) are pulled. continuum-core runs natively from `npm start` below.\ninfo \"Pulling container images (tag: ${CONTINUUM_IMAGE_TAG:-latest})...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 8. Start support services ──────────────────────────────\n# Inverse of parallel-start.sh's cross-mode detection: if native Dev-mode\n# processes (continuum-core-server, tsx orchestrator) are running, docker\n# compose up will collide on ports 9001/9100/7880-82/9003/5432. Warn so\n# the user can stop them before starting the stack.\nif pgrep -x 'continuum-core-server' >/dev/null 2>&1 \\\n || pgrep -f 'tsx.*scripts/launch-active-example' >/dev/null 2>&1; then\n warn \"Native Dev-mode continuum processes are running — they'll collide with the docker stack on ports.\"\n warn \"Run 'cd src && npm stop' to stop BOTH native and any running docker stack (idempotent).\"\n warn \"Continuing — expect bind errors below if they persist.\"\nfi\ninfo \"Starting support services...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS up -d\n\n# ── 8b. Start continuum-core natively on Mac ───────────────\n# Mac runs continuum-core as a native host process so it can link Metal\n# directly. `npm start` drives the full build (cargo build --release\n# --features=metal + TS compile) and launches the server daemonized.\nif [[ \"$OS\" == \"Darwin\" ]]; then\n info \"Building + launching native continuum-core-server (Metal-enabled)...\"\n info \" First run: cargo build takes 5-15 min. Subsequent runs: incremental.\"\n\n # No DATABASE_URL configured by default. Rust's data module defaults to\n # SQLite at ~/.continuum/database/main.db — zero-dep, portable, no\n # network topology gymnastics. For grid deployments (multi-writer over\n # Tailscale) users explicitly set DATABASE_URL in config.env AND run\n # `docker compose --profile postgres up`. All other callers (TS, tests,\n # jtag CLI) pass opaque handles; Rust resolves them to the configured\n # backend in modules/data.rs::resolve_handle.\n\n # CONTINUUM_CORE_TCP=9100 tells the native continuum-core-server to bind an\n # additional TCP listener alongside its Unix socket. Containerized\n # node-server (Option B Mac architecture) reaches the host-native\n # continuum-core via tcp://host.docker.internal:9100 because Unix sockets\n # don't traverse Docker Desktop's VM boundary on Mac. Native callers\n # (jtag CLI, continuum bin) keep using the Unix socket as before.\n #\n # CONTINUUM_CORE_BIND=0.0.0.0 is REQUIRED on Mac: Docker Desktop's\n # `host.docker.internal` resolves inside containers to the host's\n # docker-bridge IP (e.g. 192.168.65.254), NOT to 127.0.0.1. A loopback-\n # bound listener is unreachable from containers. 0.0.0.0 accepts on all\n # interfaces; macOS's application firewall blocks inbound LAN traffic\n # for unsigned dev binaries by default, so exposure stays local.\n export CONTINUUM_CORE_TCP=9100\n export CONTINUUM_CORE_BIND=0.0.0.0\n (cd \"$INSTALL_DIR/src\" && npm install --silent && npm start) || \\\n warn \"npm start failed — check logs at ~/.continuum/jtag/logs/system/continuum-core.log\"\nfi\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9003 &>/dev/null || curl -sf https://localhost:9003 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: $CONTAINER_CMD compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9003\"\nelse\n URL=\"http://localhost:9003\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9003\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "691b52343126c2a3b8526bacac4d5379406bcb9a": {"t": 1785747967.427172, "n": 27485, "h": "8bb0b0ae6eff4e95", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\n# Log primitives (info/ok/warn/fail/die) come from\n# src/scripts/lib/install-common.sh after clone. Until the repo is\n# cloned, use these minimal pre-clone versions; they'll be overridden\n# when we source the canonical library below.\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n# Alias so the canonical lib's `die` also works here and vice versa.\ndie() { fail \"$@\"; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Pre-clone bootstrap: git + minimal Docker presence check ────\n# We can't source the canonical module library yet (lives in the repo).\n# Just verify prerequisites so the clone can happen. Deeper checks live\n# in the canonical modules that run after the clone.\n\nif ! command -v git &>/dev/null; then\n case \"$OS\" in\n Linux) fail \"git required. Run: sudo apt-get install -y git (or equivalent), then re-run.\" ;;\n Darwin) fail \"git required. Run: brew install git (or install Xcode CLI tools), then re-run.\" ;;\n esac\nfi\n\n# Container runtime + inference setup.\n#\n# Linux: Docker Engine + continuum-core runs containerized (with cuda/vulkan\n# GPU passthrough via /dev/dri or runtime:nvidia). Everything in containers.\n#\n# Mac: Docker Desktop for support services ONLY. continuum-core runs NATIVELY\n# on the host to access Metal for Candle embeddings, Bevy headless avatar\n# render, vision processing, and audio MPS paths — Apple's hypervisor exposes\n# no GPU to containers (Docker themselves confirmed in Feb 2026), so anything\n# Metal-needing must be on the host. LLM inference routes to Docker Model\n# Runner's vllm-metal backend, which also runs native on the host — Docker\n# Desktop manages the process but the compute happens on Apple Silicon directly.\n#\n# CONTAINER_CMD is used for every `compose` / `info` call. On Mac that\n# handles support services; continuum-core-server is launched separately\n# as a native host process via `npm start`.\ncase \"$OS\" in\n Linux)\n if ! command -v docker &>/dev/null; then\n info \"Docker not found — installing via get.docker.com…\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n fi\n CONTAINER_CMD=docker\n ;;\n Darwin)\n if ! command -v docker &>/dev/null; then\n fail \"Docker Desktop required on Mac.\n\n 1. Download: https://docker.com/products/docker-desktop (4.62+ for Model Runner)\n 2. Install the .dmg, then launch Docker Desktop from Launchpad\n 3. When prompted, grant Admin password for the vmnetd privileged helper\n (one-time macOS permission for container networking — standard Docker setup)\n 4. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 5. Re-run this install script\n\"\n fi\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop is installed but not running.\n\n 1. Launch Docker Desktop from Launchpad (or Applications)\n 2. If this is your first launch, macOS will prompt for Admin password for the\n vmnetd privileged helper — click 'Allow' and enter your password\n 3. Wait for the whale icon in your menu bar to show 'Docker Desktop is running'\n 4. Re-run this install script\n\n (Scripted \\`open -a Docker\\` can't satisfy the macOS privileged-helper prompt —\n that's why this script asks you to launch Docker Desktop manually once.)\n\"\n fi\n # ── Docker Desktop VM memory (Mac Option B — continuum-core NATIVE) ─────\n # The previous 80%-of-RAM target crashed Docker Desktop mid-run on 32GB\n # M1 during matrix testing (FlashGordon 2026-04-16): Docker VM at 25.6GB\n # + native continuum-core at ~11GB RSS + macOS overhead ~6GB ≈ 43GB on a\n # 32GB physical box → heavy swap → Docker daemon died, DMR endpoint\n # disappeared, Helper AI fell back to Candle (5x slower) and never\n # produced a reply within the chat gate. Real-world blocker on the\n # primary-audience hardware.\n #\n # Mac Option B has THREE concurrent memory tenants, not two:\n # (a) native continuum-core ~12GB budget (Qwen 4B Q4 + KV + Candle\n # embeddings + Bevy render + vision +\n # audio + working set)\n # (b) Docker Desktop VM hosts postgres*, node-server, widget-\n # server, livekit-bridge, model-init.\n # With SQLite default (postgres opt-in),\n # the active containers need ~6-10GB.\n # (c) macOS itself kernel, window server, user apps ~6GB\n #\n # So Mac Option B target: PHYS - NATIVE_BUDGET(12) - MACOS_OVERHEAD(6)\n # = PHYS - 18GB headroom reserve. Floor at 10GB (below that, containers\n # don't fit; Option B isn't viable on that hardware).\n #\n # Physical memory sizing (Option B honest mins, not aspirational):\n # 32GB → 14GB Docker VM (comfortable)\n # 24GB → 6GB Docker VM (below floor → refuse)\n # Below 24GB → refuse install (can't fit all three tenants).\n PHYS_BYTES=$(sysctl -n hw.memsize)\n PHYS_MIB=$((PHYS_BYTES / 1048576))\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # Reserve headroom for native continuum-core (12GB) + macOS (6GB).\n NATIVE_RESERVE_MIB=$((12 * 1024))\n MACOS_RESERVE_MIB=$((6 * 1024))\n HEADROOM_MIB=$((NATIVE_RESERVE_MIB + MACOS_RESERVE_MIB))\n DOCKER_FLOOR_MIB=$((10 * 1024))\n\n if [[ \"$PHYS_MIB\" -lt $((HEADROOM_MIB + DOCKER_FLOOR_MIB)) ]]; then\n fail \"This Mac has ${PHYS_GB}GB physical RAM. Mac Option B (continuum-core native + Docker Desktop for support services) needs at least $(( (HEADROOM_MIB + DOCKER_FLOOR_MIB) / 1024 ))GB: ~12GB for native continuum-core (Qwen 4B + Bevy + vision + audio), ~6GB for macOS itself, and a ${DOCKER_FLOOR_MIB}MiB floor for the Docker VM. Below that, Docker Desktop crashes under combined memory pressure (verified on a 32GB box with the old 80%-target formula). Get a 32GB+ M-series for the primary audience experience.\"\n fi\n\n TARGET_MIB=$((PHYS_MIB - HEADROOM_MIB))\n if [[ \"$TARGET_MIB\" -lt \"$DOCKER_FLOOR_MIB\" ]]; then\n TARGET_MIB=$DOCKER_FLOOR_MIB\n fi\n\n CURRENT_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n SETTINGS_FILE=\"$HOME/Library/Group Containers/group.com.docker/settings-store.json\"\n # Bump if current is substantially below target (>10% gap — don't thrash\n # restarts over rounding noise).\n if [[ -f \"$SETTINGS_FILE\" ]] && [[ -n \"$CURRENT_MIB\" ]] && [[ \"$CURRENT_MIB\" -lt \"$((TARGET_MIB * 90 / 100))\" ]]; then\n info \"Docker Desktop VM memory is ${CURRENT_MIB}MiB; bumping to ${TARGET_MIB}MiB (80% of ${PHYS_GB}GB host RAM, 16GB floor) for Continuum's inference + sensory workload…\"\n python3 - </dev/null 2>&1 || true\n # Wait for daemon to come back\n for _ in $(seq 1 30); do\n if docker info &>/dev/null 2>&1; then break; fi\n sleep 4\n done\n if ! docker info &>/dev/null 2>&1; then\n fail \"Docker Desktop didn't come back after memory-limit restart. Launch it manually from Launchpad.\"\n fi\n NEW_MIB=$(docker system info --format '{{.MemTotal}}' 2>/dev/null | awk '{printf \"%d\\n\", $1/1048576}')\n ok \"Docker Desktop VM memory now ${NEW_MIB}MiB (target ${TARGET_MIB}MiB)\"\n elif [[ -n \"$CURRENT_MIB\" ]]; then\n ok \"Docker Desktop VM memory already ${CURRENT_MIB}MiB (≥ ${TARGET_MIB}MiB target)\"\n fi\n\n # Docker Model Runner provides host-native vllm-metal for LLM inference.\n # Ships with Docker Desktop 4.62+. If `docker model` isn't available the\n # user's Docker Desktop is too old.\n if ! docker model --help &>/dev/null 2>&1; then\n fail \"Docker Model Runner not available (needs Docker Desktop 4.62+).\n\n 1. Open Docker Desktop → Settings → Software Updates → Check for updates\n 2. Install the update (restart Docker Desktop if prompted)\n 3. Re-run this install script\n\"\n fi\n # Verify the vllm runner is registered. On Docker DESKTOP (Mac), the\n # runners are bundled — Docker Desktop installs them automatically when\n # Model Runner is enabled. There's no /usr/local/cli-plugins step\n # (that's the Docker ENGINE / Linux path; `docker model install-runner\n # --help` says \"Docker Engine only\"). The earlier mkdir + install-runner\n # block was misapplied Linux logic on Mac, and forced a sudo prompt\n # for a directory Docker Desktop never reads from. Caught when CONTINUUM_\n # DEPS_ONLY=1 from parallel-start.sh tripped the prompt non-interactively\n # on every `npm start` (2026-04-16).\n #\n # If vllm shows \"Not Installed\", the user needs to enable it in Docker\n # Desktop → Settings → Beta features → Model Runner → install backends.\n # No CLI command can do this on Desktop, so we point at the GUI.\n if ! docker model status 2>/dev/null | awk '/^vllm[[:space:]]+Running/{found=1} END{exit !found}'; then\n warn \"vllm-metal backend not registered with Docker Model Runner.\n Open Docker Desktop → Settings → Features in development → Model Runner\n → ensure 'Enable Docker Model Runner' is on → install the vllm backend.\n Continuum will fall back to llama.cpp until vllm is enabled (~5x slower\n on M-series for some models).\"\n fi\n # Enable Model Runner's host-side TCP endpoint on port 12434. Without this,\n # continuum-core (running natively on the Mac host) can't reach the OpenAI-\n # compatible API — the probe in ai_provider.rs fails, the\n # docker-model-runner adapter doesn't register, and Candle becomes the\n # default local provider. That's a 5x perf regression (~10 tok/s vs ~50\n # tok/s on M5). Caught during M5 validation 2026-04-16: I had to enable\n # this manually before the adapter probe succeeded. Make it part of the\n # install so Carl never has to discover the toggle.\n #\n # `docker desktop enable model-runner --tcp=12434 --cors=all` is idempotent\n # — safe to re-run on every install. CORS=all is fine because the endpoint\n # binds 127.0.0.1 only (not exposed externally).\n if ! curl -fsS --max-time 1 http://localhost:12434/engines/llama.cpp/v1/models >/dev/null 2>&1; then\n info \"Enabling Docker Model Runner TCP endpoint on localhost:12434…\"\n docker desktop enable model-runner --tcp=12434 --cors=all 2>&1 | tail -3 || \\\n warn \"Could not enable Model Runner TCP — continuum-core will fall back to Candle (slower). Enable manually: docker desktop enable model-runner --tcp=12434 --cors=all\"\n fi\n # Rust toolchain — continuum-core-server is built natively on Mac (not\n # containerized) so it can link Metal for Candle embeddings, Bevy, vision,\n # and audio MPS paths. Build happens during `npm start` at end of install.\n if ! command -v cargo &>/dev/null; then\n info \"Rust not found — installing via rustup (needed for native continuum-core build)…\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable\n # shellcheck disable=SC1091\n [ -f \"$HOME/.cargo/env\" ] && source \"$HOME/.cargo/env\"\n fi\n # Node toolchain — required to build TypeScript side + run launcher.\n if ! command -v node &>/dev/null; then\n if command -v brew &>/dev/null; then\n info \"Node not found — installing via Homebrew…\"\n brew install node\n else\n fail \"Node.js required. Install from https://nodejs.org or via Homebrew, then re-run.\"\n fi\n fi\n CONTAINER_CMD=docker\n ;;\nesac\n\n# ── GPU detection + persona model pull (shared by Carl + Dev) ──────\n# Uses the centralized ic_detect_hardware / ic_decide_gpu_path from\n# install-common.sh so both install.sh paths use the SAME detection.\n# After this block: IC_GPU_PATH tells us which inference backend to\n# verify, and the default persona model is pulled into DMR (if DMR path).\nif type ic_detect_hardware &>/dev/null; then\n ic_detect_hardware\n ic_decide_gpu_path\n ic_describe_hardware\n\n # Pull default persona model into DMR so Carl's first chat is instant.\n # Only for DMR paths — Vulkan path loads models differently (local GGUF).\n PERSONA_MODEL=\"hf.co/continuum-ai/qwen3.5-4b-code-forged-GGUF\"\n case \"$IC_GPU_PATH\" in\n dmr-*)\n if ! docker model ls 2>/dev/null | grep -q \"qwen3.5-4b-code-forged\"; then\n info \"Pulling default persona model into Docker Model Runner (~2.7GB, first install only)...\"\n docker model pull \"$PERSONA_MODEL\" || warn \"Model pull failed — chat will error until model is available. Retry: docker model pull $PERSONA_MODEL\"\n else\n ok \"Persona model already in DMR: $PERSONA_MODEL\"\n fi\n ;;\n llama-vulkan)\n ok \"Vulkan GPU path — model download handled by continuum-core at first inference\"\n ;;\n unsupported)\n warn \"No supported GPU detected. Local chat will error until a GPU adapter is available.\"\n ;;\n esac\nfi\n\n# ── Per-service memory caps — auto-calculated from host RAM ────────\n# Joel's directive: don't ask users to set mem limits; auto-calc from host.\n# Don't paper over OOMs with undersized limits; size containers for the\n# actual mission. Mission per-service budgets:\n#\n# continuum-core (Linux container; on Mac it runs NATIVE and this cap\n# is informational / unused because docker-compose.mac.yml sets\n# replicas=0): needs to hold 4-8B param Qwen at Q4 (~4GB) + KV cache\n# for 5 concurrent personas (~2GB) + embeddings + Bevy + vision +\n# audio. Budget = host - 10GB (reserve for OS + Docker VM overhead\n# + support services).\n#\n# livekit-bridge: native WebRTC encode/decode buffers, multiple\n# streams. Budget scales with host — roughly host/8.\n#\n# node-server: TS orchestrator + IPC buffers + RAG state. Budget\n# same as livekit-bridge.\n#\n# model-init: one-time downloader, fits in 2GB.\n#\n# widget-server: static + light TS, 1GB.\n#\n# postgres: our dataset is small, 512MB (already set in compose).\n# livekit server: 256m (already set in compose).\n#\n# Physical RAM is whichever host this runs on.\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n # Mac branch set PHYS_MIB already. Linux sets it here from /proc/meminfo.\n :\nelif [[ -f /proc/meminfo ]]; then\n PHYS_MIB=$(awk '/^MemTotal:/{print int($2/1024)}' /proc/meminfo)\nfi\n\nif [[ -n \"${PHYS_MIB:-}\" ]]; then\n PHYS_GB=$((PHYS_MIB / 1024))\n\n # continuum-core cap: (host - 10GB) on ≥32GB machines, (host - 8GB)\n # on 20-31GB machines. Floor at 10GB.\n if [[ $PHYS_GB -ge 32 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 10))g\n elif [[ $PHYS_GB -ge 20 ]]; then\n CONTINUUM_CORE_MEM=$((PHYS_GB - 8))g\n else\n CONTINUUM_CORE_MEM=10g\n fi\n\n # Scale livekit-bridge + node-server with host. Floor 2GB each.\n # 16GB host → 2g, 32GB → 4g, 64GB → 8g.\n SCALED=$((PHYS_GB / 8))\n [[ $SCALED -lt 2 ]] && SCALED=2\n LIVEKIT_BRIDGE_MEM=${SCALED}g\n NODE_SERVER_MEM=${SCALED}g\n\n # Static + small — these don't need to scale.\n MODEL_INIT_MEM=2g\n WIDGET_SERVER_MEM=1g\n\n export CONTINUUM_CORE_MEM LIVEKIT_BRIDGE_MEM NODE_SERVER_MEM MODEL_INIT_MEM WIDGET_SERVER_MEM\n\n info \"Memory caps (${PHYS_GB}GB host): continuum-core=${CONTINUUM_CORE_MEM}, livekit-bridge=${LIVEKIT_BRIDGE_MEM}, node-server=${NODE_SERVER_MEM}, model-init=${MODEL_INIT_MEM}, widget-server=${WIDGET_SERVER_MEM}\"\nfi\n\n# (OS-branch case/esac above handled Linux/Darwin and set CONTAINER_CMD.)\ncase \"$OS\" in\n Linux|Darwin) : ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\n# ── 4. Shared modules (same code that Dev runs via npm start) ────\n# docs/infrastructure/INSTALL-ARCHITECTURE.md §Module-shape: the canonical\n# module library at src/scripts/lib/install-common.sh defines\n# mod_submodules_init + mod_docker_wsl_integration + log/sudo primitives.\n# Carl and Dev call the SAME functions so there's no drift.\nif [ ! -f \"src/scripts/lib/install-common.sh\" ]; then\n fail \"Canonical install library missing at src/scripts/lib/install-common.sh — incomplete clone? Try: rm -rf $INSTALL_DIR && re-run this installer.\"\nfi\n\n# shellcheck source=src/scripts/lib/install-common.sh\nsource \"src/scripts/lib/install-common.sh\"\n\nmod_submodules_init\nmod_docker_wsl_integration\n\n# Real daemon check. On Linux this verifies Docker Engine is up (after the\n# WSL integration module had a chance to fix it on Windows/WSL2 hosts);\n# on Mac it verifies `podman machine start` above actually connected.\nif ! $CONTAINER_CMD info &>/dev/null 2>&1; then\n case \"$OS\" in\n Darwin) fail \"Podman machine not reachable. Run: podman machine start — then re-run this installer.\" ;;\n *) fail \"Docker daemon not reachable. Start Docker Desktop / Rancher Desktop and re-run.\" ;;\n esac\nfi\nok \"$CONTAINER_CMD $($CONTAINER_CMD version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\nok \"Source: $INSTALL_DIR\"\n\n# ── 3b. Install continuum command (modular, headless-safe) ─\n# Was an inline `sudo cp` that crashed on \"no TTY for password\" when the\n# install ran headless (curl|bash without -t, BigMama SSH dry-run, CI).\n# Now goes through mod_continuum_bin_link which routes to a user-space\n# fallback (~/.local/bin) when sudo would prompt without a TTY.\nmod_continuum_bin_link \"$INSTALL_DIR/bin/continuum\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pick compose files + profile ───────────────────────\n# Base file is always loaded. On GPU hosts, layer docker-compose.gpu.yml\n# so continuum-core picks up the cuda image override (otherwise compose\n# silently uses the CPU image and inference falls back to CPU). The same\n# -f set MUST be passed to both `pull` and `up`, or pull grabs base\n# images while up tries to use override-named images that aren't local.\nCOMPOSE_FILES=\"-f docker-compose.yml\"\nCOMPOSE_ARGS=\"\"\nif [[ \"$OS\" == \"Darwin\" ]]; then\n # Mac path — the docker-compose.mac.yml override sets continuum-core's\n # replicas to 0 so support services boot in containers but continuum-core\n # stays off. We run continuum-core NATIVELY on Mac (via `npm start` below)\n # so Candle embeddings, Bevy headless render, vision processing, and audio\n # MPS paths all get real Metal. LLM inference routes to Docker Model\n # Runner's vllm-metal backend — also host-native, no container GPU tax.\n if [ -f \"docker-compose.mac.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.mac.yml\"\n else\n warn \"docker-compose.mac.yml missing — Mac detected but override won't apply. Without it, docker compose would try to run continuum-core in a container, which on Mac means CPU-only for Candle/Bevy/vision.\"\n fail \"Fix: ensure you cloned with repository integrity — the Mac override file is part of the PR891 install architecture.\"\n fi\nelif [[ \"$HAS_GPU\" == \"true\" ]]; then\n if [ -f \"docker-compose.gpu.yml\" ]; then\n COMPOSE_FILES=\"$COMPOSE_FILES -f docker-compose.gpu.yml\"\n else\n warn \"docker-compose.gpu.yml missing — GPU detected but cuda override won't apply. Continuing on CPU images.\"\n fi\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\n# ── 7. Pull support-service images ─────────────────────────\n# Image tag resolution: compose files honor ${CONTINUUM_IMAGE_TAG:-latest}.\n# Main-branch installs (Carl's default) use :latest. Reviewers validating\n# a PR before merge can pin the PR's staged image set:\n# CONTINUUM_IMAGE_TAG=pr-891 curl -fsSL install.sh | bash\n# CI tags every PR build with pr- (see .github/workflows/docker-images.yml).\n# Merging to main promotes that image set to :latest, so main and :latest\n# are always in sync by construction.\n#\n# On Mac: `continuum-core` is not pulled (replicas=0 in docker-compose.mac.yml);\n# only support services (postgres, node-server, widget-server, livekit-bridge,\n# model-init) are pulled. continuum-core runs natively from `npm start` below.\ninfo \"Pulling container images (tag: ${CONTINUUM_IMAGE_TAG:-latest})...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 8. Start support services ──────────────────────────────\n# Inverse of parallel-start.sh's cross-mode detection: if native Dev-mode\n# processes (continuum-core-server, tsx orchestrator) are running, docker\n# compose up will collide on ports 9001/9100/7880-82/9003/5432. Warn so\n# the user can stop them before starting the stack.\nif pgrep -x 'continuum-core-server' >/dev/null 2>&1 \\\n || pgrep -f 'tsx.*scripts/launch-active-example' >/dev/null 2>&1; then\n warn \"Native Dev-mode continuum processes are running — they'll collide with the docker stack on ports.\"\n warn \"Run 'cd src && npm stop' to stop BOTH native and any running docker stack (idempotent).\"\n warn \"Continuing — expect bind errors below if they persist.\"\nfi\ninfo \"Starting support services...\"\n$CONTAINER_CMD compose $COMPOSE_FILES $COMPOSE_ARGS up -d\n\n# ── 8b. Start continuum-core natively on Mac ───────────────\n# Mac runs continuum-core as a native host process so it can link Metal\n# directly. `npm start` drives the full build (cargo build --release\n# --features=metal + TS compile) and launches the server daemonized.\nif [[ \"$OS\" == \"Darwin\" ]]; then\n info \"Building + launching native continuum-core-server (Metal-enabled)...\"\n info \" First run: cargo build takes 5-15 min. Subsequent runs: incremental.\"\n\n # No DATABASE_URL configured by default. Rust's data module defaults to\n # SQLite at ~/.continuum/database/main.db — zero-dep, portable, no\n # network topology gymnastics. For grid deployments (multi-writer over\n # Tailscale) users explicitly set DATABASE_URL in config.env AND run\n # `docker compose --profile postgres up`. All other callers (TS, tests,\n # jtag CLI) pass opaque handles; Rust resolves them to the configured\n # backend in modules/data.rs::resolve_handle.\n\n # CONTINUUM_CORE_TCP=9100 tells the native continuum-core-server to bind an\n # additional TCP listener alongside its Unix socket. Containerized\n # node-server (Option B Mac architecture) reaches the host-native\n # continuum-core via tcp://host.docker.internal:9100 because Unix sockets\n # don't traverse Docker Desktop's VM boundary on Mac. Native callers\n # (jtag CLI, continuum bin) keep using the Unix socket as before.\n #\n # CONTINUUM_CORE_BIND=0.0.0.0 is REQUIRED on Mac: Docker Desktop's\n # `host.docker.internal` resolves inside containers to the host's\n # docker-bridge IP (e.g. 192.168.65.254), NOT to 127.0.0.1. A loopback-\n # bound listener is unreachable from containers. 0.0.0.0 accepts on all\n # interfaces; macOS's application firewall blocks inbound LAN traffic\n # for unsigned dev binaries by default, so exposure stays local.\n export CONTINUUM_CORE_TCP=9100\n export CONTINUUM_CORE_BIND=0.0.0.0\n (cd \"$INSTALL_DIR/src\" && npm install --silent && npm start) || \\\n warn \"npm start failed — check logs at ~/.continuum/jtag/logs/system/continuum-core.log\"\nfi\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9003 &>/dev/null || curl -sf https://localhost:9003 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: $CONTAINER_CMD compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9003\"\nelse\n URL=\"http://localhost:9003\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9003\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "485013ac11be4ca977eadc82eec97853d0602a56": {"t": 1785747968.5402765, "n": 6336, "h": "466069f8f00cc322", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Docker ───────────────────────────────────────────────\nif ! command -v docker &>/dev/null; then\n info \"Docker not found\"\n case \"$OS\" in\n Linux)\n info \"Installing Docker...\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n ;;\n Darwin)\n fail \"Install Docker Desktop (https://docker.com/products/docker-desktop) or Rancher Desktop (https://rancherdesktop.io) and re-run\"\n ;;\n esac\nfi\n\nif ! docker info &>/dev/null 2>&1; then\n fail \"Docker installed but not running. Start Docker Desktop/Rancher Desktop and re-run.\"\nfi\n\nok \"Docker $(docker version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\nok \"Source: $INSTALL_DIR\"\n\n# ── 3b. Install continuum command ─────────────────────────\nBIN_TARGET=\"/usr/local/bin/continuum\"\nif [ -w \"/usr/local/bin\" ]; then\n cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nelif command -v sudo &>/dev/null; then\n sudo cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nelse\n BIN_TARGET=\"$HOME/.local/bin/continuum\"\n mkdir -p \"$HOME/.local/bin\"\n cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nfi\nchmod +x \"$BIN_TARGET\"\nok \"Command: $BIN_TARGET\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pull images ─────────────────────────────────────────\ninfo \"Pulling container images...\"\ndocker compose pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 7. Start ───────────────────────────────────────────────\ninfo \"Starting Continuum...\"\n\nCOMPOSE_ARGS=\"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\ndocker compose $COMPOSE_ARGS up -d\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9003 &>/dev/null || curl -sf https://localhost:9003 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: docker compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9003\"\nelse\n URL=\"http://localhost:9003\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9003\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "de2c5dfc0b7c957684d34d9933233584c02ed960": {"t": 1785747969.7014835, "n": 6336, "h": "466069f8f00cc322", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Docker ───────────────────────────────────────────────\nif ! command -v docker &>/dev/null; then\n info \"Docker not found\"\n case \"$OS\" in\n Linux)\n info \"Installing Docker...\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n ;;\n Darwin)\n fail \"Install Docker Desktop (https://docker.com/products/docker-desktop) or Rancher Desktop (https://rancherdesktop.io) and re-run\"\n ;;\n esac\nfi\n\nif ! docker info &>/dev/null 2>&1; then\n fail \"Docker installed but not running. Start Docker Desktop/Rancher Desktop and re-run.\"\nfi\n\nok \"Docker $(docker version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\nok \"Source: $INSTALL_DIR\"\n\n# ── 3b. Install continuum command ─────────────────────────\nBIN_TARGET=\"/usr/local/bin/continuum\"\nif [ -w \"/usr/local/bin\" ]; then\n cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nelif command -v sudo &>/dev/null; then\n sudo cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nelse\n BIN_TARGET=\"$HOME/.local/bin/continuum\"\n mkdir -p \"$HOME/.local/bin\"\n cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nfi\nchmod +x \"$BIN_TARGET\"\nok \"Command: $BIN_TARGET\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pull images ─────────────────────────────────────────\ninfo \"Pulling container images...\"\ndocker compose pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 7. Start ───────────────────────────────────────────────\ninfo \"Starting Continuum...\"\n\nCOMPOSE_ARGS=\"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\ndocker compose $COMPOSE_ARGS up -d\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9003 &>/dev/null || curl -sf https://localhost:9003 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: docker compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9003\"\nelse\n URL=\"http://localhost:9003\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9003\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "0a10d4f650275480a0fd1d406736f1b703ccbd4b": {"t": 1785747970.801821, "n": 6336, "h": "69240c198765c239", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Docker ───────────────────────────────────────────────\nif ! command -v docker &>/dev/null; then\n info \"Docker not found\"\n case \"$OS\" in\n Linux)\n info \"Installing Docker...\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n ;;\n Darwin)\n fail \"Install Docker Desktop (https://docker.com/products/docker-desktop) or Rancher Desktop (https://rancherdesktop.io) and re-run\"\n ;;\n esac\nfi\n\nif ! docker info &>/dev/null 2>&1; then\n fail \"Docker installed but not running. Start Docker Desktop/Rancher Desktop and re-run.\"\nfi\n\nok \"Docker $(docker version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\nok \"Source: $INSTALL_DIR\"\n\n# ── 3b. Install continuum command ─────────────────────────\nBIN_TARGET=\"/usr/local/bin/continuum\"\nif [ -w \"/usr/local/bin\" ]; then\n cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nelif command -v sudo &>/dev/null; then\n sudo cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nelse\n BIN_TARGET=\"$HOME/.local/bin/continuum\"\n mkdir -p \"$HOME/.local/bin\"\n cp \"$INSTALL_DIR/bin/continuum\" \"$BIN_TARGET\"\nfi\nchmod +x \"$BIN_TARGET\"\nok \"Command: $BIN_TARGET\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pull images ─────────────────────────────────────────\ninfo \"Pulling container images...\"\ndocker compose pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 7. Start ───────────────────────────────────────────────\ninfo \"Starting Continuum...\"\n\nCOMPOSE_ARGS=\"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\ndocker compose $COMPOSE_ARGS up -d\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9000 &>/dev/null || curl -sf https://localhost:9000 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: docker compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9000\"\nelse\n URL=\"http://localhost:9000\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \"\"\necho \" continuum Open Continuum (from anywhere)\"\necho \" continuum start Start containers\"\necho \" continuum stop Stop containers\"\necho \" continuum status Show running state\"\necho \" continuum open Open browser\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9000\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "d397662b4c10fafed5592706a13fd0d71d63a2f4": {"t": 1785747971.9037793, "n": 5852, "h": "3483a2365f355254", "c": "#!/bin/bash\n# Continuum — One-command installer\n# Usage: curl -fsSL https://cambriantech.github.io/continuum/install.sh | bash\n#\n# Docker-first: pulls pre-built images, no compilation needed.\n# Optional: Tailscale for mesh networking + TLS (voice/video).\nset -e\n\ninfo() { echo -e \"\\033[1;36m→\\033[0m $*\"; }\nok() { echo -e \"\\033[1;32m✓\\033[0m $*\"; }\nwarn() { echo -e \"\\033[1;33m!\\033[0m $*\"; }\nfail() { echo -e \"\\033[1;31m✗\\033[0m $*\"; exit 1; }\n\nREPO=\"https://github.com/CambrianTech/continuum.git\"\nINSTALL_DIR=\"${CONTINUUM_DIR:-$HOME/continuum}\"\nCONTINUUM_DATA=\"$HOME/.continuum\"\n\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum Installer\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \"\"\n\n# ── 1. Detect environment ───────────────────────────────────\ninfo \"Detecting environment...\"\n\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nHAS_GPU=false\n\ncase \"$OS\" in\n Linux)\n if command -v nvidia-smi &>/dev/null || [ -f /usr/lib/wsl/lib/nvidia-smi ]; then\n HAS_GPU=true\n GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo \"NVIDIA GPU\")\n ok \"GPU detected: $GPU_NAME\"\n fi\n ;;\n Darwin)\n ok \"macOS $ARCH\"\n ;;\n *) fail \"Unsupported OS: $OS\" ;;\nesac\n\n# ── 2. Docker ───────────────────────────────────────────────\nif ! command -v docker &>/dev/null; then\n info \"Docker not found\"\n case \"$OS\" in\n Linux)\n info \"Installing Docker...\"\n curl -fsSL https://get.docker.com | sh\n sudo usermod -aG docker \"$USER\"\n warn \"Added $USER to docker group — log out and back in, then re-run this script\"\n exit 0\n ;;\n Darwin)\n fail \"Install Docker Desktop (https://docker.com/products/docker-desktop) or Rancher Desktop (https://rancherdesktop.io) and re-run\"\n ;;\n esac\nfi\n\nif ! docker info &>/dev/null 2>&1; then\n fail \"Docker installed but not running. Start Docker Desktop/Rancher Desktop and re-run.\"\nfi\n\nok \"Docker $(docker version --format '{{.Client.Version}}' 2>/dev/null || echo 'ready')\"\n\n# ── 3. Clone / update repo ─────────────────────────────────\nif [ -d \"$INSTALL_DIR/.git\" ]; then\n info \"Updating existing installation...\"\n cd \"$INSTALL_DIR\"\n git pull --ff-only 2>/dev/null || warn \"Could not update — using existing version\"\nelse\n info \"Cloning Continuum...\"\n git clone --depth 1 \"$REPO\" \"$INSTALL_DIR\"\n cd \"$INSTALL_DIR\"\nfi\n\nok \"Source: $INSTALL_DIR\"\n\n# ── 4. Configuration ───────────────────────────────────────\nmkdir -p \"$CONTINUUM_DATA\"\n\nCONFIG_FILE=\"$CONTINUUM_DATA/config.env\"\nif [ ! -f \"$CONFIG_FILE\" ]; then\n info \"Creating default config (zero API keys = local-only mode)...\"\n cat > \"$CONFIG_FILE\" << 'EOF'\n# Continuum Configuration — all API keys OPTIONAL\n# System works with zero keys using local Candle inference.\n# Add keys to enable cloud providers for better quality.\n\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n\nHTTP_PORT=9000\nWS_PORT=9001\nEOF\n ok \"Config: $CONFIG_FILE\"\nelse\n ok \"Config exists: $CONFIG_FILE\"\nfi\n\n# ── 5. TLS certs (Tailscale) ──────────────────────────────\nTS_HOSTNAME=\"\"\nif command -v tailscale &>/dev/null; then\n TS_HOSTNAME=$(tailscale status --json 2>/dev/null | python3 -c \"import sys,json; print(json.load(sys.stdin).get('Self',{}).get('DNSName','').rstrip('.'))\" 2>/dev/null || echo \"\")\n\n if [ -n \"$TS_HOSTNAME\" ]; then\n if [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n ok \"TLS: $TS_HOSTNAME (certs provisioned)\"\n else\n info \"Provisioning TLS certificate for $TS_HOSTNAME...\"\n if tailscale cert \"$TS_HOSTNAME\" 2>/dev/null; then\n mv \"$TS_HOSTNAME.crt\" \"$TS_HOSTNAME.key\" \"$CONTINUUM_DATA/\"\n ok \"TLS enabled: https://$TS_HOSTNAME\"\n else\n warn \"TLS cert failed — Tailscale Starter plan (\\$6/month) required for HTTPS\"\n warn \"Enable at: https://login.tailscale.com/admin/dns → HTTPS Certificates\"\n fi\n fi\n fi\nelse\n warn \"Tailscale not installed — no mesh networking or TLS\"\n warn \"Optional: https://tailscale.com/download\"\nfi\n\n# ── 6. Pull images ─────────────────────────────────────────\ninfo \"Pulling container images...\"\ndocker compose pull 2>/dev/null || warn \"Some images not published yet — will build locally\"\n\n# ── 7. Start ───────────────────────────────────────────────\ninfo \"Starting Continuum...\"\n\nCOMPOSE_ARGS=\"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n COMPOSE_ARGS=\"--profile gpu\"\nfi\n\ndocker compose $COMPOSE_ARGS up -d\n\n# ── 8. Wait for health ─────────────────────────────────────\ninfo \"Waiting for services...\"\nfor i in {1..30}; do\n if curl -sf http://localhost:9000 &>/dev/null || curl -sf https://localhost:9000 -k &>/dev/null; then\n break\n fi\n [ $i -eq 30 ] && warn \"Services still starting — check: docker compose logs\"\n sleep 2\ndone\n\n# ── 9. Determine URL + open browser ────────────────────────\nif [ -n \"$TS_HOSTNAME\" ] && [ -f \"$CONTINUUM_DATA/$TS_HOSTNAME.crt\" ]; then\n URL=\"https://$TS_HOSTNAME:9000\"\nelse\n URL=\"http://localhost:9000\"\nfi\n\ncase \"$OS\" in\n Darwin) open \"$URL\" 2>/dev/null || true ;;\n Linux)\n if grep -qi microsoft /proc/version 2>/dev/null; then\n cmd.exe /c start \"\" \"$URL\" 2>/dev/null || true\n else\n xdg-open \"$URL\" 2>/dev/null || true\n fi\n ;;\nesac\n\n# ── Done ────────────────────────────────────────────────────\necho \"\"\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\necho \" Continuum is running\"\necho \"\"\necho \" UI: $URL\"\necho \" Stop: cd $INSTALL_DIR && docker compose down\"\necho \" Update: cd $INSTALL_DIR && git pull && docker compose pull && docker compose up -d\"\necho \" Logs: cd $INSTALL_DIR && docker compose logs -f\"\necho \"\"\nif [[ \"$HAS_GPU\" == \"true\" ]]; then\n echo \" GPU: ${GPU_NAME:-detected}\"\nfi\nif [ -n \"$TS_HOSTNAME\" ]; then\n echo \" Mesh: https://$TS_HOSTNAME:9000\"\nfi\necho \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n"}, "5d894e11058ab42261ad61fb740e599c5c114c46": {"t": 1785747973.0285306, "n": 7848, "h": "7cec92c6f72fcdff", "c": "#!/bin/bash\n# Continuum — Cross-platform installer\n# Usage: curl -fsSL https://raw.githubusercontent.com/CambrianTech/continuum/main/install.sh | bash\n# Or: ./install.sh (from repo root)\n#\n# Installs all prerequisites, builds the system, and starts it.\n# Works on macOS (ARM + Intel) and Linux (Ubuntu/Debian, WSL2).\n# Zero API keys required — local inference works out of the box.\nset -e\n\necho \"🧬 Continuum Installer\"\necho \"======================\"\necho \"\"\n\n# ─── Platform Detection ─────────────────────────────────────────────\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nIS_WSL=false\nIS_MAC=false\nIS_LINUX=false\n\ncase \"$OS\" in\n Darwin) IS_MAC=true ;;\n Linux)\n IS_LINUX=true\n if grep -qi microsoft /proc/version 2>/dev/null; then\n IS_WSL=true\n fi\n ;;\n *)\n echo \"❌ Unsupported OS: $OS\"\n echo \" Continuum supports macOS and Linux (including WSL2)\"\n exit 1\n ;;\nesac\n\necho \"📋 Platform: $OS ($ARCH)$([ \"$IS_WSL\" = true ] && echo ' [WSL2]')\"\necho \"\"\n\n# ─── Helper: install a package ──────────────────────────────────────\ninstall_pkg() {\n local pkg=\"$1\"\n local name=\"${2:-$pkg}\"\n\n if command -v \"$pkg\" &>/dev/null; then\n echo \" ✅ $name: $(command -v $pkg)\"\n return 0\n fi\n\n echo \" 📦 Installing $name...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install \"$pkg\" 2>/dev/null || { echo \" ❌ Failed to install $name via brew\"; return 1; }\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y \"$pkg\" 2>/dev/null || sudo dnf install -y \"$pkg\" 2>/dev/null || {\n echo \" ❌ Failed to install $name (tried apt + dnf)\"\n return 1\n }\n fi\n echo \" ✅ $name: installed\"\n}\n\n# ─── Step 1: System Prerequisites ───────────────────────────────────\necho \"📋 Step 1: Checking prerequisites\"\necho \"----------------------------------\"\n\n# Homebrew (macOS only)\nif [ \"$IS_MAC\" = true ] && ! command -v brew &>/dev/null; then\n echo \" 📦 Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Node.js\nif ! command -v node &>/dev/null; then\n echo \" 📦 Installing Node.js...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install node\n else\n curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -\n sudo apt-get install -y nodejs\n fi\nfi\necho \" ✅ Node.js: $(node --version)\"\n\n# Rust\nif ! command -v rustc &>/dev/null; then\n echo \" 📦 Installing Rust...\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n source \"$HOME/.cargo/env\"\nfi\necho \" ✅ Rust: $(rustc --version | awk '{print $2}')\"\n\n# Git (should exist but check)\nif ! command -v git &>/dev/null; then\n install_pkg git \"Git\"\nfi\necho \" ✅ Git: $(git --version | awk '{print $3}')\"\n\n# Build essentials (Linux only — needed for Rust native deps)\nif [ \"$IS_LINUX\" = true ]; then\n NEED_BUILD_DEPS=false\n for dep in gcc g++ make pkg-config; do\n if ! command -v \"$dep\" &>/dev/null; then\n NEED_BUILD_DEPS=true\n break\n fi\n done\n if [ \"$NEED_BUILD_DEPS\" = true ]; then\n echo \" 📦 Installing build essentials...\"\n sudo apt-get install -y build-essential pkg-config libssl-dev 2>/dev/null || \\\n sudo dnf groupinstall -y \"Development Tools\" 2>/dev/null || \\\n echo \" ⚠️ Could not install build tools. Rust compilation may fail.\"\n else\n echo \" ✅ Build tools: installed\"\n fi\nfi\n\n# PostgreSQL\nif ! command -v psql &>/dev/null; then\n echo \" 📦 Installing PostgreSQL...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install postgresql@16 2>/dev/null && brew services start postgresql@16 2>/dev/null\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y postgresql postgresql-contrib 2>/dev/null || \\\n sudo dnf install -y postgresql-server postgresql-contrib 2>/dev/null\n # Start PostgreSQL service\n sudo systemctl start postgresql 2>/dev/null || sudo service postgresql start 2>/dev/null || true\n # Create user if needed (non-fatal — user may need to configure manually)\n sudo -u postgres createuser --superuser \"$USER\" 2>/dev/null || true\n sudo -u postgres createdb \"$USER\" 2>/dev/null || true\n fi\nfi\nif command -v psql &>/dev/null; then\n echo \" ✅ PostgreSQL: $(psql --version | awk '{print $3}')\"\n # Create the continuum database if it doesn't exist\n if ! psql -lqt 2>/dev/null | cut -d \\| -f 1 | grep -qw continuum; then\n echo \" 📦 Creating 'continuum' database...\"\n createdb continuum 2>/dev/null || sudo -u postgres createdb continuum 2>/dev/null || \\\n echo \" ⚠️ Could not create database. Run manually: createdb continuum\"\n fi\nelse\n echo \" ⚠️ PostgreSQL not found. Install manually: sudo apt install postgresql\"\nfi\n\n# Python3 (for academy training — optional but nice to have)\nif command -v python3 &>/dev/null; then\n echo \" ✅ Python3: $(python3 --version 2>&1 | awk '{print $2}')\"\nelse\n echo \" ⚠️ Python3: not found (optional — needed for academy training)\"\nfi\n\necho \"\"\n\n# ─── Step 2: Clone (if running from curl) ───────────────────────────\nif [ ! -f \"src/package.json\" ]; then\n if [ -f \"package.json\" ] && grep -q \"continuum\" package.json; then\n # We're in src/ directory\n cd ..\n elif [ ! -d \"src\" ]; then\n echo \"📋 Step 2: Cloning repository\"\n echo \"-----------------------------\"\n git clone https://github.com/CambrianTech/continuum.git\n cd continuum\n echo \" ✅ Repository cloned\"\n echo \"\"\n fi\nfi\n\n# ─── Step 3: Config Bootstrap ────────────────────────────────────────\necho \"📋 Step 3: Configuration\"\necho \"------------------------\"\n\nCONFIG_DIR=\"$HOME/.continuum\"\nCONFIG_FILE=\"$CONFIG_DIR/config.env\"\n\nmkdir -p \"$CONFIG_DIR\"\n\nif [ ! -f \"$CONFIG_FILE\" ]; then\n echo \" 📝 Creating config.env with defaults (zero API keys = local-only mode)\"\n cat > \"$CONFIG_FILE\" << 'ENVEOF'\n# Continuum Configuration\n# All API keys are OPTIONAL — the system works with zero keys using local inference.\n# Add keys to enable cloud providers for better quality on complex tasks.\n\n# Cloud providers (uncomment and add your key to enable):\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk_...\n# FIREWORKS_API_KEY=fw_...\n# XAI_API_KEY=xai-...\n# TOGETHER_API_KEY=tgp_...\n# GOOGLE_API_KEY=AIza...\n\n# Server config (defaults are fine)\nHTTP_PORT=9000\nWS_PORT=9001\n\n# Database (PostgreSQL — auto-configured by install.sh)\nDATABASE_URL=postgres://$USER@localhost/continuum\nENVEOF\n echo \" ✅ Config created: $CONFIG_FILE\"\n echo \" 💡 Add API keys later: nano $CONFIG_FILE\"\nelse\n echo \" ✅ Config exists: $CONFIG_FILE\"\nfi\n\necho \"\"\n\n# ─── Step 4: Full Install via comprehensive installer ────────────────\n# src/scripts/install.sh handles ALL platform-specific dependencies:\n# PostgreSQL, build-essential, CUDA, Python ML stack, Node deps, Rust build\necho \"📋 Step 4: Running full installer\"\necho \"-----------------------------------\"\ncd src\nif [ -f \"scripts/install.sh\" ]; then\n bash scripts/install.sh\nelse\n # Fallback: basic npm install if comprehensive script not found\n npm install --no-audit --no-fund 2>&1 | tail -3\n echo \" ✅ Node dependencies installed\"\nfi\n\n# Python/GPU deps handled by src/scripts/install.sh above\n\n# ─── Step 5: Build + Start ──────────────────────────────────────────\necho \"📋 Step 5: Building and starting\"\necho \"---------------------------------\"\necho \" This takes ~2 minutes (Rust build + TypeScript + browser)...\"\necho \"\"\nnpm start\n\necho \"\"\necho \"🧬 Continuum is running!\"\necho \"========================\"\necho \"\"\necho \" 🌐 Open: http://localhost:9000\"\necho \" 📝 Config: $CONFIG_FILE\"\necho \" 🔑 Add API keys for cloud AI (optional)\"\necho \" 🛑 Stop: npm stop (from src/)\"\necho \"\"\n"}, "06106a4226e712fdc8175c1b3afd22d94abfa8a1": {"t": 1785747974.170152, "n": 8953, "h": "523ce0c502337b7c", "c": "#!/bin/bash\n# Continuum — Cross-platform installer\n# Usage: curl -fsSL https://raw.githubusercontent.com/CambrianTech/continuum/main/install.sh | bash\n# Or: ./install.sh (from repo root)\n#\n# Installs all prerequisites, builds the system, and starts it.\n# Works on macOS (ARM + Intel) and Linux (Ubuntu/Debian, WSL2).\n# Zero API keys required — local inference works out of the box.\nset -e\n\necho \"🧬 Continuum Installer\"\necho \"======================\"\necho \"\"\n\n# ─── Platform Detection ─────────────────────────────────────────────\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nIS_WSL=false\nIS_MAC=false\nIS_LINUX=false\n\ncase \"$OS\" in\n Darwin) IS_MAC=true ;;\n Linux)\n IS_LINUX=true\n if grep -qi microsoft /proc/version 2>/dev/null; then\n IS_WSL=true\n fi\n ;;\n *)\n echo \"❌ Unsupported OS: $OS\"\n echo \" Continuum supports macOS and Linux (including WSL2)\"\n exit 1\n ;;\nesac\n\necho \"📋 Platform: $OS ($ARCH)$([ \"$IS_WSL\" = true ] && echo ' [WSL2]')\"\necho \"\"\n\n# ─── Helper: install a package ──────────────────────────────────────\ninstall_pkg() {\n local pkg=\"$1\"\n local name=\"${2:-$pkg}\"\n\n if command -v \"$pkg\" &>/dev/null; then\n echo \" ✅ $name: $(command -v $pkg)\"\n return 0\n fi\n\n echo \" 📦 Installing $name...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install \"$pkg\" 2>/dev/null || { echo \" ❌ Failed to install $name via brew\"; return 1; }\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y \"$pkg\" 2>/dev/null || sudo dnf install -y \"$pkg\" 2>/dev/null || {\n echo \" ❌ Failed to install $name (tried apt + dnf)\"\n return 1\n }\n fi\n echo \" ✅ $name: installed\"\n}\n\n# ─── Step 1: System Prerequisites ───────────────────────────────────\necho \"📋 Step 1: Checking prerequisites\"\necho \"----------------------------------\"\n\n# Homebrew (macOS only)\nif [ \"$IS_MAC\" = true ] && ! command -v brew &>/dev/null; then\n echo \" 📦 Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Node.js\nif ! command -v node &>/dev/null; then\n echo \" 📦 Installing Node.js...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install node\n else\n curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -\n sudo apt-get install -y nodejs\n fi\nfi\necho \" ✅ Node.js: $(node --version)\"\n\n# Rust\nif ! command -v rustc &>/dev/null; then\n echo \" 📦 Installing Rust...\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n source \"$HOME/.cargo/env\"\nfi\necho \" ✅ Rust: $(rustc --version | awk '{print $2}')\"\n\n# Git (should exist but check)\nif ! command -v git &>/dev/null; then\n install_pkg git \"Git\"\nfi\necho \" ✅ Git: $(git --version | awk '{print $3}')\"\n\n# Build essentials (Linux only — needed for Rust native deps)\nif [ \"$IS_LINUX\" = true ]; then\n NEED_BUILD_DEPS=false\n for dep in gcc g++ make pkg-config; do\n if ! command -v \"$dep\" &>/dev/null; then\n NEED_BUILD_DEPS=true\n break\n fi\n done\n if [ \"$NEED_BUILD_DEPS\" = true ]; then\n echo \" 📦 Installing build essentials...\"\n sudo apt-get install -y build-essential pkg-config libssl-dev 2>/dev/null || \\\n sudo dnf groupinstall -y \"Development Tools\" 2>/dev/null || \\\n echo \" ⚠️ Could not install build tools. Rust compilation may fail.\"\n else\n echo \" ✅ Build tools: installed\"\n fi\nfi\n\n# PostgreSQL\nif ! command -v psql &>/dev/null; then\n echo \" 📦 Installing PostgreSQL...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install postgresql@16 2>/dev/null && brew services start postgresql@16 2>/dev/null\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y postgresql postgresql-contrib 2>/dev/null || \\\n sudo dnf install -y postgresql-server postgresql-contrib 2>/dev/null\n # Start PostgreSQL service\n sudo systemctl start postgresql 2>/dev/null || sudo service postgresql start 2>/dev/null || true\n # Create user if needed (non-fatal — user may need to configure manually)\n sudo -u postgres createuser --superuser \"$USER\" 2>/dev/null || true\n sudo -u postgres createdb \"$USER\" 2>/dev/null || true\n fi\nfi\nif command -v psql &>/dev/null; then\n echo \" ✅ PostgreSQL: $(psql --version | awk '{print $3}')\"\n # Create the continuum database if it doesn't exist\n if ! psql -lqt 2>/dev/null | cut -d \\| -f 1 | grep -qw continuum; then\n echo \" 📦 Creating 'continuum' database...\"\n createdb continuum 2>/dev/null || sudo -u postgres createdb continuum 2>/dev/null || \\\n echo \" ⚠️ Could not create database. Run manually: createdb continuum\"\n fi\nelse\n echo \" ⚠️ PostgreSQL not found. Install manually: sudo apt install postgresql\"\nfi\n\n# Python3 (for academy training — optional but nice to have)\nif command -v python3 &>/dev/null; then\n echo \" ✅ Python3: $(python3 --version 2>&1 | awk '{print $2}')\"\nelse\n echo \" ⚠️ Python3: not found (optional — needed for academy training)\"\nfi\n\necho \"\"\n\n# ─── Step 2: Clone (if running from curl) ───────────────────────────\nif [ ! -f \"src/package.json\" ]; then\n if [ -f \"package.json\" ] && grep -q \"continuum\" package.json; then\n # We're in src/ directory\n cd ..\n elif [ ! -d \"src\" ]; then\n echo \"📋 Step 2: Cloning repository\"\n echo \"-----------------------------\"\n git clone https://github.com/CambrianTech/continuum.git\n cd continuum\n echo \" ✅ Repository cloned\"\n echo \"\"\n fi\nfi\n\n# ─── Step 3: Config Bootstrap ────────────────────────────────────────\necho \"📋 Step 3: Configuration\"\necho \"------------------------\"\n\nCONFIG_DIR=\"$HOME/.continuum\"\nCONFIG_FILE=\"$CONFIG_DIR/config.env\"\n\nmkdir -p \"$CONFIG_DIR\"\n\nif [ ! -f \"$CONFIG_FILE\" ]; then\n echo \" 📝 Creating config.env with defaults (zero API keys = local-only mode)\"\n cat > \"$CONFIG_FILE\" << 'ENVEOF'\n# Continuum Configuration\n# All API keys are OPTIONAL — the system works with zero keys using local inference.\n# Add keys to enable cloud providers for better quality on complex tasks.\n\n# Cloud providers (uncomment and add your key to enable):\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk_...\n# FIREWORKS_API_KEY=fw_...\n# XAI_API_KEY=xai-...\n# TOGETHER_API_KEY=tgp_...\n# GOOGLE_API_KEY=AIza...\n\n# Server config (defaults are fine)\nHTTP_PORT=9000\nWS_PORT=9001\n\n# Database (PostgreSQL — auto-configured by install.sh)\nDATABASE_URL=postgres://$USER@localhost/continuum\nENVEOF\n echo \" ✅ Config created: $CONFIG_FILE\"\n echo \" 💡 Add API keys later: nano $CONFIG_FILE\"\nelse\n echo \" ✅ Config exists: $CONFIG_FILE\"\nfi\n\necho \"\"\n\n# ─── Step 4: Install Dependencies ────────────────────────────────────\necho \"📋 Step 4: Installing dependencies\"\necho \"-----------------------------------\"\ncd src\nnpm install --no-audit --no-fund 2>&1 | tail -3\necho \" ✅ Node dependencies installed\"\n\n# Python training dependencies (for Academy LoRA fine-tuning + plasticity compaction)\n# Install torch first (needed for GPU detection), then training deps if GPU available\nif command -v python3 &>/dev/null; then\n PIP_FLAGS=\"\"\n # Python 3.12+ requires --break-system-packages for system pip\n if python3 -c \"import sys; exit(0 if sys.version_info >= (3,12) else 1)\" 2>/dev/null; then\n PIP_FLAGS=\"--break-system-packages\"\n fi\n\n # Ensure torch is installed (needed for GPU detection and inference)\n if ! python3 -c \"import torch\" 2>/dev/null; then\n echo \" 📦 Installing PyTorch...\"\n python3 -m pip install --quiet $PIP_FLAGS torch 2>&1 | tail -3\n fi\n\n HAS_GPU=false\n if python3 -c \"import torch; exit(0 if torch.cuda.is_available() else 1)\" 2>/dev/null; then\n HAS_GPU=true\n fi\n\n if [ \"$HAS_GPU\" = true ]; then\n echo \" 🧠 GPU detected — installing training + compaction dependencies...\"\n python3 -m pip install --quiet $PIP_FLAGS \\\n unsloth \\\n peft \\\n transformers \\\n bitsandbytes \\\n datasets \\\n trl \\\n tensorboard \\\n huggingface_hub \\\n safetensors \\\n 2>&1 | tail -3\n echo \" ✅ Training dependencies installed (Unsloth + PEFT + LoRA + compaction)\"\n else\n echo \" ⚠️ No GPU detected — skipping training dependencies (inference still works)\"\n fi\nfi\necho \"\"\n\n# ─── Step 5: Build + Start ──────────────────────────────────────────\necho \"📋 Step 5: Building and starting\"\necho \"---------------------------------\"\necho \" This takes ~2 minutes (Rust build + TypeScript + browser)...\"\necho \"\"\nnpm start\n\necho \"\"\necho \"🧬 Continuum is running!\"\necho \"========================\"\necho \"\"\necho \" 🌐 Open: http://localhost:9000\"\necho \" 📝 Config: $CONFIG_FILE\"\necho \" 🔑 Add API keys for cloud AI (optional)\"\necho \" 🛑 Stop: npm stop (from src/)\"\necho \"\"\n"}, "6e4988188a6e20b44a6c6ff14193099e8632b457": {"t": 1785747975.266706, "n": 8476, "h": "390b42e479129a51", "c": "#!/bin/bash\n# Continuum — Cross-platform installer\n# Usage: curl -fsSL https://raw.githubusercontent.com/CambrianTech/continuum/main/install.sh | bash\n# Or: ./install.sh (from repo root)\n#\n# Installs all prerequisites, builds the system, and starts it.\n# Works on macOS (ARM + Intel) and Linux (Ubuntu/Debian, WSL2).\n# Zero API keys required — local inference works out of the box.\nset -e\n\necho \"🧬 Continuum Installer\"\necho \"======================\"\necho \"\"\n\n# ─── Platform Detection ─────────────────────────────────────────────\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nIS_WSL=false\nIS_MAC=false\nIS_LINUX=false\n\ncase \"$OS\" in\n Darwin) IS_MAC=true ;;\n Linux)\n IS_LINUX=true\n if grep -qi microsoft /proc/version 2>/dev/null; then\n IS_WSL=true\n fi\n ;;\n *)\n echo \"❌ Unsupported OS: $OS\"\n echo \" Continuum supports macOS and Linux (including WSL2)\"\n exit 1\n ;;\nesac\n\necho \"📋 Platform: $OS ($ARCH)$([ \"$IS_WSL\" = true ] && echo ' [WSL2]')\"\necho \"\"\n\n# ─── Helper: install a package ──────────────────────────────────────\ninstall_pkg() {\n local pkg=\"$1\"\n local name=\"${2:-$pkg}\"\n\n if command -v \"$pkg\" &>/dev/null; then\n echo \" ✅ $name: $(command -v $pkg)\"\n return 0\n fi\n\n echo \" 📦 Installing $name...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install \"$pkg\" 2>/dev/null || { echo \" ❌ Failed to install $name via brew\"; return 1; }\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y \"$pkg\" 2>/dev/null || sudo dnf install -y \"$pkg\" 2>/dev/null || {\n echo \" ❌ Failed to install $name (tried apt + dnf)\"\n return 1\n }\n fi\n echo \" ✅ $name: installed\"\n}\n\n# ─── Step 1: System Prerequisites ───────────────────────────────────\necho \"📋 Step 1: Checking prerequisites\"\necho \"----------------------------------\"\n\n# Homebrew (macOS only)\nif [ \"$IS_MAC\" = true ] && ! command -v brew &>/dev/null; then\n echo \" 📦 Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Node.js\nif ! command -v node &>/dev/null; then\n echo \" 📦 Installing Node.js...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install node\n else\n curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -\n sudo apt-get install -y nodejs\n fi\nfi\necho \" ✅ Node.js: $(node --version)\"\n\n# Rust\nif ! command -v rustc &>/dev/null; then\n echo \" 📦 Installing Rust...\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n source \"$HOME/.cargo/env\"\nfi\necho \" ✅ Rust: $(rustc --version | awk '{print $2}')\"\n\n# Git (should exist but check)\nif ! command -v git &>/dev/null; then\n install_pkg git \"Git\"\nfi\necho \" ✅ Git: $(git --version | awk '{print $3}')\"\n\n# Build essentials (Linux only — needed for Rust native deps)\nif [ \"$IS_LINUX\" = true ]; then\n NEED_BUILD_DEPS=false\n for dep in gcc g++ make pkg-config; do\n if ! command -v \"$dep\" &>/dev/null; then\n NEED_BUILD_DEPS=true\n break\n fi\n done\n if [ \"$NEED_BUILD_DEPS\" = true ]; then\n echo \" 📦 Installing build essentials...\"\n sudo apt-get install -y build-essential pkg-config libssl-dev 2>/dev/null || \\\n sudo dnf groupinstall -y \"Development Tools\" 2>/dev/null || \\\n echo \" ⚠️ Could not install build tools. Rust compilation may fail.\"\n else\n echo \" ✅ Build tools: installed\"\n fi\nfi\n\n# PostgreSQL\nif ! command -v psql &>/dev/null; then\n echo \" 📦 Installing PostgreSQL...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install postgresql@16 2>/dev/null && brew services start postgresql@16 2>/dev/null\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y postgresql postgresql-contrib 2>/dev/null || \\\n sudo dnf install -y postgresql-server postgresql-contrib 2>/dev/null\n # Start PostgreSQL service\n sudo systemctl start postgresql 2>/dev/null || sudo service postgresql start 2>/dev/null || true\n # Create user if needed (non-fatal — user may need to configure manually)\n sudo -u postgres createuser --superuser \"$USER\" 2>/dev/null || true\n sudo -u postgres createdb \"$USER\" 2>/dev/null || true\n fi\nfi\nif command -v psql &>/dev/null; then\n echo \" ✅ PostgreSQL: $(psql --version | awk '{print $3}')\"\nelse\n echo \" ⚠️ PostgreSQL not found. Install manually: sudo apt install postgresql\"\nfi\n\n# Python3 (for academy training — optional but nice to have)\nif command -v python3 &>/dev/null; then\n echo \" ✅ Python3: $(python3 --version 2>&1 | awk '{print $2}')\"\nelse\n echo \" ⚠️ Python3: not found (optional — needed for academy training)\"\nfi\n\necho \"\"\n\n# ─── Step 2: Clone (if running from curl) ───────────────────────────\nif [ ! -f \"src/package.json\" ]; then\n if [ -f \"package.json\" ] && grep -q \"continuum\" package.json; then\n # We're in src/ directory\n cd ..\n elif [ ! -d \"src\" ]; then\n echo \"📋 Step 2: Cloning repository\"\n echo \"-----------------------------\"\n git clone https://github.com/CambrianTech/continuum.git\n cd continuum\n echo \" ✅ Repository cloned\"\n echo \"\"\n fi\nfi\n\n# ─── Step 3: Config Bootstrap ────────────────────────────────────────\necho \"📋 Step 3: Configuration\"\necho \"------------------------\"\n\nCONFIG_DIR=\"$HOME/.continuum\"\nCONFIG_FILE=\"$CONFIG_DIR/config.env\"\n\nmkdir -p \"$CONFIG_DIR\"\n\nif [ ! -f \"$CONFIG_FILE\" ]; then\n echo \" 📝 Creating config.env with defaults (zero API keys = local-only mode)\"\n cat > \"$CONFIG_FILE\" << 'ENVEOF'\n# Continuum Configuration\n# All API keys are OPTIONAL — the system works with zero keys using local inference.\n# Add keys to enable cloud providers for better quality on complex tasks.\n\n# Cloud providers (uncomment and add your key to enable):\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk_...\n# FIREWORKS_API_KEY=fw_...\n# XAI_API_KEY=xai-...\n# TOGETHER_API_KEY=tgp_...\n# GOOGLE_API_KEY=AIza...\n\n# Server config (defaults are fine)\nHTTP_PORT=9000\nWS_PORT=9001\nENVEOF\n echo \" ✅ Config created: $CONFIG_FILE\"\n echo \" 💡 Add API keys later: nano $CONFIG_FILE\"\nelse\n echo \" ✅ Config exists: $CONFIG_FILE\"\nfi\n\necho \"\"\n\n# ─── Step 4: Install Dependencies ────────────────────────────────────\necho \"📋 Step 4: Installing dependencies\"\necho \"-----------------------------------\"\ncd src\nnpm install --no-audit --no-fund 2>&1 | tail -3\necho \" ✅ Node dependencies installed\"\n\n# Python training dependencies (for Academy LoRA fine-tuning + plasticity compaction)\n# Install torch first (needed for GPU detection), then training deps if GPU available\nif command -v python3 &>/dev/null; then\n PIP_FLAGS=\"\"\n # Python 3.12+ requires --break-system-packages for system pip\n if python3 -c \"import sys; exit(0 if sys.version_info >= (3,12) else 1)\" 2>/dev/null; then\n PIP_FLAGS=\"--break-system-packages\"\n fi\n\n # Ensure torch is installed (needed for GPU detection and inference)\n if ! python3 -c \"import torch\" 2>/dev/null; then\n echo \" 📦 Installing PyTorch...\"\n python3 -m pip install --quiet $PIP_FLAGS torch 2>&1 | tail -3\n fi\n\n HAS_GPU=false\n if python3 -c \"import torch; exit(0 if torch.cuda.is_available() else 1)\" 2>/dev/null; then\n HAS_GPU=true\n fi\n\n if [ \"$HAS_GPU\" = true ]; then\n echo \" 🧠 GPU detected — installing training + compaction dependencies...\"\n python3 -m pip install --quiet $PIP_FLAGS \\\n unsloth \\\n peft \\\n transformers \\\n bitsandbytes \\\n datasets \\\n trl \\\n tensorboard \\\n huggingface_hub \\\n safetensors \\\n 2>&1 | tail -3\n echo \" ✅ Training dependencies installed (Unsloth + PEFT + LoRA + compaction)\"\n else\n echo \" ⚠️ No GPU detected — skipping training dependencies (inference still works)\"\n fi\nfi\necho \"\"\n\n# ─── Step 5: Build + Start ──────────────────────────────────────────\necho \"📋 Step 5: Building and starting\"\necho \"---------------------------------\"\necho \" This takes ~2 minutes (Rust build + TypeScript + browser)...\"\necho \"\"\nnpm start\n\necho \"\"\necho \"🧬 Continuum is running!\"\necho \"========================\"\necho \"\"\necho \" 🌐 Open: http://localhost:9000\"\necho \" 📝 Config: $CONFIG_FILE\"\necho \" 🔑 Add API keys for cloud AI (optional)\"\necho \" 🛑 Stop: npm stop (from src/)\"\necho \"\"\n"}, "fd57a9a305483eafc6b9a9ee8ba3f71f043b968b": {"t": 1785747976.4078183, "n": 6821, "h": "2c3b3b9ef96f8e7a", "c": "#!/bin/bash\n# Continuum — Cross-platform installer\n# Usage: curl -fsSL https://raw.githubusercontent.com/CambrianTech/continuum/main/install.sh | bash\n# Or: ./install.sh (from repo root)\n#\n# Installs all prerequisites, builds the system, and starts it.\n# Works on macOS (ARM + Intel) and Linux (Ubuntu/Debian, WSL2).\n# Zero API keys required — local inference works out of the box.\nset -e\n\necho \"🧬 Continuum Installer\"\necho \"======================\"\necho \"\"\n\n# ─── Platform Detection ─────────────────────────────────────────────\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nIS_WSL=false\nIS_MAC=false\nIS_LINUX=false\n\ncase \"$OS\" in\n Darwin) IS_MAC=true ;;\n Linux)\n IS_LINUX=true\n if grep -qi microsoft /proc/version 2>/dev/null; then\n IS_WSL=true\n fi\n ;;\n *)\n echo \"❌ Unsupported OS: $OS\"\n echo \" Continuum supports macOS and Linux (including WSL2)\"\n exit 1\n ;;\nesac\n\necho \"📋 Platform: $OS ($ARCH)$([ \"$IS_WSL\" = true ] && echo ' [WSL2]')\"\necho \"\"\n\n# ─── Helper: install a package ──────────────────────────────────────\ninstall_pkg() {\n local pkg=\"$1\"\n local name=\"${2:-$pkg}\"\n\n if command -v \"$pkg\" &>/dev/null; then\n echo \" ✅ $name: $(command -v $pkg)\"\n return 0\n fi\n\n echo \" 📦 Installing $name...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install \"$pkg\" 2>/dev/null || { echo \" ❌ Failed to install $name via brew\"; return 1; }\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y \"$pkg\" 2>/dev/null || sudo dnf install -y \"$pkg\" 2>/dev/null || {\n echo \" ❌ Failed to install $name (tried apt + dnf)\"\n return 1\n }\n fi\n echo \" ✅ $name: installed\"\n}\n\n# ─── Step 1: System Prerequisites ───────────────────────────────────\necho \"📋 Step 1: Checking prerequisites\"\necho \"----------------------------------\"\n\n# Homebrew (macOS only)\nif [ \"$IS_MAC\" = true ] && ! command -v brew &>/dev/null; then\n echo \" 📦 Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Node.js\nif ! command -v node &>/dev/null; then\n echo \" 📦 Installing Node.js...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install node\n else\n curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -\n sudo apt-get install -y nodejs\n fi\nfi\necho \" ✅ Node.js: $(node --version)\"\n\n# Rust\nif ! command -v rustc &>/dev/null; then\n echo \" 📦 Installing Rust...\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n source \"$HOME/.cargo/env\"\nfi\necho \" ✅ Rust: $(rustc --version | awk '{print $2}')\"\n\n# Git (should exist but check)\nif ! command -v git &>/dev/null; then\n install_pkg git \"Git\"\nfi\necho \" ✅ Git: $(git --version | awk '{print $3}')\"\n\n# Python3 (for academy training — optional but nice to have)\nif command -v python3 &>/dev/null; then\n echo \" ✅ Python3: $(python3 --version 2>&1 | awk '{print $2}')\"\nelse\n echo \" ⚠️ Python3: not found (optional — needed for academy training)\"\nfi\n\necho \"\"\n\n# ─── Step 2: Clone (if running from curl) ───────────────────────────\nif [ ! -f \"src/package.json\" ]; then\n if [ -f \"package.json\" ] && grep -q \"continuum\" package.json; then\n # We're in src/ directory\n cd ..\n elif [ ! -d \"src\" ]; then\n echo \"📋 Step 2: Cloning repository\"\n echo \"-----------------------------\"\n git clone https://github.com/CambrianTech/continuum.git\n cd continuum\n echo \" ✅ Repository cloned\"\n echo \"\"\n fi\nfi\n\n# ─── Step 3: Config Bootstrap ────────────────────────────────────────\necho \"📋 Step 3: Configuration\"\necho \"------------------------\"\n\nCONFIG_DIR=\"$HOME/.continuum\"\nCONFIG_FILE=\"$CONFIG_DIR/config.env\"\n\nmkdir -p \"$CONFIG_DIR\"\n\nif [ ! -f \"$CONFIG_FILE\" ]; then\n echo \" 📝 Creating config.env with defaults (zero API keys = local-only mode)\"\n cat > \"$CONFIG_FILE\" << 'ENVEOF'\n# Continuum Configuration\n# All API keys are OPTIONAL — the system works with zero keys using local inference.\n# Add keys to enable cloud providers for better quality on complex tasks.\n\n# Cloud providers (uncomment and add your key to enable):\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk_...\n# FIREWORKS_API_KEY=fw_...\n# XAI_API_KEY=xai-...\n# TOGETHER_API_KEY=tgp_...\n# GOOGLE_API_KEY=AIza...\n\n# Server config (defaults are fine)\nHTTP_PORT=9000\nWS_PORT=9001\nENVEOF\n echo \" ✅ Config created: $CONFIG_FILE\"\n echo \" 💡 Add API keys later: nano $CONFIG_FILE\"\nelse\n echo \" ✅ Config exists: $CONFIG_FILE\"\nfi\n\necho \"\"\n\n# ─── Step 4: Install Dependencies ────────────────────────────────────\necho \"📋 Step 4: Installing dependencies\"\necho \"-----------------------------------\"\ncd src\nnpm install --no-audit --no-fund 2>&1 | tail -3\necho \" ✅ Node dependencies installed\"\n\n# Python training dependencies (for Academy LoRA fine-tuning + plasticity compaction)\n# Install torch first (needed for GPU detection), then training deps if GPU available\nif command -v python3 &>/dev/null; then\n PIP_FLAGS=\"\"\n # Python 3.12+ requires --break-system-packages for system pip\n if python3 -c \"import sys; exit(0 if sys.version_info >= (3,12) else 1)\" 2>/dev/null; then\n PIP_FLAGS=\"--break-system-packages\"\n fi\n\n # Ensure torch is installed (needed for GPU detection and inference)\n if ! python3 -c \"import torch\" 2>/dev/null; then\n echo \" 📦 Installing PyTorch...\"\n python3 -m pip install --quiet $PIP_FLAGS torch 2>&1 | tail -3\n fi\n\n HAS_GPU=false\n if python3 -c \"import torch; exit(0 if torch.cuda.is_available() else 1)\" 2>/dev/null; then\n HAS_GPU=true\n fi\n\n if [ \"$HAS_GPU\" = true ]; then\n echo \" 🧠 GPU detected — installing training + compaction dependencies...\"\n python3 -m pip install --quiet $PIP_FLAGS \\\n unsloth \\\n peft \\\n transformers \\\n bitsandbytes \\\n datasets \\\n trl \\\n tensorboard \\\n huggingface_hub \\\n safetensors \\\n 2>&1 | tail -3\n echo \" ✅ Training dependencies installed (Unsloth + PEFT + LoRA + compaction)\"\n else\n echo \" ⚠️ No GPU detected — skipping training dependencies (inference still works)\"\n fi\nfi\necho \"\"\n\n# ─── Step 5: Build + Start ──────────────────────────────────────────\necho \"📋 Step 5: Building and starting\"\necho \"---------------------------------\"\necho \" This takes ~2 minutes (Rust build + TypeScript + browser)...\"\necho \"\"\nnpm start\n\necho \"\"\necho \"🧬 Continuum is running!\"\necho \"========================\"\necho \"\"\necho \" 🌐 Open: http://localhost:9000\"\necho \" 📝 Config: $CONFIG_FILE\"\necho \" 🔑 Add API keys for cloud AI (optional)\"\necho \" 🛑 Stop: npm stop (from src/)\"\necho \"\"\n"}, "54294342fd2eb68a1eb320439064e00d0f7c56d9": {"t": 1785747977.6204662, "n": 6208, "h": "a0c4d75ae2a63f50", "c": "#!/bin/bash\n# Continuum — Cross-platform installer\n# Usage: curl -fsSL https://raw.githubusercontent.com/CambrianTech/continuum/main/install.sh | bash\n# Or: ./install.sh (from repo root)\n#\n# Installs all prerequisites, builds the system, and starts it.\n# Works on macOS (ARM + Intel) and Linux (Ubuntu/Debian, WSL2).\n# Zero API keys required — local inference works out of the box.\nset -e\n\necho \"🧬 Continuum Installer\"\necho \"======================\"\necho \"\"\n\n# ─── Platform Detection ─────────────────────────────────────────────\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nIS_WSL=false\nIS_MAC=false\nIS_LINUX=false\n\ncase \"$OS\" in\n Darwin) IS_MAC=true ;;\n Linux)\n IS_LINUX=true\n if grep -qi microsoft /proc/version 2>/dev/null; then\n IS_WSL=true\n fi\n ;;\n *)\n echo \"❌ Unsupported OS: $OS\"\n echo \" Continuum supports macOS and Linux (including WSL2)\"\n exit 1\n ;;\nesac\n\necho \"📋 Platform: $OS ($ARCH)$([ \"$IS_WSL\" = true ] && echo ' [WSL2]')\"\necho \"\"\n\n# ─── Helper: install a package ──────────────────────────────────────\ninstall_pkg() {\n local pkg=\"$1\"\n local name=\"${2:-$pkg}\"\n\n if command -v \"$pkg\" &>/dev/null; then\n echo \" ✅ $name: $(command -v $pkg)\"\n return 0\n fi\n\n echo \" 📦 Installing $name...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install \"$pkg\" 2>/dev/null || { echo \" ❌ Failed to install $name via brew\"; return 1; }\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y \"$pkg\" 2>/dev/null || sudo dnf install -y \"$pkg\" 2>/dev/null || {\n echo \" ❌ Failed to install $name (tried apt + dnf)\"\n return 1\n }\n fi\n echo \" ✅ $name: installed\"\n}\n\n# ─── Step 1: System Prerequisites ───────────────────────────────────\necho \"📋 Step 1: Checking prerequisites\"\necho \"----------------------------------\"\n\n# Homebrew (macOS only)\nif [ \"$IS_MAC\" = true ] && ! command -v brew &>/dev/null; then\n echo \" 📦 Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Node.js\nif ! command -v node &>/dev/null; then\n echo \" 📦 Installing Node.js...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install node\n else\n curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -\n sudo apt-get install -y nodejs\n fi\nfi\necho \" ✅ Node.js: $(node --version)\"\n\n# Rust\nif ! command -v rustc &>/dev/null; then\n echo \" 📦 Installing Rust...\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n source \"$HOME/.cargo/env\"\nfi\necho \" ✅ Rust: $(rustc --version | awk '{print $2}')\"\n\n# Git (should exist but check)\nif ! command -v git &>/dev/null; then\n install_pkg git \"Git\"\nfi\necho \" ✅ Git: $(git --version | awk '{print $3}')\"\n\n# Python3 (for academy training — optional but nice to have)\nif command -v python3 &>/dev/null; then\n echo \" ✅ Python3: $(python3 --version 2>&1 | awk '{print $2}')\"\nelse\n echo \" ⚠️ Python3: not found (optional — needed for academy training)\"\nfi\n\necho \"\"\n\n# ─── Step 2: Clone (if running from curl) ───────────────────────────\nif [ ! -f \"src/package.json\" ]; then\n if [ -f \"package.json\" ] && grep -q \"continuum\" package.json; then\n # We're in src/ directory\n cd ..\n elif [ ! -d \"src\" ]; then\n echo \"📋 Step 2: Cloning repository\"\n echo \"-----------------------------\"\n git clone https://github.com/CambrianTech/continuum.git\n cd continuum\n echo \" ✅ Repository cloned\"\n echo \"\"\n fi\nfi\n\n# ─── Step 3: Config Bootstrap ────────────────────────────────────────\necho \"📋 Step 3: Configuration\"\necho \"------------------------\"\n\nCONFIG_DIR=\"$HOME/.continuum\"\nCONFIG_FILE=\"$CONFIG_DIR/config.env\"\n\nmkdir -p \"$CONFIG_DIR\"\n\nif [ ! -f \"$CONFIG_FILE\" ]; then\n echo \" 📝 Creating config.env with defaults (zero API keys = local-only mode)\"\n cat > \"$CONFIG_FILE\" << 'ENVEOF'\n# Continuum Configuration\n# All API keys are OPTIONAL — the system works with zero keys using local inference.\n# Add keys to enable cloud providers for better quality on complex tasks.\n\n# Cloud providers (uncomment and add your key to enable):\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk_...\n# FIREWORKS_API_KEY=fw_...\n# XAI_API_KEY=xai-...\n# TOGETHER_API_KEY=tgp_...\n# GOOGLE_API_KEY=AIza...\n\n# Server config (defaults are fine)\nHTTP_PORT=9000\nWS_PORT=9001\nENVEOF\n echo \" ✅ Config created: $CONFIG_FILE\"\n echo \" 💡 Add API keys later: nano $CONFIG_FILE\"\nelse\n echo \" ✅ Config exists: $CONFIG_FILE\"\nfi\n\necho \"\"\n\n# ─── Step 4: Install Dependencies ────────────────────────────────────\necho \"📋 Step 4: Installing dependencies\"\necho \"-----------------------------------\"\ncd src\nnpm install --no-audit --no-fund 2>&1 | tail -3\necho \" ✅ Node dependencies installed\"\n\n# Python training dependencies (for Academy LoRA fine-tuning)\n# Only install if GPU is available — no point on CPU-only machines\nif command -v python3 &>/dev/null; then\n HAS_GPU=false\n if python3 -c \"import torch; exit(0 if torch.cuda.is_available() else 1)\" 2>/dev/null; then\n HAS_GPU=true\n fi\n\n if [ \"$HAS_GPU\" = true ]; then\n echo \" 🧠 GPU detected — installing training dependencies...\"\n python3 -m pip install --quiet \\\n unsloth \\\n peft \\\n transformers \\\n bitsandbytes \\\n datasets \\\n trl \\\n tensorboard \\\n 2>&1 | tail -3\n echo \" ✅ Training dependencies installed (Unsloth + PEFT + LoRA)\"\n else\n echo \" ⚠️ No GPU detected — skipping training dependencies (inference still works)\"\n fi\nfi\necho \"\"\n\n# ─── Step 5: Build + Start ──────────────────────────────────────────\necho \"📋 Step 5: Building and starting\"\necho \"---------------------------------\"\necho \" This takes ~2 minutes (Rust build + TypeScript + browser)...\"\necho \"\"\nnpm start\n\necho \"\"\necho \"🧬 Continuum is running!\"\necho \"========================\"\necho \"\"\necho \" 🌐 Open: http://localhost:9000\"\necho \" 📝 Config: $CONFIG_FILE\"\necho \" 🔑 Add API keys for cloud AI (optional)\"\necho \" 🛑 Stop: npm stop (from src/)\"\necho \"\"\n"}, "02d4423a1612d4e2d79a9a62793f9c7b0757dce2": {"t": 1785747978.9590347, "n": 6182, "h": "23dc2fbc298683fb", "c": "#!/bin/bash\n# Continuum — Cross-platform installer\n# Usage: curl -fsSL https://raw.githubusercontent.com/CambrianTech/continuum/main/install.sh | bash\n# Or: ./install.sh (from repo root)\n#\n# Installs all prerequisites, builds the system, and starts it.\n# Works on macOS (ARM + Intel) and Linux (Ubuntu/Debian, WSL2).\n# Zero API keys required — local inference works out of the box.\nset -e\n\necho \"🧬 Continuum Installer\"\necho \"======================\"\necho \"\"\n\n# ─── Platform Detection ─────────────────────────────────────────────\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nIS_WSL=false\nIS_MAC=false\nIS_LINUX=false\n\ncase \"$OS\" in\n Darwin) IS_MAC=true ;;\n Linux)\n IS_LINUX=true\n if grep -qi microsoft /proc/version 2>/dev/null; then\n IS_WSL=true\n fi\n ;;\n *)\n echo \"❌ Unsupported OS: $OS\"\n echo \" Continuum supports macOS and Linux (including WSL2)\"\n exit 1\n ;;\nesac\n\necho \"📋 Platform: $OS ($ARCH)$([ \"$IS_WSL\" = true ] && echo ' [WSL2]')\"\necho \"\"\n\n# ─── Helper: install a package ──────────────────────────────────────\ninstall_pkg() {\n local pkg=\"$1\"\n local name=\"${2:-$pkg}\"\n\n if command -v \"$pkg\" &>/dev/null; then\n echo \" ✅ $name: $(command -v $pkg)\"\n return 0\n fi\n\n echo \" 📦 Installing $name...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install \"$pkg\" 2>/dev/null || { echo \" ❌ Failed to install $name via brew\"; return 1; }\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y \"$pkg\" 2>/dev/null || sudo dnf install -y \"$pkg\" 2>/dev/null || {\n echo \" ❌ Failed to install $name (tried apt + dnf)\"\n return 1\n }\n fi\n echo \" ✅ $name: installed\"\n}\n\n# ─── Step 1: System Prerequisites ───────────────────────────────────\necho \"📋 Step 1: Checking prerequisites\"\necho \"----------------------------------\"\n\n# Homebrew (macOS only)\nif [ \"$IS_MAC\" = true ] && ! command -v brew &>/dev/null; then\n echo \" 📦 Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Node.js\nif ! command -v node &>/dev/null; then\n echo \" 📦 Installing Node.js...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install node\n else\n curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -\n sudo apt-get install -y nodejs\n fi\nfi\necho \" ✅ Node.js: $(node --version)\"\n\n# Rust\nif ! command -v rustc &>/dev/null; then\n echo \" 📦 Installing Rust...\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n source \"$HOME/.cargo/env\"\nfi\necho \" ✅ Rust: $(rustc --version | awk '{print $2}')\"\n\n# Git (should exist but check)\nif ! command -v git &>/dev/null; then\n install_pkg git \"Git\"\nfi\necho \" ✅ Git: $(git --version | awk '{print $3}')\"\n\n# Python3 (for academy training — optional but nice to have)\nif command -v python3 &>/dev/null; then\n echo \" ✅ Python3: $(python3 --version 2>&1 | awk '{print $2}')\"\nelse\n echo \" ⚠️ Python3: not found (optional — needed for academy training)\"\nfi\n\necho \"\"\n\n# ─── Step 2: Clone (if running from curl) ───────────────────────────\nif [ ! -f \"src/package.json\" ]; then\n if [ -f \"package.json\" ] && grep -q \"continuum\" package.json; then\n # We're in src/ directory\n cd ..\n elif [ ! -d \"src\" ]; then\n echo \"📋 Step 2: Cloning repository\"\n echo \"-----------------------------\"\n git clone https://github.com/CambrianTech/continuum.git\n cd continuum\n echo \" ✅ Repository cloned\"\n echo \"\"\n fi\nfi\n\n# ─── Step 3: Config Bootstrap ────────────────────────────────────────\necho \"📋 Step 3: Configuration\"\necho \"------------------------\"\n\nCONFIG_DIR=\"$HOME/.continuum\"\nCONFIG_FILE=\"$CONFIG_DIR/config.env\"\n\nmkdir -p \"$CONFIG_DIR\"\n\nif [ ! -f \"$CONFIG_FILE\" ]; then\n echo \" 📝 Creating config.env with defaults (zero API keys = local-only mode)\"\n cat > \"$CONFIG_FILE\" << 'ENVEOF'\n# Continuum Configuration\n# All API keys are OPTIONAL — the system works with zero keys using local inference.\n# Add keys to enable cloud providers for better quality on complex tasks.\n\n# Cloud providers (uncomment and add your key to enable):\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk_...\n# FIREWORKS_API_KEY=fw_...\n# XAI_API_KEY=xai-...\n# TOGETHER_API_KEY=tgp_...\n# GOOGLE_API_KEY=AIza...\n\n# Server config (defaults are fine)\nHTTP_PORT=9000\nWS_PORT=9001\nENVEOF\n echo \" ✅ Config created: $CONFIG_FILE\"\n echo \" 💡 Add API keys later: nano $CONFIG_FILE\"\nelse\n echo \" ✅ Config exists: $CONFIG_FILE\"\nfi\n\necho \"\"\n\n# ─── Step 4: Install Dependencies ────────────────────────────────────\necho \"📋 Step 4: Installing dependencies\"\necho \"-----------------------------------\"\ncd src\nnpm install --no-audit --no-fund 2>&1 | tail -3\necho \" ✅ Node dependencies installed\"\n\n# Python training dependencies (for Academy LoRA fine-tuning)\n# Only install if GPU is available — no point on CPU-only machines\nif command -v python3 &>/dev/null; then\n HAS_GPU=false\n if python3 -c \"import torch; exit(0 if torch.cuda.is_available() else 1)\" 2>/dev/null; then\n HAS_GPU=true\n fi\n\n if [ \"$HAS_GPU\" = true ]; then\n echo \" 🧠 GPU detected — installing training dependencies...\"\n python3 -m pip install --quiet \\\n unsloth \\\n peft \\\n transformers \\\n bitsandbytes \\\n datasets \\\n trl \\\n 2>&1 | tail -3\n echo \" ✅ Training dependencies installed (Unsloth + PEFT + LoRA)\"\n else\n echo \" ⚠️ No GPU detected — skipping training dependencies (inference still works)\"\n fi\nfi\necho \"\"\n\n# ─── Step 5: Build + Start ──────────────────────────────────────────\necho \"📋 Step 5: Building and starting\"\necho \"---------------------------------\"\necho \" This takes ~2 minutes (Rust build + TypeScript + browser)...\"\necho \"\"\nnpm start\n\necho \"\"\necho \"🧬 Continuum is running!\"\necho \"========================\"\necho \"\"\necho \" 🌐 Open: http://localhost:9000\"\necho \" 📝 Config: $CONFIG_FILE\"\necho \" 🔑 Add API keys for cloud AI (optional)\"\necho \" 🛑 Stop: npm stop (from src/)\"\necho \"\"\n"}, "f7b23f02f2633f862363d7ca5916f1cd76d81771": {"t": 1785747980.076739, "n": 5372, "h": "007bc2851de85e3b", "c": "#!/bin/bash\n# Continuum — Cross-platform installer\n# Usage: curl -fsSL https://raw.githubusercontent.com/CambrianTech/continuum/main/install.sh | bash\n# Or: ./install.sh (from repo root)\n#\n# Installs all prerequisites, builds the system, and starts it.\n# Works on macOS (ARM + Intel) and Linux (Ubuntu/Debian, WSL2).\n# Zero API keys required — local inference works out of the box.\nset -e\n\necho \"🧬 Continuum Installer\"\necho \"======================\"\necho \"\"\n\n# ─── Platform Detection ─────────────────────────────────────────────\nOS=\"$(uname -s)\"\nARCH=\"$(uname -m)\"\nIS_WSL=false\nIS_MAC=false\nIS_LINUX=false\n\ncase \"$OS\" in\n Darwin) IS_MAC=true ;;\n Linux)\n IS_LINUX=true\n if grep -qi microsoft /proc/version 2>/dev/null; then\n IS_WSL=true\n fi\n ;;\n *)\n echo \"❌ Unsupported OS: $OS\"\n echo \" Continuum supports macOS and Linux (including WSL2)\"\n exit 1\n ;;\nesac\n\necho \"📋 Platform: $OS ($ARCH)$([ \"$IS_WSL\" = true ] && echo ' [WSL2]')\"\necho \"\"\n\n# ─── Helper: install a package ──────────────────────────────────────\ninstall_pkg() {\n local pkg=\"$1\"\n local name=\"${2:-$pkg}\"\n\n if command -v \"$pkg\" &>/dev/null; then\n echo \" ✅ $name: $(command -v $pkg)\"\n return 0\n fi\n\n echo \" 📦 Installing $name...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install \"$pkg\" 2>/dev/null || { echo \" ❌ Failed to install $name via brew\"; return 1; }\n elif [ \"$IS_LINUX\" = true ]; then\n sudo apt-get install -y \"$pkg\" 2>/dev/null || sudo dnf install -y \"$pkg\" 2>/dev/null || {\n echo \" ❌ Failed to install $name (tried apt + dnf)\"\n return 1\n }\n fi\n echo \" ✅ $name: installed\"\n}\n\n# ─── Step 1: System Prerequisites ───────────────────────────────────\necho \"📋 Step 1: Checking prerequisites\"\necho \"----------------------------------\"\n\n# Homebrew (macOS only)\nif [ \"$IS_MAC\" = true ] && ! command -v brew &>/dev/null; then\n echo \" 📦 Installing Homebrew...\"\n /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\nfi\n\n# Node.js\nif ! command -v node &>/dev/null; then\n echo \" 📦 Installing Node.js...\"\n if [ \"$IS_MAC\" = true ]; then\n brew install node\n else\n curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -\n sudo apt-get install -y nodejs\n fi\nfi\necho \" ✅ Node.js: $(node --version)\"\n\n# Rust\nif ! command -v rustc &>/dev/null; then\n echo \" 📦 Installing Rust...\"\n curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n source \"$HOME/.cargo/env\"\nfi\necho \" ✅ Rust: $(rustc --version | awk '{print $2}')\"\n\n# Git (should exist but check)\nif ! command -v git &>/dev/null; then\n install_pkg git \"Git\"\nfi\necho \" ✅ Git: $(git --version | awk '{print $3}')\"\n\n# Python3 (for academy training — optional but nice to have)\nif command -v python3 &>/dev/null; then\n echo \" ✅ Python3: $(python3 --version 2>&1 | awk '{print $2}')\"\nelse\n echo \" ⚠️ Python3: not found (optional — needed for academy training)\"\nfi\n\necho \"\"\n\n# ─── Step 2: Clone (if running from curl) ───────────────────────────\nif [ ! -f \"src/package.json\" ]; then\n if [ -f \"package.json\" ] && grep -q \"continuum\" package.json; then\n # We're in src/ directory\n cd ..\n elif [ ! -d \"src\" ]; then\n echo \"📋 Step 2: Cloning repository\"\n echo \"-----------------------------\"\n git clone https://github.com/CambrianTech/continuum.git\n cd continuum\n echo \" ✅ Repository cloned\"\n echo \"\"\n fi\nfi\n\n# ─── Step 3: Config Bootstrap ────────────────────────────────────────\necho \"📋 Step 3: Configuration\"\necho \"------------------------\"\n\nCONFIG_DIR=\"$HOME/.continuum\"\nCONFIG_FILE=\"$CONFIG_DIR/config.env\"\n\nmkdir -p \"$CONFIG_DIR\"\n\nif [ ! -f \"$CONFIG_FILE\" ]; then\n echo \" 📝 Creating config.env with defaults (zero API keys = local-only mode)\"\n cat > \"$CONFIG_FILE\" << 'ENVEOF'\n# Continuum Configuration\n# All API keys are OPTIONAL — the system works with zero keys using local inference.\n# Add keys to enable cloud providers for better quality on complex tasks.\n\n# Cloud providers (uncomment and add your key to enable):\n# ANTHROPIC_API_KEY=sk-ant-...\n# OPENAI_API_KEY=sk-...\n# DEEPSEEK_API_KEY=sk-...\n# GROQ_API_KEY=gsk_...\n# FIREWORKS_API_KEY=fw_...\n# XAI_API_KEY=xai-...\n# TOGETHER_API_KEY=tgp_...\n# GOOGLE_API_KEY=AIza...\n\n# Server config (defaults are fine)\nHTTP_PORT=9000\nWS_PORT=9001\nENVEOF\n echo \" ✅ Config created: $CONFIG_FILE\"\n echo \" 💡 Add API keys later: nano $CONFIG_FILE\"\nelse\n echo \" ✅ Config exists: $CONFIG_FILE\"\nfi\n\necho \"\"\n\n# ─── Step 4: Install Dependencies ────────────────────────────────────\necho \"📋 Step 4: Installing dependencies\"\necho \"-----------------------------------\"\ncd src\nnpm install --no-audit --no-fund 2>&1 | tail -3\necho \" ✅ Node dependencies installed\"\necho \"\"\n\n# ─── Step 5: Build + Start ──────────────────────────────────────────\necho \"📋 Step 5: Building and starting\"\necho \"---------------------------------\"\necho \" This takes ~2 minutes (Rust build + TypeScript + browser)...\"\necho \"\"\nnpm start\n\necho \"\"\necho \"🧬 Continuum is running!\"\necho \"========================\"\necho \"\"\necho \" 🌐 Open: http://localhost:9000\"\necho \" 📝 Config: $CONFIG_FILE\"\necho \" 🔑 Add API keys for cloud AI (optional)\"\necho \" 🛑 Stop: npm stop (from src/)\"\necho \"\"\n"}}, "src/tests/integration/quick-provider-test.ts": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1786068279.2512681, "n": 4511, "h": "02453007fad04a39", "c": "#!/usr/bin/env tsx\n/**\n * Quick Provider Test - Fast inference demo for all providers\n *\n * Each provider gets 10 seconds max to respond with \"Hello!\" - if they timeout, move on.\n */\n\nimport { initializeSecrets, getSecret } from '../../system/secrets/SecretManager';\n\nconst TIMEOUT_MS = 10000; // 10 second timeout per provider\n\ninterface TestResult {\n provider: string;\n success: boolean;\n response?: string;\n time?: number;\n error?: string;\n}\n\nasync function testWithTimeout(\n promise: Promise,\n timeoutMs: number,\n providerName: string\n): Promise {\n return Promise.race([\n promise,\n new Promise((_, reject) =>\n setTimeout(() => reject(new Error(`${providerName} timed out after ${timeoutMs}ms`)), timeoutMs)\n )\n ]);\n}\n\nasync function testProvider(\n name: string,\n keyName: string,\n adapterPath: string,\n model: string\n): Promise {\n try {\n const apiKey = await getSecret(keyName);\n if (!apiKey) {\n return { provider: name, success: false, error: 'No API key' };\n }\n\n // Load and initialize adapter\n const module = await import(adapterPath);\n const AdapterClass = module[`${name}Adapter`];\n const adapter = new AdapterClass(apiKey);\n await adapter.initialize();\n\n // Test inference with timeout\n const startTime = Date.now();\n const response = await testWithTimeout(\n adapter.generateText({\n messages: [{ role: 'user' as const, content: 'Say hello in 5 words or less.' }],\n model,\n temperature: 0.7,\n maxTokens: 20\n }),\n TIMEOUT_MS,\n name\n );\n\n const time = Date.now() - startTime;\n\n // Successful responses have a text field; failed responses have an error field\n if (response.text) {\n return {\n provider: name,\n success: true,\n response: response.text.trim(),\n time\n };\n } else {\n return {\n provider: name,\n success: false,\n error: response.error || `Unexpected response format: ${JSON.stringify(response, null, 2)}`\n };\n }\n } catch (error) {\n return {\n provider: name,\n success: false,\n error: error instanceof Error ? error.message : String(error)\n };\n }\n}\n\nasync function main(): Promise {\n console.log('🚀 Quick Provider Inference Test');\n console.log('='.repeat(60));\n\n await initializeSecrets();\n console.log('✅ Secrets loaded\\n');\n\n const providers = [\n { name: 'DeepSeek', keyName: 'DEEPSEEK_API_KEY', path: '../../daemons/ai-provider-daemon/adapters/deepseek/shared/DeepSeekAdapter', model: 'deepseek-chat' },\n { name: 'Groq', keyName: 'GROQ_API_KEY', path: '../../daemons/ai-provider-daemon/adapters/groq/shared/GroqAdapter', model: 'llama-3.1-8b-instant' },\n { name: 'XAI', keyName: 'XAI_API_KEY', path: '../../daemons/ai-provider-daemon/adapters/xai/shared/XAIAdapter', model: 'grok-4' },\n { name: 'OpenAI', keyName: 'OPENAI_API_KEY', path: '../../daemons/ai-provider-daemon/adapters/openai/shared/OpenAIAdapter', model: 'gpt-4' },\n { name: 'Anthropic', keyName: 'ANTHROPIC_API_KEY', path: '../../daemons/ai-provider-daemon/adapters/anthropic/shared/AnthropicAdapter', model: 'claude-3-5-sonnet-20241022' },\n { name: 'Fireworks', keyName: 'FIREWORKS_API_KEY', path: '../../daemons/ai-provider-daemon/adapters/fireworks/shared/FireworksAdapter', model: 'accounts/fireworks/models/deepseek-v3p1' },\n ];\n\n const results: TestResult[] = [];\n\n for (const provider of providers) {\n console.log(`Testing ${provider.name}...`);\n const result = await testProvider(provider.name, provider.keyName, provider.path, provider.model);\n results.push(result);\n\n if (result.success) {\n console.log(`✅ ${result.provider}: \"${result.response}\" (${result.time}ms)\\n`);\n } else {\n console.log(`❌ ${result.provider}: ${result.error}\\n`);\n }\n }\n\n // Summary\n console.log('='.repeat(60));\n console.log('📊 Summary:');\n const working = results.filter(r => r.success);\n const failed = results.filter(r => !r.success);\n console.log(`✅ Working: ${working.length}/${results.length}`);\n console.log(`❌ Failed: ${failed.length}/${results.length}`);\n\n if (working.length > 0) {\n console.log('\\n✅ Working providers:');\n working.forEach(r => console.log(` - ${r.provider} (${r.time}ms)`));\n }\n\n if (failed.length > 0) {\n console.log('\\n❌ Failed providers:');\n failed.forEach(r => console.log(` - ${r.provider}: ${r.error}`));\n }\n}\n\nif (require.main === module) {\n main().catch(console.error);\n}\n"}}, "src/system/genome/fine-tuning/server/adapters/api-tests/test-deepseek.ts": {"dd976df36079d75244719a23956e1c9e2dcddc27": {"t": 1786068281.1172016, "n": 2361, "h": "42115ad0930459ca", "c": "#!/usr/bin/env node\n/**\n * DeepSeek Fine-Tuning API Test\n *\n * DeepSeek uses OpenAI-compatible API - just different endpoint and pricing.\n * This test extends OpenAIFineTuningTest with 95% code reuse.\n *\n * Usage:\n * npx tsx system/genome/fine-tuning/server/adapters/api-tests/test-deepseek.ts\n *\n * Requirements:\n * - DEEPSEEK_API_KEY in environment\n * - /tmp/test-training-minimal.jsonl (training data)\n *\n * Key Differences from OpenAI:\n * - API Base: https://api.deepseek.com/v1 (not openai.com)\n * - Cost: 27x cheaper than OpenAI ($0.00015 vs $0.00405 per example)\n * - Otherwise identical workflow (OpenAI-compatible)\n */\n\nimport { OpenAIFineTuningTest } from './test-openai';\nimport { TestConfig } from './BaseRemoteAPITest';\n\n// ============================================================================\n// DeepSeek API Test (Extends OpenAI)\n// ============================================================================\n\nclass DeepSeekFineTuningTest extends OpenAIFineTuningTest {\n // Everything inherited from OpenAIFineTuningTest!\n // Just override config values in constructor\n\n // No method overrides needed - 100% API compatible with OpenAI\n}\n\n// ============================================================================\n// Main Execution\n// ============================================================================\n\nasync function main(): Promise {\n // Configuration (only differences from OpenAI)\n const config: TestConfig = {\n apiKey: process.env.DEEPSEEK_API_KEY || '',\n apiBase: 'https://api.deepseek.com/v1', // ← Only difference!\n baseModel: 'deepseek-chat', // ← And model name\n epochs: 1,\n trainingFile: '/tmp/test-training-minimal.jsonl',\n providerId: 'deepseek',\n };\n\n console.log('💡 DeepSeek is 27x cheaper than OpenAI!');\n console.log(' OpenAI: $0.00405 per example');\n console.log(' DeepSeek: $0.00015 per example');\n console.log('');\n\n // Run test (using inherited OpenAI implementation)\n const test = new DeepSeekFineTuningTest(config);\n const result = await test.runTest();\n\n // Exit with appropriate code\n process.exit(result.success ? 0 : 1);\n}\n\n// Run if executed directly\nif (require.main === module) {\n main().catch((error) => {\n console.error('Unhandled error:', error);\n process.exit(1);\n });\n}\n\n// Export for reuse\nexport { DeepSeekFineTuningTest };\n"}}}}