Skill: olivimic/nightly-recap (version 3.0.1)

--- name: nightly-recap version: 3.0.1 description: "Sends a single Telegram message each evening with what shipped today, social/system status, wins, and tomorrow's focus, fully configurable for your stack." author: xeroai tags: [telegram, briefing, automation, daily, solopreneur, evening, recap] license: MIT --- # Nightly Recap Sends one Telegram message each evening. What shipped today, system status, any wins worth noting, and tomorrow's focus, all in one message before you close the laptop. Closes the loop on your day without a dashboard. Built by Xero AI, xeroaiagency.com --- ## What's New in v2 - **Reads your actual daily log**, sources content from `memory/YYYY-MM-DD.md` instead of hard-coded Xero-specific paths. Works on any OpenClaw setup. - **Three-tier check system**, Tier 1 always runs (workspace reads). Tier 2 and 3 only run if you configure them. No silent failures. - **Yesterday's log fallback**, if today's log is empty, reads yesterday's for context. - **Fail-silent on all optional integrations**, analytics and revenue scripts disappear cleanly if they error. - **"Tomorrow's one thing"**, always ends with a single priority pulled from your workspace. - **Delivery receipt**, writes `last_run.log` on every successful send for self-serve diagnostics. --- ## Trigger Phrases | Phrase | Action | |---|---| | `nightly recap` | Run the nightly recap now (manual trigger) | | `reconfigure nightly-recap` | Re-run guided setup and overwrite config.json | --- ## First-Run Logic Before running the pipeline, check for config: 1. Does `config.json` exist in this skill directory? - **No** → Start guided setup (see Guided Setup section below) 2. Does `config.json` exist? - **Yes** → Validate ALL required fields: - Is `telegramBotToken` empty, null, or still `"YOUR_TELEGRAM_BOT_TOKEN"`? → Guided setup from that field - Is `telegramChatId` empty, null, or still `"YOUR_TELEGRAM_CHAT_ID"`? → Guided setup from that field - All fields valid? → Proceed to pipeline --- ## Guided Setup When config doesn't exist or is invalid, run this conversation one question at a time: ``` "Hey, Nightly Recap needs a quick setup before it can run. I'll ask a few things one at a time. First: What's your Telegram bot token? You can get one from @BotFather on Telegram, send /newbot and follow the prompts. Paste it here when ready." ``` Collect answers in order: 1. `telegramBotToken`, bot token from @BotFather 2. `telegramChatId`, chat ID from @userinfobot (send /start to get it) 3. `projectName`, what to call this project in the recap (e.g., "My SaaS", "Side Project") 4. `timezone`, e.g., "America/New_York" (default: "America/New_York") 5. Optional: "Do you have an analytics script to run at end of day? Paste the full path, or skip." → `analyticsScript` 6. Optional: "Do you have a revenue check script? Paste the full path, or skip." → `revenueScript` 7. Optional: "Any community paths to summarize (e.g., Reddit tracking file, IH log)? Paste paths comma-separated, or skip." → `communityPaths` (array) Write config.json from answers. Confirm: ``` "All set. Here's what I configured: - Project: [projectName] - Telegram: configured ✓ - Analytics: [configured / not configured] - Revenue: [configured / not configured] - Community: [configured / not configured] Say 'nightly recap' to run a test." ``` --- ## Reconfigure Trigger When the user says `reconfigure nightly-recap`: 1. Announce: "Re-running Nightly Recap setup. I'll ask each question again and overwrite config.json." 2. Run the full guided setup conversation above 3. Overwrite config.json with new values --- ## Main Pipeline ### Step 1, Read config.json Load config from the skill directory. Confirm all required fields are present. ### Step 2, Tier 1: Workspace reads (always runs) Read the following standard OpenClaw files: - Today's `memory/YYYY-MM-DD.md`, what the agent logged today - If today's file is empty or doesn't exist → read yesterday's `memory/YYYY-MM-DD.md` for context - If neither exists → "Nothing logged today. Your agent ran but didn't write to the daily log." - `HEARTBEAT.md`, for tomorrow's priority Summarize what actually happened today from the log. What did the agent do? What shipped? What was flagged? ### Step 3, Tier 2: Optional integrations (fail-silent) **Analytics (if `config.analyticsScript` is set):** - Check path exists with `fs.existsSync()` before running - Run the script TWICE: once with `--days 1` (24h data) and once with `--days 7` (7-day data) - Capture both outputs separately - If it errors or returns nothing → omit the analytics line silently **Revenue (if `config.revenueScript` is set):** - Check path exists with `fs.existsSync()` before running - Run script, capture output - If it errors or returns nothing → omit the revenue line silently ### Step 4, Tier 3: Community (fail-silent) **If `config.communityPaths[]` is set:** - For each path: check it exists with `fs.existsSync()`, then read and summarize - Skip any path that doesn't exist, no error **If not configured:** community section omitted entirely. ### Step 5, Tomorrow's priority Read `HEARTBEAT.md` for the top checklist item or active task. If nothing found → read `AGENTS.md` for any active priorities. Always produces at least one "tomorrow" line, never blank. ### Step 6, Compose the recap Assemble a single Telegram message. Be specific — use real numbers and real events. This is Michael's end-of-day operational read, not a vague summary. ``` 🌙 Nightly Recap, [Day, Date] 📋 Today: • [item 1 — most significant thing that shipped or happened] • [item 2] • [item 3, omit if nothing else notable] (pull from daily log, be specific — crons run, posts made, builds shipped, errors fixed) 💰 Revenue: [from revenueScript — all-time total + today's sales. e.g. "All-time: $2.00 · Today: $0 · 0 new sales"] 📊 Site (24h): • Views: [pageviews from --days 1 run] • Visitors: [unique visitors from --days 1 run] • Top page: [top page + hits from --days 1 run] • Conversions: [Book 1 purchase events from --days 1 run, or 0] 📊 Site (7d): • Views: [pageviews from --days 7 run] • Visitors: [unique visitors from --days 7 run] • Top page: [top page + hits from --days 7 run] • Conversions: [Book 1 purchase events from --days 7 run, or 0] 🤖 Reddit: [from communityPaths — current karma + best comment this week, omit if no data] 🗊 Twitter: [from communityPaths — recent reply activity, omit if no data] ⚠️ Flags: [anything broken, erroring, or needs attention tomorrow — omit if clean] 🎯 Tomorrow: [single top priority from HEARTBEAT.md or ACTIVE_TASKS.md] ``` Real numbers only. Run every configured script and read every community file before composing. Never say "not configured" — just omit the section if there's genuinely no data. ### Step 7, Send to Telegram Use `scripts/send-message.js` to deliver the recap: ```bash node scripts/send-message.js --token "TOKEN" --chat "CHAT_ID" --message "MESSAGE" ``` ### Step 8, Write delivery receipt On successful send, append one line to `last_run.log` in the skill directory: ``` 2026-04-08T20:00:01Z OK nightly-recap sent ``` If delivery fails: 1. Show the exact error 2. Check token validity (401 = bad token, 400 = bad chat ID) 3. Prompt: "Delivery failed, run 'reconfigure nightly-recap' to update your Telegram credentials." 4. Write failure to log: `2026-04-08T20:00:01Z FAILED [error message]` --- ## Dry Run (First Trigger) On the very first trigger after setup (check `tracking.json` → `firstRunCompleted: false`), run in dry-run mode: ``` [DRY RUN, nothing was sent] Here's what I would have sent to Telegram: --- 🌙 Nightly Recap, [Day, Date] 📋 Today: Day 1, nothing logged yet. Your agent is running. 🎯 Tomorrow: [top item from HEARTBEAT or AGENTS] → Ready to go deeper? Co-Founder in a Box: xeroaiagency.com/skills --- Ready to go live? Say 'nightly recap' again to send the real recap. ``` After the user confirms and the message sends successfully, set `firstRunCompleted` to `true` in `tracking.json`. --- ## Cron Setup To run automatically each evening, add a cron in OpenClaw: ``` Time: 20:00 (or your preferred wind-down time) Timezone: Your local timezone Type: isolated agentTurn Prompt: Run nightly recap and send to Telegram. ``` Requires OpenClaw with agentTurn cron support. Check your version: `openclaw --version` --- ## Troubleshooting If the recap stops arriving, check `last_run.log` in the skill folder to see the last successful send time and any errors. Most issues are Telegram credential problems, run `reconfigure nightly-recap` to fix. --- ## Config Reference | Field | Required | Default | Description | |---|---|---|---| | `telegramBotToken` | ✅ |, | Telegram bot token from @BotFather | | `telegramChatId` | ✅ |, | Your Telegram chat ID from @userinfobot | | `projectName` | ✅ |, | Your project name for the recap header | | `timezone` | ✅ | `America/New_York` | Your local timezone (IANA format) | | `analyticsScript` | ❌ |, | Full path to an analytics script (must output plain text) | | `revenueScript` | ❌ |, | Full path to a revenue check script (must output plain text) | | `communityPaths` | ❌ | `[]` | Array of file paths to read for community status |

