Skill: fret774/memweaver (version 0.1.0)

--- name: memweaver description: "Memory Profiler — Mine hidden patterns from your Agent's memory, confirm via interactive quiz, and generate a structured user profile." version: 0.1.0 metadata: openclaw: requires: bins: - python3 --- # MemWeaver — Memory Profiler > Your Agent reads your memory every day, but does it truly *understand* you? MemWeaver digs into your memory files — long-term memory (`MEMORY.md`) and daily logs — to uncover preferences, behavioral patterns, and **hidden traits you might not even be aware of**, then confirms findings through an interactive questionnaire and outputs a structured user profile (YAML). ## What Makes This Different | Existing tools | What they do | What MemWeaver does differently | |---|---|---| | Mem0 / Zep | Memory **retrieval** | Not retrieval — **understanding** | | SimpleMem / LightMem | Memory **compression** | Not compression — **insight mining** | | ai-persona-os | Give AI a persona | Opposite direction: discover **your** persona from memory | **Core value**: MemWeaver finds the gap between what you *say* you prefer and what you *actually do* — then asks you about it. ## Overview - **Input**: `MEMORY.md` (long-term memory) + recent daily logs (`memory/*.md`) - **Process**: LLM deep analysis → batch interactive questionnaire - **Output**: Structured user profile at `output/profile_YYYYMMDD.yaml` ## Workflow ### Step 1: Collect Memory ```bash cd {baseDir} && python3 scripts/collect_memory.py --days 14 ``` The script reads long-term memory and recent logs, outputs JSON to stdout. The Agent parses `content.long_term` and `content.daily_logs` fields from the JSON. **Note**: If `estimated_tokens` exceeds 8000, consider reducing the `--days` parameter. ### Step 2: LLM Deep Analysis The Agent analyzes collected memory in 3 sub-tasks: #### 2.1 Basic Profile Extraction Extract factual information from memory across these dimensions: | Dimension | What to extract | Confidence source | |---|---|---| | Identity | Role, tech stack, MBTI | Explicit statements → 1.0 | | Work patterns | Active projects, decision style, creation preference | Behavioral inference → 0.7-0.9 | | Interests | Professional interests, hobbies, depth of engagement | Topic frequency → 0.6-0.9 | | Communication | Response depth preference, format preference, dislikes | Interaction pattern → 0.7-0.85 | | Long-term goals | Career direction, product plans, values | Explicit statements → 0.9-1.0 | Tag each field with a `confidence` score. #### 2.2 Hidden Pattern Mining This is MemWeaver's most valuable part. The Agent specifically analyzes these 6 types of hidden patterns: 1. **Decision patterns**: What does the user lean toward when facing multiple options? (Analysis-driven vs intuition? Fast vs slow decisions?) 2. **Time & energy allocation**: Does actual energy distribution (from log frequency) match user's self-description? 3. **Overlooked interests**: Topics that appear repeatedly but the user hasn't formally tracked 4. **Statement vs behavior contradictions**: Are stated preferences inconsistent with actual actions? 5. **Emotion/energy triggers**: What scenarios make the user especially productive or resistant? 6. **Unlabeled skills**: Abilities the user demonstrates but hasn't self-recognized Each finding needs **evidence** (citing specific memory content) and **reasoning logic**. #### 2.3 Project Importance Re-evaluation List every project and idea recorded in MEMORY.md, provide reassessment: - Current status (active / paused / archived / concept) - Suggested importance (high / medium / low / shelved) - Assessment reasoning (frequency in logs, recent activity, user investment) - Questions to confirm with user (if uncertain) ### Step 3: Interactive Confirmation (Batch Questionnaire) Interact with the user in **batch mode**, similar to a personality test. Each question is based on the user's actual memory content, not just showing analysis conclusions. **Core design principles**: - **Push 5 questions per batch**, user answers at once (e.g., "1A 2C 3B 4D 5A"), Agent provides feedback then pushes next batch - Questions reference user's real memories as context - Provide options (A/B/C/D or open-ended), user can choose or free-form - **Type B (hidden insight) questions should be ≥50% of total** — this is MemWeaver's core value - Three question types interleaved, not strictly separated by rounds **Question Design Rules**: The Agent designs 10-15 questions based on Step 2 analysis. Three types: #### Type A: Scenario Recall (validate profile facts, ≤25%) Reconstruct a real scene from memory, let user choose the best description. ``` 📋 Q1. Your memory shows you did [specific behavior] on [specific date]. For you, this was more like: A. [option: engineering intuition / habit-driven] B. [option: lesson learned] C. [option: personality-driven] D. Other: ___ ``` #### Type B: Hidden Insight (core value, ≥50%) **This is MemWeaver's most important question type.** The Agent uses specific evidence from memory to point out contradictions or blind spots between user's "self-perception" and "actual behavior". **Methodology**: 1. Find user's **explicit statement** (e.g., "I prefer X") 2. Find **contradictory behavioral records** (e.g., logs show consistently doing Y) 3. Present the contradiction to user, guide explanation via options 4. Options should include: acknowledge contradiction, deny, offer new explanation, other ``` 📋 Q5. Your memory says "[user's explicit statement]". But logs show from [date A] to [date B] you've been consistently doing [contradictory behavior]. These two things: A. Don't contradict — [reasonable explanation] B. Actually contradict — my real preference differs from self-perception C. Depends on context — [conditional explanation] D. Other: ___ > 🔍 Your words say X, but your actions say Y ``` **Hidden insight mining directions** (look for these 6 types of clues in memory): 1. **Statement vs behavior contradictions**: Stated preferences inconsistent with actual actions 2. **Time allocation truth**: Log frequency/length reveals real energy distribution vs stated priorities 3. **Silence signals**: Topics in MEMORY that disappear from logs → possible priority drift 4. **Energy fingerprint**: Length differences across log types → reveals energy sources 5. **Choice patterns**: Consistent tendencies when user faces decisions 6. **Unlabeled skills**: Abilities demonstrated but not self-recognized #### Type C: Priority Trade-off (re-evaluate project importance, ≤25%) Create resource-constraint scenarios, force user to choose between projects, revealing true priorities. ``` 📋 Q10. If you could only advance 2 personal projects next month (work doesn't count), your memory mentions these: [project list from MEMORY.md] Which two? A. [Project1] + [Project2] B. [Project1] + [Project3] C. [Project2] + [Project3] D. Other combination: ___ ``` **Question count and batching**: - Total **10-15 questions**, pushed in **2-3 batches** (~5 per batch) - Batch 1 (5 questions): 1-2 warm-up Type A + 3 Type B (hidden insights) - Batch 2 (5 questions): 3-4 Type B + 1-2 Type C (priority trade-offs) - Optional Batch 3 (2-3 questions): follow-up questions based on unexpected findings from previous batches - After each batch, Agent waits for user to answer all at once **Answer processing**: - After user submits a batch (e.g., "1C 2B 3A 4B 5C"), Agent processes collectively - Give 1 brief feedback per question, noting profile inference - If an answer reveals new insight leads, add follow-up questions in next batch - All answers recorded internally as profile evidence, aggregated into Step 4 **Completion**: - After the last batch, Agent outputs a brief **profile summary** (like a personality test result page) - Then proceeds to Step 4 ### Step 4: Generate and Save Profile Generate the confirmed profile as YAML and save via script: 1. Agent generates complete YAML profile (see "Profile Template" below) 2. Save via script: ```bash cd {baseDir} && python3 scripts/save_profile.py --file /tmp/memweaver_profile.yaml ``` Or via stdin: ```bash echo '<YAML content>' | cd {baseDir} && python3 scripts/save_profile.py ``` The script automatically backs up old profiles and saves to `output/profile_YYYYMMDD.yaml`. --- ## Profile Template ```yaml # MemWeaver User Profile # Generated: YYYY-MM-DD # Version: 1 identity: role: "" tech_stack: [] mbti: "" confidence: 1.0 work_patterns: decision_style: "" # data_driven / intuitive / consultative detail_preference: "" # high / medium / low creation_preference: "" # 0to1 / polish / both energy_source: "" # ideation / execution / collaboration work_rhythm: "" # burst / steady / mixed confidence: 0.0 interests: professional: - topic: "" depth: "" # expert / active_research / exploring / casual importance: "" # high / medium / low last_active: "" personal: - topic: "" depth: "" importance: "" communication: preferred_depth: "" # deep_analysis / balanced / brief preferred_format: "" # structured / narrative / mixed language: "" dislikes: [] goals: career: - goal: "" priority: "" # high / medium / low / shelved timeframe: "" # immediate / short_term / long_term products: - name: "" priority: "" status: "" # active / designing / idea / archived hidden_patterns: - pattern: "" evidence: "" confirmed: false projects: - name: "" importance: "" # high / medium_high / medium / low / shelved status: "" # active / iterating / designing / exploring / archived / idea meta: generated_at: "" memory_files_analyzed: 0 total_memory_lines: 0 user_confirmed: true next_review: "" # Suggest re-profiling in 2 weeks ``` ## Output - User profile: `{baseDir}/output/profile_YYYYMMDD.yaml` - Analysis cache: `{baseDir}/cache/analysis_cache.json` (future version) ## Requirements No external dependencies. Python 3.8+ standard library only.

