Loading...
Loading...
Native macOS interface for AI agents running on Orgo cloud computers with built-in terminal, sessions, kanban, and voice mode
npx skill4agent add aradotso/hermes-skills hermes-desktop-os1-native-macos-clientSkill by ara.so — Hermes Skills collection.
OS1.app.zipOS1.app/Applicationsgit clone https://github.com/nickvasilescu/hermes-desktop-os1.git
cd hermes-desktop-os1
./scripts/build-macos-app.shdist/OS1.appswift testORGO_LIVE_TESTS=1 \
ORGO_API_KEY="sk_live_..." \
ORGO_DEFAULT_COMPUTER_ID="<uuid>" \
swift test --filter OrgoTransportLiveTestspython3// HTTP operations try platform proxy first
// https://www.orgo.ai/api/computers/{id}/...
// Falls back to direct VM URL on 5xx routing failures
// https://<fly_instance_id>.orgo.dev/...
// Terminal websocket connects directly
// wss://<fly_instance_id>.orgo.dev/terminal?token=<vncPassword>POST /bash - Execute bash command
POST /exec - Execute arbitrary command
GET /files - List files
PUT /files - Write file
GET /sessions - List agent sessions
GET /tasks - Get kanban tasks
GET /skills - List available skills
GET /cron - List cron jobs// Orgo connection persists API key in Keychain
// Access pattern in Swift:
import Security
func saveOrgoAPIKey(_ key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.elementsoftware.os1.orgo",
kSecAttrAccount as String: "api-key",
kSecValueData as String: key.data(using: .utf8)!
]
SecItemAdd(query as CFDictionary, nil)
}# Required: OpenAI API key
OPENAI_API_KEY="sk-..."
# Optional: Orgo MCP configuration
OS1_ORGO_MCP_JS_PATH="/absolute/path/to/dist/index.js"
OS1_ORGO_MCP_PACKAGE="@orgo-ai/mcp"
OS1_REALTIME_ORGO_TOOLSETS="core,screen,files"
OS1_REALTIME_ORGO_DISABLED_TOOLS="orgo_upload_file"
OS1_REALTIME_ORGO_READ_ONLY="true"# From source
OPENAI_API_KEY="sk-..." swift run OS1
# Packaged app
./scripts/build-macos-app.sh
OPENAI_API_KEY="sk-..." ./dist/OS1.app/Contents/MacOS/OS1POST /sessionhttps://api.openai.com/v1/realtime/callsnpx -y @orgo-ai/mcpcore,screen,filesshell,admin# Enable shell and admin toolsets (WARNING: gives voice model system access)
OS1_REALTIME_ORGO_TOOLSETS="core,screen,files,shell,admin"./scripts/build-macos-app.shdist/OS1.appcom.elementsoftware.os1# Use specific identity
OS1_CODESIGN_IDENTITY="Developer ID Application: Your Name (TEAM123)" \
./scripts/build-macos-app.sh
# Or use first available Apple Development identity
OS1_AUTO_CODESIGN=1 ./scripts/build-macos-app.sh# Legacy name (also supported)
HERMES_CODESIGN_IDENTITY="Developer ID Application: Your Name"
# Custom identity
OS1_CODESIGN_IDENTITY="Developer ID Application: Your Name"
# Auto-select first Apple Development cert
OS1_AUTO_CODESIGN=1// After connecting to a VM without agent
// 1. Overview screen detects missing agent
// 2. Shows "Install Hermes Agent" button
// 3. Installation handles:
// - VM clock drift synchronization
// - System git installation
// - Stale apt lock cleanup
// - Hermes agent installation (~60-90s)// Sessions view provides:
// - Full-text search across all sessions
// - Session browser with metadata
// - Direct navigation to session details
// - Export/archive capabilities// File editor includes:
// - Syntax highlighting
// - Conflict detection (checks mtime before save)
// - Profile-aware path resolution
// - Direct VM filesystem access via HTTP API// Terminal features:
// - Real-time websocket connection
// - Full TTY resize support
// - Output reflow on window resize
// - History persistence
// - Direct byte streaming (no SSH overhead)Solutions:
1. Verify API key at orgo.ai/settings/api-keys
2. Check workspace has active computers
3. Ensure computer is running (not stopped/suspended)
4. Check Connections tab shows green status indicatorBehavior: Transport automatically falls back to direct VM URL
Check: Look for fallback message in logs
Note: Long-running ops (installer) skip proxy to avoid 30s timeoutCommon causes:
- VM clock drift (installer syncs automatically)
- Missing system git (installer apt-gets it)
- Stale apt locks (installer cleans them)
Solution: Let installer run full course (~90s)
Manual check: Use terminal tab to inspect /tmp/hermes-install.logChecklist:
- Can you `ssh user@host` from terminal without password prompt?
- Is python3 in non-interactive PATH? (ssh user@host 'which python3')
- Is Hermes already installed on remote host?
- Does ~/.ssh/config have correct settings for host?1. Check System Settings → Privacy & Security → Microphone
2. Grant OS1.app microphone access
3. Restart app after granting permissionCheck:
1. OPENAI_API_KEY is set (env or Providers tab)
2. No firewall blocking WebRTC
3. Console.app for OS1 logs
4. Look for "oai-events data channel ready" in logsCheck:
1. ORGO_API_KEY is set or saved in OS1
2. ORGO_DEFAULT_COMPUTER_ID matches active connection
3. OS1_REALTIME_ORGO_TOOLSETS includes desired toolsets
4. MCP server can start: npx -y @orgo-ai/mcp --version# 1. Make code changes
# 2. Run tests
swift test
# 3. Run from source
swift run OS1
# 4. Build and test packaged app
./scripts/build-macos-app.sh
open dist/OS1.app// Enable verbose logging in OrgoTransport.swift
print("[OrgoTransport] Attempting proxy: \(proxyURL)")
print("[OrgoTransport] Fallback to direct: \(directURL)")
// Check websocket handshake
print("[Terminal] WebSocket connecting to: \(wsURL)")
print("[Terminal] WebSocket state: \(webSocket.readyState)")# Get computer ID from Orgo dashboard or API
ORGO_DEFAULT_COMPUTER_ID=$(curl -H "Authorization: Bearer $ORGO_API_KEY" \
https://www.orgo.ai/api/computers | jq -r '.[0].id')
# Run live tests
ORGO_LIVE_TESTS=1 \
ORGO_API_KEY="sk_live_..." \
ORGO_DEFAULT_COMPUTER_ID="$ORGO_DEFAULT_COMPUTER_ID" \
swift test --filter OrgoTransportLiveTestsimport Foundation
struct OrgoConnection {
let apiKey: String
let workspaceId: String
let computerId: String
func saveToKeychain() {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.elementsoftware.os1.orgo",
kSecAttrAccount as String: "api-key",
kSecValueData as String: apiKey.data(using: .utf8)!,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
SecItemDelete(query as CFDictionary) // Remove old
SecItemAdd(query as CFDictionary, nil)
}
}
// Usage
let connection = OrgoConnection(
apiKey: ProcessInfo.processInfo.environment["ORGO_API_KEY"] ?? "",
workspaceId: "workspace-uuid",
computerId: "computer-uuid"
)
connection.saveToKeychain()import Foundation
func executeBashCommand(
computerId: String,
command: String,
apiKey: String
) async throws -> String {
let url = URL(string: "https://www.orgo.ai/api/computers/\(computerId)/bash")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body = ["command": command]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
let (data, _) = try await URLSession.shared.data(for: request)
struct Response: Codable {
let stdout: String
let stderr: String
let exitCode: Int
}
let response = try JSONDecoder().decode(Response.self, from: data)
return response.stdout
}
// Usage
Task {
let output = try await executeBashCommand(
computerId: "abc-123",
command: "ls -la /home/agent",
apiKey: ProcessInfo.processInfo.environment["ORGO_API_KEY"]!
)
print(output)
}import Foundation
struct HermesSession: Codable {
let id: String
let title: String
let createdAt: String
let messages: [Message]
struct Message: Codable {
let role: String
let content: String
let timestamp: String
}
}
func fetchSessions(
computerId: String,
apiKey: String
) async throws -> [HermesSession] {
let url = URL(string: "https://www.orgo.ai/api/computers/\(computerId)/sessions")!
var request = URLRequest(url: url)
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode([HermesSession].self, from: data)
}
// Usage with search
Task {
let sessions = try await fetchSessions(
computerId: "abc-123",
apiKey: ProcessInfo.processInfo.environment["ORGO_API_KEY"]!
)
// Full-text search
let searchTerm = "deployment"
let matching = sessions.filter { session in
session.messages.contains { message in
message.content.localizedCaseInsensitiveContains(searchTerm)
}
}
print("Found \(matching.count) sessions mentioning '\(searchTerm)'")
}