Skip to main content

codexmonitor-orchestration

Expert in CodexMonitor, a Tauri app for orchestrating multiple Codex agents across local workspaces with threads, git integration, and remote daemon support.

설치로 이동

소스 정보

저장소
reason-machines/codex-skills
최근 소스 활동
2026년 5월 16일 19:30
감지된 SKILL.md 언어
영어
스타
0
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
codexmonitor-orchestration
description
Expert in CodexMonitor, a Tauri app for orchestrating multiple Codex agents across local workspaces with threads, git integration, and remote daemon support.
triggers
["how do I use CodexMonitor","set up CodexMonitor workspaces","manage Codex agent threads","CodexMonitor remote backend","build CodexMonitor from source","configure CodexMonitor iOS","CodexMonitor git integration","run CodexMonitor daemon"]
# CodexMonitor Orchestration Skill > Skill by [ara.so](https://ara.so) — Codex Skills collection. CodexMonitor is a Tauri-based desktop and mobile app for orchestrating multiple Codex agents across local workspaces. It provides workspace management, thread persistence, git/GitHub integration, file browsing, prompt libraries, and a remote daemon mode for connecting iOS clients or headless setups. ## What CodexMonitor Does - **Multi-workspace orchestration**: Spawn one `codex app-server` per workspace, resume threads, track unread/running state - **Thread management**: Pin, rename, archive, copy threads; per-thread drafts; stop/interrupt in-flight turns - **Worktree agents**: Clone agents for isolated work under app data directory (legacy `.codex-worktrees` supported) - **Git & GitHub**: Diff stats, staged/unstaged files, commit log, branch management, GitHub Issues/PRs via `gh` - **Composer**: Image attachments, autocomplete for skills (`$`), prompts (`/prompts:`), reviews (`/review`), file paths (`@`) - **Remote daemon**: Run Codex on another machine, connect iOS client via TCP (Tailscale support) - **Prompt library**: Global/workspace prompts with create/edit/delete/move and run in threads - **File tree**: Search, file-type icons, reveal in Finder/Explorer - **Terminal dock**: Multiple tabs for background commands (experimental) ## Installation ### Requirements - Node.js + npm - Rust toolchain (stable) - CMake (for native dependencies, dictation/Whisper) - LLVM/Clang (Windows only, for bindgen) - Codex CLI installed and in `PATH` - Git CLI (for worktree operations) - GitHub CLI `gh` (optional, for GitHub integrations) ### Install Dependencies ```bash npm install ``` ### Check Environment ```bash npm run doctor ``` ### Run in Development ```bash npm run tauri:dev ``` ### Build Production Bundle ```bash # macOS/Linux npm run tauri:build # Windows (opt-in, uses separate config) npm run tauri:build:win ``` Artifacts: `src-tauri/target/release/bundle/` (platform-specific subfolders) ## Workspace Management ### Adding a Workspace Workspaces persist to `workspaces.json` in app data directory. **Via UI**: Sidebar → Add workspace → Select directory **Data structure** (`src-tauri/src/workspaces/mod.rs`): ```rust #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Workspace { pub id: String, pub name: String, pub path: String, pub codex_home: Option<String>, // Overrides global Codex home pub is_remote: bool, pub remote_host: Option<String>, pub remote_token: Option<String>, } ``` ### Programmatic Workspace Access Frontend service (`src/services/tauri.ts`): ```typescript import { invoke } from '@tauri-apps/api/tauri'; // Load all workspaces const workspaces = await invoke<Workspace[]>('workspace_list'); // Add workspace const newWorkspace = await invoke<Workspace>('workspace_add', { path: '/path/to/project', name: 'My Project', }); // Update workspace await invoke('workspace_update', { id: 'workspace-id', updates: { codex_home: '/custom/codex/home' }, }); // Remove workspace await invoke('workspace_remove', { id: 'workspace-id' }); ``` Backend command (`src-tauri/src/lib.rs`): ```rust #[tauri::command] async fn workspace_list( state: tauri::State<'_, AppState>, ) -> Result<Vec<Workspace>, String> { state.workspace_manager.lock().await.list_workspaces() .map_err(|e| e.to_string()) } ``` ## Thread Management ### Thread Reducer Architecture Thread state is managed by a reducer with slices in `src/features/threads/hooks/threadReducer/`. **Thread reducer pattern** (`src/features/threads/hooks/threadReducer/index.ts`): ```typescript export type ThreadAction = | { type: 'SET_MESSAGES'; messages: Message[] } | { type: 'ADD_MESSAGE'; message: Message } | { type: 'UPDATE_MESSAGE'; messageId: string; updates: Partial<Message> } | { type: 'SET_RUNNING'; running: boolean } | { type: 'SET_UNREAD'; unread: number } | { type: 'RESET' }; export function threadReducer(state: ThreadState, action: ThreadAction): ThreadState { switch (action.type) { case 'SET_MESSAGES': return { ...state, messages: action.messages }; case 'ADD_MESSAGE': return { ...state, messages: [...state.messages, action.message] }; case 'UPDATE_MESSAGE': return { ...state, messages: state.messages.map(m => m.id === action.messageId ? { ...m, ...action.updates } : m ), }; case 'SET_RUNNING': return { ...state, running: action.running }; case 'RESET': return initialThreadState; default: return state; } } ``` ### Resuming a Thread Frontend (`src/features/threads/hooks/useThreadResume.ts`): ```typescript import { invoke } from '@tauri-apps/api/tauri'; async function resumeThread(workspaceId: string, threadId: string) { const result = await invoke<{ messages: Message[] }>('thread_resume', { workspaceId, threadId, }); dispatch({ type: 'SET_MESSAGES', messages: result.messages }); dispatch({ type: 'SET_UNREAD', unread: 0 }); } ``` Backend (`src-tauri/src/codex/mod.rs`): ```rust #[tauri::command] async fn thread_resume( workspace_id: String, thread_id: String, state: tauri::State<'_, AppState>, ) -> Result<serde_json::Value, String> { let manager = state.workspace_manager.lock().await; let workspace = manager.get_workspace(&workspace_id) .ok_or("Workspace not found")?; let server = state.codex_servers.lock().await .get(&workspace_id) .ok_or("Server not running")?; server.call("thread/resume", json!({ "thread_id": thread_id })).await .map_err(|e| e.to_string()) } ``` ### Sending a Message ```typescript async function sendMessage( workspaceId: string, threadId: string, content: string, attachments?: { path: string; mime_type: string }[] ) { await invoke('thread_send_message', { workspaceId, threadId, message: { role: 'user', content, attachments, }, }); } ``` ### Thread Lifecycle Commands ```typescript // Stop in-flight turn await invoke('thread_interrupt', { workspaceId, threadId }); // Pin thread await invoke('thread_pin', { workspaceId, threadId, pinned: true }); // Rename thread await invoke('thread_rename', { workspaceId, threadId, name: 'New Name' }); // Archive thread await invoke('thread_archive', { workspaceId, threadId }); // Copy thread (clone messages) await invoke('thread_copy', { workspaceId, threadId }); ``` ## Worktree Agents Worktree agents create isolated git worktrees under `<app-data>/worktrees/<workspace-id>/`. ### Creating a Worktree Frontend: ```typescript const worktree = await invoke<{ path: string; branch: string }>('worktree_create', { workspaceId: 'workspace-id', branch: 'feature-branch', }); console.log(`Worktree created at ${worktree.path}`); ``` Backend (`src-tauri/src/shared/workspaces_core/worktree.rs`): ```rust pub async fn create_worktree( workspace_path: &str, workspace_id: &str, branch: &str, app_data_dir: &Path, ) -> Result<Worktree, WorktreeError> { let worktree_dir = app_data_dir.join("worktrees").join(workspace_id); std::fs::create_dir_all(&worktree_dir)?; let worktree_path = worktree_dir.join(branch); let output = Command::new("git") .args(&["worktree", "add", worktree_path.to_str().unwrap(), branch]) .current_dir(workspace_path) .output()?; if !output.status.success() { return Err(WorktreeError::GitError( String::from_utf8_lossy(&output.stderr).to_string() )); } Ok(Worktree { path: worktree_path.to_string_lossy().to_string(), branch: branch.to_string(), }) } ``` ### Listing Worktrees ```typescript const worktrees = await invoke<Worktree[]>('worktree_list', { workspaceId: 'workspace-id', }); ``` ### Removing a Worktree ```typescript await invoke('worktree_remove', { workspaceId: 'workspace-id', path: '/path/to/worktree', }); ``` ## Git Integration ### Git Diff Stats Frontend: ```typescript const stats = await invoke<{ staged: { path: string; status: string }[]; unstaged: { path: string; status: string }[]; }>('git_diff_stats', { workspaceId: 'workspace-id' }); ``` Backend (`src-tauri/src/shared/git_ui_core/diff.rs`): ```rust pub fn get_diff_stats(repo_path: &str) -> Result<DiffStats, GitError> { let repo = Repository::open(repo_path)?; let mut index = repo.index()?; let head_tree = repo.head()?.peel_to_tree()?; let diff_index_tree = repo.diff_tree_to_index(Some(&head_tree), Some(&index), None)?; let diff_index_workdir = repo.diff_index_to_workdir(Some(&index), None)?; let staged = collect_diff_entries(&diff_index_tree)?; let unstaged = collect_diff_entries(&diff_index_workdir)?; Ok(DiffStats { staged, unstaged }) } ``` ### Branch Management ```typescript // List branches const branches = await invoke<{ name: string; current: boolean; ahead: number; behind: number }[]>( 'git_list_branches', { workspaceId: 'workspace-id' } ); // Checkout branch await invoke('git_checkout_branch', { workspaceId: 'workspace-id', branch: 'main', }); // Create branch await invoke('git_create_branch', { workspaceId: 'workspace-id', branch: 'feature-new', fromBranch: 'main', }); ``` ### GitHub Integration Requires `gh` CLI: ```typescript // List issues const issues = await invoke<GitHubIssue[]>('github_list_issues', { workspaceId: 'workspace-id', }); // List PRs const prs = await invoke<GitHubPR[]>('github_list_prs', { workspaceId: 'workspace-id', }); // Get PR diff const diff = await invoke<string>('github_pr_diff', { workspaceId: 'workspace-id', prNumber: 42, }); // Ask PR (send PR context to new thread) await invoke('github_ask_pr', {
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기