path: README.md
content: # 🧬 MemWeaver — Memory Profiler

> **Your Agent reads your memory every day. But does it truly *understand* you?**

MemWeaver is an OpenClaw Skill that mines your Agent's memory files to uncover preferences, behavioral patterns, and **hidden traits you might not even be aware of** — then confirms them through an interactive questionnaire and generates a structured user profile.

---

## ✨ The Problem

You've been chatting with your AI agent for weeks. It has your memory files, your daily logs, your project notes. But ask it *"what kind of person am I?"* and you'll get a generic answer.

**Your memory is a goldmine. Nobody's mining it.**

MemWeaver changes that.

## 🔍 What It Does

```
📖 Collect Memory  →  🧠 Deep Analysis  →  💬 Interactive Quiz  →  📄 Profile YAML
    (script)            (Agent LLM)         (personality test)      (structured output)
```

1. **Collects** your `MEMORY.md` and recent daily logs
2. **Analyzes** them for explicit facts AND hidden patterns
3. **Asks** you 10-15 questions (like a personality test, but based on YOUR real memories)
4. **Generates** a structured YAML profile with confidence scores

## 🎯 The Secret Sauce: Hidden Pattern Mining

Most profiling tools ask you to fill in a form. MemWeaver reads what you've *already done* and finds what you **didn't notice**:

