Initial commit (Sanitized)

This commit is contained in:
sol 2026-02-23 17:14:33 +00:00
commit a0acc38fa6
4 changed files with 245 additions and 0 deletions

26
README.md Normal file
View File

@ -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..."
```

69
install.sh Executable file
View File

@ -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" <<EOF
## 📡 Live Status Protocol (MANDATORY)
**For ANY multi-step task (e.g., research, downloading, installing, compiling):**
1. **Read Skill:** Load \`/app/skills/live-status/SKILL.md\`.
2. **Initialize:** Run \`live-status create\` with a code block (\`[STATUS] INITIALIZING...\`).
3. **Capture ID:** Store the Post ID returned by \`create\`.
4. **Update:** Frequently run \`live-status update <ID>\` 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 \"...\""

40
skill/SKILL.md Normal file
View File

@ -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 <CHANNEL_ID> "🚀 **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 <POST_ID> "🚀 **Task Started:** Initializing...
\`\`\`
[10:00] Checking files... OK
[10:01] Downloading assets...
\`\`\`"
```
### 3. Complete (End of Task)
Mark as done.
```bash
live-status update <POST_ID> "✅ **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.

110
src/live-status.js Executable file
View File

@ -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 <text>");
console.log(" live-status update <id> <text>");
process.exit(1);
}