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
2 changes: 1 addition & 1 deletion docs/agent-types.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
6 changes: 3 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ while [[ $# -gt 0 ]]; do
echo "Options:"
echo " --cmd <name> Command & skill folder name (default: agmsg)"
echo " Claude Code: /<cmd>, Codex/Gemini/Antigravity: \$<cmd>"
echo " --agent-type <t> Agent type: claude-code, codex, gemini, antigravity, opencode, hermes"
echo " --agent-type <t> Agent type: claude-code, codex, gemini, antigravity, opencode, hermes, cursor"
echo " Selects which template becomes SKILL.md (matches the"
echo " <type> arg passed to join.sh / whoami.sh)"
echo " --update Update skill scripts only (preserve DB and teams)"
Expand Down Expand Up @@ -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/)
Expand Down Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions scripts/drivers/types/cursor/_delivery.sh
Original file line number Diff line number Diff line change
@@ -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 <<EOF > "$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 "$@"; }
139 changes: 139 additions & 0 deletions scripts/drivers/types/cursor/template.md
Original file line number Diff line number Diff line change
@@ -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=<name> teams=<t1,t2,...> type=cursor project=<path>`
→ Remember AGENT and TEAMS, then go to **Execute**.

**B) Multiple identities:**
`multiple=true agents=<n1,n2,...> teams=<t1,t2,...> type=cursor project=<path>`
→ 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=<t1,t2,...>` (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: <list from output>)
> - **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 <team> <agent_name> 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 <agent> <message>` — 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 <mode> 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=<n1,n2,...> teams=<t1,t2,...> type=cursor project=<path> available_teams=<t1,t2,...>`
→ 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 <team> <agent_name> 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 <to_agent> "<message>"`

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 <to_agent> "<message>"`

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 <key> <value>`

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 <team> <name> cursor "$(pwd)"`. For multiple teams, ask the user which team to join the new role into.
4. Set the session's active FROM to `<name>` for every `send.sh` call until another `actas`.
5. Tell the user: "Now acting as `<name>`. Sends will use `<name>` 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 <name>` to remove that role's registration.
3. If the session's active FROM was `<name>`, clear that state.
4. Tell the user: "Dropped role `<name>` 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 <mode> 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.
8 changes: 8 additions & 0 deletions scripts/drivers/types/cursor/type.conf
Original file line number Diff line number Diff line change
@@ -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
58 changes: 58 additions & 0 deletions tests/test_delivery.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions tests/test_install.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/test_type_registry.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
Loading