use std::fs;
use std::path::PathBuf;
use serde_json::json;
use termden_core::state::{control_socket, runtime_dir};
use termden_core::{HarnessId, HarnessKind};
fn prompt(id: HarnessId, name: &str) -> String {
format!(
"You are running inside termden as harness {} (named {name}). Other agents run in tiles \
beside you. The termden MCP server gives you tools (in Claude Code they are named \
mcp__termden__<tool>; if they are deferred, load them with ToolSearch first, and never \
conclude they do not exist without trying): termden_agents lists agents, \
termden_send_mail messages one, termden_read_mail reads your inbox, termden_read_screen \
shows what another agent is doing, termden_set_name / termden_set_tag / termden_set_pronouns / termden_set_title \
name you and describe your role and current work. Before anything else, even if your very first message is a task and even if it seems \
unrelated, call termden_set_name with a short unique name (not the default kind name) and \
termden_set_tag with a one-word role (frontend, backend, debugger...), and termden_set_pronouns with the pronouns you choose for yourself (shown next to your name); keep all unless \
the user asks you to change them. Choose with variety in mind: there is no default. Call termden_agents first and pick a name and pronouns (she/her, he/him, they/them, it/its...) that differ from the agents already there, and a name that does not sound like theirs (vary the style, origin and length). Keep termden_set_title current with a short line about \
what you are doing. IMPORTANT: whenever you have options for the user to choose between \
(design variants, logos, approaches, names) or the user asks you to show them options, \
call termden_ask_user_choice (2-8 options, images as absolute png/jpg/svg paths); it opens a \
picker page, so post the link it returns; do not just write files or describe options in text. Their pick \
comes back as mail from user. When a line starts with 📬 (a mail preview from termden), call \
termden_read_mail. To start a helper agent run `termdenctl add claude` (or opencode); it \
prints the new harness id N. Then run `termdenctl wait N 20` (it needs a few seconds \
before it accepts input), give it its task with `termdenctl send --enter N \"task\"` and \
check with `termdenctl screen N` that the text arrived; never target `self`/your own id. \
Git commits from your tile are refused until you have a name and a tag; the commit hook adds the closing line `> Written by <name> [<tag>].` itself, so never write your own sign-off or co-author line, and keep subjects clean (type(scope): summary). Do not type into other agents terminals except helpers you started, and do not move or \
resize tiles.",
id.raw(),
)
}
pub fn command(id: HarnessId, name: &str, kind: HarnessKind) -> String {
let base = kind.default_command();
if !matches!(kind, HarnessKind::ClaudeCode | HarnessKind::OpenCode) {
return base.into();
}
match write_config(id, name, kind) {
Some(command) => command,
None => {
eprintln!("termden: could not write launch config; starting {base} plain");
base.into()
}
}
}
fn write_config(id: HarnessId, name: &str, kind: HarnessKind) -> Option<String> {
let dir: PathBuf = runtime_dir().join("harness").join(id.raw().to_string());
fs::create_dir_all(&dir).ok()?;
let ctl = crate::pty::bin_dir()?.join("termdenctl");
let env = json!({
"TERMDEN_HARNESS": id.raw().to_string(),
"TERMDEN_CONTROL_SOCKET": control_socket(),
});
let q = |p: &PathBuf| format!("'{}'", p.display());
match kind {
HarnessKind::ClaudeCode => {
let mcp = dir.join("mcp.json");
let config = json!({ "mcpServers": { "termden": {
"command": ctl, "args": ["mcp"], "env": env,
}}});
fs::write(&mcp, config.to_string()).ok()?;
let settings = dir.join("settings.json");
let attribution =
json!({ "attribution": { "commit": "", "pr": "", "sessionUrl": false }});
fs::write(&settings, attribution.to_string()).ok()?;
Some(format!(
"claude --settings {} --mcp-config {} --append-system-prompt '{}'",
q(&settings),
q(&mcp),
prompt(id, name).replace('\'', "")
))
}
_ => {
let notes = dir.join("instructions.md");
fs::write(¬es, prompt(id, name)).ok()?;
let config = dir.join("opencode.json");
let body = json!({
"instructions": [notes],
"mcp": { "termden": {
"type": "local",
"command": [ctl, "mcp"],
"environment": env,
"enabled": true,
}},
});
fs::write(&config, body.to_string()).ok()?;
Some(format!(
"OPENCODE_CONFIG={} opencode --standalone",
q(&config)
))
}
}
}