diff --git a/docs/agent-types.md b/docs/agent-types.md index b3fca42..3dde72f 100644 --- a/docs/agent-types.md +++ b/docs/agent-types.md @@ -1,7 +1,7 @@ # Agent types agmsg supports several agent runtimes — claude-code, codex, gemini, antigravity, -copilot, opencode — and each is described by a small **manifest** so that the rest +copilot, opencode, hermes, cursor — and each is described by a small **manifest** so that the rest of agmsg (detection, the join whitelist, spawn, and delivery routing) discovers it from data instead of hardcoded `case` arms. diff --git a/install.sh b/install.sh index f2cb1f2..08da978 100755 --- a/install.sh +++ b/install.sh @@ -171,7 +171,7 @@ while [[ $# -gt 0 ]]; do echo "Options:" echo " --cmd Command & skill folder name (default: agmsg)" echo " Claude Code: /, Codex/Gemini/Antigravity: \$" - echo " --agent-type Agent type: claude-code, codex, gemini, antigravity, opencode, hermes" + echo " --agent-type Agent type: claude-code, codex, gemini, antigravity, opencode, hermes, cursor" echo " Selects which template becomes SKILL.md (matches the" echo " arg passed to join.sh / whoami.sh)" echo " --update Update skill scripts only (preserve DB and teams)" @@ -250,7 +250,7 @@ if [ "$UPDATE_ONLY" = true ]; then # shared SKILL.md; their dedicated copies are dropped separately below.) TPL_TYPE="codex" case "$AGENT_TYPE" in - gemini|antigravity|opencode|hermes) TPL_TYPE="$AGENT_TYPE" ;; + gemini|antigravity|opencode|hermes|cursor) TPL_TYPE="$AGENT_TYPE" ;; esac sed "s/__SKILL_NAME__/$SKILL_NAME/g" "$(agmsg_type_template_path "$TPL_TYPE")" > "$SKILL_DIR/SKILL.md" # Recursive copy so nested helper dirs (scripts/lib/, scripts/drivers/types/) @@ -340,7 +340,7 @@ mkdir -p "$SKILL_DIR"/{scripts,types,db,agents} # codex template by default; gemini/antigravity/opencode get their own. TPL_TYPE="codex" case "$AGENT_TYPE" in - gemini|antigravity|opencode|hermes) TPL_TYPE="$AGENT_TYPE" ;; + gemini|antigravity|opencode|hermes|cursor) TPL_TYPE="$AGENT_TYPE" ;; esac sed "s/__SKILL_NAME__/$CMD_NAME/g" "$(agmsg_type_template_path "$TPL_TYPE")" > "$SKILL_DIR/SKILL.md" # Recursive copy so nested helper dirs (scripts/lib/, scripts/drivers/types/) ship diff --git a/scripts/drivers/types/cursor/_delivery.sh b/scripts/drivers/types/cursor/_delivery.sh new file mode 100644 index 0000000..1db4ba1 --- /dev/null +++ b/scripts/drivers/types/cursor/_delivery.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# cursor delivery plug — Cursor CLI (cursor-agent) rule file (#131). +# +# The Cursor CLI auto-loads project rules from .cursor/rules/*.mdc. An .mdc with +# `alwaysApply: true` in its frontmatter is applied on every turn, which is the +# always-on instruction channel agmsg needs — the cursor-agent equivalent of +# gemini/opencode's markdown rules file. Only turn|off reach this function: +# cursor's manifest declares delivery_modes=turn off, so delivery.sh's central +# gate rejects monitor/both first. Uses resolve_hooks_file + SKILL_DIR from +# delivery.sh's sourced context. +agmsg_delivery_apply() { + local type="$1" + local project="$2" + local mode="$3" + local rule_file + rule_file=$(resolve_hooks_file "$type" "$project") + + rm -f "$rule_file" + + if [ "$mode" = "turn" ]; then + mkdir -p "$(dirname "$rule_file")" + cat < "$rule_file" +--- +alwaysApply: true +--- +# agmsg Integration Rule + +## PostToolUse +After each tool call, automatically check the agmsg inbox for unread messages. +- Command: '$SKILL_DIR/scripts/check-inbox.sh' '$type' '$project' +EOF + fi +} +agmsg_delivery_status() { rulefile_status "$@"; } diff --git a/scripts/drivers/types/cursor/template.md b/scripts/drivers/types/cursor/template.md new file mode 100644 index 0000000..5960218 --- /dev/null +++ b/scripts/drivers/types/cursor/template.md @@ -0,0 +1,139 @@ +--- +name: __SKILL_NAME__ +description: Cross-agent messaging via SQLite. Send messages between Claude Code, Codex, Gemini CLI, and other agents. No daemon, no network, no dependencies beyond bash and sqlite3. +--- + +Agent messaging command. **IMPORTANT: Always use the provided scripts. NEVER directly read or edit config files, DB, or team data. There is NO register.sh — use join.sh to join a team.** + +## Identity + +If you already know your AGENT and TEAMS from a previous `$__SKILL_NAME__` call in this session, skip to **Execute** below. + +Otherwise, run: `~/.agents/skills/__SKILL_NAME__/scripts/whoami.sh "$(pwd)" cursor` + +Four possible outputs: + +**A) Single identity:** +`agent= teams= type=cursor project=` +→ Remember AGENT and TEAMS, then go to **Execute**. + +**B) Multiple identities:** +`multiple=true agents= teams= type=cursor project=` +→ Ask the user which agent name to use for this session, then go to **Execute**. + +**C) Not in a team:** +`not_joined=true available_teams=` (or `available_teams=none`) +→ Show the user the available teams from the output, then: + + > **First-time setup required.** + > Joining a team so this agent can send and receive messages. + > - **Team name**: a group of agents that can message each other (available: ) + > - **Agent name**: this agent's identity within the team + + 1. Ask: "Enter a team name (joins existing or creates new)" + 2. Ask: "Enter a name for this agent" + 3. **You MUST use join.sh** — run: `~/.agents/skills/__SKILL_NAME__/scripts/join.sh cursor "$(pwd)"` + 4. Show the result and explain: + + > **Joined!** You can now use `$__SKILL_NAME__` to check and send messages. + > - `$__SKILL_NAME__` — check inbox + > - `$__SKILL_NAME__ send ` — send a message + > - `$__SKILL_NAME__ team` — list team members + > - `$__SKILL_NAME__ history` — message history + + 5. **REQUIRED — Do NOT skip this step.** Ask the user to pick a delivery mode using exactly this prompt: + + ``` + Choose delivery mode for incoming messages: + + 1) turn — Check inbox at the end of each assistant turn + Stop hook pulls after each response. + + 2) off — No automatic delivery + Manual $__SKILL_NAME__ only. + + [1]: + ``` + + - **Wait for the user's answer before proceeding.** Empty input means `1` (turn). + - Map the chosen number to a mode (`1`→`turn`, `2`→`off`) and run: + `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set cursor "$(pwd)"` + - OpenCode has no Monitor tool, so `monitor` and `both` modes are not offered here. + + 6. Then check inbox for the newly joined team. + +**D) Suggestions for reuse:** +`suggest=true agents= teams= type=cursor project= available_teams=` +→ No exact registration exists for this project, but there are same-type agent names registered elsewhere. + + 1. Show the suggested agent names to the user. + 2. Ask whether to reuse one of those names or choose a new one. + 3. Ask for the team name to join (existing or new). + 4. Run: `~/.agents/skills/__SKILL_NAME__/scripts/join.sh cursor "$(pwd)"` + 5. Then continue with the normal post-join flow above. + +## Execute + +**Only use scripts in `~/.agents/skills/__SKILL_NAME__/scripts/` — do not read or modify files under `teams/` or `db/` directly.** + +**If no arguments provided (DEFAULT action — always do this when the command is invoked without arguments):** +1. **IMMEDIATELY** run inbox check for each TEAM: `~/.agents/skills/__SKILL_NAME__/scripts/inbox.sh $TEAM $AGENT` +2. Do NOT ask the user what to do — just run the inbox check. +3. If there are messages, read and respond appropriately. To reply: + `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +If argument is "history": +1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/history.sh $TEAM $AGENT` + +If argument is "team": +1. For each TEAM, run: `~/.agents/skills/__SKILL_NAME__/scripts/team.sh $TEAM` + +If argument starts with "send" (e.g. "send misaki check the server"): +1. Parse target agent and message from the arguments +2. Determine which team the target agent belongs to, then run: + `~/.agents/skills/__SKILL_NAME__/scripts/send.sh $TEAM $AGENT ""` + +If argument is "config": +1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh show` +2. Show the output to the user. + +If argument starts with "config set" (e.g. "config set hook.check_interval 30"): +1. Parse key and value from the arguments. +2. Run: `~/.agents/skills/__SKILL_NAME__/scripts/config.sh set ` + +If argument starts with "actas" followed by an agent name (e.g. "actas alice"): +1. Parse the new role name. +2. Run `~/.agents/skills/__SKILL_NAME__/scripts/identities.sh "$(pwd)" cursor` to see whether the role is already registered for this (project, type). +3. If the name does not appear in the output, join under the existing team. For a single team, run `~/.agents/skills/__SKILL_NAME__/scripts/join.sh cursor "$(pwd)"`. For multiple teams, ask the user which team to join the new role into. +4. Set the session's active FROM to `` for every `send.sh` call until another `actas`. +5. Tell the user: "Now acting as ``. Sends will use `` as the from agent. (OpenCode has no Monitor tool, so receive still covers all of your registered roles in this project.)" + +If argument starts with "drop" followed by an agent name (e.g. "drop alice"): +1. Parse the role name. +2. Run `~/.agents/skills/__SKILL_NAME__/scripts/reset.sh "$(pwd)" cursor ` to remove that role's registration. +3. If the session's active FROM was ``, clear that state. +4. Tell the user: "Dropped role `` from this project." + +If argument is "mode" (no further args): +1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh status cursor "$(pwd)"` +2. Show the output to the user. + +If argument starts with "mode" followed by a mode name (e.g. "mode turn"): +1. Parse the mode. OpenCode supports only `turn` and `off` — reject `monitor` and `both` with: "OpenCode has no Monitor tool; only `turn` or `off` modes are supported." +2. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set cursor "$(pwd)"` + +If argument is "hook on" (legacy alias): +1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set turn cursor "$(pwd)"` +2. Tell the user: "Delivery mode set to 'turn' (legacy hook on behavior)." + +If argument is "hook off" (legacy alias): +1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/delivery.sh set off cursor "$(pwd)"` +2. Tell the user: "Delivery mode set to 'off'." + +If argument is "version": +1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/version.sh` +2. Show the output — the installed version (git-describe provenance recorded at install time). + +If argument is "reset": +1. Run: `~/.agents/skills/__SKILL_NAME__/scripts/reset.sh "$(pwd)" cursor` +2. Tell the user the result. diff --git a/scripts/drivers/types/cursor/type.conf b/scripts/drivers/types/cursor/type.conf new file mode 100644 index 0000000..b78afae --- /dev/null +++ b/scripts/drivers/types/cursor/type.conf @@ -0,0 +1,8 @@ +# agmsg agent-type manifest — read-only key=value DATA. NEVER sourced. +name=cursor +template=template.md +cli=cursor-agent +detect_proc=cursor-agent cursor-agent-* +hooks_file=.cursor/rules/agmsg.mdc +monitor=no +delivery_modes=turn off diff --git a/tests/test_delivery.bats b/tests/test_delivery.bats index c6b98af..eba2c0f 100644 --- a/tests/test_delivery.bats +++ b/tests/test_delivery.bats @@ -1179,6 +1179,64 @@ JSON [ "$count" -eq 1 ] } +# --- cursor agent tests (#131) --- + +@test "cursor is accepted as an agent type (turn mode)" { + run bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" + [ "$status" -eq 0 ] + [[ "$output" =~ "Delivery mode set to 'turn'" ]] + [ -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] + grep -q "check-inbox.sh" "$TEST_PROJECT/.cursor/rules/agmsg.mdc" +} + +@test "cursor rule file is an always-apply .mdc (Cursor CLI auto-load)" { + bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" >/dev/null + # First non-empty line opens the frontmatter; alwaysApply must be declared so + # the Cursor CLI applies the rule on every turn. + [ "$(head -1 "$TEST_PROJECT/.cursor/rules/agmsg.mdc")" = "---" ] + grep -q "alwaysApply: true" "$TEST_PROJECT/.cursor/rules/agmsg.mdc" +} + +@test "cursor supports off mode: removes rule file" { + bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" + [ -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] + run bash "$SCRIPTS/delivery.sh" set off cursor "$TEST_PROJECT" + [ "$status" -eq 0 ] + [ ! -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] +} + +@test "cursor rejects monitor mode" { + run bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" + [ "$status" -ne 0 ] + [[ "$output" =~ "not supported" ]] + [ ! -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] +} + +@test "cursor rejects both mode" { + run bash "$SCRIPTS/delivery.sh" set both cursor "$TEST_PROJECT" + [ "$status" -ne 0 ] + [[ "$output" =~ "not supported" ]] + [ ! -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] +} + +@test "cursor rejects monitor: does NOT delete an existing turn rule" { + bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" >/dev/null + [ -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] + run bash "$SCRIPTS/delivery.sh" set monitor cursor "$TEST_PROJECT" + [ "$status" -ne 0 ] + [ -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] +} + +@test "cursor set turn: idempotent across repeats" { + bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" + bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" + bash "$SCRIPTS/delivery.sh" set turn cursor "$TEST_PROJECT" + [ -f "$TEST_PROJECT/.cursor/rules/agmsg.mdc" ] + local count + count=$(grep -c "check-inbox.sh" "$TEST_PROJECT/.cursor/rules/agmsg.mdc") + [ "$count" -eq 1 ] +} + # --- Codex monitor bridge (#41) --- @test "session-start.sh for codex starts bridge when monitor launcher env is present" { bash "$SCRIPTS/join.sh" team alice codex "$TEST_PROJECT" >/dev/null diff --git a/tests/test_install.bats b/tests/test_install.bats index 893cef6..c892e3a 100644 --- a/tests/test_install.bats +++ b/tests/test_install.bats @@ -489,6 +489,15 @@ PY ! grep -q "whoami.sh \"\$(pwd)\" antigravity" "$SK/SKILL.md" } +@test "install: --agent-type cursor makes shared SKILL.md Cursor-typed (#131)" { + # Regression guard: the TPL_TYPE case must list cursor, or --agent-type cursor + # silently falls through to the codex template and the install ships a + # codex-typed SKILL.md (delivery/join then run as codex, not cursor). + HOME="$FAKE_HOME" bash "$REPO_ROOT/install.sh" --cmd agmsg --agent-type cursor + grep -q "whoami.sh \"\$(pwd)\" cursor" "$SK/SKILL.md" + ! grep -q "whoami.sh \"\$(pwd)\" codex" "$SK/SKILL.md" +} + @test "install --update: refreshes the Hermes skill if it was previously installed" { mkdir -p "$FAKE_HOME/.hermes" HOME="$FAKE_HOME" bash "$REPO_ROOT/install.sh" --cmd agmsg diff --git a/tests/test_type_registry.bats b/tests/test_type_registry.bats index 209d8f8..4c995e8 100644 --- a/tests/test_type_registry.bats +++ b/tests/test_type_registry.bats @@ -29,11 +29,11 @@ write_node_launcher_fixtures() { printf '// stub node launcher fixture\n' > "$nd/nodetype-launcher.mjs" } -@test "type-registry: known_types lists the seven built-ins" { +@test "type-registry: known_types lists the eight built-ins" { run env -i PATH="$PATH" bash -c \ "source '$SCRIPTS/lib/type-registry.sh'; agmsg_known_types | sort -u | paste -sd, -" [ "$status" -eq 0 ] - [ "$output" = "antigravity,claude-code,codex,copilot,gemini,hermes,opencode" ] + [ "$output" = "antigravity,claude-code,codex,copilot,cursor,gemini,hermes,opencode" ] } @test "type-registry: is_known_type accepts a built-in and rejects a bogus type" {