phoenixclaw
Passive journaling skill that scans daily conversations from ALL session paths (main, agents, cron) via cron to generate markdown journals using semantic understanding. Use when: - User requests journaling ("Show me my journal", "What did I do today?") - User asks for pattern analysis ("Analyze my patterns", "How am I doing?") - User requests summaries ("Generate weekly/monthly summary")
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-phoenixclaw
Repository
Skill path: skills/goforu/phoenixclaw
Passive journaling skill that scans daily conversations from ALL session paths (main, agents, cron) via cron to generate markdown journals using semantic understanding. Use when: - User requests journaling ("Show me my journal", "What did I do today?") - User asks for pattern analysis ("Analyze my patterns", "How am I doing?") - User requests summaries ("Generate weekly/monthly summary")
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install phoenixclaw into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding phoenixclaw to shared team environments
- Use phoenixclaw for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: phoenixclaw
description: |
Passive journaling skill that scans daily conversations from ALL session paths
(main, agents, cron) via cron to generate markdown journals using semantic understanding.
Use when:
- User requests journaling ("Show me my journal", "What did I do today?")
- User asks for pattern analysis ("Analyze my patterns", "How am I doing?")
- User requests summaries ("Generate weekly/monthly summary")
metadata:
version: 0.0.19
---
# PhoenixClaw: Zero-Tag Passive Journaling
PhoenixClaw automatically distills daily conversations into meaningful reflections using semantic intelligence.
Automatically identifies journal-worthy moments, patterns, and growth opportunities.
## 🛠️ Core Workflow
> [!critical] **MANDATORY: Complete Workflow Execution**
> This 9-step workflow MUST be executed in full regardless of invocation method:
> - **Cron execution** (10 PM nightly)
> - **Manual invocation** ("Show me my journal", "Generate today's journal", etc.)
> - **Regeneration requests** ("Regenerate my journal", "Update today's entry")
>
> **Never skip steps.** Partial execution causes:
> - Missing images (session logs not scanned)
> - Missing finance data (Ledger plugin not triggered)
> - Incomplete journals (plugins not executed)
PhoenixClaw follows a structured pipeline to ensure consistency and depth:
1. **User Configuration:** Check for `~/.phoenixclaw/config.yaml`. If missing, initiate the onboarding flow defined in `references/user-config.md`.
2. **Context Retrieval:**
- **Scan memory files (NEW):** Read `memory/YYYY-MM-DD.md` and `memory/YYYY-MM-DD-*.md` files for manually recorded daily reflections. These files contain personal thoughts, emotions, and context that users explicitly ask the AI to remember via commands like "记一下" (remember this). **CRITICAL**: Do not skip these files - they contain explicit user reflections that session logs may miss.
- **Scan session logs:** Call `memory_get` for the current day's memory, then **CRITICAL: Scan ALL raw session logs and filter by message timestamp**. Session files are often split across multiple files. Do NOT classify images by session file `mtime`:
```bash
# Read all session logs from ALL known OpenClaw locations, then filter by per-message timestamp
# Use timezone-aware epoch range to avoid UTC/local-day mismatches.
TARGET_DAY="$(date +%Y-%m-%d)"
TARGET_TZ="${TARGET_TZ:-Asia/Shanghai}"
read START_EPOCH END_EPOCH < <(
python3 - <<'PY' "$TARGET_DAY" "$TARGET_TZ"
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import sys
day, tz = sys.argv[1], sys.argv[2]
start = datetime.strptime(day, "%Y-%m-%d").replace(tzinfo=ZoneInfo(tz))
end = start + timedelta(days=1)
print(int(start.timestamp()), int(end.timestamp()))
PY
)
# Recursively scan all session directories (multi-agent architecture support)
for dir in "$HOME/.openclaw/sessions" \
"$HOME/.openclaw/agents" \
"$HOME/.openclaw/cron/runs" \
"$HOME/.agent/sessions"; do
[ -d "$dir" ] || continue
find "$dir" -type f -name "*.jsonl" -print0
done |
xargs -0 jq -cr --argjson start "$START_EPOCH" --argjson end "$END_EPOCH" '
(.timestamp // .created_at // empty) as $ts
| ($ts | split(".")[0] + "Z" | fromdateiso8601?) as $epoch
| select($epoch != null and $epoch >= $start and $epoch < $end)
'
```
Read **all matching files** regardless of their numeric naming (e.g., file_22, file_23 may be earlier in name but still contain today's messages).
- **EXTRACT IMAGES FROM SESSION LOGS**: Session logs contain `type: "image"` entries with file paths. You MUST:
1. Find all image entries (e.g., `"type":"image"`)
2. Keep only entries where message `timestamp` is in the target date range
3. Extract the `file_path` or `url` fields
4. Copy files into `assets/YYYY-MM-DD/`
5. Rename with descriptive names when possible
- **Why session logs are mandatory**: `memory_get` returns **text only**. Image metadata, photo references, and media attachments are **only available in session logs**. Skipping session logs = missing all photos.
- **Activity signal quality**: Do not treat heartbeat/cron system noise as user activity. Extract user/assistant conversational content and media events first, then classify moments.
- **FILTER HEARTBEAT MESSAGES (CRITICAL)**: Session logs contain system heartbeat messages that MUST be excluded from journaling. When scanning messages, SKIP any message matching these criteria:
1. **User heartbeat prompts**: Messages containing "Read HEARTBEAT.md" AND "reply HEARTBEAT_OK"
2. **Assistant heartbeat responses**: Messages containing ONLY "HEARTBEAT_OK" (with optional leading/trailing whitespace)
3. **Cron system messages**: Messages with role "system" or "cron" containing job execution summaries (e.g., "Cron job completed", "A cron job")
Example jq filter to exclude heartbeats:
```jq
# Exclude heartbeat messages
| select(
(.message.content? | type == "array" and
(.message.content | map(.text?) | join("") |
test("Read HEARTBEAT\.md"; "i") | not))
and
(.message.content? | type == "array" and
(.message.content | map(.text?) | join("") |
test("^\\s*HEARTBEAT_OK\\s*$"; "i") | not))
)
```
- **Edge case - Midnight boundary**: For late-night activity that spans midnight, expand the **timestamp** range to include spillover windows (for example, previous day 23:00-24:00) and still filter per-message by `timestamp`.
- **Merge sources:** Combine content from both memory files and session logs. Memory files capture explicit user reflections; session logs capture conversational flow and media. Use both to build complete context.
- **Fallback:** If memory is sparse, reconstruct context from session logs, then update memory so future runs use the enriched memory. Incorporate historical context via `memory_search` (skip if embeddings unavailable)
3. **Moment Identification:** Identify "journal-worthy" content: critical decisions, emotional shifts, milestones, or shared media. See `references/media-handling.md` for photo processing. This step generates the `moments` data structure that plugins depend on.
**Image Processing (CRITICAL)**:
- For each extracted image, generate descriptive alt-text via Vision Analysis
- Categorize images (food, selfie, screenshot, document, etc.)
**Filter Finance Screenshots (NEW)**:
Payment screenshots (WeChat Pay, Alipay, etc.) should NOT be included in the journal narrative. These are tool images, not life moments.
Detection criteria (check any):
1. **OCR keywords**: "支付成功", "支付完成", "微信支付", "支付宝", "订单号", "交易单号", "¥" + amount
2. **Context clues**: Image sent with nearby text containing "记账", "支付", "付款", "转账"
3. **Visual patterns**: Standard payment app UI layouts (green WeChat, blue Alipay)
Handling rules:
- Mark as `finance_screenshot` type
- Route to Ledger plugin (if enabled) for transaction recording
- **EXCLUDE from journal main narrative** unless explicitly described as part of a life moment (e.g., "今天请朋友吃饭" with payment screenshot)
- Never include raw payment screenshots in daily journal images section
- Match images to moments (e.g., breakfast photo → breakfast moment)
- Store image metadata with moments for journal embedding
4. **Pattern Recognition:** Detect recurring themes, mood fluctuations, and energy levels. Map these to growth opportunities using `references/skill-recommendations.md`.
5. **Plugin Execution:** Execute all registered plugins at their declared hook points. See `references/plugin-protocol.md` for the complete plugin lifecycle:
- `pre-analysis` → before conversation analysis
- `post-moment-analysis` → **Ledger and other primary plugins execute here**
- `post-pattern-analysis` → after patterns detected
- `journal-generation` → plugins inject custom sections
- `post-journal` → after journal complete
6. **Journal Generation:** Synthesize the day's events into a beautiful Markdown file using `assets/daily-template.md`. Follow the visual guidelines in `references/visual-design.md`. **Include all plugin-generated sections** at their declared `section_order` positions.
- **Embed curated images only**, not every image. Prioritize highlights and moments.
- **Route finance screenshots to Ledger** sections (receipts, invoices, transaction proofs).
- Use Obsidian format from `references/media-handling.md` with descriptive captions.
- **Generate image links from filesystem truth**: compute the image path relative to the current journal file directory. Never output absolute paths.
- **Do not hardcode path depth** (`../` or `../../`): calculate dynamically from `daily_file_path` and `image_path`.
- **Use copied filename as source of truth**: if asset file is `image_124917_2.jpg`, the link must reference that exact filename.
7. **Timeline Integration:** If significant events occurred, append them to the master index in `timeline.md` using the format from `assets/timeline-template.md` and `references/obsidian-format.md`.
8. **Growth Mapping:** Update `growth-map.md` (based on `assets/growth-map-template.md`) if new behavioral patterns or skill interests are detected.
9. **Profile Evolution:** Update the long-term user profile (`profile.md`) to reflect the latest observations on values, goals, and personality traits. See `references/profile-evolution.md` and `assets/profile-template.md`.
## ⏰ Cron & Passive Operation
PhoenixClaw is designed to run without user intervention. It utilizes OpenClaw's built-in cron system to trigger its analysis daily at 10:00 PM local time (0 22 * * *).
- Setup details can be found in `references/cron-setup.md`.
- **Mode:** Primarily Passive. The AI proactively summarizes the day's activities without being asked.
### Rolling Journal Window (NEW)
To solve the 22:00-24:00 content loss issue, PhoenixClaw now supports a **rolling journal window** mechanism:
**Problem**: Fixed 24-hour window (00:00-22:00) misses content between 22:00-24:00 when journal is generated at 22:00.
**Solution**: `scripts/rolling-journal.js` scans from **last journal time → now** instead of fixed daily boundaries.
**Features**:
- Configurable schedule hour (default: 22:00, customizable via `~/.phoenixclaw/config.yaml`)
- Rolling window: No content loss even if generation time varies
- Backward compatible with existing `late-night-supplement.js`
**Configuration** (`~/.phoenixclaw/config.yaml`):
```yaml
schedule:
hour: 22 # Journal generation time
minute: 0
rolling_window: true # Enable rolling window (recommended)
```
**Usage**:
```bash
# Default: generate from last journal to now
node scripts/rolling-journal.js
# Specific date
node scripts/rolling-journal.js 2026-02-12
```
## 💬 Explicit Triggers
While passive by design, users can interact with PhoenixClaw directly using these phrases:
- *"Show me my journal for today/yesterday."*
- *"What did I accomplish today?"*
- *"Analyze my mood patterns over the last week."*
- *"Generate my weekly/monthly summary."*
- *"How am I doing on my personal goals?"*
- *"Regenerate my journal."* / *"重新生成日记"*
> [!warning] **Manual Invocation = Full Pipeline**
> When users request journal generation/regeneration, you MUST execute the **complete 9-step Core Workflow** above. This ensures:
> - **Photos are included** (via session log scanning)
> - **Ledger plugin runs** (via `post-moment-analysis` hook)
> - **All plugins execute** (at their respective hook points)
>
> **Common mistakes to avoid:**
> - ❌ Only calling `memory_get` (misses photos)
> - ❌ Skipping moment identification (plugins never trigger)
> - ❌ Generating journal directly without plugin sections
## 📚 Documentation Reference
### References (`references/`)
- `user-config.md`: Initial onboarding and persistence settings.
- `cron-setup.md`: Technical configuration for nightly automation.
- `plugin-protocol.md`: Plugin architecture, hook points, and integration protocol.
- `media-handling.md`: Strategies for extracting meaning from photos and rich media.
- `session-day-audit.js`: Diagnostic utility for verifying target-day message coverage across session logs.
- `visual-design.md`: Layout principles for readability and aesthetics.
- `obsidian-format.md`: Ensuring compatibility with Obsidian and other PKM tools.
- `profile-evolution.md`: How the system maintains a long-term user identity.
- `skill-recommendations.md`: Logic for suggesting new skills based on journal insights.
### Assets (`assets/`)
- `daily-template.md`: The blueprint for daily journal entries.
- `weekly-template.md`: The blueprint for high-level weekly summaries.
- `profile-template.md`: Structure for the `profile.md` persistent identity file.
- `timeline-template.md`: Structure for the `timeline.md` chronological index.
- `growth-map-template.md`: Structure for the `growth-map.md` thematic index.
---
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/rolling-journal.js
```javascript
#!/usr/bin/env node
/**
* PhoenixClaw Rolling Journal - 滚动日记生成器
*
* 改进版日记生成逻辑:
* 1. 允许用户配置生成时间(默认 22:00)
* 2. 扫描范围:上次日记时间 → 现在(滚动窗口)
* 3. 解决 22:00-24:00 内容遗漏问题
*
* Usage: node rolling-journal.js [YYYY-MM-DD]
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// 配置
const CONFIG = {
journalPath: process.env.PHOENIXCLAW_JOURNAL_PATH || '/mnt/synology/zpro/notes/日记',
sessionRoots: (process.env.OPENCLAW_SESSIONS_PATH || '').split(path.delimiter).filter(Boolean),
configPath: path.join(require('os').homedir(), '.phoenixclaw/config.yaml'),
timezone: 'Asia/Shanghai',
defaultHour: 22 // 默认生成时间
};
/**
* 默认 session 搜索路径列表(覆盖所有已知位置)
*/
function getDefaultSessionRoots() {
const home = require('os').homedir();
return [
path.join(home, '.openclaw', 'sessions'),
path.join(home, '.openclaw', 'agents'),
path.join(home, '.openclaw', 'cron', 'runs'),
path.join(home, '.agent', 'sessions'),
];
}
/**
* 读取用户配置
*/
function loadConfig() {
const config = {
scheduleHour: CONFIG.defaultHour,
scheduleMinute: 0,
rollingWindow: true // 是否启用滚动窗口
};
if (fs.existsSync(CONFIG.configPath)) {
try {
const content = fs.readFileSync(CONFIG.configPath, 'utf-8');
// 简单 YAML 解析
const hourMatch = content.match(/schedule_hour:\s*(\d+)/);
const minuteMatch = content.match(/schedule_minute:\s*(\d+)/);
const rollingMatch = content.match(/rolling_window:\s*(true|false)/);
// FIX: 读取 journal_path 配置
const pathMatch = content.match(/journal_path:\s*(.+)/);
if (hourMatch) config.scheduleHour = parseInt(hourMatch[1]);
if (minuteMatch) config.scheduleMinute = parseInt(minuteMatch[1]);
if (rollingMatch) config.rollingWindow = rollingMatch[1] === 'true';
// FIX: 解析并展开 journal_path
if (pathMatch) {
let jp = pathMatch[1].trim();
// 展开 ~ 为 home 目录
if (jp.startsWith('~/')) {
jp = path.join(require('os').homedir(), jp.slice(2));
}
config.journalPath = jp;
}
} catch (e) {
console.error('Error reading config:', e.message);
}
}
return config;
}
/**
* 找到最后一次日记的时间
*/
function getLastJournalTime(journalPath) {
const dailyDir = path.join(journalPath || CONFIG.journalPath, 'daily');
if (!fs.existsSync(dailyDir)) return null;
const files = fs.readdirSync(dailyDir)
.filter(f => f.endsWith('.md'))
.map(f => ({
file: f,
date: f.replace('.md', ''),
mtime: fs.statSync(path.join(dailyDir, f)).mtime
}))
.sort((a, b) => b.mtime - a.mtime);
if (files.length === 0) return null;
// 返回最新日记的修改时间
return files[0].mtime;
}
/**
* 递归查找目录下所有 .jsonl 文件
*/
function findJsonlFiles(dir) {
const results = [];
if (!fs.existsSync(dir)) return results;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch (e) {
return results;
}
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findJsonlFiles(fullPath));
} else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
results.push(fullPath);
}
}
return results;
}
/**
* 读取所有 session 日志文件(扫描多个目录 + 递归)
*/
function readSessionLogs() {
const roots = CONFIG.sessionRoots.length > 0
? CONFIG.sessionRoots
: getDefaultSessionRoots();
const allFiles = [];
for (const root of roots) {
const files = findJsonlFiles(root);
allFiles.push(...files);
if (files.length > 0) {
console.log(` [scan] ${root} → ${files.length} file(s)`);
}
}
if (allFiles.length === 0) {
console.error('No session log files found in any directory');
return [];
}
console.log(` [scan] Total session files: ${allFiles.length}`);
const logs = [];
let parseErrors = 0;
for (const file of allFiles) {
try {
const content = fs.readFileSync(file, 'utf-8');
const lines = content.split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const entry = JSON.parse(line);
logs.push(entry);
} catch (e) {
parseErrors++;
}
}
} catch (e) {
console.error(`Error reading ${file}:`, e.message);
}
}
if (parseErrors > 0) {
console.log(` [scan] Skipped ${parseErrors} malformed line(s)`);
}
logs.sort((a, b) => {
const ta = new Date(a.timestamp || a.created_at || 0).getTime();
const tb = new Date(b.timestamp || b.created_at || 0).getTime();
return ta - tb;
});
return logs;
}
/**
* 过滤从上次日记到现在的消息
*/
function filterRollingWindowMessages(logs, lastJournalTime) {
const startTime = lastJournalTime || new Date(Date.now() - 24 * 60 * 60 * 1000); // 默认24小时前
const endTime = new Date();
return logs.filter(entry => {
const timestamp = entry.timestamp || entry.created_at;
if (!timestamp) return false;
const entryTime = new Date(timestamp);
return entryTime >= startTime && entryTime <= endTime;
});
}
/**
* 从嵌套 message 结构中提取完整文本
*/
function extractText(entry) {
if (typeof entry.content === 'string') return entry.content;
if (entry.message && Array.isArray(entry.message.content)) {
return entry.message.content.map(c => c.text || '').join(' ');
}
if (Array.isArray(entry.content)) {
return entry.content.map(c => (typeof c === 'string' ? c : c.text || '')).join(' ');
}
return '';
}
/**
* 判断消息是否是"有意义的"
*/
function isMeaningfulMessage(entry) {
const text = extractText(entry);
// 排除心跳检测 — 用户端:包含 "Read HEARTBEAT.md" 且包含 "reply HEARTBEAT_OK"
if (/Read HEARTBEAT\.md/i.test(text) && /reply HEARTBEAT_OK/i.test(text)) return false;
// 排除心跳检测 — 助手端:仅包含 "HEARTBEAT_OK"
if (/^\s*HEARTBEAT_OK\s*$/i.test(text)) return false;
// 排除 cron 系统消息
if ((entry.role === 'system' || entry.role === 'cron') &&
/cron job|nightly reflection|scheduler/i.test(text)) return false;
// 排除纯系统消息(保留带附件的系统消息)
if (entry.role === 'system' && !text.includes('attached')) return false;
// 保留用户消息和助手回复
if (entry.role === 'user' || entry.role === 'assistant') return true;
// 保留图片等媒体
if (entry.type === 'image') return true;
return false;
}
/**
* 提取时刻信息
*/
function extractMoments(messages) {
const moments = [];
let currentDate = null;
for (const msg of messages) {
const time = new Date(msg.timestamp || msg.created_at);
const dateStr = time.toISOString().split('T')[0];
const timeStr = time.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
// 检测日期变化
if (currentDate !== dateStr) {
currentDate = dateStr;
moments.push({
type: 'date-marker',
date: dateStr
});
}
if (msg.type === 'image') {
moments.push({
time: timeStr,
type: 'image',
description: '分享图片'
});
} else if (msg.content) {
// 简化内容(只取前50字)
const preview = msg.content.substring(0, 50).replace(/\n/g, ' ');
const suffix = msg.content.length > 50 ? '...' : '';
moments.push({
time: timeStr,
type: 'text',
role: msg.role === 'user' ? '你' : 'Claw',
preview: preview + suffix
});
}
}
return moments;
}
/**
* 生成日记内容
*/
function generateJournal(moments, startTime, endTime) {
if (moments.length === 0) return null;
const startDate = startTime.toISOString().split('T')[0];
const endDate = endTime.toISOString().split('T')[0];
const dateRange = startDate === endDate ? startDate : `${startDate} ~ ${endDate}`;
let content = `---\n`;
content += `date: ${endDate}\n`;
content += `type: daily\n`;
content += `time_range: ${startTime.toLocaleString('zh-CN')} ~ ${endTime.toLocaleString('zh-CN')}\n`;
content += `---\n\n`;
content += `# 日记 ${dateRange}\n\n`;
let currentDate = null;
for (const moment of moments) {
if (moment.type === 'date-marker') {
if (currentDate !== null) content += '\n';
currentDate = moment.date;
content += `## ${moment.date}\n\n`;
} else if (moment.type === 'image') {
content += `- **${moment.time}** 📸 ${moment.description}\n`;
} else {
content += `- **${moment.time}** ${moment.role}: ${moment.preview}\n`;
}
}
content += `\n---\n`;
content += `*Generated by PhoenixClaw Rolling Journal at ${new Date().toLocaleString('zh-CN')}*\n`;
return content;
}
/**
* 保存日记
*/
function saveJournal(content, date, journalPath) {
const dailyDir = path.join(journalPath || CONFIG.journalPath, 'daily');
// 确保目录存在
if (!fs.existsSync(dailyDir)) {
try {
fs.mkdirSync(dailyDir, { recursive: true });
} catch (e) {
// FIX: 如果 mkdir 失败,尝试使用 shell
try {
execSync(`mkdir -p "${dailyDir}"`, { encoding: 'utf-8' });
} catch (shellErr) {
console.error('Failed to create directory:', shellErr.message);
throw e;
}
}
}
const filename = path.join(dailyDir, `${date}.md`);
// FIX: 尝试直接写入,失败则使用 shell
try {
fs.writeFileSync(filename, content);
} catch (e) {
console.log('Direct write failed, trying shell escape...');
try {
// 使用 shell 写入,处理特殊字符
const escapedContent = content.replace(/'/g, "'\"'\"'");
execSync(`cat > "${filename}" << 'EOF'
${escapedContent}
EOF`, { encoding: 'utf-8' });
} catch (shellErr) {
console.error('Shell write also failed:', shellErr.message);
throw e;
}
}
return filename;
}
/**
* 主函数
*/
async function main() {
console.log('[PhoenixClaw Rolling Journal] Starting...');
// 1. 加载配置
const userConfig = loadConfig();
console.log(`Schedule: ${userConfig.scheduleHour}:${String(userConfig.scheduleMinute).padStart(2, '0')}`);
console.log(`Rolling window: ${userConfig.rollingWindow ? 'enabled' : 'disabled'}`);
console.log(`Journal path: ${userConfig.journalPath}`); // FIX: 显示使用的路径
// 2. 找到上次日记时间
const lastJournalTime = getLastJournalTime(userConfig.journalPath); // FIX: 传递 journalPath
if (lastJournalTime) {
console.log(`Last journal: ${lastJournalTime.toLocaleString('zh-CN')}`);
} else {
console.log('No previous journal found, using default 24h window');
}
// 3. 读取会话日志
console.log('Scanning session directories...');
const logs = readSessionLogs();
console.log(`Read ${logs.length} total log entries`);
// 4. 过滤滚动窗口消息
const windowStart = userConfig.rollingWindow ? lastJournalTime : new Date(Date.now() - 24 * 60 * 60 * 1000);
const windowMessages = filterRollingWindowMessages(logs, windowStart);
console.log(`Messages in window: ${windowMessages.length}`);
// 5. 过滤有意义的消息
const meaningfulMessages = windowMessages.filter(isMeaningfulMessage);
const imageCount = meaningfulMessages.filter(m => m.type === 'image').length;
const textCount = meaningfulMessages.filter(m => m.type !== 'image').length;
console.log(`Meaningful messages: ${meaningfulMessages.length} (text: ${textCount}, images: ${imageCount})`);
const userCount = meaningfulMessages.filter(m => m.role === 'user').length;
const assistantCount = meaningfulMessages.filter(m => m.role === 'assistant').length;
console.log(` user: ${userCount}, assistant: ${assistantCount}`);
if (meaningfulMessages.length === 0) {
console.log('No content to journal, skipping');
process.exit(0);
}
// 6. 提取时刻并生成日记
const moments = extractMoments(meaningfulMessages);
const journalContent = generateJournal(moments, windowStart || new Date(Date.now() - 24 * 60 * 60 * 1000), new Date());
if (journalContent) {
const today = new Date().toISOString().split('T')[0];
const filename = saveJournal(journalContent, today, userConfig.journalPath); // FIX: 传递 journalPath
console.log(`✅ Journal saved: ${filename}`);
console.log(` Contains ${moments.filter(m => m.type !== 'date-marker').length} moments`);
}
}
if (require.main === module) {
main().catch(err => {
console.error('Error:', err);
process.exit(1);
});
}
module.exports = {
findJsonlFiles,
readSessionLogs,
filterRollingWindowMessages,
isMeaningfulMessage,
extractMoments,
extractText,
getDefaultSessionRoots,
getLastJournalTime, // FIX: 导出以支持测试
saveJournal, // FIX: 导出以支持测试
};
```
### references/user-config.md
```markdown
## Configuration Storage Location
The user configuration for PhoenixClaw is stored in a YAML file located at the user's home directory to ensure persistence across sessions and environment updates.
**Path:** `~/.phoenixclaw/config.yaml`
This directory and file are created during the initial onboarding process if they do not already exist.
## Configuration File Format
The configuration file uses standard YAML syntax. It must be valid YAML to be parsed correctly by the PhoenixClaw skill.
```yaml
# ~/.phoenixclaw/config.yaml
journal_path: ~/PhoenixClaw/Journal
timezone: auto # or "Asia/Shanghai", "America/New_York", etc.
language: auto # or "zh-CN", "en-US", etc.
```
## Configurable Options
| Option | Description | Type | Default |
| :--- | :--- | :--- | :--- |
| `journal_path` | Absolute or relative path to the directory where journal entries are saved. | String | `~/PhoenixClaw/Journal` |
| `timezone` | The timezone used for timestamping journal entries. "auto" attempts to detect system timezone. | String | `auto` |
| `language` | The language used for generating journal content and reflections. "auto" follows system locale. | String | `auto` |
## Onboarding Flow
When the PhoenixClaw skill is invoked and no configuration file is detected at `~/.phoenixclaw/config.yaml`, the following onboarding flow must be triggered:
1. **Detection:** Check for existence of the config file.
2. **Greeting:** Provide a warm welcome message to the user.
3. **Path Inquiry:** Ask the user for their preferred journal storage location.
4. **Path Confirmation:** Present the default path (`~/PhoenixClaw/Journal`) and allow the user to accept it or provide a new one.
5. **Initialization:**
* Create the `~/.phoenixclaw/` directory if missing.
* Expand `~` in the provided path to the absolute home directory path.
* Create the target journal directory.
* Generate the `config.yaml` file with the confirmed settings.
6. **Transition:** Proceed immediately to the first journaling task (e.g., summarizing recent activity).
## Onboarding Conversation Examples
### Scenario: Accepting Defaults
**Assistant:** "Welcome to PhoenixClaw! I'm here to help you maintain a passive journal of your work. First, I need to set up your workspace. Where would you like to store your journals? The default is `~/PhoenixClaw/Journal`."
**User:** "That works."
**Assistant:** "Great! I've set up your configuration. I'll now check your recent activity to start today's journal."
### Scenario: Custom Path
**Assistant:** "Welcome to PhoenixClaw! Where would you like to store your journals? (Default: `~/PhoenixClaw/Journal`)"
**User:** "/Users/dev/Documents/MyLogs"
**Assistant:** "Understood. I've configured PhoenixClaw to save entries to `/Users/dev/Documents/MyLogs`. Let's get started!"
## Update Procedures
Users can modify their settings at any time by requesting an update through the assistant.
**Command Triggers:**
* "Update my PhoenixClaw settings"
* "Change my journal storage path"
* "Change my PhoenixClaw language to English"
**Logic for Updates:**
1. Read the existing `~/.phoenixclaw/config.yaml`.
2. Ask the user which setting they wish to change.
3. Validate the new input (especially for `journal_path`).
4. Write the updated values back to the YAML file.
5. Confirm the change to the user.
## Path Validation and Error Handling
To ensure reliability, the following validation steps must be performed when setting or updating the `journal_path`:
* **Existence Check:** If a user provides a path, check if it exists. If it does not, attempt to create it.
* **Permission Check:** Ensure the assistant has write permissions to the target directory.
* **Absolute Paths:** Internally, always resolve paths to absolute paths before writing to the config file or using them for file operations to avoid ambiguity.
* **YAML Integrity:** If the `config.yaml` file becomes corrupted or unreadable, inform the user and offer to reset it to defaults.
* **Graceful Failures:** If a path cannot be created (e.g., due to permission denied), explain the issue clearly: "I couldn't create the directory at `/protected/path`. Please provide a location I have access to, or check your folder permissions."
## Timezone and Language Handling
* **Timezone:** If set to `auto`, the skill should attempt to retrieve the local timezone from the environment (e.g., `Intl.DateTimeFormat().resolvedOptions().timeZone` in JS environments or system clock). If detection fails, default to UTC and notify the user.
* **Language:** If set to `auto`, use the language of the current user session or system locale. If the user explicitly sets a language code (e.g., `zh-CN`), all generated journal content must adhere to that language, regardless of system settings.
```
### references/media-handling.md
```markdown
### Supported Channels
| Channel | Format | Capabilities |
| :--- | :--- | :--- |
| **Telegram** | Photo/Document | Direct bot upload, supports captions and compressed/uncompressed |
| **WhatsApp** | Photo | Media bridge required, handled as transient buffer |
| **Discord** | Attachment | Webhook or bot listener, handles multiple attachments in one message |
| **CLI** | Local Path | User provides file path via command line |
> [!important] Media & Memory Tools
> Note: `memory_get` and `memory_search` return text only. Image metadata and binary references must be sourced from session logs (JSONL) or the local `assets` directory. Scan session logs even when daily memory exists to capture in-progress media.
### Photo Processing Workflow
1. **Receive**: Capture media from the active channel (Telegram/WhatsApp/Discord/CLI).
2. **Buffer**: Store the raw media in a transient memory buffer with metadata (timestamp, source).
3. **Detect**: Cron job or trigger detects unresolved media references in the message stream.
4. **Vision Analysis**: AI processes the photo to extract a descriptive alt-text (e.g., "Spicy miso ramen with soft-boiled egg").
5. **Relocate**: Move file from transient buffer to the permanent assets directory.
6. **Rename**: Apply standard naming convention to the file.
7. **Embed**: Generate the Obsidian-style markdown link and insert into the journal with the appropriate layout.
### Image Extraction Commands
**Filter session messages by target day (not file mtime):**
```bash
# Step 1: Define target day and timezone
TARGET_DAY="$(date +%Y-%m-%d)"
TARGET_TZ="${TARGET_TZ:-Asia/Shanghai}"
# Step 2: Build timezone-aware [start, end) epoch range for TARGET_DAY
read START_EPOCH END_EPOCH < <(
python3 - <<'PY' "$TARGET_DAY" "$TARGET_TZ"
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import sys
day, tz = sys.argv[1], sys.argv[2]
start = datetime.strptime(day, "%Y-%m-%d").replace(tzinfo=ZoneInfo(tz))
end = start + timedelta(days=1)
print(int(start.timestamp()), int(end.timestamp()))
PY
)
# Step 3: Recursively read all session files from ALL known locations and keep only messages inside TARGET_DAY
for dir in "$HOME/.openclaw/sessions" \
"$HOME/.openclaw/agents" \
"$HOME/.openclaw/cron/runs" \
"$HOME/.agent/sessions"; do
[ -d "$dir" ] || continue
find "$dir" -type f -name "*.jsonl" -print0
done |
xargs -0 jq -cr --argjson start "$START_EPOCH" --argjson end "$END_EPOCH" '
(.timestamp // .created_at // empty) as $ts
| ($ts | split(".")[0] + "Z" | fromdateiso8601?) as $epoch
| select($epoch != null and $epoch >= $start and $epoch < $end)
'
```
**Extract image entries from target-day messages:**
```bash
# Keep image entries whose message timestamp is in TARGET_DAY (recursive scan)
for dir in "$HOME/.openclaw/sessions" \
"$HOME/.openclaw/agents" \
"$HOME/.openclaw/cron/runs" \
"$HOME/.agent/sessions"; do
[ -d "$dir" ] || continue
find "$dir" -type f -name "*.jsonl" -print0
done |
xargs -0 jq -r --argjson start "$START_EPOCH" --argjson end "$END_EPOCH" '
(.timestamp // .created_at // empty) as $ts
| ($ts | split(".")[0] + "Z" | fromdateiso8601?) as $epoch
| select($epoch != null and $epoch >= $start and $epoch < $end and .type == "image")
| (.file_path // .url)
'
```
> Do not classify images by session file modification time. Always classify by each image message's `timestamp`.
**Copy images to journal assets:**
```bash
# Images are stored in ~/.openclaw/media/inbound/
# Copy to journal assets
TODAY=$(date +%Y-%m-%d)
mkdir -p ~/PhoenixClaw/Journal/assets/$TODAY/
cp ~/.openclaw/media/inbound/file_*.jpg ~/PhoenixClaw/Journal/assets/$TODAY/
```
**Vision Analysis (example output):**
- "早餐:猪儿粑配咖啡,放在白色桌面上"
### Curation Rules (Do Not Embed Everything)
- **Embed highlights only**: pick photos that represent meaningful moments, milestones, or emotional peaks.
- **Finance screenshots** (receipts, invoices, transaction proofs) should be routed to the Ledger plugin output, not the main narrative flow.
- **Low-signal images** (screenshots of chats, generic docs) should be omitted unless they support a key moment.
### Storage & Naming
**Path**: `~/PhoenixClaw/Journal/assets/YYYY-MM-DD/`
**File Naming**:
- Primary format: `img_XXX.jpg` (zero-padded)
- Example: `img_001.jpg`, `img_002.jpg`
- For specific events: `img_001_lunch.jpg` (optional suffix)
### Link Path Generation (Required)
When embedding an image into a journal file, always compute the link from actual file paths.
**Rules:**
- Never use absolute paths in journal markdown.
- Never hardcode `../assets` or `../../assets`.
- Compute the relative path from the journal file directory to the asset file.
- Use the exact copied filename from disk; do not infer or rewrite timestamp fragments.
```python
from pathlib import Path
import os
def get_relative_image_path(daily_file_path: str, image_path: str) -> str:
daily_file = Path(daily_file_path).resolve()
image_file = Path(image_path).resolve()
rel = os.path.relpath(image_file, start=daily_file.parent)
return rel.replace(os.sep, "/")
```
**Validation before render:**
- `Path(image_path).exists()` is true.
- Embedded filename equals `Path(image_path).name` exactly.
- Link does not start with `/`.
- Link depth is produced by computation, not template constants.
### Journal Photo Layouts
#### Single Photo (Moment)
Used for capturing a specific point in time or a single highlight.
```markdown
> [!moment] 🍜 12:30 Lunch
> ![[../assets/2026-02-01/img_001.jpg|400]]
> Spicy miso ramen with a perfectly soft-boiled egg at the new shop downtown.
```
#### Multiple Photos (Gallery)
Used when several photos are sent together or relate to the same context.
```markdown
> [!gallery] 📷 Today's Moments
> ![[../assets/2026-02-01/img_002.jpg|200]] ![[../assets/2026-02-01/img_003.jpg|200]]
> ![[../assets/2026-02-01/img_004.jpg|200]]
```
#### Photo with Narrative Context
Used when the photo is part of a larger story or reflection.
```markdown
### Weekend Hike
The trail was steeper than I remembered, but the view from the summit made every step worth it.
![[../assets/2026-02-01/img_005.jpg|600]]
*The fog rolling over the valley at 8:00 AM.*
I spent about twenty minutes just sitting there, listening to the wind and watching the light change across the ridgeline.
```
### Metadata Handling
- **Exif Data**: Preserve original Exif data (GPS, Timestamp) where possible to assist in auto-locating "Moments".
- **Captions**: If a user sends a caption with the photo, prioritize it as the primary description.
- **Deduplication**: Check file hashes before copying to prevent duplicate assets for the same entry.
```
### references/skill-recommendations.md
```markdown
PhoenixClaw employs a pattern-based recommendation system to guide users toward relevant OpenClaw skills based on their journaling content.
### Pattern Detection & Skill Mapping
| Category | User Pattern / Trigger Example | Recommended Skill |
| :--- | :--- | :--- |
| **Mental Health** | "feeling anxious", "racing thoughts" | `mindfulness-coach` |
| | "can't sleep", "waking up tired" | `sleep-optimizer` |
| | "feeling overwhelmed", "high pressure" | `stress-relief` |
| **Productivity** | "putting things off", "stuck" | `pomodoro-master` |
| | "ran out of time", "busy but unproductive" | `time-blocks` |
| | "constant distractions", "hard to focus" | `deep-work` |
| **Health & Fitness**| "need to exercise", "gym routine" | `workout-buddy` |
| | "eating poorly", "tracking macros" | `nutrition-log` |
| | "forgetting to drink water" | `water-reminder` |
| **Habits** | "want to start [habit]", "consistency" | `habit-streak` |
| | "learning [skill]", "studying" | `learning-tracker` |
| **Relationships** | "argument with [name]", "communication" | `relationship-coach` |
| | "awkward in groups", "meeting people" | `social-skills` |
| **Finance** | "花了", "买了", "支出", "消费" | `phoenixclaw-ledger` |
| | "记账", "预算", "开销太大" | `phoenixclaw-ledger` |
| | "工资", "收入", "到账" | `phoenixclaw-ledger` |
| | Payment screenshots detected | `phoenixclaw-ledger` |
### Recommendation Guidelines
Recommendations must be earned through consistent pattern detection to avoid "recommendation fatigue."
#### Frequency & Persistence Rules
1. **1st Detection:** Observe and log the pattern in internal metadata. Do not recommend.
2. **3+ Occurrences:** If the pattern persists across 3 separate entries within a 14-day window, flag for recommendation.
3. **Persistent Pattern:** If the user explicitly asks for help or the pattern is severe/ongoing (5+ entries), prioritize the recommendation.
#### Constraint: Volume Control
* **Max 1 new skill recommendation per week.**
* Do **NOT** recommend skills for one-time mentions or transient moods.
* Do **NOT** re-recommend a skill the user has already rejected or installed.
### Recommendation Format
Recommendations should be subtle, integrated into the "Growth Insights" or "Action Items" sections of journals and `growth-map.md`.
**Journal Entry Example:**
> **Growth Insight:** I've noticed mentions of sleep difficulties in your last few entries. You might find the `sleep-optimizer` skill helpful for establishing a better wind-down routine.
**Growth Map Example:**
> ### Recommended Skills
> * **Deep Work (`deep-work`):** Based on recent focus challenges mentioned in your productivity reflections.
### Verification Flow
When PhoenixClaw detects a match:
1. Check `skill-inventory.json` (or equivalent) to see if the skill is already active.
2. Cross-reference `user-preferences.md` for any "do not suggest" categories.
3. Verify the last recommendation date to ensure the 1-per-week limit is respected.
```
### references/plugin-protocol.md
```markdown
# PhoenixClaw Plugin Protocol v1
This document defines the protocol for building plugins that extend PhoenixClaw's core functionality.
## Overview
PhoenixClaw supports a plugin architecture where specialized skills can hook into the core journaling pipeline to add domain-specific features (finance tracking, health monitoring, reading logs, etc.).
## Plugin Declaration
Plugins declare their relationship with PhoenixClaw in their SKILL.md frontmatter:
```yaml
---
name: phoenixclaw-{plugin-name}
description: |
Brief description of the plugin functionality.
depends: phoenixclaw
hook_point: post-moment-analysis
data_access:
- moments
- user_config
- memory
export_to_journal: true
---
```
### Required Fields
| Field | Type | Description |
|-------|------|-------------|
| `depends` | string | Must be `phoenixclaw` to indicate Core dependency |
| `hook_point` | string | When in the pipeline this plugin executes |
| `data_access` | array | What data the plugin needs access to |
| `export_to_journal` | boolean | Whether plugin output merges into daily journal |
## Hook Points
Plugins can attach to these points in PhoenixClaw's core workflow:
| Hook Point | Timing | Use Case |
|------------|--------|----------|
| `pre-analysis` | Before conversation analysis | Pre-processing, filtering |
| `post-moment-analysis` | After moments identified | Finance, health tracking |
| `post-pattern-analysis` | After patterns detected | Advanced analytics |
| `journal-generation` | During journal writing | Custom sections |
| `post-journal` | After journal complete | Notifications, exports |
### Hook Execution Order
```
1. Core loads config
2. Core retrieves memory
└── [pre-analysis] plugins execute
3. Core identifies moments
└── [post-moment-analysis] plugins execute
4. Core detects patterns
└── [post-pattern-analysis] plugins execute
5. Core generates journal
└── [journal-generation] plugins inject sections
6. Core writes files
└── [post-journal] plugins execute
```
## Data Access
### Available Data Objects
Plugins can request access to these data structures:
#### `moments`
```yaml
moments:
- timestamp: "2026-02-02T12:30:00"
type: activity|decision|emotion|milestone|media
content: "Had lunch with colleagues"
metadata:
location: "Downtown"
people: ["Alice", "Bob"]
media: ["img_001.jpg"]
```
#### `patterns`
```yaml
patterns:
themes: ["productivity", "social"]
mood_trajectory: positive|negative|stable
energy_level: high|medium|low
recurring: ["morning coffee", "evening walk"]
```
#### `user_config`
```yaml
user_config:
journal_path: "~/PhoenixClaw/Journal"
timezone: "Asia/Shanghai"
language: "zh-CN"
plugins:
phoenixclaw-ledger:
enabled: true
budget_monthly: 5000
```
#### `memory`
Raw memory data from `memory_get` for the current day.
## Plugin Output
### Exporting to Journal
Plugins with `export_to_journal: true` must provide their output in this format:
```yaml
plugin_output:
section_id: "finance" # Unique section identifier
section_title: "💰 财务记录" # Display title with emoji
section_order: 50 # Position (0-100, lower = earlier)
content: |
> [!expense] 🍜 12:30 午餐
> 和同事吃火锅 | **¥150** | 餐饮
summary: # Optional: for Growth Notes
- "今日支出 ¥449"
- "本月已用预算 62%"
```
### Section Ordering
| Order Range | Reserved For |
|-------------|--------------|
| 0-19 | Core: Highlights |
| 20-39 | Core: Moments |
| 40-59 | Plugins: Primary (Finance, Health) |
| 60-79 | Core: Reflections |
| 80-89 | Plugins: Secondary |
| 90-100 | Core: Growth Notes |
### Standalone Files
Plugins may also create their own files outside the journal:
```yaml
plugin_files:
- path: "Finance/ledger.yaml"
type: data
- path: "Finance/monthly/2026-02.md"
type: report
```
## Plugin Discovery
PhoenixClaw Core discovers plugins through:
1. **Installed Skills**: Skills with `depends: phoenixclaw` in their frontmatter
2. **Config Reference**: Plugins listed in `~/.phoenixclaw/config.yaml`
```yaml
# ~/.phoenixclaw/config.yaml
plugins:
phoenixclaw-ledger:
enabled: true
# Plugin-specific config
phoenixclaw-health:
enabled: false
```
## Communication Protocol
### Core → Plugin
Core passes context to plugins via structured data:
```yaml
plugin_context:
date: "2026-02-02"
hook_point: "post-moment-analysis"
moments: [...]
patterns: [...]
user_config: {...}
previous_output: {...} # Output from earlier plugins
```
### Plugin → Core
Plugins return results:
```yaml
plugin_result:
success: true
section_output: {...} # For journal integration
files_written: [...] # List of created/updated files
insights: [...] # For Growth Notes
errors: [] # Any errors encountered
```
## Error Handling
Plugins should handle errors gracefully without breaking the core pipeline:
1. **Fail Soft**: Return empty output rather than throwing
2. **Log Errors**: Include errors in `plugin_result.errors`
3. **Degrade Gracefully**: Partial results are acceptable
```yaml
plugin_result:
success: false
section_output: null
errors:
- code: "SCREENSHOT_OCR_FAILED"
message: "Could not extract text from receipt_001.jpg"
severity: warning # warning|error
```
## Versioning
Plugins should declare protocol compatibility:
```yaml
---
name: phoenixclaw-ledger
protocol_version: 1
min_core_version: 1.0.0
---
```
## Example Plugin Registration
When PhoenixClaw Core runs, it:
1. Scans for installed skills with `depends: phoenixclaw`
2. Validates protocol compatibility
3. Registers plugins at their declared hook points
4. Executes plugins in order during the journaling pipeline
---
*This protocol is designed for extensibility. Future versions may add new hook points and data access patterns while maintaining backward compatibility.*
```
### assets/daily-template.md
```markdown
---
date: {{DATE}}
weekday: {{WEEKDAY}}
type: daily
mood: {{MOOD}}
energy: {{ENERGY}}
tags: [journal, phoenixclaw]
---
# 🌅 {{DATE}} — {{WEEKDAY}}
## ✨ Highlights
{{HIGHLIGHTS}}
## 🖼️ Moments
{{MOMENTS}}
## 💭 Reflections
{{REFLECTIONS}}
## 🌱 Growth Notes
{{GROWTH_NOTES}}
---
*Generated by PhoenixClaw 🔥*
```
### references/visual-design.md
```markdown
## Image Placement Patterns
### Single Photo (The Moment)
Highlight a specific moment with a focused layout.
```markdown
> [!NOTE] 📸 The View
> 
> *The sky turned purple and gold as we reached the peak.*
```
### Gallery Grid (Collage)
Group related images for visual impact without clutter.
```markdown
<div class="gallery-grid">



</div>
*Sunday morning essentials: caffeine, literature, and mood.*
```
### Contextual Inline
Place images next to relevant text for flow.
```markdown
The trail was steeper than expected. {align=right}
We had to stop every few minutes to catch our breath, but the view was worth it.
```
---
## Emoji Usage Guidelines
Use emojis as visual anchors, not decoration. Maintain a consistent "voice".
**Time-based Markers:**
- 🌅 Morning / Start
- ☀️ Afternoon / Active
- 🌇 Evening / Wind-down
- 🌙 Night / Sleep
**Theme Markers:**
- ✨ Highlights / Wins
- 🖼️ Visuals / Moments
- 💭 Reflections / Thoughts
- 🌱 Growth / Learning
- 🎯 Focus / Goals
**Don't:** ❌ Sprinkle random emojis in every sentence.
**Do:** ✅ Use them to header sections or bullet points for scanability.
---
## Section Layouts
### Time-Based Flow
Chronological storytelling for daily logs.
```markdown
### 🌅 Morning Routine
Slow start. Coffee on the balcony.
### ☀️ Mid-Day Push
Deep work session. Completed the core feature.
### 🌇 Evening Unwind
Dinner with friends.
```
### Theme-Based Flow
Categorical grouping for weekly reviews or specific events.
```markdown
### ✨ Highlights
- Shipped the new release
- Found a great new cafe
### 💭 Headspace
Feeling productive but needing more rest.
### 🌱 Growth
Learned about CSS Grid subgrid today.
```
---
## Quote Formatting
Use blockquotes for insights, overheard dialogue, or key takeaways.
**Standard Insight:**
> "Consistency is the only currency that matters."
**Dialogue Snippet:**
> **Her:** "Did you see that?"
> **Me:** "I missed it."
> *We both laughed at the absurdity of the timing.*
**Callout for Emphasis:**
> [!TIP] Lesson Learned
> Always check the weather before hiking. Always.
---
## Social-Share Friendly Format
Designed to look good in a screenshot or when shared.
```markdown
# 📅 Sunday Reset
> "Recharging is productive." 💭
**✨ Top 3 Wins:**
1. Read 50 pages
2. Meal prepped for the week
3. Zero screen time after 8 PM
**📸 Mood:**

```
```
### assets/timeline-template.md
```markdown
---
updated: {{UPDATED_DATE}}
---
# 🔥 Life Timeline
## {{YEAR}}
### {{MONTH}}
<!-- Format: **YYYY-MM-DD** 🎯|🎉|💡 [Type] Description → [[daily/YYYY-MM-DD]] -->
{{EVENTS}}
---
*Last updated: {{UPDATED_DATE}}*
```
### references/obsidian-format.md
```markdown
---
date: 2026-02-01
weekday: Sunday
type: daily-journal
mood: 🌟
energy: ⚡
tags: [journal, reflection]
---
## YAML Frontmatter
All journals must begin with a YAML block for metadata:
```yaml
---
date: YYYY-MM-DD
weekday: Full Day Name
type: journal-type (e.g., daily, project, review)
mood: Emoji (🌅, ✨, 🌧️, ⚡)
energy: 1-10 or Emoji (🔋, 🪫)
tags: [category, project-name]
---
```
## Bidirectional Links
Connect notes using double brackets:
- Standard link: [[2026-02-01]]
- Link with alias: [[2026-02-01|Today's Entry]]
- Section link: [[Project Alpha#Milestones]]
## Tags
Use inline tags for categorization:
#achievement #milestone #reflection #insight #daily-win #blocker
## Callout Blocks
Use specific types to highlight content:
> [!moment] 🌅 Significant Moment
> Describe a fleeting or impactful moment.
> [!highlight] ✨ Highlight
> Key event or win of the day.
> [!reflection] 💭 Reflection
> Deep thoughts on experiences or feelings.
> [!insight] 💡 Insight
> Realizations or lessons learned.
> [!milestone] 🎯 Milestone
> Significant progress on a goal or project.
> [!gallery] 🖼️ Media
> Used for grouping images or visual logs.
## Image Embedding
Embed assets with optional size control:
![[../assets/2026/02/capture.jpg|400]]
![[screenshot.png|200]]
Path rules:
- Use relative paths from the current note location.
- Do not use absolute paths such as `/mnt/...` or `C:\\...`.
- Compute path depth dynamically from the note file and target asset file.
## Mermaid Charts
Visualize timelines or processes:
```mermaid
gantt
title Daily Progress
section Morning
Deep Work :a1, 2026-02-01, 4h
section Afternoon
Project Meeting :a2, after a1, 1h
Coding :a3, after a2, 3h
```
## Collapsible Sections
Hide dense technical logs or details:
<details>
<summary>Technical Logs (Click to expand)</summary>
- Debugged memory leak in worker
- Updated dependencies
- Ran benchmark suite
</details>
## Horizontal Rules
Use to separate distinct parts of the day:
---
## Emoji Usage Patterns
- 🌅 Morning / Start
- ✨ Key Highlight
- 🖼️ Visual/Gallery
- 💭 Internal Thought
- 🌱 Growth / Learning
- 🔥 Intensity / Focus
- 🎯 Goal / Achievement
- 🎉 Celebration
- 💡 Idea / Realization
- 🐛 Debugging / Problem
- 🔋 High Energy
- 🪫 Low Energy
```
### assets/growth-map-template.md
```markdown
---
updated: {{UPDATED_DATE}}
---
# 🗺️ Growth Map
## Active Themes
<!-- Format:
### 🏷️ Theme Name
- **Pattern**: What's been observed
- **Progress**: Current status
- **Recommended**: [[skill:name]] if applicable
- **Related**: [[daily/date1]], [[daily/date2]]
-->
{{THEMES}}
## Emerging Patterns
{{PATTERNS}}
## Skill Recommendations
| Pattern | Recommended Skill | Status |
|---------|------------------|--------|
{{SKILL_RECOMMENDATIONS}}
---
*Last updated: {{UPDATED_DATE}}*
```
### references/profile-evolution.md
```markdown
---
created: 2026-02-01
updated: 2026-02-01
version: 1.0.0
---
The user profile in PhoenixClaw is a living document that captures the essence of the user's personality, preferences, and growth. It is built through passive observation and refined over time using a non-destructive, evidence-based approach.
## Profile Architecture
The profile is structured to separate explicit user self-expression from inferred AI observations. This ensures the user retains ultimate control while the AI provides a data-driven mirror of their behavior.
### Required Sections
1. **Identity**: Basic demographic and situational context (e.g., role, current focus).
2. **Personality Traits**: Core behavioral patterns and psychological archetypes.
3. **Communication Style**: Preferences for tone, brevity, technical depth, and medium.
4. **Interests**: Topics of focus, hobbies, and areas of curiosity.
5. **Growth Journey**: Evolution of skills and mindset over time.
## Initial Creation Template
On the first execution, the AI scans available conversation history to bootstrap the profile.
```yaml
# Profile Bootstrap Structure
identity:
summary: ""
role: ""
traits: []
communication:
tone: ""
style: ""
interests: []
user_notes: |
# Manual edits go here. AI must not touch this section.
ai_observations: []
```
## Append-Only Update Strategy
To maintain historical integrity and prevent data loss, the AI uses an append-only strategy for observations.
- **Rule**: Never overwrite existing observations.
- **Action**: Add new observations as new entries with timestamps.
- **Synthesis**: Every 30 days, the AI may summarize older observations into "Archived Patterns" to keep the active profile concise, but the raw evidence remains accessible.
## Trait Detection Patterns
Traits are inferred from specific actions and outcomes within the workspace.
| Observed Action | Potential Trait | Evidence Required |
| :--- | :--- | :--- |
| Resolves complex race conditions | Analytical Problem-solver | 3+ instances of deep debugging |
| Frequently asks "Why?" before "How?" | First-principles Thinker | Repeated inquiry into underlying logic |
| Documents every edge case | Thorough / Detail-oriented | Consistent high-quality documentation |
| Rapidly adopts new libraries | Early Adopter / Fast Learner | Minimal lag between tool release and use |
| Simplifies complex architectures | Minimalist Architect | Pattern of refactoring for simplicity |
## Confidence Levels
Each entry in the AI Observations section must be tagged with a confidence level based on frequency and duration.
- **Low (1 observation)**: A single event. Used for interests or transient states.
- **Medium (3+ observations OR 7 days consistent behavior)**: A developing pattern.
- **High (10+ observations AND 30+ days consistent behavior)**: A core aspect of the user's identity or style.
## Handling Manual Edits
The `user_notes` section is sacred.
1. **Respect**: If the user writes "I am a night owl" in `user_notes`, the AI must prioritize this over its own observation that the user works at 9 AM.
2. **Separation**: Keep AI-derived insights in the `ai_observations` list.
3. **Conflict Resolution**: If AI observations contradict `user_notes`, the AI should note the discrepancy as a "Growth Opportunity" or "Context Shift" rather than deleting either.
## Privacy and Ethics
- **No Secrets**: Never record passwords, API keys, financial details, or sensitive health data.
- **No Verbatim Quotes**: Summarize the *pattern* or *sentiment* rather than quoting the user directly to avoid leaking sensitive conversational context.
- **Utility Focus**: Only store information that helps the AI better serve the user.
## Update Frequency
- **Interests**: Updated daily or upon discovery of new topics.
- **Communication Style**: Updated weekly based on response patterns.
- **Personality Traits**: Updated monthly or upon reaching a high-confidence milestone.
- **Growth Journey**: Updated when significant milestones (e.g., project completion) are reached.
```
### assets/profile-template.md
```markdown
---
created: {{CREATED_DATE}}
updated: {{UPDATED_DATE}}
version: {{VERSION}}
---
# 🔥 Personal Profile
## Identity
- **Name**: {{NAME}}
- **Timezone**: {{TIMEZONE}}
- **Primary Language**: {{LANGUAGE}}
- **Active Since**: {{ACTIVE_SINCE}}
## Personality Traits
> These traits are observed from conversations and may evolve over time.
| Trait | Evidence | Confidence |
|-------|----------|------------|
{{TRAITS_TABLE}}
## Communication Style
- **Tone**: {{TONE}}
- **Language mix**: {{LANGUAGES}}
- **Emoji usage**: {{EMOJI_USAGE}}
## Interests & Topics
{{INTERESTS}}
## Daily Rhythms
- **Most active hours**: {{ACTIVE_HOURS}}
- **Peak energy**: {{PEAK_ENERGY}}
## Growth Journey
### Active Themes
{{ACTIVE_THEMES}}
### Milestones
{{MILESTONES}}
## User Notes
<!-- This section is maintained by the user -->
## AI Observations
<!-- This section is maintained by PhoenixClaw -->
```
### references/cron-setup.md
```markdown
PhoenixClaw leverages OpenClaw's built-in cron system for automated, passive journaling. This configuration ensures nightly reflections occur without manual triggers.
### One-Time Setup
Run the following command to register the PhoenixClaw nightly reflection job. This schedules the task to run every day at 10:00 PM local time.
```bash
openclaw cron add \
--name "PhoenixClaw nightly reflection" \
--cron "0 22 * * *" \
--tz "auto" \
--session isolated \
--message "Execute PhoenixClaw with COMPLETE 9-step Core Workflow. CRITICAL STEPS:
1. Load config
2. memory_get + Scan session logs and filter by each message timestamp for today (not file mtime)
3. Identify moments (decisions, emotions, milestones, photos) -> creates 'moments' data
4. Detect patterns
5. Execute ALL plugins at hook points (Ledger runs at post-moment-analysis)
6. Generate journal WITH all plugin sections
7-9. Update timeline, growth-map, profile
NEVER skip session log scanning - images are ONLY there. NEVER skip step 3 - plugins depend on moments data."
```
> **Memory & Session Scan**: Always scan session logs from all known directories (`~/.openclaw/sessions/`, `~/.openclaw/agents/*/sessions/`, `~/.openclaw/cron/runs/`, `~/.agent/sessions/`) alongside daily memory to capture in-progress activity. Use recursive scanning to find `.jsonl` files in nested subdirectories. If daily memory is missing or sparse, use session logs to reconstruct context, then update daily memory.
### Configuration Details
- **--name**: Unique identifier for the job. Useful for management.
- **--cron**: Standard crontab syntax. "0 22 * * *" represents 10:00 PM daily.
- **--tz auto**: Automatically detects the system's local timezone. You can also specify a specific timezone like "America/New_York".
- **--session isolated**: Ensures the job runs in a clean environment with full tool access, preventing interference from active coding sessions.
- **--message**: Keep this payload task-focused and version-agnostic. Do not hardcode skill version numbers here; treat `metadata.version` in `SKILL.md` as the source of truth.
### Verification and Monitoring
To ensure the job is correctly registered and active:
```bash
openclaw cron list
```
To view the execution history, including status codes and timestamps of previous runs:
```bash
openclaw cron history "PhoenixClaw nightly reflection"
```
### Modifying and Removing Jobs
If you need to change the schedule or the instructions, you can update the job using the same name:
```bash
openclaw cron update "PhoenixClaw nightly reflection" --cron "0 23 * * *"
```
To completely stop and delete the automated reflection job:
```bash
openclaw cron remove "PhoenixClaw nightly reflection"
```
### Post-Execution Verification
After cron runs, verify the full workflow executed:
```bash
# 1. Check target-day messages were scanned (by message timestamp)
TARGET_DAY="$(date +%Y-%m-%d)"
TARGET_TZ="${TARGET_TZ:-Asia/Shanghai}"
read START_EPOCH END_EPOCH < <(
python3 - <<'PY' "$TARGET_DAY" "$TARGET_TZ"
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
import sys
day, tz = sys.argv[1], sys.argv[2]
start = datetime.strptime(day, "%Y-%m-%d").replace(tzinfo=ZoneInfo(tz))
end = start + timedelta(days=1)
print(int(start.timestamp()), int(end.timestamp()))
PY
)
# Recursively scan all session directories (multi-agent architecture support)
for dir in "$HOME/.openclaw/sessions" \
"$HOME/.openclaw/agents" \
"$HOME/.openclaw/cron/runs" \
"$HOME/.agent/sessions"; do
[ -d "$dir" ] || continue
find "$dir" -type f -name "*.jsonl" -print0
done |
xargs -0 jq -cr --argjson start "$START_EPOCH" --argjson end "$END_EPOCH" '
(.timestamp // .created_at // empty) as $ts
| ($ts | split(".")[0] + "Z" | fromdateiso8601?) as $epoch
| select($epoch != null and $epoch >= $start and $epoch < $end)
' | wc -l
# 2. Check images were extracted (if any existed)
ls -la ~/PhoenixClaw/Journal/assets/$(date +%Y-%m-%d)/ 2>/dev/null || echo "No assets dir"
# 3. Check Ledger plugin ran (if installed)
grep -q "财务\|Finance\|💰" ~/PhoenixClaw/Journal/daily/$(date +%Y-%m-%d).md && echo "Ledger OK" || echo "Ledger section missing"
# 4. Check journal contains callout sections
grep -c "\[!" ~/PhoenixClaw/Journal/daily/$(date +%Y-%m-%d).md
```
**Diagnostic interpretation:**
- If images are missing → session logs were not properly scanned
- If Ledger section is missing → moment identification (step 3) was skipped
- If no callouts → journal generation used minimal template
Optional JS audit (structured summary, user/noise split):
```bash
node skills/phoenixclaw/references/session-day-audit.js --day "$(date +%Y-%m-%d)" --tz "Asia/Shanghai"
```
### Troubleshooting
If journals are not appearing as expected, check the following:
1. **System Wake State**: OpenClaw cron requires the host machine to be awake. On macOS/Linux, ensure the machine isn't sleeping during the scheduled time.
2. **Path Resolution**: Ensure `openclaw` is in the system PATH available to the cron daemon.
3. **Log Inspection**: Check the internal OpenClaw logs for task-specific errors:
```bash
openclaw logs --task "PhoenixClaw nightly reflection"
```
4. **Timezone Mismatch**: If jobs run at unexpected hours, verify the detected timezone with `openclaw cron list` and consider hardcoding the timezone if `auto` fails.
5. **Tool Access**: Ensure the `isolated` session has proper permissions to read the memory directories and write to the journal storage.
6. **Memory Search Availability**: If `memory_search` is unavailable due to a missing embeddings provider (OpenAI/Gemini/Local), journaling will still continue by scanning daily memory and session logs directly. For cross-day pattern recognition and long-term recall, consider configuring an embeddings provider.
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "goforu",
"slug": "phoenixclaw",
"displayName": "phoenixclaw",
"latest": {
"version": "0.0.19",
"publishedAt": 1773805821800,
"commit": "https://github.com/openclaw/skills/commit/78845de1d0082ef200f3dd87ecc8ea96c93a4b25"
},
"history": [
{
"version": "0.0.18",
"publishedAt": 1773216535455,
"commit": "https://github.com/openclaw/skills/commit/e78aea2205bd672216e6c1dde26e2f054bc3ef2a"
},
{
"version": "0.0.16",
"publishedAt": 1772264855577,
"commit": "https://github.com/openclaw/skills/commit/e13fc477dd69fde43f056c2c9ac4f5994cc3ffbd"
},
{
"version": "0.0.15",
"publishedAt": 1771925031143,
"commit": "https://github.com/openclaw/skills/commit/133ea32c61dc95e144dab5ff84c56fcc81da1ca4"
},
{
"version": "0.0.14",
"publishedAt": 1771054068281,
"commit": "https://github.com/openclaw/skills/commit/d06c39be583a3c62dee2894cdf5babb50e2e1658"
},
{
"version": "0.0.13",
"publishedAt": 1770824347180,
"commit": "https://github.com/openclaw/skills/commit/bfb35239d8682edf9ed97043f86bed98fa131187"
},
{
"version": "0.0.12",
"publishedAt": 1770607493358,
"commit": "https://github.com/openclaw/skills/commit/c0cdc1961358749e5c0821346ae1827a827d7ac0"
},
{
"version": "0.0.11",
"publishedAt": 1770541285333,
"commit": "https://github.com/openclaw/skills/commit/31418b68fcbd440ad92879683db6831efb243949"
},
{
"version": "0.0.10",
"publishedAt": 1770394666348,
"commit": "https://github.com/openclaw/skills/commit/15b654ce5e3ba49da98984c238c3191df7c5c253"
},
{
"version": "0.0.8",
"publishedAt": 1770134182949,
"commit": "https://github.com/clawdbot/skills/commit/c1cc19624a7be4b3d9ae9b16fc21aef9a00fa59d"
},
{
"version": "0.0.7",
"publishedAt": 1770092854411,
"commit": "https://github.com/clawdbot/skills/commit/6eb591a565deecb5fd857445172a772ce52d6401"
},
{
"version": "0.0.6",
"publishedAt": 1770052178008,
"commit": "https://github.com/clawdbot/skills/commit/fc820dd05552121b2edfa0d75b937fd2d26a6494"
},
{
"version": "0.0.5",
"publishedAt": 1770049125417,
"commit": "https://github.com/clawdbot/skills/commit/0aec2f621627d70f52805e6054a9d4400eb0fc3f"
},
{
"version": "0.0.3",
"publishedAt": 1770005541692,
"commit": "https://github.com/clawdbot/skills/commit/fdfe0f024eb11f0da6fe878b8f622560c6114051"
},
{
"version": "0.0.2",
"publishedAt": 1769968835203,
"commit": "https://github.com/clawdbot/skills/commit/bfef591351e2aed585ef96d6670a4b152f27fb84"
}
]
}
```
### assets/weekly-template.md
```markdown
---
week: {{WEEK}}
start_date: {{START_DATE}}
end_date: {{END_DATE}}
type: weekly
tags: [journal, phoenixclaw, weekly]
---
# 📅 Week {{WEEK}} — {{START_DATE}} to {{END_DATE}}
## 🎯 Week at a Glance
{{WEEK_SUMMARY}}
## 🔥 Key Moments
{{KEY_MOMENTS}}
## 📊 Patterns Observed
{{PATTERNS}}
## 💡 Insights
{{INSIGHTS}}
## 🌱 Growth This Week
{{GROWTH}}
## 📅 Daily Links
{{DAILY_LINKS}}
---
*Weekly reflection by PhoenixClaw 🔥*
```
### references/session-day-audit.js
```javascript
#!/usr/bin/env node
const fs = require("fs");
const os = require("os");
const path = require("path");
function parseArgs(argv) {
const args = {
day: null,
tz: "Asia/Shanghai",
verbose: false,
};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === "--day" && i + 1 < argv.length) {
args.day = argv[i + 1];
i += 1;
continue;
}
if (arg === "--tz" && i + 1 < argv.length) {
args.tz = argv[i + 1];
i += 1;
continue;
}
if (arg === "--verbose") {
args.verbose = true;
continue;
}
if (arg === "--help" || arg === "-h") {
printHelp();
process.exit(0);
}
}
if (!args.day) {
args.day = formatDateInTz(new Date(), args.tz);
}
return args;
}
function printHelp() {
console.log(`Session day audit for PhoenixClaw
Usage:
node skills/phoenixclaw/references/session-day-audit.js [--day YYYY-MM-DD] [--tz Asia/Shanghai] [--verbose]
Examples:
node skills/phoenixclaw/references/session-day-audit.js --day 2026-02-07 --tz Asia/Shanghai
node skills/phoenixclaw/references/session-day-audit.js --verbose
`);
}
function formatDateInTz(date, tz) {
const fmt = new Intl.DateTimeFormat("en-CA", {
timeZone: tz,
year: "numeric",
month: "2-digit",
day: "2-digit",
});
const parts = fmt.formatToParts(date);
const year = parts.find((p) => p.type === "year")?.value;
const month = parts.find((p) => p.type === "month")?.value;
const day = parts.find((p) => p.type === "day")?.value;
if (!year || !month || !day) {
throw new Error(`Unable to format date in timezone: ${tz}`);
}
return `${year}-${month}-${day}`;
}
function expandHome(inputPath) {
if (!inputPath.startsWith("~/")) {
return inputPath;
}
return path.join(os.homedir(), inputPath.slice(2));
}
function findJsonlFilesRecursive(dir) {
const results = [];
if (!fs.existsSync(dir)) return results;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch (e) {
return results;
}
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findJsonlFilesRecursive(fullPath));
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
results.push(fullPath);
}
}
return results;
}
function listSessionFiles() {
const roots = [
"~/.openclaw/sessions",
"~/.openclaw/agents",
"~/.openclaw/cron/runs",
"~/.agent/sessions",
].map(expandHome);
const files = [];
for (const root of roots) {
files.push(...findJsonlFilesRecursive(root));
}
return files;
}
function safeJsonParse(line, file, lineNumber) {
try {
return JSON.parse(line);
} catch {
return {
_parseError: true,
_file: file,
_line: lineNumber,
};
}
}
function getTimestamp(entry) {
const ts = entry.timestamp || entry.created_at;
if (typeof ts !== "string" || ts.length === 0) {
return null;
}
const d = new Date(ts);
if (Number.isNaN(d.getTime())) {
return null;
}
return d;
}
function flattenText(value) {
if (typeof value === "string") {
return value;
}
if (Array.isArray(value)) {
return value.map(flattenText).join(" ");
}
if (value && typeof value === "object") {
return Object.values(value).map(flattenText).join(" ");
}
return "";
}
/**
* Get role from entry, handling nested message structure
* OpenClaw session logs store role in entry.message.role, not entry.role
*/
function getRole(entry) {
// Try entry.message.role first (OpenClaw format)
const nestedRole = entry.message?.role;
if (typeof nestedRole === "string") {
return nestedRole.toLowerCase();
}
// Fallback to entry.role for backward compatibility
const directRole = entry.role;
if (typeof directRole === "string") {
return directRole.toLowerCase();
}
return "";
}
/**
* Get content from entry, handling nested message structure
*/
function getContent(entry) {
// Try entry.message.content first (OpenClaw format)
if (entry.message?.content) {
return entry.message.content;
}
// Fallback to entry.content for backward compatibility
return entry.content;
}
function isLikelyUserEntry(entry) {
const role = getRole(entry);
if (role === "user") {
return true;
}
const type = typeof entry.type === "string" ? entry.type.toLowerCase() : "";
if (type.includes("user")) {
return true;
}
const payloadText = flattenText(entry).toLowerCase();
if (payloadText.includes("\"role\":\"user\"") || payloadText.includes("role:user")) {
return true;
}
return false;
}
function isNoise(entry) {
// Check both nested and direct content
const nestedContent = flattenText(entry.message?.content);
const directContent = flattenText(entry.content);
const payloadText = (nestedContent + " " + directContent).toLowerCase();
const noiseTokens = [
"heartbeat",
"cron",
"nightly reflection",
"scheduler",
"system pulse",
"system heartbeat",
];
return noiseTokens.some((token) => payloadText.includes(token));
}
function auditDay({ day, tz, verbose }) {
const files = listSessionFiles();
const summary = {
targetDay: day,
timezone: tz,
scannedFiles: files.length,
parseErrors: 0,
matchedMessages: 0,
userMessages: 0,
assistantMessages: 0,
imageMessages: 0,
noiseMessages: 0,
filesWithMatches: new Map(),
};
for (const file of files) {
const content = fs.readFileSync(file, "utf8");
const lines = content.split(/\r?\n/);
for (let i = 0; i < lines.length; i += 1) {
const line = lines[i].trim();
if (!line) {
continue;
}
const entry = safeJsonParse(line, file, i + 1);
if (entry._parseError) {
summary.parseErrors += 1;
continue;
}
const ts = getTimestamp(entry);
if (!ts) {
continue;
}
const messageDay = formatDateInTz(ts, tz);
if (messageDay !== day) {
continue;
}
summary.matchedMessages += 1;
summary.filesWithMatches.set(file, (summary.filesWithMatches.get(file) || 0) + 1);
// Use getRole() to handle nested message structure
const role = getRole(entry);
if (role === "assistant") {
summary.assistantMessages += 1;
}
const type = typeof entry.type === "string" ? entry.type.toLowerCase() : "";
if (type === "image") {
summary.imageMessages += 1;
}
if (isLikelyUserEntry(entry)) {
summary.userMessages += 1;
}
if (isNoise(entry)) {
summary.noiseMessages += 1;
}
if (verbose) {
const tsText = entry.timestamp || entry.created_at;
const displayRole = getRole(entry) || "?";
console.log(`[${path.basename(file)}:${i + 1}] ${tsText} role=${displayRole} type=${entry.type || "?"}`);
}
}
}
return summary;
}
function printSummary(summary) {
console.log("PhoenixClaw Session Day Audit");
console.log("-----------------------------");
console.log(`target_day: ${summary.targetDay}`);
console.log(`timezone: ${summary.timezone}`);
console.log(`scanned_files: ${summary.scannedFiles}`);
console.log(`matched_messages: ${summary.matchedMessages}`);
console.log(`user_messages: ${summary.userMessages}`);
console.log(`assistant_messages: ${summary.assistantMessages}`);
console.log(`image_messages: ${summary.imageMessages}`);
console.log(`noise_messages: ${summary.noiseMessages}`);
console.log(`parse_errors: ${summary.parseErrors}`);
const activeFiles = Array.from(summary.filesWithMatches.entries())
.sort((a, b) => b[1] - a[1]);
if (activeFiles.length === 0) {
console.log("files_with_matches: none");
return;
}
console.log("files_with_matches:");
for (const [file, count] of activeFiles) {
console.log(` - ${file} (${count})`);
}
}
function main() {
const args = parseArgs(process.argv.slice(2));
if (!/^\d{4}-\d{2}-\d{2}$/.test(args.day)) {
throw new Error(`Invalid --day value: ${args.day} (expected YYYY-MM-DD)`);
}
const summary = auditDay(args);
printSummary(summary);
if (summary.userMessages === 0 && summary.matchedMessages > 0) {
process.exitCode = 2;
}
}
try {
main();
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
```