Skill: cyresearch/chatgpt-memory-extraction (version 1.0.1)

--- name: chatgpt-memory-extraction description: "Extract structured personal memories from ChatGPT export data (conversations JSON). Produces organized timeline, people profiles, and thematic records by deeply reading every conversation. Use when: (1) user exported or wants to export ChatGPT data, (2) user wants to organize/analyze/search their chat history, (3) user wants to build a personal memory archive or diary from conversations, (4) user asks to extract people/events/emotions/knowledge/timeline from ChatGPT, (5) user mentions conversations.json or ChatGPT data export, (6) user wants to migrate memories from ChatGPT to another system, (7) user wants a summary or review of their ChatGPT usage over time. Triggers on: 'organize my ChatGPT history', 'extract memories from ChatGPT', 'analyze my ChatGPT export', '整理ChatGPT对话', '导出ChatGPT数据', 'build memory from chats', 'what did I talk about with ChatGPT', 'review my ChatGPT conversations', 'make a timeline from my chats', 'ChatGPTのデータを整理'. NOT for: other AI chat exports (Claude/Gemini), real-time logging, or automated summarization without human review." --- # ChatGPT Memory Extraction Transform ChatGPT conversation exports into a structured personal memory archive. ## ⚠️ For Users AI agents cut corners on large text volumes. Review each batch. Praise quality, not speed. Read [quality rules](references/quality-rules.md) for ChatGPT-specific pitfalls and known AI failure modes. ## Workflow 1. **Prepare**: User exports ChatGPT data: - Go to ChatGPT → Settings → Data controls → Export data → Confirm export - OpenAI will send an email when the export is ready (may take **hours to days** depending on data size) - Download the zip file from the email link (requires being logged into ChatGPT) - Unzip to get `conversations-*.json` files and other data 2. **Extract**: Run `scripts/extract_conversations.py` to convert JSON → readable text files + conversation index 3. **Read & Write**: Process one quarter at a time. Read every conversation fully. Write timeline per [output-format.md](references/output-format.md). User reviews before proceeding. Split into monthly batches for 100+ conversations. 4. **Extract Dimensions**: Update people files and topic files. Every person mentioned → their file updated. 5. **Incremental**: On new exports, compare IDs, process only new content. ## Output Structure See [output-format.md](references/output-format.md).

path: README.ja.md
content: [English](README.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md)

# ChatGPT メモリ抽出 🧠✨