| What MemWeaver Finds | Example |
|---|---|
| 🪞 **Say vs Do contradictions** | *"You say you prefer deep analysis, but your logs show you make snap decisions 70% of the time"* |
| ⏰ **Time allocation truth** | *"You listed Project A as top priority, but 80% of your recent logs are about Project B"* |
| 🔇 **Silence signals** | *"You stopped mentioning [Topic X] 3 weeks ago — priority drift?"* |
| ⚡ **Energy fingerprint** | *"Your logs are 3x longer when working on [Type Y] tasks — that's where your energy comes from"* |
| 🧩 **Unlabeled skills** | *"You've been doing [Skill Z] consistently but never listed it in your tech stack"* |

## 📋 Interactive Quiz Experience

Instead of a boring table to review, MemWeaver runs a **personality-test-style questionnaire** based on your real memories:

```
📋 Batch 1 (5 questions)

Q1. Your memory shows you mass-refactored 3 projects last Tuesday.
    For you, this was more like:
    A. Engineering discipline — tech debt bothers me
    B. Procrastination — avoiding the harder task
    C. Flow state — I was on a roll
    D. Other: ___

Q2. Your memory says "I prefer structured, data-driven decisions."
    But logs show you chose your last 3 projects based on gut feeling within 5 minutes.
    These two things:
    A. Don't contradict — different contexts
    B. Actually contradict — I'm more intuitive than I thought
    C. Depends — big decisions get analysis, small ones get intuition
    D. Other: ___

... (3 more questions)

Reply like: 1C 2B 3A 4D 5C
```

