Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ serde_json = "1"
sha2 = "0.10"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] }
toml = "0.8"
toml_edit = "0.22"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
unicode-width = "0.2"
Expand Down
6 changes: 4 additions & 2 deletions docs/next/api/herdr-api.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2120,7 +2120,8 @@
"cursor",
"mastracode",
"antigravity_cli",
"grok"
"grok",
"jcode"
],
"type": "string"
},
Expand Down Expand Up @@ -7115,7 +7116,8 @@
"cursor",
"mastracode",
"antigravity_cli",
"grok"
"grok",
"jcode"
],
"type": "string"
},
Expand Down
1 change: 1 addition & 0 deletions docs/next/website/src/content/docs/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Automatic detection works out of the box for common coding agents. The important
| Antigravity CLI | screen manifest | session |
| Kiro CLI | screen manifest | none |
| Maki | screen manifest | none |
| Jcode | screen manifest | session |

Detected but less thoroughly tested: Gemini CLI and Cline. Unsupported agents still run normally as terminal processes. They just may not get rich state unless you add an integration or report state over the socket API.

Expand Down
24 changes: 20 additions & 4 deletions docs/next/website/src/content/docs/integrations.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Integrations
description: Install Herdr integrations for Pi, OMP, Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Kimi Code CLI, OpenCode, Kilo Code CLI, Hermes Agent, Qoder CLI, Cursor Agent CLI, MastraCode, Antigravity CLI, and Grok CLI.
description: Install Herdr integrations for Pi, OMP, Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Kimi Code CLI, OpenCode, Kilo Code CLI, Hermes Agent, Qoder CLI, Cursor Agent CLI, MastraCode, Antigravity CLI, Grok CLI, and Jcode.
---

Herdr detects supported agents automatically. Official integrations can add native session identity for restore, lifecycle state reports, or both.
Expand Down Expand Up @@ -28,6 +28,7 @@ herdr integration install cursor
herdr integration install mastracode
herdr integration install antigravity-cli
herdr integration install grok
herdr integration install jcode
```

## Uninstall integrations
Expand All @@ -49,6 +50,7 @@ herdr integration uninstall cursor
herdr integration uninstall mastracode
herdr integration uninstall antigravity-cli
herdr integration uninstall grok
herdr integration uninstall jcode
```

## How Herdr uses integrations
Expand All @@ -58,13 +60,13 @@ Herdr uses integrations in two different ways:
| Integration type | Agents | Effect |
| --- | --- | --- |
| Lifecycle authority | Pi, OMP, Kimi Code CLI, OpenCode, Kilo Code CLI, MastraCode | When installed and actively reporting for the pane, hook or plugin events author `idle`, `working`, and `blocked`. Herdr does not also use screen manifest fallback for that same lifecycle authority. |
| Session identity | Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Qoder CLI, Cursor Agent CLI, Hermes Agent, Antigravity CLI, Grok CLI | The integration reports native session references for restore. State still comes from Herdr's screen manifest detection. |
| Session identity | Claude Code, Codex, GitHub Copilot CLI, Devin CLI, Droid, Qoder CLI, Cursor Agent CLI, Hermes Agent, Antigravity CLI, Grok CLI, Jcode | The integration reports native session references for restore. State still comes from Herdr's screen manifest detection. |

Custom socket integrations can also report state when they define state that is not visible in the native terminal UI.

Some integrations report native agent session references. Herdr uses official session references to resume Claude Code, Codex, Devin CLI, Droid, Kimi Code CLI, Qoder CLI, Cursor Agent CLI, Grok CLI, GitHub Copilot CLI, Pi, OMP, Hermes Agent, OpenCode, Kilo Code CLI, MastraCode, and Antigravity CLI panes after a Herdr server restart unless `[session] resume_agents_on_restore = false` disables it.
Some integrations report native agent session references. Herdr uses official session references to resume Claude Code, Codex, Devin CLI, Droid, Kimi Code CLI, Qoder CLI, Cursor Agent CLI, Grok CLI, Jcode, GitHub Copilot CLI, Pi, OMP, Hermes Agent, OpenCode, Kilo Code CLI, MastraCode, and Antigravity CLI panes after a Herdr server restart unless `[session] resume_agents_on_restore = false` disables it.

