From a0acc38fa6d4454ee5d3a9b772e420598cdfedce Mon Sep 17 00:00:00 2001 From: sol Date: Mon, 23 Feb 2026 17:14:33 +0000 Subject: [PATCH] Initial commit (Sanitized) --- README.md | 26 +++++++++++ install.sh | 69 ++++++++++++++++++++++++++++ skill/SKILL.md | 40 +++++++++++++++++ src/live-status.js | 110 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 README.md create mode 100755 install.sh create mode 100644 skill/SKILL.md create mode 100755 src/live-status.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..55b0df5 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# OpenClaw Live Status Tool + +A lightweight CLI tool for OpenClaw agents to provide "Antigravity-style" live status updates in Mattermost channels (and others) without spamming. + +## Features +- **Live Updates:** Create a single message and update it repeatedly. +- **Sub-Agent Support:** Works in clean environments via embedded config or CLI flags. +- **Cross-Channel:** Supports dynamic channel targeting via `--channel`. +- **One-Click Install:** Updates binaries, skills, and agent protocols automatically. + +## Installation + +Run the interactive installer wizard: +```bash +./install.sh +``` + +## Usage + +```bash +# Create a new status box +ID=$(live-status create "Initializing...") + +# Update the status box +live-status update $ID "Working..." +``` diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..16ee7ef --- /dev/null +++ b/install.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# OpenClaw Live Status Installer (Wizard Mode) + +echo "==========================================" +echo " OpenClaw Live Status Tool - Installer " +echo "==========================================" + +# 1. Install Binary +echo "[+] Installing binary to /usr/local/bin/live-status..." +cp src/live-status.js /usr/local/bin/live-status +chmod +x /usr/local/bin/live-status + +# 2. Install Skill +echo "[+] Installing skill to /app/skills/live-status..." +mkdir -p /app/skills/live-status +cp skill/SKILL.md /app/skills/live-status/SKILL.md + +# 3. Global Agent Configuration (Injection) +echo "[+] Configuring global agent defaults..." +AGENTS_FILE="/home/node/.openclaw/workspace/AGENTS.md" + +if [ -f "$AGENTS_FILE" ]; then + if grep -q "Live Status Protocol" "$AGENTS_FILE"; then + echo " > AGENTS.md already configured." + else + echo " > Injecting mandatory protocol into AGENTS.md..." + cat >> "$AGENTS_FILE" <\` to stream progress. +5. **Complete:** Mark as \`[COMPLETED]\` when done. + +**Why:** This provides "Antigravity-style" visibility without spamming channels. Sub-agents MUST use this tool instead of \`message\` for progress reports. +EOF + fi +else + echo " > Warning: AGENTS.md not found. Skipping injection." +fi + +# 4. Mattermost Configuration (Wizard) +echo "" +echo "[?] Mattermost Configuration Check:" +# Check if the installed tool still has the default placeholder +if grep -q "DEFAULT_TOKEN_PLACEHOLDER" /usr/local/bin/live-status; then + echo " > Default token detected." + read -p " > Enter your Mattermost Bot Token: " NEW_TOKEN + if [[ -n "$NEW_TOKEN" ]]; then + # Replace the placeholder in the INSTALLED binary (not the source) + sed -i "s/DEFAULT_TOKEN_PLACEHOLDER/$NEW_TOKEN/g" /usr/local/bin/live-status + echo " > Token configured successfully." + else + echo " > No token entered. Tool may not function." + fi +else + echo " > Custom token already configured." +fi + +echo "" +echo "==========================================" +echo " Installation Complete!" +echo "==========================================" +echo "Usage: live-status create \"...\"" diff --git a/skill/SKILL.md b/skill/SKILL.md new file mode 100644 index 0000000..5eca6f5 --- /dev/null +++ b/skill/SKILL.md @@ -0,0 +1,40 @@ +# Live Status Skill + +**Use this tool to report real-time progress updates to Mattermost.** +It allows you to create a "Live Log" post that you update in-place, reducing chat spam. + +## Usage + +### 1. Initialize (Start of Task) +Create a new status post. It will print the `POST_ID`. +**Required:** Pass the `CHANNEL_ID` if known (otherwise it defaults to system channel). +```bash +live-status create "🚀 **Task Started:** Initializing..." +``` +**Output:** `p6...` (The Post ID) + +### 2. Update (During Task) +Update the post with new log lines. Use a code block for logs. +```bash +live-status update "🚀 **Task Started:** Initializing... +\`\`\` +[10:00] Checking files... OK +[10:01] Downloading assets... +\`\`\`" +``` + +### 3. Complete (End of Task) +Mark as done. +```bash +live-status update "✅ **Task Complete.** +\`\`\` +[10:00] Checking files... OK +[10:01] Downloading assets... Done. +[10:05] Verifying... Success. +\`\`\`" +``` + +## Protocol +- **Always** capture the `POST_ID` from the `create` command. +- **Always** append to the previous log (maintain history). +- **Use Code Blocks** for technical logs. diff --git a/src/live-status.js b/src/live-status.js new file mode 100755 index 0000000..51d98d2 --- /dev/null +++ b/src/live-status.js @@ -0,0 +1,110 @@ +#!/usr/bin/env node + +const https = require('http'); // Using http for mattermost:8065 (no ssl inside docker network) +const fs = require('fs'); + +// --- HELPER: PARSE ARGS --- +const args = process.argv.slice(2); +let command = null; +let options = {}; +let otherArgs = []; + +for (let i = 0; i < args.length; i++) { + if (args[i] === '--channel') { + options.channel = args[i+1]; + i++; // Skip next arg (the channel ID) + } else if (!command && (args[i] === 'create' || args[i] === 'update')) { + command = args[i]; + } else { + otherArgs.push(args[i]); + } +} + +// --- CONFIGURATION (DYNAMIC) --- +const CONFIG = { + host: 'mattermost', + port: 8065, + token: 'DEFAULT_TOKEN_PLACEHOLDER', // Set via install.sh wizard + // Priority: 1. CLI Flag, 2. Env Var, 3. Hardcoded Fallback (Project-0) + channel_id: options.channel || process.env.MM_CHANNEL_ID || process.env.CHANNEL_ID || 'obzja4hb8pd85xk45xn4p31jye' +}; + +// --- HELPER: HTTP REQUEST --- +function request(method, path, data) { + return new Promise((resolve, reject) => { + const options = { + hostname: CONFIG.host, + port: CONFIG.port, + path: '/api/v4' + path, + method: method, + headers: { + 'Authorization': `Bearer ${CONFIG.token}`, + 'Content-Type': 'application/json' + } + }; + + const req = https.request(options, (res) => { + let body = ''; + res.on('data', (chunk) => body += chunk); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + resolve(JSON.parse(body)); + } catch (e) { + resolve(body); + } + } else { + reject(new Error(`Request Failed (${res.statusCode}): ${body}`)); + } + }); + }); + + req.on('error', (e) => reject(e)); + if (data) req.write(JSON.stringify(data)); + req.end(); + }); +} + +// --- COMMANDS --- + +async function createPost(text) { + try { + const result = await request('POST', '/posts', { + channel_id: CONFIG.channel_id, + message: text + }); + console.log(result.id); + } catch (e) { + console.error(`Error creating post in channel ${CONFIG.channel_id}:`, e.message); + process.exit(1); + } +} + +async function updatePost(postId, text) { + try { + const current = await request('GET', `/posts/${postId}`); + await request('PUT', `/posts/${postId}`, { + id: postId, + message: text, + props: current.props + }); + console.log("updated"); + } catch (e) { + console.error("Error updating post:", e.message); + process.exit(1); + } +} + +// --- CLI ROUTER --- +if (command === 'create') { + const text = otherArgs.join(' '); + createPost(text); +} else if (command === 'update') { + const id = otherArgs[0]; + const text = otherArgs.slice(1).join(' '); + updatePost(id, text); +} else { + console.log("Usage: live-status [--channel ID] create "); + console.log(" live-status update "); + process.exit(1); +}