> 💡 **≥50% of questions are "Hidden Insight" questions** — the kind that make you go *"huh, I never noticed that"*

## 📄 Output: Structured Profile

```yaml
# MemWeaver User Profile
identity:
  role: "Backend Engineer"
  tech_stack: [Python, Go, PostgreSQL]
  mbti: "INTJ"
  confidence: 1.0

work_patterns:
  decision_style: "intuitive"      # Surprise! Not "data_driven" as stated
  creation_preference: "0to1"
  energy_source: "ideation"
  confidence: 0.85

hidden_patterns:
  - pattern: "Says prefers deep analysis but makes fast intuitive decisions"
    evidence: "3 recent project choices made in <5 min despite stated preference"
    confirmed: true
  - pattern: "Unlabeled skill: technical writing"
    evidence: "Consistently produces detailed docs but not listed in skills"
    confirmed: true

# ... full 7-dimension profile with confidence scores
```

## 🚀 Quick Start

### Prerequisites
- An OpenClaw-compatible agent (CodeBuddy, Claude with Skills, etc.)
- Python 3.8+ (standard library only, zero dependencies)
- Some memory files (MEMORY.md + daily logs in `.codebuddy/memory/`)

### Installation

**Via ClawHub** (recommended):
```bash
clawhub install memweaver
```

**Manual**:
1. Copy the `memweaver` folder to your workspace
2. Tell your agent: *"Load the memweaver skill"* or *"Analyze my profile"*

### Usage

Just say one of these to your agent:
- *"Analyze my profile"*
- *"Mine my memory for patterns"*
- *"Run memweaver"*
- *"What kind of person am I based on my memory?"*

The agent will guide you through the 4-step workflow automatically.

## 📁 File Structure

```
memweaver/
├── SKILL.md                    # Skill definition (workflow + templates)
├── scripts/
│   ├── collect_memory.py       # Step 1: Collect and merge memory files
│   └── save_profile.py         # Step 4: Save confirmed profile
├── config/
│   └── dimensions.yaml         # Profile dimension definitions (customizable)
└── output/                     # Generated profiles (gitignored)
    └── profile_YYYYMMDD.yaml
```

## 🤔 How Is This Different?

| Tool | What it does | MemWeaver's difference |
|---|---|---|
| **Mem0 / Zep** | Memory *retrieval* | Not retrieval — **understanding** |
| **SimpleMem** | Memory *compression* | Not compression — **insight mining** |
| **ai-persona-os** | Give AI a persona | Opposite: discover **your** persona from memory |
| **Personality tests** | Generic questions | Questions based on **your real behavior** |

## 🛠 Customization

- **Dimensions**: Edit `config/dimensions.yaml` to add/modify profile dimensions
- **Log range**: Use `--days N` to control how far back to analyze
- **Questions**: The agent auto-generates questions; the SKILL.md defines the methodology

## 📜 License

MIT — see [LICENSE](./LICENSE)

## 🙋 FAQ

**Q: How much memory do I need for good results?**
A: At least 2 weeks of daily logs + a MEMORY.md. More data = better hidden pattern detection.

**Q: Will it work if my memory is in Chinese/other languages?**
A: Yes! The Agent (LLM) handles multi-language analysis natively.

**Q: Is my data sent anywhere?**
A: No. Everything runs locally within your agent session. No external APIs, no telemetry.

