Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ authors = ["PFigs"]
keywords = ["voice", "terminal", "claude", "tts", "stt"]
categories = ["command-line-utilities"]

[lib]
name = "ok_claude"
path = "src/lib.rs"

[[bin]]
name = "claudio"
path = "src/main.rs"
Expand All @@ -34,3 +38,9 @@ gpui = "0.2"
gpui-terminal = { path = "gpui-terminal" }
flume = "0.11"
strip-ansi-escapes = "0.2"
serde_yaml = "0.9"
chrono = { version = "0.4", default-features = false, features = ["serde", "clock"] }
glob = "0.3"

[dev-dependencies]
tempfile = "3"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Voice-activated terminal session manager for [Claude Code](https://docs.anthropi
- **File browser** -- resizable sidebar with multi-root folder navigation. Click files to open in your editor (`$VISUAL` / `$EDITOR` / `zed`).
- **Desktop notifications** -- terminal output is scanned for approval prompts ("do you want to proceed", "allow once", etc.) and triggers a `notify-send` notification so you don't miss them.
- **Git worktrees** -- right-click a repo folder to create a named worktree with its own session and branch.
- **Orchestrator integration** -- the `/orchestrator` Claude Code skill can spawn its feature sessions directly into claudio's PTY grid. The new `Orchestrator` activity (icon `O` in the left rail, or `Ctrl+Shift+O`) lists every active feature pulled from `~/.claude/orchestrator/<repo>/<feature>/STATUS.md`, with state badges, last log line, and a blocker indicator when the spawned session has open questions. Click a row to focus its tile or spawn one if it's not running yet. CLI form: `claudio new --cwd <path> --mode listening -- claude "Read <starter>"`.
- **Resizable grid** -- drag borders between terminal panes to resize them.

## Install
Expand Down
22 changes: 21 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ pub enum Command {
/// Session mode
#[arg(short, long, default_value = "speaking")]
mode: SessionMode,
/// Working directory for the new session's PTY
#[arg(long)]
cwd: Option<std::path::PathBuf>,
/// Command and args to run in the PTY (everything after `--`).
/// Example: claudio new --cwd /tmp -- claude "Read foo.md"
/// If omitted, the existing default (claude; exec $SHELL) is used.
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
command: Vec<String>,
},
/// List all sessions
List,
Expand Down Expand Up @@ -103,12 +111,24 @@ pub async fn run(cli: Cli) -> anyhow::Result<()> {
let config = crate::config::Config::load()?;
crate::gui::run(&config.socket_path())
}
Some(Command::New { name, mode }) => {
Some(Command::New {
name,
mode,
cwd,
command,
}) => {
let config = crate::config::Config::load()?;
let cmd = if command.is_empty() {
None
} else {
Some(command)
};
let resp = crate::ipc::client::send_new_session(
&config.socket_path(),
name,
mode,
cwd,
cmd,
)
.await?;
println!("Created session: {} ({})", resp.name, resp.id);
Expand Down
2 changes: 2 additions & 0 deletions src/gui/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ actions!(
ToggleMode,
MinimizeSession,
ToggleFileTree,
ToggleOrchestrator,
AddFolder,
FocusFileTreeSearch,
ReadAloud,
Expand All @@ -30,6 +31,7 @@ pub fn register(cx: &mut App) {
KeyBinding::new("ctrl-m", ToggleMode, Some("ClaudioApp")),
KeyBinding::new("ctrl-shift-m", MinimizeSession, Some("ClaudioApp")),
KeyBinding::new("ctrl-b", ToggleFileTree, Some("ClaudioApp")),
KeyBinding::new("ctrl-shift-o", ToggleOrchestrator, Some("ClaudioApp")),
KeyBinding::new("ctrl-f", FocusFileTreeSearch, Some("ClaudioApp")),
KeyBinding::new("ctrl-r", ReadAloud, Some("ClaudioApp")),
KeyBinding::new("ctrl-shift-r", StopSpeech, Some("ClaudioApp")),
Expand Down
79 changes: 79 additions & 0 deletions src/gui/activity_bar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use gpui::*;

use super::app::ClaudioApp;
use super::theme;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Activity {
Files,
Orchestrator,
}

impl Activity {
pub fn label(self) -> &'static str {
match self {
Self::Files => "Files",
Self::Orchestrator => "Orchestrator",
}
}

/// Single-character icon. ASCII-friendly to avoid font fallback paths.
pub fn icon(self) -> &'static str {
match self {
Self::Files => "F",
Self::Orchestrator => "O",
}
}

pub const ALL: [Activity; 2] = [Activity::Files, Activity::Orchestrator];
}

impl ClaudioApp {
pub fn render_activity_bar(
&self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let mut bar = div()
.w(px(48.0))
.h_full()
.flex()
.flex_col()
.items_center()
.py(px(8.0))
.gap(px(4.0))
.bg(rgb(theme::CRUST));

for activity in Activity::ALL {
let is_active = self.active_activity == activity;
let bg = if is_active { theme::SURFACE0 } else { theme::CRUST };
let fg = if is_active { theme::BLUE } else { theme::OVERLAY0 };
let id: SharedString = format!("activity-{}", activity.label()).into();

bar = bar.child(
div()
.id(id)
.w(px(40.0))
.h(px(40.0))
.flex()
.items_center()
.justify_center()
.rounded(px(4.0))
.bg(rgb(bg))
.text_color(rgb(fg))
.text_size(px(16.0))
.cursor_pointer()
.hover(|s| s.bg(rgb(theme::SURFACE0)))
.child(activity.icon())
.on_mouse_down(
MouseButton::Left,
cx.listener(move |app, _ev: &MouseDownEvent, _window, cx| {
app.set_active_activity(activity, cx);
}),
),
);
}

bar
}
}
Loading
Loading