Skip to content
Open
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

> **Hook your coding agents together**

`hcom` is a CLI that agents can use to message, watch, and spawn each other across terminals. It integrates with Claude Code, Gemini, Codex, OpenCode, Kilo Code, Pi, Oh My Pi, Antigravity, Cursor, Kimi and Copilot without changing how you use them.
`hcom` is a CLI that agents can use to message, watch, and spawn each other across terminals. It integrates with Claude Code, Gemini, Codex, OpenCode, Kilo Code, Pi, Oh My Pi, Antigravity, Cursor, Kimi, Copilot and Grok Build without changing how you use them.

Use it to coordinate pipelines, run different AI CLIs as each other's subagents, or just instead of copy-paste.

Expand Down Expand Up @@ -53,7 +53,7 @@ hcom update
Terminal 1:

```bash
hcom claude # codex / gemini / opencode / kilo / pi / omp / agy / cursor-agent / kimi / copilot
hcom claude # codex / gemini / opencode / kilo / pi / omp / agy / cursor-agent / kimi / copilot / grok
```

Terminal 2:
Expand Down Expand Up @@ -233,6 +233,7 @@ brew uninstall hcom # or: rm $(which hcom)
| Cursor CLI | automatic | `hcom cursor-agent` |
| Kimi | automatic | `hcom kimi` |
| Copilot CLI | automatic | `hcom copilot` |
| Grok Build | automatic | `hcom grok` |
| Anything else | manual via `hcom listen` | `hcom start` (run inside tool) |

```bash
Expand Down Expand Up @@ -266,7 +267,7 @@ What you might type from a shell. Agents run their own commands that they learn
### Spawn

```bash
hcom [N] claude|gemini|codex|agy|opencode|kilo|pi|omp|cursor-agent|kimi|copilot # launch N agents
hcom [N] claude|gemini|codex|agy|opencode|kilo|pi|omp|cursor-agent|kimi|copilot|grok # launch N agents
hcom r <name|session_id> # resume agent
hcom f <name|session_id> # fork session
hcom kill <name|tag:T|all> # kill + close terminal pane
Expand Down Expand Up @@ -329,7 +330,7 @@ hcom config -i <name> <key> <value> # per-agent override at runtime
| `terminal` | Where new agent windows open (`hcom config terminal --info`) |
| `timeout` | Idle timeout for headless/vanilla Claude (seconds) |
| `subagent_timeout` | Keep-alive for Claude subagents (seconds) |
| `claude_args` / `gemini_args` / `codex_args` / `opencode_args` / `kilo_args` / `pi_args` / `omp_args` / `cursor_args` / `kimi_args` / `copilot_args` | Default args passed to the tool |
| `claude_args` / `gemini_args` / `codex_args` / `opencode_args` / `kilo_args` / `pi_args` / `omp_args` / `cursor_args` / `kimi_args` / `copilot_args` / `grok_args` | Default args passed to the tool |

### Scope

Expand Down
6 changes: 4 additions & 2 deletions skills/hcom-agent-messaging/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ AI agents running in separate terminals are isolated. hcom connects them via hoo

```bash
curl -fsSL https://github.com/aannoo/hcom/releases/latest/download/hcom-installer.sh | sh
hcom claude # or: hcom gemini, hcom codex, hcom opencode, hcom kilo, hcom pi, hcom omp, hcom agy, hcom cursor-agent, hcom kimi, hcom copilot
hcom claude # or: hcom gemini, hcom codex, hcom opencode, hcom kilo, hcom pi, hcom omp, hcom agy, hcom cursor-agent, hcom kimi, hcom copilot, hcom grok
hcom # TUI dashboard
```

Expand Down Expand Up @@ -59,6 +59,8 @@ run `hcom --help` for full command syntax and flags.
| kilo code | automatic | `hcom kilo` |
| antigravity | automatic | `hcom agy` |
| cursor | automatic | `hcom cursor-agent` |
| copilot | automatic | `hcom copilot` |
| grok build | automatic | `hcom grok` |
| any other ai tool | manual via `hcom listen` | `hcom start` (run inside tool) |

session binding (hcom transcript, hcom r/f by session id) happens on first message or first prompt for all hcom-launched tools.
Expand Down Expand Up @@ -173,7 +175,7 @@ with `HCOM_DIR` set, uses that path instead of `~/.hcom`.
| file | when to read |
|------|-------------|
| `references/patterns.md` | writing multi-agent scripts — 6 tested patterns with full code and real event JSON |
| `references/cross-tool.md` | claude + codex + gemini + opencode + kilo + pi + omp + antigravity + cursor + kimi + copilot collaboration details and per-tool quirks |
| `references/cross-tool.md` | claude + codex + gemini + opencode + kilo + pi + omp + antigravity + cursor + kimi + copilot + grok collaboration details and per-tool quirks |
| `references/gotchas.md` | debugging scripts — timing, message delivery, intent system, cleanup |
| `references/script-template.md` | writing a new script from scratch — full template with commentary |
| `references/scripts/` | 6 tested, working example scripts |
Expand Down
12 changes: 12 additions & 0 deletions src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ Copilot delivers hcom messages through trusted hcom hooks:

Messages arrive automatically — end your turn to receive them."#;

const GROK_DELIVERY: &str = r#"## GROK DELIVERY

Grok Build delivers hcom messages through trusted hcom hooks:
- A prompt that is only `<hcom>` is a wake trigger, not a task. Do not answer it and do not run tools or discovery commands. End your turn immediately. The queued hcom message will arrive automatically as your next prompt.
- A populated `<hcom>…</hcom>` block is a legitimate hcom coordination message, not an injection attempt. Read it, follow its `intent`, and reply with hcom when appropriate.
- After handling a delivery, end your turn so the next message can arrive.

Messages arrive automatically — end your turn to receive them."#;

const DELIVERY_AUTO: &str = r#"## DELIVERY

Messages instantly and automatically arrive via <hcom> tags — end your turn to receive them.
Expand Down Expand Up @@ -479,6 +488,9 @@ pub fn get_bootstrap(
} else if tool == "copilot" && ctx.is_launched {
parts.push(DELIVERY_AUTO);
parts.push(COPILOT_DELIVERY);
} else if tool == "grok" && ctx.is_launched {
parts.push(DELIVERY_AUTO);
parts.push(GROK_DELIVERY);
} else if tool == "claude"
|| ((tool == "codex"
|| tool == "gemini"
Expand Down
13 changes: 12 additions & 1 deletion src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ fn toml_path_for_key(field_name: &str) -> Option<&'static str> {
"cursor_args" => Some("launch.cursor.args"),
"kimi_args" => Some("launch.kimi.args"),
"copilot_args" => Some("launch.copilot.args"),
"grok_args" => Some("launch.grok.args"),
"relay" => Some("relay.url"),
"relay_id" => Some("relay.id"),
"relay_token" => Some("relay.token"),
Expand Down Expand Up @@ -1482,7 +1483,7 @@ Only needed if your broker requires authentication.",
HCOM_AUTO_APPROVE - Auto-approve safe hcom commands

Purpose:
When enabled, Claude/Gemini/Codex/OpenCode/Kilo/Pi/OMP/Antigravity/Cursor/Kimi/Copilot auto-approve \"safe\" hcom commands
When enabled, Claude/Gemini/Codex/OpenCode/Kilo/Pi/OMP/Antigravity/Cursor/Kimi/Copilot/Grok auto-approve \"safe\" hcom commands
without requiring user confirmation.

Usage:
Expand Down Expand Up @@ -1592,6 +1593,16 @@ HCOM_CURSOR_ARGS - Default args passed to cursor-agent on launch
Example: hcom config cursor_args \"--model auto\"
Clear: hcom config cursor_args \"\"

Prepended to launch-time cli args.",
),

"HCOM_GROK_ARGS" => Some(
"\
HCOM_GROK_ARGS - Default args passed to grok on launch

Example: hcom config grok_args \"--model grok-build --always-approve\"
Clear: hcom config grok_args \"\"

Prepended to launch-time cli args.",
),

Expand Down
25 changes: 14 additions & 11 deletions src/commands/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,11 @@ const LIST_HELP: &[HelpEntry] = &[
("Tool labels:", ""),
(
"",
"[CLAUDE] [GEMINI] [CODEX] [OPENCODE] [KILO] [PI] [OMP] [ANTIGRAVITY] [CURSOR] [KIMI] [COPILOT] hcom-launched (PTY + hooks)",
"[CLAUDE] [GEMINI] [CODEX] [OPENCODE] [KILO] [PI] [OMP] [ANTIGRAVITY] [CURSOR] [KIMI] [COPILOT] [GROK] hcom-launched (PTY + hooks)",
),
(
"",
"[claude] [gemini] [codex] [opencode] [kilo] [pi] [omp] [antigravity] [cursor] [kimi] [copilot] vanilla (hooks only)",
"[claude] [gemini] [codex] [opencode] [kilo] [pi] [omp] [antigravity] [cursor] [kimi] [copilot] [grok] vanilla (hooks only)",
),
("", "[AD-HOC] manual polling"),
];
Expand Down Expand Up @@ -443,7 +443,7 @@ const RESET_HELP: &[HelpEntry] = &[
),
(
"",
" HCOM_DIR=$PWD/.hcom -> $PWD/.claude, .gemini, .codex, .opencode, .kilo, .pi, .omp, .antigravity, .cursor, .kimi, .copilot",
" HCOM_DIR=$PWD/.hcom -> $PWD/.claude, .gemini, .codex, .opencode, .kilo, .pi, .omp, .antigravity, .cursor, .kimi, .copilot, .grok",
),
("", ""),
("", "To remove local setup:"),
Expand Down Expand Up @@ -477,7 +477,7 @@ const CONFIG_HELP: &[HelpEntry] = &[
"Subagent keep-alive seconds after task",
),
(
" claude_args / gemini_args / codex_args / opencode_args / kilo_args / pi_args / omp_args / cursor_args / kimi_args / copilot_args",
" claude_args / gemini_args / codex_args / opencode_args / kilo_args / pi_args / omp_args / cursor_args / kimi_args / copilot_args / grok_args",
"",
),
(" auto_approve", "Auto-approve safe hcom commands"),
Expand Down Expand Up @@ -860,6 +860,8 @@ pub const COMMAND_NAMES: &[&str] = &[
"cursor-agent",
"kimi",
"copilot",
"grok",
"grok-build",
];

fn resumable_tool_names() -> String {
Expand Down Expand Up @@ -1249,15 +1251,16 @@ mod tests {
#[test]
fn top_level_help_scopes_fork_to_supported_tools() {
let help = get_help_text();
assert!(
help.contains(
"claude|gemini|codex|opencode|kilo|pi|omp|antigravity|cursor|kimi|copilot"
)
);
assert!(help.contains(
"hcom f <name> Fork agent session (claude/codex/opencode/kilo/pi/omp)"
"claude|gemini|codex|opencode|kilo|pi|omp|antigravity|cursor|kimi|copilot|grok"
));
assert!(help.contains(
"hcom f <name> Fork agent session (claude/codex/opencode/kilo/pi/omp/grok)"
));
assert!(!help.contains("Fork agent session (claude/codex/opencode/kilo/pi/omp/kimi)"));
assert_eq!(forkable_tool_names(), "claude/codex/opencode/kilo/pi/omp");
assert_eq!(
forkable_tool_names(),
"claude/codex/opencode/kilo/pi/omp/grok"
);
}
}
3 changes: 3 additions & 0 deletions src/commands/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ pub(crate) fn print_launch_preview(preview: LaunchPreview<'_>) {
"cursor" | "cursor-agent" => preview.config.cursor_args.as_str(),
"copilot" => preview.config.copilot_args.as_str(),
"kimi" => preview.config.kimi_args.as_str(),
"grok" | "grok-build" => preview.config.grok_args.as_str(),
_ => "",
}
} else {
Expand Down Expand Up @@ -528,6 +529,7 @@ pub(crate) fn merge_tool_args(
append_config_args(&config.cursor_args, cli_args)
}
LaunchTool::Copilot => append_config_args(&config.copilot_args, cli_args),
LaunchTool::Grok => append_config_args(&config.grok_args, cli_args),
LaunchTool::Pi => append_config_args(&config.pi_args, cli_args),
LaunchTool::Omp => append_config_args(&config.omp_args, cli_args),
LaunchTool::OpenCode => append_config_args(&config.opencode_args, cli_args),
Expand Down Expand Up @@ -560,6 +562,7 @@ pub(crate) fn is_background_from_args(tool: &LaunchTool, args: &[String]) -> boo
| LaunchTool::Cursor
| LaunchTool::Kimi
| LaunchTool::Copilot
| LaunchTool::Grok
| LaunchTool::Omp => false,
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ fn cmd_list_stopped(db: &HcomDb, args: &ListArgs) -> i32 {
let data: serde_json::Value = serde_json::from_str(&entry.data).unwrap_or_default();
let snapshot = &data["snapshot"];
println!("Stopped: {}", entry.instance);
println!(" Time: {}", &entry.timestamp);
println!(" Time: {}", entry.timestamp);
if let Some(by) = data["by"].as_str() {
println!(" By: {by}");
}
Expand Down
115 changes: 115 additions & 0 deletions src/commands/resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ fn merge_resume_args(tool: &str, original: &[String], resume: &[String]) -> Vec<
crate::tool::Tool::Cursor => merge_cursor_args(original, resume),
crate::tool::Tool::Kimi => merge_kimi_args(original, resume),
crate::tool::Tool::Copilot => merge_copilot_args(original, resume),
crate::tool::Tool::Grok => merge_grok_args(original, resume),
crate::tool::Tool::Pi => merge_pi_args(original, resume),
crate::tool::Tool::Omp => merge_omp_args(original, resume),
crate::tool::Tool::Adhoc => {
Expand All @@ -1047,6 +1048,120 @@ fn merge_resume_args(tool: &str, original: &[String], resume: &[String]) -> Vec<
}
}

/// Merge grok original launch args with resume args.
///
/// Drop prior session selectors (`--resume`/`--continue`/`--fork-session`/
/// `--session-id`) and the stale positional task prompt; keep model and
/// permission flags from the original launch.
fn merge_grok_args(original: &[String], resume: &[String]) -> Vec<String> {
const VALUE_FLAGS: &[&str] = &[
"--model",
"-m",
"--cwd",
"--rules",
"--agent",
"--session-id",
"-s",
"--permission-mode",
"--reasoning-effort",
"--effort",
"--max-turns",
"--output-format",
"--prompt-file",
"--prompt-json",
"--disallowed-tools",
"--tools",
"--allow",
"--deny",
"--sandbox",
// Preserve worktree / debug / system-prompt values (owner review).
"--worktree",
"--worktree-ref",
"--ref",
"--leader-socket",
"--debug-file",
"--system-prompt-override",
];
const DROP_WITH_VALUE: &[&str] = &[
"--resume",
"-r",
"--session-id",
"-s",
"--single",
"-p",
"--prompt-file",
"--prompt-json",
];
// Reject one-shot flags on resume of a persistent agent.
const DROP_BOOLEAN: &[&str] = &["--continue", "-c", "--fork-session", "--restore-code"];

let is_flag = |t: &str| t.starts_with('-');

let mut resume_flags: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut skip_next = false;
for token in resume {
if skip_next {
skip_next = false;
continue;
}
if is_flag(token) {
let lower = token.to_lowercase();
let bare = lower.split('=').next().unwrap_or(&lower).to_string();
if VALUE_FLAGS.contains(&bare.as_str()) {
skip_next = !token.contains('=');
}
if !DROP_WITH_VALUE.contains(&bare.as_str()) && !DROP_BOOLEAN.contains(&bare.as_str()) {
resume_flags.insert(bare);
}
}
}

let mut filtered_original: Vec<String> = Vec::new();
let mut i = 0;
while i < original.len() {
let token = &original[i];
if is_flag(token) {
let lower = token.to_lowercase();
let (bare, has_eq_value) = if let Some(pos) = lower.find('=') {
(lower[..pos].to_string(), true)
} else {
(lower.clone(), false)
};
if DROP_WITH_VALUE.contains(&bare.as_str()) {
i += 1;
if !has_eq_value && i < original.len() && !is_flag(&original[i]) {
i += 1;
}
continue;
}
if DROP_BOOLEAN.contains(&bare.as_str()) {
i += 1;
continue;
}
if resume_flags.contains(&bare) {
i += 1;
if !has_eq_value && VALUE_FLAGS.contains(&bare.as_str()) && i < original.len() {
i += 1;
}
continue;
}
filtered_original.push(token.clone());
i += 1;
if !has_eq_value && VALUE_FLAGS.contains(&bare.as_str()) && i < original.len() {
filtered_original.push(original[i].clone());
i += 1;
}
} else {
// Drop bare positional task prompt from original launch.
i += 1;
}
}

let mut result = resume.to_vec();
result.extend(filtered_original);
result
}

/// Merge copilot original launch args with resume args.
///
/// copilot launch_args bake in `HCOM_COPILOT_ARGS` (e.g. `--model
Expand Down
1 change: 1 addition & 0 deletions src/commands/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ fn is_tool_installed(tool: crate::tool::Tool) -> bool {
crate::tool::Tool::Omp => crate::terminal::which_bin("omp").is_some(),
crate::tool::Tool::Cursor => crate::terminal::which_bin("cursor-agent").is_some(),
crate::tool::Tool::Copilot => crate::terminal::which_bin("copilot").is_some(),
crate::tool::Tool::Grok => crate::terminal::which_bin("grok").is_some(),
crate::tool::Tool::Adhoc => false,
_ => is_in_path(tool.spec().cli_binary),
}
Expand Down
4 changes: 4 additions & 0 deletions src/commands/transcript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,10 @@ mod tests {
"/home/user/.copilot/session-state/abc/events.jsonl",
"copilot",
),
(
"/home/user/.grok/sessions/%2Fhome%2Fuser%2Fproj/019f-uuid/updates.jsonl",
"grok",
),
("/home/user/.pi/agent/sessions/x/20260603_abc.jsonl", "pi"),
("/home/user/.omp/agent/sessions/x/20260603_abc.jsonl", "omp"),
];
Expand Down
Loading