**Q: Can I re-run it periodically?**
A: Absolutely! We recommend re-profiling every 2-4 weeks. The script auto-backs up previous profiles.

---

<p align="center">
  <strong>Your memory already knows who you are. Let MemWeaver show you.</strong>
</p>


path: config/dimensions.yaml
content: # MemWeaver Profile Dimension Definitions
# The Agent extracts and infers along these dimensions when analyzing memory

# === Basic Profile (extracted from explicit statements) ===
identity:
  description: "User's basic identity information"
  fields:
    - role          # Job title / role
    - tech_stack    # Technology stack
    - mbti          # Personality type
    - organization  # Organization (optional)

# === Work Patterns (inferred from behavior) ===
work_patterns:
  description: "User's working style and decision-making patterns"
  fields:
    - decision_style      # Decision style: data_driven / intuitive / consultative
    - detail_preference   # Detail preference: high / medium / low
    - creation_preference # Creation preference: 0to1 / polish / both
    - work_rhythm         # Work rhythm: burst / steady / mixed
    - energy_source       # Energy source: ideation / execution / collaboration

# === Interests (extracted from discussion topics) ===
interests:
  description: "Professional and personal interests"
  depth_levels:
    - expert          # Deep expertise
    - active_research # Actively researching
    - exploring       # Initial exploration
    - casual          # Casual interest
    - heavy_player    # Deep engagement (non-professional)
    - long_term_habit # Long-term habit

# === Communication Preferences (inferred from interaction patterns) ===
communication:
  description: "User's interaction preferences with Agent"
  fields:
    - preferred_depth   # Response depth: deep_analysis / balanced / brief
    - preferred_format  # Format preference: structured / narrative / mixed
    - language          # Language
    - dislikes          # Explicitly disliked response types

# === Long-term Goals (from explicit statements and implied directions) ===
goals:
  description: "Career, product, and personal goals"
  priority_levels:
    - high      # Currently actively pursuing
    - medium    # Planned but not urgent
    - low       # Want to do but deferred
    - shelved   # Archived / on hold

# === Hidden Patterns (Agent should specifically mine for these) ===
hidden_patterns:
  description: "Behavioral patterns the user may not be aware of"
  analysis_prompts:
    - "Decision patterns: What do they lean toward when facing multiple options?"
    - "Time & energy allocation: Actual vs ideal distribution?"
    - "Overlooked interests: Repeatedly mentioned but not formally tracked?"
    - "Statement vs behavior contradictions: Where do words and actions diverge?"
    - "Emotion triggers: What makes the user especially engaged or resistant?"
    - "Unlabeled skills: Abilities demonstrated but not self-recognized?"


path: scripts/collect_memory.py
content: #!/usr/bin/env python3
"""
MemWeaver — Memory Collection Script

Reads long-term memory (MEMORY.md) and recent daily logs (memory/*.md)
from the workspace, merges them into structured JSON output for Agent analysis.

Usage:
    python3 collect_memory.py [--days 14] [--workspace /path/to/workspace]

Output:
    stdout: JSON-formatted memory collection result
    stderr: Progress information
"""

import argparse
import json
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path


def parse_args():
    parser = argparse.ArgumentParser(description="MemWeaver Memory Collector")
    parser.add_argument(
        "--days",
        type=int,
        default=14,
        help="Collect logs from the last N days (default: 14)",
    )
    parser.add_argument(
        "--workspace",
        default=None,
        help="Workspace root directory (default: auto-detect)",
    )
    parser.add_argument(
        "--profile",
        default=None,
        help="Path to existing profile file (for incremental updates)",
    )
    return parser.parse_args()


def find_workspace(script_dir):
    """Walk up from script directory to find workspace root (directory containing .codebuddy/)"""
    current = Path(script_dir).resolve()
    for _ in range(5):
        if (current / ".codebuddy").is_dir():
            return current
        current = current.parent
    return None