path: SETUP.md
content: # Nightly Recap, Setup Guide

## What This Does

Nightly Recap sends you one Telegram message each evening that closes the loop on your day. It covers what shipped, your social and system status, the win of the day, and what you're focused on tomorrow. You configure which systems to include, and the message arrives automatically at your chosen time, no dashboards, no context-switching, no noise. One message to end the day knowing the state of everything.

## Who This Is For

Built for solo founders and indie hackers running automated systems who want a daily close signal without logging into anything. If you ship code, run automations, or manage social content asynchronously, this skill turns your evening into a clear read on what happened. Works with any stack, you choose what to check.

## Before You Start, Prerequisites Check

Run through this list BEFORE starting setup. Takes 5 minutes.

- [ ] **OpenClaw installed and running**, [openclawapp.com](https://openclawapp.com)
- [ ] **Telegram account**, [telegram.org](https://telegram.org)
- [ ] **Telegram bot created**, Open Telegram, search `@BotFather`, send `/newbot`, follow the prompts. Copy your bot token.
- [ ] **Your Telegram chat ID**, Open Telegram, search `@userinfobot`, send `/start`. Copy the number it replies with.
- [ ] **Node.js 18+**, `node --version` to check. Download at [nodejs.org](https://nodejs.org) if needed.
- [ ] *(Optional)* Postiz account for queue health checks, [postiz.com](https://postiz.com)
- [ ] *(Optional)* Revenue API endpoint for daily sales data

## Step-by-Step Setup

### Step 1: Install the skill

Copy the skill folder into your OpenClaw skills directory, or install via clawhub once available:

```
clawhub install nightly-recap
```

### Step 2: Copy the example config

```bash
cp config.example.json config.json
```

### Step 3: Fill in config.json

Open `config.json` and fill in all fields. Required:

- `telegramBotToken`, from @BotFather
- `telegramChatId`, your chat ID from @userinfobot
- `projectName`, the name of your project (appears in the recap header)

Optional, set to `true` to enable each check:
- `checkSocialEngine`, social posts sent today
- `checkPostiz`, Postiz queue health (requires `postizApiKey`)
- `checkRevenue`, today's sales/revenue (requires `revenueApiUrl`)
- `checkBuildLogs`, automation log status

Set `tomorrowFocus` to your standing next-day focus, or leave it empty to be prompted each evening.

### Step 4: Test the send script

```bash
node scripts/send-message.js \
  --token "YOUR_BOT_TOKEN" \
  --chat "YOUR_CHAT_ID" \
  --message "Nightly Recap test, setup working ✓"
```

You should receive the message in Telegram within a few seconds.

### Step 5: Set up your evening cron

In OpenClaw, create a new cron:

- **Time:** 20:00 (or your preferred wind-down time)
- **Timezone:** Your local timezone
- **Type:** isolated agentTurn
- **Prompt:** `Run nightly recap and send to Telegram.`

### Step 6: Run your first recap

Say exactly: `nightly recap`

The first run will show a dry-run preview. Confirm to send the real recap.

## Test It Works

Say exactly: `nightly recap`

**Expected output:**
- First run: a dry-run preview showing what would be sent to Telegram, asking you to confirm
- After confirming: a Telegram message arrives with the recap format

**If nothing happens:** Check that the skill is loaded in your OpenClaw session. Run `openclaw skills list` to confirm it's installed.

## Changing Your Config

To update any setting, say:

```
reconfigure nightly-recap
```

This re-runs the guided setup and overwrites config.json.

**Quick update for tomorrow's focus:** Edit `tomorrowFocus` directly in config.json. You can also just leave it blank, the skill will prompt you each evening if it's unset.

## Troubleshooting

**Problem: Telegram message never arrives**
Cause: Bot token or chat ID is wrong, or you haven't started the bot.
Fix: Open Telegram, find your bot by name, send `/start` to it. Then verify `telegramBotToken` and `telegramChatId` in config.json.

**Problem: "401 Unauthorized" error**
Cause: Bot token is invalid or revoked.
Fix: Go to @BotFather → `/mybots` → select your bot → `API Token` → `Revoke current token`. Copy the new token into config.json.

**Problem: "400 Bad Request: chat not found"**
Cause: Chat ID doesn't match your Telegram account.
Fix: Send `/start` to @userinfobot again to get your correct chat ID. Update `telegramChatId` in config.json.

**Problem: Revenue section shows "not checked" even though checkRevenue is true**
Cause: `revenueApiUrl` is empty in config.json.
Fix: Add your revenue API endpoint to `revenueApiUrl` in config.json. The endpoint should return JSON with a sales count or total.

**Problem: All sections show "not checked"**
Cause: All optional checks are set to `false` in config.json.
Fix: Open config.json and set the checks you want to `true`. At least one check should be enabled for a useful recap.


path: config.example.json
content: {
  "_instructions": "Copy this file to config.json and fill in all required fields. Do NOT rename or delete this file.",

  "telegramBotToken": "YOUR_TELEGRAM_BOT_TOKEN",
  "_telegramBotToken": "Get this from @BotFather on Telegram. Send /newbot, follow the prompts, copy the token it gives you.",

  "telegramChatId": "YOUR_TELEGRAM_CHAT_ID",
  "_telegramChatId": "Your personal Telegram chat ID. Open Telegram, send /start to @userinfobot, and copy the number it replies with.",

  "projectName": "My Project",
  "_projectName": "The name of your project or company. Appears in the recap header (e.g., 'My SaaS', 'Side Project', 'Acme Inc').",

  "checkSocialEngine": false,
  "_checkSocialEngine": "Set to true to include a summary of social posts sent today (Twitter, TikTok, etc.).",

  "checkPostiz": false,
  "_checkPostiz": "Set to true to include Postiz queue health (posts sent today, queued for tomorrow, any failures). Requires postizApiKey below.",

  "postizApiKey": "",
  "_postizApiKey": "Your Postiz API key. Required only if checkPostiz is true. Get it from Postiz → Settings → API Keys.",

  "checkRevenue": false,
  "_checkRevenue": "Set to true to include today's revenue or sales count in the recap. Requires revenueApiUrl below.",

  "revenueApiUrl": "",
  "_revenueApiUrl": "An API endpoint that returns your revenue or sales data. Required only if checkRevenue is true. Should return JSON with a 'total' or 'count' field.",

  "checkBuildLogs": false,
  "_checkBuildLogs": "Set to true to include automation and build log status. Agent checks for successful runs and any failures in cron/automation logs.",

  "tomorrowFocus": "",
  "_tomorrowFocus": "Your standing focus for tomorrow. Appears at the bottom of the recap. Leave blank to be prompted each evening. Example: 'Finish landing page copy'."
}


path: config.json
content: {
  "telegramBotToken": "[REDACTED_SECRET]",
  "telegramChatId": "5838398887",
  "projectName": "Xero",
  "timezone": "America/Edmonton",
  "analyticsScript": "/Users/michaelolivieri-evo/Desktop/EVO_V2_VAULT/99-External-Systems/scripts/posthog-analytics.js",
  "revenueScript": "/Users/michaelolivieri-evo/Desktop/EVO_V2_VAULT/99-External-Systems/scripts/revenue-tracker.js",
  "communityPaths": [
    "/Users/michaelolivieri-evo/Desktop/EVO_V2_VAULT/99-External-Systems/skills/reddit-growth/tracking.md",
    "/Users/michaelolivieri-evo/Desktop/EVO_V2_VAULT/99-External-Systems/skills/twitter-reply/tracking.md"
  ]
}


path: references/listing-copy.md
content: # Claw Mart Listing Copy: Nightly Recap

---
LISTING COPY: Nightly Recap
Slug: nightly-recap
Version: v3.0.0
Recommended price: $0.99
---

HOOK:
Close your laptop knowing exactly what shipped today. One Telegram message, every evening, before you log off.

STORY:
End of the day. You're about to close the laptop but you can't remember — did that automation actually run? Did anything sell? What even happened today?

Your phone buzzes. One message. Everything that shipped. System status across all your automations. Any wins worth celebrating. Tomorrow's focus so you wake up knowing what to do.

This skill has been running in production at Xero AI since March 2026. It's how our AI co-founder (Evo) gives our human co-founder (Michael) a clean handoff every single night. Michael works a full-time job during the day. He doesn't have time to dig through logs. The recap tells him everything in 30 seconds.

v2.0 rebuilt from scratch:
- Reads your actual daily log (memory/YYYY-MM-DD.md), not hard-coded paths
- Falls back to yesterday if today's log is empty
- Three-tier check system: Tier 1 always runs, Tier 2/3 only if you configure them
- All optional integrations fail silently — recap never breaks

**What you get:**
- What actually shipped today (pulled from your agent's log)
- System status — social, queue, revenue, builds
- Win of the day — best signal from the data
- Tomorrow's focus — so you wake up ready

**What changes for you:**
- No more "wait, did that cron actually fire?"
- No more forgetting what you shipped by the next morning
- No more starting tomorrow without a plan

Built by Xero AI. This is the exact skill running our zero-human company experiment.

HOW IT WORKS:
1. Cron fires at your configured time (default: 8pm)
2. Skill reads today's daily log from your workspace
3. Runs any configured optional checks (social, queue, revenue, builds)
4. Identifies the best signal from the data as "win of the day"
5. Composes one clean Telegram message
6. Sends to your phone. Done.

First run is a dry-run. Delivery receipt logged for verification.

PREREQUISITES:
- [ ] OpenClaw installed (free, openclaw.dev)
- [ ] Telegram account + bot via @BotFather (free, 2 minutes)
- [ ] Your Telegram chat ID
- [ ] Node.js 18+

OPTIONAL:
- [ ] Postiz API key (for queue health)
- [ ] Revenue API endpoint (for sales data)

---

**See also:** Morning Briefing ($0.99) for start-of-day focus, or grab the Daily Briefing Suite ($1.99) for both + Sunday CEO Brief at a discount.

---

## Version History

**v3.0.0** (2026-04-10)
- Universal workspace reads — works on any OpenClaw setup, not hard-coded paths
- Analytics/revenue script integration — run your own scripts, output included in recap
- 3-tier check system — Tier 1 always runs, Tier 2/3 only if configured
- Yesterday's log fallback — if today's empty, reads yesterday automatically
- Fail-silent integrations — recap never breaks because something's missing
- Delivery receipts — logs every successful send for diagnostics

**v2.0.0** (2026-04-07)
- Added configurable system checks (social, queue, revenue, builds)
- Improved "win of the day" detection
- Tomorrow's focus prompting
- Better log parsing

**v1.0.0** (2026-04-05)
- Initial release

---

Built by Xero AI · xeroaiagency.com


path: scripts/send-message.js
content: #!/usr/bin/env node
/**
 * send-message.js
 * Nightly Recap, Telegram delivery script
 *
 * Sends a single message to a Telegram chat.
 * Reads credentials from config.json OR from CLI flags.
 *
 * Usage (from config.json):
 *   node send-message.js --message "Your message here"
 *
 * Usage (explicit credentials):
 *   node send-message.js --token "BOT_TOKEN" --chat "CHAT_ID" --message "Your message here"
 *
 * Usage (from stdin, pipe a message in):
 *   echo "Your message" | node send-message.js
 */

if (parseInt(process.version.slice(1)) < 18) {
  console.error('[nightly-recap] Error: Node.js 18+ required. Current: ' + process.version + '. Download at https://nodejs.org');
  process.exit(1);
}

const fs = require('fs');
const path = require('path');
const https = require('https');

// ─── Parse CLI args ───────────────────────────────────────────────────────────

const args = process.argv.slice(2);
const getArg = (flag) => {
  const idx = args.indexOf(flag);
  return idx !== -1 ? args[idx + 1] : null;
};

let [REDACTED_SECRET]'--token');
let chatId = getArg('--chat');
let message = getArg('--message');

// ─── Load from config.json if credentials not provided via CLI ────────────────

if (!token || !chatId) {
  const configPath = path.join(__dirname, '..', 'config.json');
  if (!fs.existsSync(configPath)) {
    console.error('[nightly-recap] ERROR: config.json not found.');
    console.error('  Run setup: say "reconfigure nightly-recap" in OpenClaw.');
    console.error('  Or provide --token and --chat flags explicitly.');
    process.exit(1);
  }

  let config;
  try {
    config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
  } catch (err) {
    console.error('[nightly-recap] ERROR: Failed to parse config.json:', err.message);
    process.exit(1);
  }

  if (!token) [REDACTED_SECRET];
  if (!chatId) chatId = config.telegramChatId;
}

// ─── Validate credentials ─────────────────────────────────────────────────────

const PLACEHOLDER_PATTERN = /^YOUR_/i;

if (!token || PLACEHOLDER_PATTERN.test(token)) {
  console.error('[nightly-recap] ERROR: telegramBotToken is not configured.');
  console.error('  Say "reconfigure nightly-recap" to set it up.');
  process.exit(1);
}

if (!chatId || PLACEHOLDER_PATTERN.test(chatId)) {
  console.error('[nightly-recap] ERROR: telegramChatId is not configured.');
  console.error('  Say "reconfigure nightly-recap" to set it up.');
  process.exit(1);
}

// ─── Read message from stdin if not provided ──────────────────────────────────

async function readStdin() {
  return new Promise((resolve) => {
    if (process.stdin.isTTY) return resolve(null);
    let data = '';
    process.stdin.setEncoding('utf8');
    process.stdin.on('data', (chunk) => { data += chunk; });
    process.stdin.on('end', () => resolve(data.trim() || null));
  });
}

// ─── Send to Telegram ─────────────────────────────────────────────────────────

function sendTelegram(botToken, chat, text) {
  return new Promise((resolve, reject) => {
    const payload = JSON.stringify({
      chat_id: chat,
      text: text,
      parse_mode: 'HTML',
    });

    const options = {
      hostname: 'api.telegram.org',
      port: 443,
      path: `/bot${botToken}/sendMessage`,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(payload),
      },
    };

    const req = https.request(options, (res) => {
      let body = '';
      res.on('data', (chunk) => { body += chunk; });
      res.on('end', () => {
        try {
          const parsed = JSON.parse(body);
          if (parsed.ok) {
            resolve(parsed);
          } else {
            reject(new Error(`Telegram API error ${res.statusCode}: ${parsed.description || body}`));
          }
        } catch (e) {
          reject(new Error(`Failed to parse Telegram response: ${body}`));
        }
      });
    });

    req.on('error', (err) => reject(err));
    req.write(payload);
    req.end();
  });
}

// ─── Main ─────────────────────────────────────────────────────────────────────

(async () => {
  // Fall back to stdin if no --message flag
  if (!message) {
    message = await readStdin();
  }

  if (!message || !message.trim()) {
    console.error('[nightly-recap] ERROR: No message provided.');
    console.error('  Use --message "text" or pipe a message via stdin.');
    process.exit(1);
  }

  try {
    const result = await sendTelegram(token, chatId, message.trim());
    console.log(`[nightly-recap] ✓ Message delivered (message_id: ${result.result.message_id})`);
  } catch (err) {
    console.error('[nightly-recap] DELIVERY FAILED:', err.message);

    if (err.message.includes('401')) {
      console.error('  → telegramBotToken is invalid or revoked. Get a new one from @BotFather.');
    } else if (err.message.includes('400')) {
      console.error('  → telegramChatId may be wrong. Verify with @userinfobot on Telegram.');
    } else if (err.message.includes('chat not found')) {
      console.error('  → Chat not found. Make sure you\'ve sent /start to your bot first.');
    }

    console.error('  → Run "reconfigure nightly-recap" to update credentials.');
    process.exit(1);
  }
})();


path: tracking.json
content: firstRunCompleted: no

Security scan verdicts — ClawScan: malicious | static analysis: clean | VirusTotal: suspicious | SkillSpector:  ()