#!/usr/bin/env bash # Plexum-fabric-channel-plugin local installer. # # Builds + installs: # ~/.plexum/plugins/plexum-fabric-channel/plexum-fabric-channel-plugin # ~/.plexum/plugins/plexum-fabric-channel/manifest.json (initial; empty channels list) # ~/.local/bin/plexum-fabric-register (CLI for binding agents) # # Re-runnable: rebuilds binaries; overwrites manifest only if --reset-manifest. # Profile data (identity registry, channel configs) is never touched. # # Flags: # --profile

Override profile root (default ~/.plexum) # --reset-manifest Overwrite the manifest's channels list with one # derived from current channels/*.json (matches what # the plugin advertises at runtime). Useful after # adding/removing channels. set -euo pipefail REPO="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" PROFILE_DIR="${HOME}/.plexum" USER_BIN="${HOME}/.local/bin" RESET_MANIFEST=0 while [[ $# -gt 0 ]]; do case "$1" in --profile) PROFILE_DIR="$2"; shift 2 ;; --reset-manifest) RESET_MANIFEST=1; shift ;; -h|--help) sed -n '2,/^set -euo/p' "$0" | sed -n '/^#/p' | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "unknown flag: $1" >&2; exit 2 ;; esac done log() { printf '\033[1;34m[fabric-install]\033[0m %s\n' "$*"; } command -v go >/dev/null || { echo "go not found on PATH" >&2; exit 1; } PLUGIN_DIR="${PROFILE_DIR}/plugins/plexum-fabric-channel" mkdir -p "${PLUGIN_DIR}" "${USER_BIN}" cd "${REPO}" VERSION="$(git describe --tags --always 2>/dev/null || echo dev)" LDFLAGS="-X main.Version=${VERSION}" log "building plexum-fabric-channel-plugin (v=${VERSION})" CGO_ENABLED=0 go build -ldflags="${LDFLAGS}" \ -o "${PLUGIN_DIR}/plexum-fabric-channel-plugin" ./cmd/plexum-fabric-channel-plugin log "building plexum-fabric-register" CGO_ENABLED=0 go build -ldflags="${LDFLAGS}" \ -o "${USER_BIN}/plexum-fabric-register" ./cmd/plexum-fabric-register MANIFEST_PATH="${PLUGIN_DIR}/manifest.json" if [[ ! -f "${MANIFEST_PATH}" || ${RESET_MANIFEST} -eq 1 ]]; then log "writing initial manifest at ${MANIFEST_PATH}" # Build channels[] from any channels/*.json that name our plugin. CHANNELS_DIR="${PROFILE_DIR}/channels" CHANNELS_JSON='[]' if [[ -d "${CHANNELS_DIR}" ]]; then CHANNELS_JSON=$(python3 -c " import json, os, sys out = [] for f in sorted(os.listdir('${CHANNELS_DIR}')): if not f.endswith('.json'): continue try: d = json.load(open(os.path.join('${CHANNELS_DIR}', f))) except Exception: continue if d.get('plugin') == 'plexum-fabric-channel': out.append({'name': f[:-5], 'outboundTool': 'send'}) print(json.dumps(out)) ") fi # Tools list: kept in sync with internal/tools/contracts.go. Each # entry mirrors a ToolContract in allContracts. TOOLS_JSON='[ {"name":"send","description":"Outbound channel reply — host calls this after an inbound dispatch.", "inputSchema":{"type":"object","properties":{"channel_name":{"type":"string"},"session_id":{"type":"string"},"message":{"type":"string"}},"required":["channel_name","message"]}}, {"name":"fabric-send-message","description":"Post a normal message to a Fabric channel as the bound agent.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"channel_id":{"type":"string"},"content":{"type":"string"}},"required":["agent_id","channel_id","content"]}}, {"name":"fabric-send-sys-msg","description":"Post a system-kind message (not subject to turn engine / wakeup).", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"channel_id":{"type":"string"},"content":{"type":"string"}},"required":["agent_id","guild_node_id","channel_id","content"]}}, {"name":"fabric-channel-list","description":"List channels visible to the agent in a guild.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"}},"required":["agent_id","guild_node_id"]}}, {"name":"fabric-guild-list","description":"List the guilds the agent belongs to.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"}},"required":["agent_id"]}}, {"name":"fabric-message-history","description":"Paginate channel messages by seq.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"channel_id":{"type":"string"},"seq_from":{"type":"integer"},"seq_to":{"type":"integer"},"limit":{"type":"integer"}},"required":["agent_id","guild_node_id","channel_id"]}}, {"name":"fabric-channel-set-purpose","description":"Update the channel'"'"'s free-form purpose text.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"channel_id":{"type":"string"},"purpose":{"type":"string"}},"required":["agent_id","guild_node_id","channel_id","purpose"]}}, {"name":"fabric-channel","description":"Fetch metadata + member list for a specific channel.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"channel_id":{"type":"string"}},"required":["agent_id","guild_node_id","channel_id"]}}, {"name":"fabric-canvas","description":"Read or write the channel canvas (op: get|share|update|remove).", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"channel_id":{"type":"string"},"op":{"type":"string","enum":["get","share","update","remove"]},"title":{"type":"string"},"format":{"type":"string","enum":["md","html","text"]},"source":{"type":"string"}},"required":["agent_id","guild_node_id","channel_id","op"]}}, {"name":"create-chat-channel","description":"Create a chat channel (xType=general).", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"name":{"type":"string"},"is_public":{"type":"boolean"},"member_user_ids":{"type":"array","items":{"type":"string"}},"listeners":{"type":"array","items":{"type":"string"}},"on_duty":{"type":"string"},"purpose":{"type":"string"}},"required":["agent_id","guild_node_id","name"]}}, {"name":"create-work-channel","description":"Create a work channel (xType=work).", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"name":{"type":"string"},"is_public":{"type":"boolean"},"member_user_ids":{"type":"array","items":{"type":"string"}},"listeners":{"type":"array","items":{"type":"string"}},"on_duty":{"type":"string"},"purpose":{"type":"string"}},"required":["agent_id","guild_node_id","name"]}}, {"name":"create-report-channel","description":"Create a report channel (xType=report).", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"name":{"type":"string"},"is_public":{"type":"boolean"},"member_user_ids":{"type":"array","items":{"type":"string"}},"listeners":{"type":"array","items":{"type":"string"}},"on_duty":{"type":"string"},"purpose":{"type":"string"}},"required":["agent_id","guild_node_id","name"]}}, {"name":"create-discussion-channel","description":"Create a discussion channel (xType=discuss).", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"name":{"type":"string"},"is_public":{"type":"boolean"},"member_user_ids":{"type":"array","items":{"type":"string"}},"listeners":{"type":"array","items":{"type":"string"}},"on_duty":{"type":"string"},"purpose":{"type":"string"}},"required":["agent_id","guild_node_id","name"]}}, {"name":"discussion-complete","description":"Post a summary then close the channel.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"channel_id":{"type":"string"},"summary":{"type":"string"}},"required":["agent_id","guild_node_id","channel_id","summary"]}}, {"name":"create-sub-discussion","description":"Spawn a sub-discussion channel with the caller as host + invited guests.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"guild_node_id":{"type":"string"},"name":{"type":"string"},"guest_user_ids":{"type":"array","items":{"type":"string"}},"host_guide":{"type":"string"},"guest_guide":{"type":"string"},"parent_channel_id":{"type":"string"},"greeting":{"type":"string"},"x_type":{"type":"string","enum":["discuss","work","report","general"]}},"required":["agent_id","guild_node_id","name","parent_channel_id"]}}, {"name":"close-sub-discussion","description":"Close a sub-discussion the caller hosts and post a callback to its parent channel.", "inputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"sub_channel_id":{"type":"string"},"summary":{"type":"string"}},"required":["agent_id","sub_channel_id"]}} ]' cat > "${MANIFEST_PATH}" < --api-key fak_... 2. Bind channels: write \`${PROFILE_DIR}/channels/.json\` (see README); re-run install.sh --reset-manifest after adding/removing channels. 3. Allow plugin: add "plexum-fabric-channel" to \`${PROFILE_DIR}/plexum.json\` under .plugins.allow. 4. Restart: systemctl --user restart plexum EOF