def read_file_safe(filepath):
    """Safely read a file, return None on failure"""
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            return f.read()
    except (OSError, UnicodeDecodeError) as e:
        print(f"  ⚠️ Cannot read {filepath}: {e}", file=sys.stderr)
        return None


def estimate_tokens(text):
    """Rough token estimation (Chinese ~1.5 chars/token, English ~4 chars/token)"""
    if not text:
        return 0
    chinese_chars = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
    other_chars = len(text) - chinese_chars
    return int(chinese_chars / 1.5 + other_chars / 4)


def collect_daily_logs(memory_dir, days):
    """Collect daily log files from the last N days"""
    logs = {}
    cutoff = datetime.now() - timedelta(days=days)

    if not memory_dir.is_dir():
        print(f"  ⚠️ Log directory does not exist: {memory_dir}", file=sys.stderr)
        return logs

    for f in sorted(memory_dir.glob("*.md"), reverse=True):
        # Filename format: 2026-03-20.md
        try:
            date_str = f.stem  # "2026-03-20"
            file_date = datetime.strptime(date_str, "%Y-%m-%d")
        except ValueError:
            continue

        if file_date < cutoff:
            continue

        content = read_file_safe(f)
        if content and content.strip():
            logs[date_str] = content.strip()
            print(f"  ✅ {f.name} ({len(content.splitlines())} lines)", file=sys.stderr)

    return logs


def main():
    args = parse_args()

    # Locate workspace
    if args.workspace:
        workspace = Path(args.workspace)
    else:
        script_dir = os.path.dirname(os.path.abspath(__file__))
        workspace = find_workspace(script_dir)

    if not workspace or not (workspace / ".codebuddy").is_dir():
        print("Error: Cannot locate workspace (.codebuddy/ directory not found)", file=sys.stderr)
        sys.exit(1)

    print(f"Workspace: {workspace}", file=sys.stderr)
    print(f"Collection range: last {args.days} days", file=sys.stderr)
    print("", file=sys.stderr)

    # 1. Read long-term memory
    print("📖 Reading long-term memory...", file=sys.stderr)
    memory_file = workspace / ".codebuddy" / "MEMORY.md"
    long_term = read_file_safe(memory_file)
    if long_term:
        print(
            f"  ✅ MEMORY.md ({len(long_term.splitlines())} lines)",
            file=sys.stderr,
        )
    else:
        print("  ❌ MEMORY.md does not exist or is empty", file=sys.stderr)
        long_term = ""

    # 2. Collect daily logs
    print("📋 Collecting daily logs...", file=sys.stderr)
    memory_dir = workspace / ".codebuddy" / "memory"
    daily_logs = collect_daily_logs(memory_dir, args.days)
    print(f"  Collected {len(daily_logs)} days of logs", file=sys.stderr)

    # 3. Read existing profile (if any)
    existing_profile = None
    if args.profile and os.path.exists(args.profile):
        print(f"📄 Reading existing profile: {args.profile}", file=sys.stderr)
        existing_profile = read_file_safe(args.profile)
    else:
        # Auto-detect latest profile in output/
        # Look for memweaver/output/ relative to the skill directory
        skill_dir = Path(os.path.dirname(os.path.abspath(__file__))).parent
        output_dir = skill_dir / "output"
        if output_dir.is_dir():
            profiles = sorted(output_dir.glob("profile_*.yaml"), reverse=True)
            if profiles:
                print(
                    f"📄 Found existing profile: {profiles[0].name}",
                    file=sys.stderr,
                )
                existing_profile = read_file_safe(profiles[0])

    # 4. Statistics
    all_files = []
    total_lines = 0

    if long_term:
        all_files.append("MEMORY.md")
        total_lines += len(long_term.splitlines())

    for date_str in daily_logs:
        all_files.append(f"memory/{date_str}.md")
        total_lines += len(daily_logs[date_str].splitlines())

    all_text = long_term + "\n".join(daily_logs.values())
    total_tokens = estimate_tokens(all_text)

    # 5. Output JSON
    result = {
        "workspace": str(workspace),
        "collected_at": datetime.now().isoformat(),
        "files_read": all_files,
        "total_lines": total_lines,
        "estimated_tokens": total_tokens,
        "days_covered": args.days,
        "existing_profile": existing_profile,
        "content": {
            "long_term": long_term,
            "daily_logs": daily_logs,
        },
    }

    print("", file=sys.stderr)
    print(f"📊 Summary: {len(all_files)} files, {total_lines} lines, ~{total_tokens} tokens", file=sys.stderr)

    json.dump(result, sys.stdout, ensure_ascii=False, indent=2)