Native session restore requires current Herdr integrations: Pi integration version `2`, OMP version `3`, Claude Code version `6`, Codex version `5`, GitHub Copilot CLI version `2`, Devin CLI version `2`, Droid version `2`, Kimi Code CLI version `3`, Qoder CLI version `2`, Cursor Agent CLI version `1`, Grok CLI version `1`, OpenCode version `5`, Kilo Code CLI version `1`, Hermes Agent version `2`, MastraCode version `1`, or Antigravity CLI version `1`. Check installed versions with `herdr integration status`.
Native session restore requires current Herdr integrations: Pi integration version `2`, OMP version `3`, Claude Code version `6`, Codex version `5`, GitHub Copilot CLI version `2`, Devin CLI version `2`, Droid version `2`, Kimi Code CLI version `3`, Qoder CLI version `2`, Cursor Agent CLI version `1`, Grok CLI version `1`, Jcode version `1`, OpenCode version `5`, Kilo Code CLI version `1`, Hermes Agent version `2`, MastraCode version `1`, or Antigravity CLI version `1`. Check installed versions with `herdr integration status`.

## Pi

Expand Down Expand Up @@ -288,6 +290,20 @@ Herdr uses `~/.grok` by default, or `GROK_HOME` when set. The Grok config direct

After Grok emits a session start event, Herdr can use the reported session id to resume the pane with `grok --resume <id>`.

## Jcode

Install the Jcode hook:

```bash
herdr integration install jcode
```

The hook reports native session identity through Jcode's `session_start` observer while Jcode runs inside a Herdr pane. Jcode state remains screen-driven: the composer marker and processing status line provide working and idle evidence, while shell mode and user-opened overlays remain idle.

Herdr uses `~/.jcode`. The Jcode config directory must already exist. Install writes `hooks/herdr-agent-state.sh` and appends it to `[hooks].session_start` in `config.toml`. Jcode runs each command in that lifecycle hook array directly. Reinstalling does not duplicate the Herdr command, and uninstall removes only the Herdr command while preserving every other observer.

After Jcode emits a session start event, Herdr can resume the pane with `jcode --resume <id>` after a server restart. The integration reports session identity only and never reports lifecycle state.

## Custom status labels

Integrations report lifecycle state as semantic state only. For example, report an agent as `working` without adding display fields to the lifecycle report.
Expand Down
1 change: 1 addition & 0 deletions docs/next/website/src/content/docs/session-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Native session restore requires these Herdr integration versions or newer:
| Codex | `5` | `codex resume <id>` |
| Cursor Agent CLI | `1` | `cursor-agent --resume <id>` |
| Grok CLI | `1` | `grok --resume <id>` |
| Jcode | `1` | `jcode --resume <id>` |
| GitHub Copilot CLI | `2` | `copilot --resume=<id>` |
| Devin CLI | `2` | `devin --resume <id>` |
| Droid | `2` | `droid --resume <id>` |
Expand Down
11 changes: 11 additions & 0 deletions docs/next/website/src/data/config-reference.json
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,17 @@
"on",
"off"
]
},
{
"key": "ui.sound.agents.jcode",
"type": "enum",
"default": "\"default\"",
"description": "Sound override for detected Jcode agents.",
"values": [
"default",
"on",
"off"
]
}
]
},
Expand Down
26 changes: 26 additions & 0 deletions src/agent_resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub fn is_reserved_native_state_source(source: &str, agent: &str) -> bool {
| ("herdr:qodercli", "qodercli")
| ("herdr:cursor", "cursor")
| ("herdr:grok", "grok")
| ("herdr:jcode", "jcode")
)
}

Expand Down Expand Up @@ -197,6 +198,9 @@ pub fn plan(source: &str, agent: &str, session_ref: &AgentSessionRef) -> Option<
("herdr:grok", "grok", AgentSessionRefKind::Id) => {
vec!["grok".into(), "--resume".into(), session_ref.value.clone()]
}
("herdr:jcode", "jcode", AgentSessionRefKind::Id) => {
vec!["jcode".into(), "--resume".into(), session_ref.value.clone()]
}
_ => return None,
};

Expand Down Expand Up @@ -233,6 +237,7 @@ pub(crate) fn is_official_agent_source(source: &str, agent: &str) -> bool {
| ("herdr:cursor", "cursor")
| ("herdr:antigravity_cli", "agy")
| ("herdr:grok", "grok")
| ("herdr:jcode", "jcode")
)
}

