@mootup/moot-templates 0.1.0-rc.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -2
- package/templates/cursor-agent-devcontainer/devcontainer.json +25 -0
- package/templates/cursor-agent-devcontainer/post-create.sh +68 -0
- package/templates/cursor-agent-devcontainer/run-moot-channel.sh +70 -0
- package/templates/cursor-agent-devcontainer/run-moot-mcp.sh +74 -0
- package/templates/cursor-agent-devcontainer/run-moot-notify.sh +59 -0
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mootup/moot-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Canonical project templates (skills, teams, devcontainer, Claude hooks) for @mootup/moot-cli, synced from the mootup Python CLI source.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/mootup-io/moot-cli-js.git",
|
|
9
|
+
"directory": "packages/moot-templates"
|
|
10
|
+
},
|
|
6
11
|
"type": "module",
|
|
7
12
|
"main": "./dist/index.js",
|
|
8
13
|
"types": "./dist/index.d.ts",
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
"README.md"
|
|
19
24
|
],
|
|
20
25
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
26
|
+
"node": ">=18.0.0"
|
|
22
27
|
},
|
|
23
28
|
"scripts": {
|
|
24
29
|
"sync:templates": "node scripts/sync-templates.mjs",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "moot-agent-team-cursor",
|
|
3
|
+
"image": "mcr.microsoft.com/devcontainers/javascript-node:22",
|
|
4
|
+
"features": {
|
|
5
|
+
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
|
6
|
+
"moby": false
|
|
7
|
+
},
|
|
8
|
+
"ghcr.io/devcontainers/features/python:1": {
|
|
9
|
+
"version": "3.11"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"postCreateCommand": "bash .devcontainer/post-create.sh",
|
|
13
|
+
"runArgs": [
|
|
14
|
+
"--name", "moot-${localWorkspaceFolderBasename}"
|
|
15
|
+
],
|
|
16
|
+
"customizations": {
|
|
17
|
+
"cursor": {
|
|
18
|
+
"extensions": [
|
|
19
|
+
"ms-python.python",
|
|
20
|
+
"ms-pyright.pyright"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"remoteUser": "node"
|
|
25
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# System packages
|
|
5
|
+
sudo apt-get update && sudo apt-get install -y tmux
|
|
6
|
+
|
|
7
|
+
# Claude Code CLI — install from npm first (puts `claude` on PATH
|
|
8
|
+
# via /usr/local/share/npm-global/bin), use it to register MCP servers,
|
|
9
|
+
# then call `claude install` to migrate to the native build at
|
|
10
|
+
# ~/.local/bin. The native build is the officially-supported path
|
|
11
|
+
# going forward; the TUI nags on first run when it's still npm-based.
|
|
12
|
+
npm install -g @anthropic-ai/claude-code
|
|
13
|
+
|
|
14
|
+
# Python tooling
|
|
15
|
+
pip install uv
|
|
16
|
+
|
|
17
|
+
# Install moot package
|
|
18
|
+
pip install mootup
|
|
19
|
+
|
|
20
|
+
# Register MCP servers for Claude Code at user scope so claude finds
|
|
21
|
+
# them regardless of cwd (agents launch in worktrees under .worktrees/,
|
|
22
|
+
# not the project root). Use absolute paths to the wrapper scripts so
|
|
23
|
+
# they resolve from any cwd. The wrappers read CONVO_ROLE at runtime
|
|
24
|
+
# to look up the per-role API key from .moot/actors.json.
|
|
25
|
+
DEVCONTAINER_DIR="$(realpath .devcontainer)"
|
|
26
|
+
claude mcp add convo "$DEVCONTAINER_DIR/run-moot-mcp.sh" -s user
|
|
27
|
+
claude mcp add convo-channel "$DEVCONTAINER_DIR/run-moot-channel.sh" -s user
|
|
28
|
+
|
|
29
|
+
# Migrate from the npm-installed claude to the native build. This runs
|
|
30
|
+
# LAST (after `claude mcp add`) because `claude install` deletes the
|
|
31
|
+
# npm symlink — anything calling `claude` after this point must rely on
|
|
32
|
+
# ~/.local/bin/claude, which `bash -lc` picks up via ~/.profile's
|
|
33
|
+
# standard "$HOME/.local/bin" snippet. Agent tmux sessions launch with
|
|
34
|
+
# `bash -lc`, so they find the native binary automatically.
|
|
35
|
+
claude install
|
|
36
|
+
|
|
37
|
+
# Rebind tmux prefix to Ctrl-Space. Claude Code intercepts Ctrl-B (the
|
|
38
|
+
# default prefix), so the usual `Ctrl-B d` detach never reaches tmux.
|
|
39
|
+
# Ctrl-Space is rarely claimed by TUIs and leaves readline-style editing
|
|
40
|
+
# bindings (Ctrl-A/E/etc.) untouched inside claude's input line.
|
|
41
|
+
cat > /home/node/.tmux.conf <<'TMUX_CONF'
|
|
42
|
+
unbind C-b
|
|
43
|
+
set -g prefix C-Space
|
|
44
|
+
bind C-Space send-prefix
|
|
45
|
+
|
|
46
|
+
# Mouse on: scroll-wheel scrolls the pane, click selects a pane/window,
|
|
47
|
+
# drag copies. Without this, scrollback is only reachable via the
|
|
48
|
+
# copy-mode keybind (<prefix> [) which is a tmux-literacy tax users
|
|
49
|
+
# shouldn't have to pay just to read recent output.
|
|
50
|
+
set -g mouse on
|
|
51
|
+
TMUX_CONF
|
|
52
|
+
|
|
53
|
+
# Register a /detach slash command for claude so the user can leave a
|
|
54
|
+
# tmux session without having to fight for the prefix key. The command
|
|
55
|
+
# calls `tmux detach-client`, which disconnects the terminal but leaves
|
|
56
|
+
# claude running in the session so `moot attach` picks up where it left
|
|
57
|
+
# off. User-scope so every worktree sees it.
|
|
58
|
+
mkdir -p /home/node/.claude/commands
|
|
59
|
+
cat > /home/node/.claude/commands/detach.md <<'DETACH_MD'
|
|
60
|
+
---
|
|
61
|
+
description: Detach from the tmux session (leaves claude running in the background)
|
|
62
|
+
allowed-tools: Bash(bash:*)
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
!bash -c 'SOCK=$(find /tmp /run -maxdepth 3 -name default -type s 2>/dev/null | head -1); if [ -n "$SOCK" ]; then tmux -S "$SOCK" detach-client; else echo "tmux socket not found"; fi'
|
|
66
|
+
DETACH_MD
|
|
67
|
+
|
|
68
|
+
echo "Container ready."
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Channel adapter wrapper — reads CONVO_ROLE and looks up API key
|
|
3
|
+
# from .moot/actors.json. Same logic as run-moot-mcp.sh.
|
|
4
|
+
|
|
5
|
+
ROLE="${CONVO_ROLE:-implementation}"
|
|
6
|
+
ACTORS_FILE=".moot/actors.json"
|
|
7
|
+
|
|
8
|
+
# Find project root (walk up to moot.toml)
|
|
9
|
+
PROJECT_ROOT="$(pwd)"
|
|
10
|
+
while [ "$PROJECT_ROOT" != "/" ]; do
|
|
11
|
+
[ -f "$PROJECT_ROOT/moot.toml" ] && break
|
|
12
|
+
PROJECT_ROOT="$(dirname "$PROJECT_ROOT")"
|
|
13
|
+
done
|
|
14
|
+
|
|
15
|
+
# Read per-role actor identity from .moot/actors.json. See run-moot-mcp.sh
|
|
16
|
+
# for the full rationale — same rule applies to the channel adapter.
|
|
17
|
+
if [ -f "$PROJECT_ROOT/$ACTORS_FILE" ]; then
|
|
18
|
+
eval $(python3 -c "
|
|
19
|
+
import json, shlex
|
|
20
|
+
with open('$PROJECT_ROOT/$ACTORS_FILE') as f:
|
|
21
|
+
data = json.load(f)
|
|
22
|
+
entry = data.get('actors', {}).get('$ROLE', {})
|
|
23
|
+
print('KEY=' + shlex.quote(entry.get('api_key', '')))
|
|
24
|
+
print('AID=' + shlex.quote(entry.get('actor_id', '')))
|
|
25
|
+
print('ANAME=' + shlex.quote(entry.get('display_name', '$ROLE')))
|
|
26
|
+
" 2>/dev/null)
|
|
27
|
+
if [ -n "$KEY" ]; then
|
|
28
|
+
export CONVO_API_KEY="$KEY"
|
|
29
|
+
else
|
|
30
|
+
echo "WARNING: No API key for role '$ROLE' in $ACTORS_FILE" >&2
|
|
31
|
+
fi
|
|
32
|
+
[ -n "$AID" ] && export CONVO_AGENT_ID="$AID"
|
|
33
|
+
[ -n "$ANAME" ] && export CONVO_AGENT_NAME="$ANAME"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Read API URL from moot.toml
|
|
37
|
+
if [ -z "$CONVO_API_URL" ] && [ -f "$PROJECT_ROOT/moot.toml" ]; then
|
|
38
|
+
URL=$(python3 -c "
|
|
39
|
+
import tomllib
|
|
40
|
+
with open('$PROJECT_ROOT/moot.toml', 'rb') as f:
|
|
41
|
+
data = tomllib.load(f)
|
|
42
|
+
print(data.get('convo', {}).get('api_url', ''))
|
|
43
|
+
" 2>/dev/null)
|
|
44
|
+
if [ -n "$URL" ]; then
|
|
45
|
+
export CONVO_API_URL="$URL"
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Read space ID from moot.toml
|
|
50
|
+
if [ -z "$CONVO_SPACE_ID" ] && [ -f "$PROJECT_ROOT/moot.toml" ]; then
|
|
51
|
+
SID=$(python3 -c "
|
|
52
|
+
import tomllib
|
|
53
|
+
with open('$PROJECT_ROOT/moot.toml', 'rb') as f:
|
|
54
|
+
data = tomllib.load(f)
|
|
55
|
+
print(data.get('convo', {}).get('space_id', ''))
|
|
56
|
+
" 2>/dev/null)
|
|
57
|
+
if [ -n "$SID" ]; then
|
|
58
|
+
export CONVO_SPACE_ID="$SID"
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Alpha-grade diagnostics: DEBUG-level logs to a per-role file under
|
|
63
|
+
# .moot/logs/ in the project root. Bind-mounted to the host so users
|
|
64
|
+
# and support can grep and share the logs without docker exec.
|
|
65
|
+
# Override with MOOT_LOG_LEVEL=INFO (or higher) once alpha stabilizes.
|
|
66
|
+
LOG_DIR="$PROJECT_ROOT/.moot/logs"
|
|
67
|
+
mkdir -p "$LOG_DIR"
|
|
68
|
+
LOG_FILE="$LOG_DIR/channel-${ROLE}.log"
|
|
69
|
+
export MOOT_LOG_LEVEL="${MOOT_LOG_LEVEL:-DEBUG}"
|
|
70
|
+
exec python -u -m moot.adapters.channel_runner "$@" 2>> "$LOG_FILE"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# MCP adapter wrapper — reads CONVO_ROLE and looks up API key
|
|
3
|
+
# from .moot/actors.json. Set CONVO_ROLE before launching Claude Code
|
|
4
|
+
# to pick a different identity.
|
|
5
|
+
|
|
6
|
+
ROLE="${CONVO_ROLE:-implementation}"
|
|
7
|
+
ACTORS_FILE=".moot/actors.json"
|
|
8
|
+
|
|
9
|
+
# Find project root (walk up to moot.toml)
|
|
10
|
+
PROJECT_ROOT="$(pwd)"
|
|
11
|
+
while [ "$PROJECT_ROOT" != "/" ]; do
|
|
12
|
+
[ -f "$PROJECT_ROOT/moot.toml" ] && break
|
|
13
|
+
PROJECT_ROOT="$(dirname "$PROJECT_ROOT")"
|
|
14
|
+
done
|
|
15
|
+
|
|
16
|
+
# Read per-role actor identity from .moot/actors.json. We MUST export
|
|
17
|
+
# CONVO_AGENT_ID and CONVO_AGENT_NAME — the mcp_runner defaults them to
|
|
18
|
+
# "unknown-agent", and the backend rejects any post whose agent_id in the
|
|
19
|
+
# body doesn't match the authenticated actor (HTTP 400, which the adapter
|
|
20
|
+
# surfaces as an empty event_id).
|
|
21
|
+
if [ -f "$PROJECT_ROOT/$ACTORS_FILE" ]; then
|
|
22
|
+
eval $(python3 -c "
|
|
23
|
+
import json, shlex
|
|
24
|
+
with open('$PROJECT_ROOT/$ACTORS_FILE') as f:
|
|
25
|
+
data = json.load(f)
|
|
26
|
+
entry = data.get('actors', {}).get('$ROLE', {})
|
|
27
|
+
print('KEY=' + shlex.quote(entry.get('api_key', '')))
|
|
28
|
+
print('AID=' + shlex.quote(entry.get('actor_id', '')))
|
|
29
|
+
print('ANAME=' + shlex.quote(entry.get('display_name', '$ROLE')))
|
|
30
|
+
" 2>/dev/null)
|
|
31
|
+
if [ -n "$KEY" ]; then
|
|
32
|
+
export CONVO_API_KEY="$KEY"
|
|
33
|
+
else
|
|
34
|
+
echo "WARNING: No API key for role '$ROLE' in $ACTORS_FILE" >&2
|
|
35
|
+
fi
|
|
36
|
+
[ -n "$AID" ] && export CONVO_AGENT_ID="$AID"
|
|
37
|
+
[ -n "$ANAME" ] && export CONVO_AGENT_NAME="$ANAME"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Read API URL from moot.toml
|
|
41
|
+
if [ -z "$CONVO_API_URL" ] && [ -f "$PROJECT_ROOT/moot.toml" ]; then
|
|
42
|
+
URL=$(python3 -c "
|
|
43
|
+
import tomllib
|
|
44
|
+
with open('$PROJECT_ROOT/moot.toml', 'rb') as f:
|
|
45
|
+
data = tomllib.load(f)
|
|
46
|
+
print(data.get('convo', {}).get('api_url', ''))
|
|
47
|
+
" 2>/dev/null)
|
|
48
|
+
if [ -n "$URL" ]; then
|
|
49
|
+
export CONVO_API_URL="$URL"
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Read space ID from moot.toml
|
|
54
|
+
if [ -z "$CONVO_SPACE_ID" ] && [ -f "$PROJECT_ROOT/moot.toml" ]; then
|
|
55
|
+
SID=$(python3 -c "
|
|
56
|
+
import tomllib
|
|
57
|
+
with open('$PROJECT_ROOT/moot.toml', 'rb') as f:
|
|
58
|
+
data = tomllib.load(f)
|
|
59
|
+
print(data.get('convo', {}).get('space_id', ''))
|
|
60
|
+
" 2>/dev/null)
|
|
61
|
+
if [ -n "$SID" ]; then
|
|
62
|
+
export CONVO_SPACE_ID="$SID"
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Alpha-grade diagnostics: DEBUG-level logs to a per-role file under
|
|
67
|
+
# .moot/logs/ in the project root. Bind-mounted to the host so users
|
|
68
|
+
# and support can grep and share the logs without docker exec.
|
|
69
|
+
# Override with MOOT_LOG_LEVEL=INFO (or higher) once alpha stabilizes.
|
|
70
|
+
LOG_DIR="$PROJECT_ROOT/.moot/logs"
|
|
71
|
+
mkdir -p "$LOG_DIR"
|
|
72
|
+
LOG_FILE="$LOG_DIR/mcp-${ROLE}.log"
|
|
73
|
+
export MOOT_LOG_LEVEL="${MOOT_LOG_LEVEL:-DEBUG}"
|
|
74
|
+
exec python -u -m moot.adapters.mcp_runner "$@" 2>> "$LOG_FILE"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tmux notification daemon wrapper -- reads CONVO_ROLE and looks up
|
|
3
|
+
# API key from .moot/actors.json. Same pattern as run-moot-channel.sh.
|
|
4
|
+
|
|
5
|
+
ROLE="${CONVO_ROLE:-implementation}"
|
|
6
|
+
ACTORS_FILE=".moot/actors.json"
|
|
7
|
+
|
|
8
|
+
# Find project root (walk up to moot.toml)
|
|
9
|
+
PROJECT_ROOT="$(pwd)"
|
|
10
|
+
while [ "$PROJECT_ROOT" != "/" ]; do
|
|
11
|
+
[ -f "$PROJECT_ROOT/moot.toml" ] && break
|
|
12
|
+
PROJECT_ROOT="$(dirname "$PROJECT_ROOT")"
|
|
13
|
+
done
|
|
14
|
+
|
|
15
|
+
# Read API key from .moot/actors.json (nested schema)
|
|
16
|
+
if [ -f "$PROJECT_ROOT/$ACTORS_FILE" ]; then
|
|
17
|
+
KEY=$(python3 -c "
|
|
18
|
+
import json
|
|
19
|
+
with open('$PROJECT_ROOT/$ACTORS_FILE') as f:
|
|
20
|
+
data = json.load(f)
|
|
21
|
+
entry = data.get('actors', {}).get('$ROLE', {})
|
|
22
|
+
print(entry.get('api_key', ''))
|
|
23
|
+
" 2>/dev/null)
|
|
24
|
+
if [ -n "$KEY" ]; then
|
|
25
|
+
export CONVO_API_KEY="$KEY"
|
|
26
|
+
else
|
|
27
|
+
echo "WARNING: No API key for role '$ROLE' in $ACTORS_FILE" >&2
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Read API URL from moot.toml
|
|
32
|
+
if [ -z "$CONVO_API_URL" ] && [ -f "$PROJECT_ROOT/moot.toml" ]; then
|
|
33
|
+
URL=$(python3 -c "
|
|
34
|
+
import tomllib
|
|
35
|
+
with open('$PROJECT_ROOT/moot.toml', 'rb') as f:
|
|
36
|
+
data = tomllib.load(f)
|
|
37
|
+
print(data.get('convo', {}).get('api_url', ''))
|
|
38
|
+
" 2>/dev/null)
|
|
39
|
+
if [ -n "$URL" ]; then
|
|
40
|
+
export CONVO_API_URL="$URL"
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Read space ID from moot.toml
|
|
45
|
+
if [ -z "$CONVO_SPACE_ID" ] && [ -f "$PROJECT_ROOT/moot.toml" ]; then
|
|
46
|
+
SID=$(python3 -c "
|
|
47
|
+
import tomllib
|
|
48
|
+
with open('$PROJECT_ROOT/moot.toml', 'rb') as f:
|
|
49
|
+
data = tomllib.load(f)
|
|
50
|
+
print(data.get('convo', {}).get('space_id', ''))
|
|
51
|
+
" 2>/dev/null)
|
|
52
|
+
if [ -n "$SID" ]; then
|
|
53
|
+
export CONVO_SPACE_ID="$SID"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Log daemon output for diagnostics
|
|
58
|
+
LOG_FILE="/tmp/moot-notify-${ROLE}.log"
|
|
59
|
+
exec python -m moot.adapters.notify_runner "$@" 2>"$LOG_FILE"
|