if __name__ == "__main__":
    main()


path: scripts/save_profile.py
content: #!/usr/bin/env python3
"""
MemWeaver — Profile Save Script

Receives a YAML-formatted user profile from stdin or file,
saves it to the output directory with automatic backup of old profiles.

Usage:
    echo '<yaml>' | python3 save_profile.py
    python3 save_profile.py --file profile_draft.yaml
    python3 save_profile.py --output custom_path.yaml --stdin

Exit codes:
    0  Success
    1  Argument error or write failure
"""

import argparse
import os
import shutil
import sys
from datetime import datetime
from pathlib import Path


def parse_args():
    parser = argparse.ArgumentParser(description="MemWeaver Profile Saver")
    parser.add_argument(
        "--output",
        default=None,
        help="Output file path (default: output/profile_YYYYMMDD.yaml)",
    )
    parser.add_argument(
        "--file",
        default=None,
        help="Read profile content from file (alternative to stdin)",
    )
    parser.add_argument(
        "--stdin",
        action="store_true",
        default=False,
        help="Read profile content from stdin",
    )
    return parser.parse_args()


def find_workspace(script_dir):
    """Walk up from script directory to find workspace root"""
    current = Path(script_dir).resolve()
    for _ in range(5):
        if (current / ".codebuddy").is_dir():
            return current
        current = current.parent
    return None


def main():
    args = parse_args()

    # Read profile content
    content = None
    if args.file:
        if not os.path.exists(args.file):
            print(f"Error: File not found: {args.file}", file=sys.stderr)
            sys.exit(1)
        with open(args.file, "r", encoding="utf-8") as f:
            content = f.read()
    else:
        # Default: read from stdin
        if sys.stdin.isatty():
            print("Error: Please provide profile content via stdin or --file", file=sys.stderr)
            sys.exit(1)
        content = sys.stdin.read()

    if not content or not content.strip():
        print("Error: Profile content is empty", file=sys.stderr)
        sys.exit(1)

    # Determine output path
    if args.output:
        output_path = Path(args.output)
    else:
        script_dir = os.path.dirname(os.path.abspath(__file__))
        skill_dir = Path(script_dir).parent
        output_dir = skill_dir / "output"
        output_dir.mkdir(parents=True, exist_ok=True)
        today = datetime.now().strftime("%Y%m%d")
        output_path = output_dir / f"profile_{today}.yaml"

    # Ensure output directory exists
    output_path.parent.mkdir(parents=True, exist_ok=True)

    # Backup old file if exists
    if output_path.exists():
        backup_path = output_path.with_suffix(".yaml.bak")
        shutil.copy2(output_path, backup_path)
        print(f"📦 Backed up old profile: {backup_path.name}", file=sys.stderr)

    # Write
    with open(output_path, "w", encoding="utf-8") as f:
        f.write(content)

    print(f"✅ Profile saved: {output_path}", file=sys.stderr)
    print(f"   Content: {len(content.splitlines())} lines", file=sys.stderr)

    # Output path to stdout for Agent reference
    print(str(output_path))


if __name__ == "__main__":
    main()


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