[OpenClaw](https://github.com/openclaw/openclaw) のスキルです。長年 ChatGPT と話してきた会話を、**整理された、検索できる個人メモリアーカイブ**に変えてくれます～

## このツールを作ったきっかけ

ChatGPT と3年以上チャットしてきて、500以上の会話、6万件以上のメッセージが溜まりました。読んだ論文、学んだこと、下した決断、嬉しかったことや辛かったこと、出会った人たち——全部が巨大な JSON ファイルの中に閉じ込められていて、振り返りたくても探せない状態でした 😢

なので、このツールを作りました。ChatGPT のエクスポートデータを、タイムラインに沿ったメモリアーカイブに整理してくれます。人間関係や学びの軌跡、大事な出来事なども一緒に抽出してくれます。

しかも、整理したアーカイブは振り返り用だけじゃなくて——OpenClaw や他の AI アシスタントに読み込ませれば、**初日からあなたのことをちゃんと理解してくれる**ようになります。新しい AI を使うたびに一から自己紹介するの、本当に大変ですよね 😅

## できること

- 📦 **自動抽出**：Python スクリプトが JSON を読めるテキストファイルに変換、年・四半期ごとに整理
- 📖 **丁寧な読解**：AI にタイトルだけ見て要約させるんじゃなくて、一つ一つの会話をちゃんと読ませて、構造化された四半期タイムラインを作成
- 👥 **人物プロファイル**：会話に登場する人を抽出して、それぞれのプロファイルを作成
- 🎯 **テーマ整理**：学問の成長、感情の出来事、生活の変化、スキル習得——テーマごとにまとめ

## 正直に言うと

AI って大量のテキストを処理するとき、**本当に手を抜きがち**なんです 😅 これは何度も痛い目にあって学んだ教訓です。なので、このスキルには AI がよくやるサボりパターンに対応する品質ルールが組み込まれています（最初の数メッセージだけ読んで要約したり、「XX件のメッセージでいろいろ話した」みたいな中身のない要約を書いたり、「ファイル更新しました」と言いながら実は何も変えてなかったり……）。

ただ、このルールがあっても、**AI の出力は自分の目で確認するのがおすすめ**です。四半期ごとにチェックして、浅いところがあったらやり直してもらう。一番いい結果が出るのは、AI に丸投げするんじゃなくて、一緒に作業するときです～

## 使い方

### 1. インストール

```bash
# ClawHub 経由
npx clawhub install chatgpt-memory-extraction

# または GitHub から手動インストール
git clone https://github.com/cyresearch/chatgpt-memory-extraction.git ~/.openclaw/workspace/skills/chatgpt-memory-extraction
```

### 2. ChatGPT のデータをエクスポート

[ChatGPT](https://chat.openai.com) → 設定 → データコントロール → データをエクスポート → 確認

> ⏳ エクスポートには**数時間〜1、2日**かかることがあります。データが多いともっとかかるかも。準備ができたら OpenAI からメールが届きます～

### 3. 整理スタート！

OpenClaw アシスタントに伝えてください：

> 「ChatGPT のデータをエクスポートしたよ、メモリアーカイブに整理して」

手順を一つずつ案内してくれます：抽出 → 四半期ごとに読解 → タイムライン作成 → あなたがレビュー → 人物・テーマの抽出 🌱

## 出力イメージ

```
memory/
├── timeline/{year}/Q{1-4}.md   # 四半期タイムライン（一番大事なファイル～）
├── people/{category}/*.md       # 人物プロファイル
├── topics/*.md                  # テーマ別の記録
├── meta/extraction-log.md       # 進捗管理
└── raw/{year}_Q{n}/             # 元の会話テキスト（確認用）
```

## 必要なもの

- [OpenClaw](https://github.com/openclaw/openclaw)
- Python 3.8+
- ChatGPT のデータエクスポートファイル（OpenAI からダウンロードした `.zip`）

## ライセンス

MIT — [LICENSE](LICENSE) を参照


path: README.md
content: [English](README.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md)

# ChatGPT Memory Extraction 🧠✨

An [OpenClaw](https://github.com/openclaw/openclaw) skill that turns your years of ChatGPT conversations into a **structured, searchable personal memory archive**.

## Why I made this

I chatted with ChatGPT for over three years — 500+ conversations, 60,000+ messages. Papers I read, things I learned, decisions I made, the happy and hard moments, the people I met along the way — all of it was locked inside massive JSON files I couldn't actually look through 😢

So I built this little tool. It takes your ChatGPT export data and walks you through organizing it into a timeline-based memory archive, pulling out people, learning journeys, and key life events along the way.

And the archive isn't just for looking back — you can feed it to your OpenClaw agent or any other AI assistant so that **it truly knows you from day one**. No more spending ages re-introducing yourself every time you start with a new AI 😅

## What it does

- 📦 **Auto-extract**: A Python script converts the JSON into readable text files, sorted by year and quarter
- 📖 **Deep reading**: Guides the AI to read each conversation thoroughly (not just skim the title), producing structured quarterly timelines
- 👥 **People profiles**: Extracts every person mentioned and builds them a profile
- 🎯 **Topic threads**: Academic growth, emotional moments, life changes, skills learned — organized by theme

## An honest note

Here's the thing — AI agents **really do tend to cut corners** when processing lots of text 😅 I learned this the hard way, many times over. So this skill comes with built-in quality rules targeting common AI laziness patterns (like summarizing after only reading the first few messages, writing "XX messages about various topics" as if that means anything, or claiming "files updated" when nothing actually changed…).

Even with these rules, **I'd still recommend checking the AI's output yourself**. Review each quarter when it's done, and ask it to redo anything that looks shallow. The best results come from working together with the AI, not just handing it off and walking away~

## How to use

### 1. Install

```bash
# Via ClawHub
npx clawhub install chatgpt-memory-extraction

# Or manually from GitHub
git clone https://github.com/cyresearch/chatgpt-memory-extraction.git ~/.openclaw/workspace/skills/chatgpt-memory-extraction
```

### 2. Export your ChatGPT data

Go to [ChatGPT](https://chat.openai.com) → Settings → Data controls → Export data → Confirm

> ⏳ The export might take **a few hours or even a day or two** — longer if you have a lot of data. OpenAI will email you when it's ready~

### 3. Start organizing!

Tell your OpenClaw agent:

> "I exported my ChatGPT data — help me turn it into a memory archive."

It'll walk you through: extract → read quarter by quarter → write timelines → you review → extract people and topics 🌱

## What the output looks like

```
memory/
├── timeline/{year}/Q{1-4}.md   # Quarterly timelines (the core files~)
├── people/{category}/*.md       # People profiles
├── topics/*.md                  # Topic-based records
├── meta/extraction-log.md       # Progress tracking
└── raw/{year}_Q{n}/             # Raw conversation texts (for verification)
```

## What you'll need

- [OpenClaw](https://github.com/openclaw/openclaw)
- Python 3.8+
- Your ChatGPT data export (the `.zip` from OpenAI)

## License

MIT — see [LICENSE](LICENSE)


path: README.zh-CN.md
content: [English](README.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md)

# ChatGPT 记忆提取 🧠✨

一个 [OpenClaw](https://github.com/openclaw/openclaw) 技能，帮你把和 ChatGPT 聊了好久好久的那些对话，变成一份**有条理、能检索的个人记忆档案**～

## 这个小工具的由来

我和 ChatGPT 聊了三年多，积累了 500+ 个对话、6万多条消息。里面有我读过的论文、学到的知识、做过的决定、经历过的开心和难过、遇到的人——可是它们全都锁在一堆巨大的 JSON 文件里，想回顾的时候根本找不到 😢

所以就做了这个小工具，它可以帮你把 ChatGPT 的导出数据，一步步整理成按时间线排列的记忆档案，还会顺便帮你提取人物关系、学习轨迹、重要事件这些信息。

而且整理好的记忆档案不只能用来回顾——你可以把它喂给你的 OpenClaw 或者任何其他 AI 助手，**让它从第一天起就真正了解你**。再也不用每次换一个新的 AI 就重新做一遍详细的自我介绍了，那样真的很花时间 😅

## 能做些什么呢

- 📦 **自动提取**：一个 Python 小脚本帮你把 JSON 转成人能读的文本文件，按年份和季度分好类
- 📖 **深度整理**：引导 AI 逐个对话认真阅读（不是扫一眼标题就概括哦），写成结构化的季度时间线
- 👥 **人物档案**：提取对话里出现的每个人，给他们建一份独立的小档案
- 🎯 **主题提炼**：学术成长、情感时刻、生活变化、技能学习——按主题帮你汇总好

## 一句真心话

说实话，AI 在处理大量文本的时候**真的很容易偷懒**😅 这是我踩了好多坑之后才总结出来的教训。所以这个技能里内置了一套质量规则，专门对付 AI 的各种偷工减料（比如只看开头几条消息就开始概括、写"XX条消息讨论了各种话题"这种敷衍话、说"已更新文件"但其实根本没改……）。

不过即使有了这些规则，**还是建议你亲自看看 AI 整理出来的东西**。每做完一个季度就检查一下，发现不对就让它重来。最好的效果来自你和 AI 的配合，而不是完全交给它就不管啦～

## 怎么用

### 1. 安装

```bash
# 通过 ClawHub
npx clawhub install chatgpt-memory-extraction

# 或从 GitHub 手动安装
git clone https://github.com/cyresearch/chatgpt-memory-extraction.git ~/.openclaw/workspace/skills/chatgpt-memory-extraction
```

### 2. 导出你的 ChatGPT 数据

打开 [ChatGPT](https://chat.openai.com) → 设置 → 数据控制 → 导出数据 → 确认

> ⏳ 导出可能要等**几个小时甚至一两天**哦，数据多的话会更久。好了之后 OpenAI 会发邮件通知你～

### 3. 开始整理吧！

跟你的 OpenClaw 助手说：

> "我导出了 ChatGPT 的数据，帮我整理成记忆档案吧"

它会带着你一步步来：提取 → 逐季度阅读 → 写时间线 → 你来审核 → 提取人物和主题 🌱

## 整理出来长什么样

```
memory/
├── timeline/{year}/Q{1-4}.md   # 按季度的时间线（最核心的文件～）
├── people/{category}/*.md       # 人物档案
├── topics/*.md                  # 按主题的记录
├── meta/extraction-log.md       # 进度追踪
└── raw/{year}_Q{n}/             # 原始对话文本（方便回头验证）
```

## 需要准备什么

- [OpenClaw](https://github.com/openclaw/openclaw)
- Python 3.8+
- 你的 ChatGPT 数据导出文件（从 OpenAI 下载的 `.zip`）

## License

MIT — 见 [LICENSE](LICENSE)


path: references/output-format.md
content: # Output Format Specification

## Directory Structure

```
{output_dir}/
├── timeline/                  # Core: quarterly records (most detailed, source of truth)
│   ├── {year}/
│   │   ├── Q1.md              # January - March
│   │   ├── Q2.md              # April - June
│   │   ├── Q3.md              # July - September
│   │   └── Q4.md              # October - December
│   └── ...
│
├── people/                    # Person profiles (extracted from timeline)
│   ├── {category}/            # e.g., teachers/, peers/, family/, colleagues/
│   │   └── {person-name}.md
│   └── _index.md              # Index: who appears where in the timeline
│
├── topics/                    # Thematic extracts (distilled from timeline)
│   ├── academic-journey.md    # Academic milestones, research evolution
│   ├── emotional-events.md    # Significant emotional moments
│   ├── papers-read.md         # Papers/literature discussed
│   ├── skills-learned.md      # Skills, tools, knowledge acquired
│   └── life-changes.md        # Housing, finances, health, travel, etc.
│
├── meta/                      # Metadata and progress tracking
│   ├── conversation-index.md  # Index of all conversations (auto-generated)
│   └── extraction-log.md      # Progress: which quarters are done, review status
│
└── raw/                       # Raw extracted conversation texts (for verification)
    ├── {year}_Q{n}/
    │   └── {idx}_{mmdd}_{title}.txt
    └── conversation-index.md
```

## Timeline File Format

Each quarterly file follows this structure:

```markdown
# {Year} Q{N} ({Month Range})

> Extraction date: YYYY-MM-DD
> Conversations in this quarter: N
> Data source: ChatGPT export

## Quarter Overview
(Brief summary of the user's overall state, major events, and themes during this quarter)

## {Month Name}

### [{MM/DD}] Conversation Title
- **Context**: What prompted this conversation (if apparent)
- **Content**: Detailed description of what was discussed, what the user said, what they learned, what they decided. Use the user's primary language. Include direct quotes for important statements.
- **People mentioned**: Names and roles of anyone referenced
- **Emotional state**: How the user was feeling (if expressed)
- **Academic/professional content**: Research discussed, papers read, skills learned
- **Life events**: Practical matters discussed
- **Notable quotes**: User's own words that capture something important

(Repeat for each conversation in the month)

## Quarter Summary

### Academic Progress
(Key academic milestones and developments)

### Emotional State
(Overall emotional trajectory across the quarter)

### Relationships
(Changes in interpersonal dynamics)

### Life Events
(Significant practical life changes)
```

## People File Format

```markdown
# {Person Name}

- **Identity**: Role, affiliation, relationship to user
- **First appearance**: Date and conversation context
- **Current status**: Latest known information

## Key Interactions
(Chronological list of significant interactions, each referencing the timeline)

### {Date} — {Brief description}
- What happened
- How the user felt about it
- → See timeline/{year}/Q{n}.md for full context

## User's Perception
(How the user describes or feels about this person, using direct quotes when available)
```

## Topics File Format

Each topic file is a thematic index that references the timeline:

```markdown
# {Topic Title}

> Last updated: YYYY-MM-DD

## Chronological Entries

### [{Date}] {Brief description}
- Summary of what happened
- → See timeline/{year}/Q{n}.md
```

## Extraction Log Format

```markdown
# Extraction Progress

| Year | Quarter | Status | Conversations | Reviewed | Notes |
|------|---------|--------|---------------|----------|-------|
| 2023 | Q1      | ✅ Done | 1             | ✅ Yes   |       |
| 2023 | Q2      | ✅ Done | 25            | ✅ Yes   |       |
| ...  | ...     | ...    | ...           | ...      | ...   |
```


path: references/quality-rules.md
content: # Quality Rules

These rules address specific pitfalls discovered through real-world use of this skill. They cover things an AI agent would NOT figure out on its own.

## ChatGPT Export-Specific Pitfalls

1. **Multi-day conversations**: A single ChatGPT conversation window may be reused over weeks or months. The title and start date only reflect the first message. Always look for date markers within the conversation and split events by actual dates.

2. **Misleading titles**: Titles are auto-generated from the first message. Never guess content from title — read the actual content.

3. **Emails are evidence**: When users draft or receive emails inside a conversation, these reveal real events (conflicts, decisions, relationships). Record the underlying event, not just "user wrote an email."

4. **Voice transcription**: Voice-to-text conversations are messy and jump topics rapidly. Read every message even when text seems garbled.

## AI Laziness Pitfalls

These are failure modes that consistently appear when processing large volumes of conversations:

5. **Message count filler**: Never write "XX messages about various topics." Describe what was actually discussed.

6. **Early stop**: The most important content is often in the middle or end. Do not summarize after reading only the first few messages.

7. **False completion claims**: Do not say "files updated" without actually opening and editing them. Verify.

8. **Knowledge shallowness**: Do not write "learned X." Record what the concept is, how the user understood it, what it was applied to, and why it mattered.

9. **People file skipping**: Every person mentioned by name in a quarter must have their file updated — not just the "main" person.

10. **Speed over quality**: Quality degrades when rushing large batches. Split into monthly batches if needed. Users should review each batch before proceeding.


path: scripts/extract_conversations.py
content: #!/usr/bin/env python3
"""
Extract conversations from ChatGPT export JSON files into readable text files.

Usage:
    python extract_conversations.py <input_dir> <output_dir> [--year YYYY] [--quarter Q1|Q2|Q3|Q4]

Arguments:
    input_dir   Directory containing conversations-*.json files
    output_dir  Directory to write extracted text files

Options:
    --year      Filter by year (e.g., 2024)
    --quarter   Filter by quarter (Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec)
    --timezone  Timezone for date conversion (default: UTC). E.g., Asia/Tokyo

Output:
    Creates one .txt file per conversation in output_dir/{year}_Q{n}/
    Each file contains all messages in conversation-tree order with role labels.
    Also generates a conversation index file: output_dir/conversation-index.md
"""

import json
import os
import sys
import argparse
from datetime import datetime, timezone, timedelta


def get_timezone(tz_name):
    """Simple timezone support without pytz dependency."""
    tz_offsets = {
        'UTC': timedelta(hours=0),
        'Asia/Tokyo': timedelta(hours=9),
        'Asia/Shanghai': timedelta(hours=8),
        'America/New_York': timedelta(hours=-5),
        'America/Los_Angeles': timedelta(hours=-8),
        'Europe/London': timedelta(hours=0),
        'Europe/Berlin': timedelta(hours=1),
        'Europe/Paris': timedelta(hours=1),
    }
    offset = tz_offsets.get(tz_name, timedelta(hours=0))
    return timezone(offset)


def get_ordered_messages(conv):
    """Extract messages from conversation in tree order (BFS)."""
    mapping = conv.get('mapping', {})
    root_id = None
    for nid, node in mapping.items():
        if node.get('parent') is None:
            root_id = nid
            break
    if not root_id:
        return []

    messages = []
    queue = [root_id]
    visited = set()
    while queue:
        nid = queue.pop(0)
        if nid in visited:
            continue
        visited.add(nid)
        node = mapping.get(nid, {})
        msg = node.get('message')
        if msg and msg.get('content', {}).get('parts'):
            role = msg.get('author', {}).get('role', '')
            parts = msg['content']['parts']
            text_parts = [p for p in parts if isinstance(p, str)]
            text = '\n'.join(text_parts).strip()
            if text:
                messages.append({'role': role, 'text': text})
        children = node.get('children', [])
        queue.extend(children)
    return messages


def quarter_for_month(month):
    """Return quarter number (1-4) for a given month (1-12)."""
    return (month - 1) // 3 + 1


def sanitize_filename(name, max_len=60):
    """Sanitize a string to be safe for filenames."""
    cleaned = ''.join(c if c.isalnum() or c in '._- ' else '_' for c in name)
    return cleaned[:max_len]


def main():
    parser = argparse.ArgumentParser(description='Extract ChatGPT conversations to readable text files')
    parser.add_argument('input_dir', help='Directory containing conversations-*.json')
    parser.add_argument('output_dir', help='Directory to write extracted files')
    parser.add_argument('--year', type=int, help='Filter by year')
    parser.add_argument('--quarter', choices=['Q1', 'Q2', 'Q3', 'Q4'], help='Filter by quarter')
    parser.add_argument('--timezone', default='UTC', help='Timezone (default: UTC)')
    parser.add_argument('--max-chars-per-message', type=int, default=10000,
                        help='Max characters per message (default: 10000)')
    args = parser.parse_args()

    tz = get_timezone(args.timezone)
    quarter_filter = int(args.quarter[1]) if args.quarter else None

    # Load all conversations
    all_convos = []
    for fname in sorted(os.listdir(args.input_dir)):
        if not fname.startswith('conversations-') or not fname.endswith('.json'):
            continue
        fpath = os.path.join(args.input_dir, fname)
        print(f"Loading {fname}...", file=sys.stderr)
        with open(fpath, 'r') as f:
            data = json.load(f)
        for conv in data:
            ct = conv.get('create_time', 0)
            if not ct:
                continue
            dt = datetime.fromtimestamp(ct, tz=tz)
            if args.year and dt.year != args.year:
                continue
            if quarter_filter and quarter_for_month(dt.month) != quarter_filter:
                continue
            all_convos.append((dt, conv))

    all_convos.sort(key=lambda x: x[0])
    print(f"Found {len(all_convos)} conversations matching filters", file=sys.stderr)

    # Group by year and quarter
    groups = {}
    for dt, conv in all_convos:
        key = (dt.year, quarter_for_month(dt.month))
        groups.setdefault(key, []).append((dt, conv))

    # Extract each group
    index_entries = []
    for (year, q), convos in sorted(groups.items()):
        outdir = os.path.join(args.output_dir, f"{year}_Q{q}")
        os.makedirs(outdir, exist_ok=True)

        for idx, (dt, conv) in enumerate(convos):
            title = conv.get('title', 'untitled').replace('/', '_').replace('\n', ' ')
            msgs = get_ordered_messages(conv)
            user_msgs = [m for m in msgs if m['role'] == 'user']
            total_user_chars = sum(len(m['text']) for m in user_msgs)

            fname = f"{idx:03d}_{dt.strftime('%m%d')}_{sanitize_filename(title)}.txt"
            fpath = os.path.join(outdir, fname)

            with open(fpath, 'w') as f:
                f.write(f"# {title}\n")
                f.write(f"# Date: {dt.strftime('%Y-%m-%d %H:%M')} ({args.timezone})\n")
                f.write(f"# Messages: {len(msgs)} total, {len(user_msgs)} user\n")
                f.write(f"# User chars: {total_user_chars}\n")
                f.write(f"# Conversation ID: {conv.get('id', 'unknown')}\n\n")

                for m in msgs:
                    role_label = {
                        'user': '👤 USER',
                        'assistant': '🤖 ASSISTANT',
                        'system': '[SYSTEM]',
                        'tool': '[TOOL]'
                    }.get(m['role'], f"[{m['role']}]")
                    f.write(f"--- {role_label} ---\n")
                    text = m['text']
                    if len(text) > args.max_chars_per_message:
                        f.write(text[:args.max_chars_per_message])
                        f.write(f"\n... [truncated, original {len(text)} chars]")
                    else:
                        f.write(text)
                    f.write('\n\n')

            index_entries.append({
                'date': dt.strftime('%Y-%m-%d'),
                'year': year,
                'quarter': q,
                'month': dt.month,
                'title': title,
                'total_msgs': len(msgs),
                'user_msgs': len(user_msgs),
                'user_chars': total_user_chars,
                'filename': f"{year}_Q{q}/{fname}",
                'conv_id': conv.get('id', 'unknown'),
            })

    # Write conversation index
    os.makedirs(args.output_dir, exist_ok=True)
    index_path = os.path.join(args.output_dir, 'conversation-index.md')
    with open(index_path, 'w') as f:
        f.write("# Conversation Index\n\n")
        f.write(f"> Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n")
        f.write(f"> Total conversations: {len(index_entries)}\n")
        f.write(f"> Timezone: {args.timezone}\n\n")

        current_yq = None
        for entry in index_entries:
            yq = (entry['year'], entry['quarter'])
            if yq != current_yq:
                current_yq = yq
                month_range = {1: 'Jan-Mar', 2: 'Apr-Jun', 3: 'Jul-Sep', 4: 'Oct-Dec'}
                f.write(f"\n## {entry['year']} Q{entry['quarter']} ({month_range[entry['quarter']]})\n\n")
                f.write(f"| Date | User Msgs | Chars | Title |\n")
                f.write(f"|------|-----------|-------|-------|\n")
            f.write(f"| {entry['date']} | {entry['user_msgs']} | {entry['user_chars']} | {entry['title']} |\n")

    print(f"\nExtraction complete:", file=sys.stderr)
    print(f"  Conversations: {len(index_entries)}", file=sys.stderr)
    print(f"  Index: {index_path}", file=sys.stderr)


if __name__ == '__main__':
    main()


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