Expand Down Expand Up @@ -264,6 +269,7 @@ mod tests {
assert!(is_reserved_native_state_source("herdr:claude", "claude"));
assert!(is_reserved_native_state_source("herdr:codex", "codex"));
assert!(is_reserved_native_state_source("herdr:devin", "devin"));
assert!(is_reserved_native_state_source("herdr:jcode", "jcode"));
assert!(!is_reserved_native_state_source("herdr:kimi", "kimi"));
assert!(!is_reserved_native_state_source(
"herdr:opencode",
Expand Down Expand Up @@ -435,6 +441,16 @@ mod tests {
.argv,
vec!["grok", "--resume", "grok-session"]
);
assert_eq!(
plan(
"herdr:jcode",
"jcode",
&AgentSessionRef::id("jcode-session").unwrap()
)
.unwrap()
.argv,
vec!["jcode", "--resume", "jcode-session"]
);
}

#[test]
Expand Down Expand Up @@ -619,6 +635,9 @@ mod tests {

let devin_plan = plan("herdr:devin", "devin", &AgentSessionRef::id(id).unwrap()).unwrap();
assert_eq!(devin_plan.argv, vec!["devin", "--resume", id]);

let jcode_plan = plan("herdr:jcode", "jcode", &AgentSessionRef::id(id).unwrap()).unwrap();
assert_eq!(jcode_plan.argv, vec!["jcode", "--resume", id]);
}

#[test]
Expand All @@ -628,6 +647,7 @@ mod tests {
let kilo_session = absolute_test_path("kilo-session");
let copilot_session = absolute_test_path("copilot-session");
let devin_session = absolute_test_path("devin-session");
let jcode_session = absolute_test_path("jcode-session");
assert!(plan(
"herdr:hermes",
"hermes",
Expand Down Expand Up @@ -658,6 +678,12 @@ mod tests {
&AgentSessionRef::path(&devin_session).unwrap()
)
.is_none());
assert!(plan(
"herdr:jcode",
"jcode",
&AgentSessionRef::path(&jcode_session).unwrap()
)
.is_none());
assert!(session_ref_from_snapshot(
"herdr:mastracode",
"mastracode",
Expand Down
4 changes: 3 additions & 1 deletion src/api/schema/integrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ pub enum IntegrationTarget {
Mastracode,
AntigravityCli,
Grok,
Jcode,
}

impl IntegrationTarget {
pub(crate) const ALL: [Self; 16] = [
pub(crate) const ALL: [Self; 17] = [
Self::Pi,
Self::Omp,
Self::Claude,
Expand All @@ -49,6 +50,7 @@ impl IntegrationTarget {
Self::Mastracode,
Self::AntigravityCli,
Self::Grok,
Self::Jcode,
];
}

Expand Down
9 changes: 6 additions & 3 deletions src/cli/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ fn parse_integration_target(
) -> std::io::Result<Option<IntegrationTarget>> {
let Some(target) = args.first().map(|arg| arg.as_str()) else {
eprintln!(
"usage: herdr integration {action} <pi|omp|claude|codex|copilot|devin|droid|kimi|opencode|kilo|hermes|qodercli|cursor|mastracode|grok>"
"usage: herdr integration {action} <pi|omp|claude|codex|copilot|devin|droid|kimi|opencode|kilo|hermes|qodercli|cursor|mastracode|antigravity-cli|grok|jcode>"
);
return Ok(None);
};
if args.len() != 1 {
eprintln!(
"usage: herdr integration {action} <pi|omp|claude|codex|copilot|devin|droid|kimi|opencode|kilo|hermes|qodercli|cursor|mastracode|grok>"
"usage: herdr integration {action} <pi|omp|claude|codex|copilot|devin|droid|kimi|opencode|kilo|hermes|qodercli|cursor|mastracode|antigravity-cli|grok|jcode>"
);
return Ok(None);
}
Expand All @@ -131,10 +131,11 @@ fn parse_integration_target(
"mastracode" => IntegrationTarget::Mastracode,
"antigravity-cli" | "antigravity_cli" => IntegrationTarget::AntigravityCli,
"grok" => IntegrationTarget::Grok,
"jcode" => IntegrationTarget::Jcode,
_ => {
eprintln!("unknown integration target: {target}");
eprintln!(
"currently supported: pi, omp, claude, codex, copilot, devin, droid, kimi, opencode, kilo, hermes, qodercli, cursor, mastracode, antigravity-cli, grok"
"currently supported: pi, omp, claude, codex, copilot, devin, droid, kimi, opencode, kilo, hermes, qodercli, cursor, mastracode, antigravity-cli, grok, jcode"
);
return Ok(None);
}
Expand All @@ -161,6 +162,7 @@ fn print_integration_help() {
eprintln!(" herdr integration install mastracode");
eprintln!(" herdr integration install antigravity-cli");
eprintln!(" herdr integration install grok");
eprintln!(" herdr integration install jcode");
eprintln!(" herdr integration uninstall pi");
eprintln!(" herdr integration uninstall omp");
eprintln!(" herdr integration uninstall claude");
Expand All @@ -177,5 +179,6 @@ fn print_integration_help() {
eprintln!(" herdr integration uninstall mastracode");
eprintln!(" herdr integration uninstall antigravity-cli");
eprintln!(" herdr integration uninstall grok");
eprintln!(" herdr integration uninstall jcode");
eprintln!(" herdr integration status [--outdated-only]");
}
2 changes: 1 addition & 1 deletion src/config/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ pub struct ExperimentalConfig {
/// if the list contains no valid names, the reveal does not apply.
/// Accepted names: pi, claude, codex, gemini, cursor, devin, cline,
/// opencode, copilot, kimi, kiro, droid, amp, grok, hermes, kilo,
/// qodercli, qoder, maki.
/// qodercli, qoder, maki, jcode.
/// Default: empty.
pub cjk_ime_agents: Vec<String>,
/// Cursor shape rendered for the IME anchor when
Expand Down
1 change: 1 addition & 0 deletions src/config/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ rows = [[{ token = "git_status", fg = "#ff00aa" }], [{ token = "$jj", bold = tru
Agent::Kilo,
Agent::Qodercli,
Agent::Maki,
Agent::Jcode,
];
let entries = agents
.iter()
Expand Down
3 changes: 3 additions & 0 deletions src/config/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct AgentSoundOverrides {
pub kilo: AgentSoundSetting,
pub qodercli: AgentSoundSetting,
pub maki: AgentSoundSetting,
pub jcode: AgentSoundSetting,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
Expand Down Expand Up @@ -140,6 +141,7 @@ impl AgentSoundOverrides {
Some(Agent::Kilo) => self.kilo,
Some(Agent::Qodercli) => self.qodercli,
Some(Agent::Maki) => self.maki,
Some(Agent::Jcode) => self.jcode,
None => AgentSoundSetting::Default,
}
}
Expand Down Expand Up @@ -179,6 +181,7 @@ impl Default for AgentSoundOverrides {
kilo: AgentSoundSetting::Default,
qodercli: AgentSoundSetting::Default,
maki: AgentSoundSetting::Default,
jcode: AgentSoundSetting::Default,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/detect/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ const BUNDLED_MANIFESTS: &[(&str, &str)] = &[
("gemini", include_str!("manifests/gemini.toml")),
("grok", include_str!("manifests/grok.toml")),
("hermes", include_str!("manifests/hermes.toml")),
("jcode", include_str!("manifests/jcode.toml")),
("kilo", include_str!("manifests/kilo.toml")),
("kimi", include_str!("manifests/kimi.toml")),
("kiro", include_str!("manifests/kiro.toml")),
Expand Down
47 changes: 47 additions & 0 deletions src/detect/manifests/jcode.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
id = "jcode"
version = "2026.08.03.1"
min_engine_version = 1
updated_at = "2026-08-03T00:00:00Z"
aliases = ["j-code", "herdr:jcode"]

# Jcode pins its composer to the bottom of the pane. The next message number is
# followed by one marker: `>` for ready, `…` while a turn is in flight, `»` for
# an armed skill, or `$` for shell mode. Only `…` means working.
[[rules]]
id = "composer_processing"
state = "working"
priority = 900
region = "bottom_non_empty_lines(4)"
visible_working = true
line_regex = ['^\s*\d+…']

# A multi-line composer can move its marker outside the recent-line window.
# Jcode's processing line then shows either a braille spinner or an animated
# three-dot tool bar directly above the composer.
[[rules]]
id = "status_line_spinner_working"
state = "working"
priority = 890
region = "bottom_non_empty_lines(4)"
visible_working = true
line_regex = ['^[\x{2800}-\x{28FF}] \S']

[[rules]]
id = "status_line_tool_bar_working"
state = "working"
priority = 890
region = "bottom_non_empty_lines(4)"
visible_working = true
line_regex = ['^[·●]{3} \S+ [·●]{3}(\s|$)']

[[rules]]
id = "composer_ready"
state = "idle"
priority = 850
region = "bottom_non_empty_lines(4)"
visible_idle = true
line_regex = ['^\s*\d+[>»$]']

# Jcode emits no state-bearing OSC progress/title sequence. It also has no
# in-session approval prompt, so there is deliberately no blocked rule. Shell
# mode and user-opened overlays correctly remain idle.
Loading
Loading