{
  "schemaVersion": "1.0",
  "item": {
    "slug": "hi-lite",
    "name": "Hi Lite",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/gofordylan/hi-lite",
    "canonicalUrl": "https://clawhub.ai/gofordylan/hi-lite",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/hi-lite",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=hi-lite",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "README.md",
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/hi-lite"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/hi-lite",
    "agentPageUrl": "https://openagent3.xyz/skills/hi-lite/agent",
    "manifestUrl": "https://openagent3.xyz/skills/hi-lite/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/hi-lite/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Hi-Lite — Kindle Highlights Skill",
        "body": "You are the Hi-Lite skill. You help users import, search, browse, and rediscover their Kindle highlights. All data stays local in the user's OpenClaw workspace."
      },
      {
        "title": "Workspace Location",
        "body": "All Hi-Lite data lives at: ~/.openclaw/workspace/hi-lite/\n\nhi-lite/\n├── raw/              # User drops raw Kindle exports here\n├── highlights/\n│   ├── _index.md     # Master index of all books\n│   └── books/        # One markdown file per book\n└── collections/      # User-curated themed collections"
      },
      {
        "title": "1. Setup (First Run)",
        "body": "When the user first invokes Hi-Lite or says \"set up hi-lite\":\n\nCheck if ~/.openclaw/workspace/hi-lite/ exists.\nIf not, create the directory structure:\n\n~/.openclaw/workspace/hi-lite/raw/\n~/.openclaw/workspace/hi-lite/highlights/books/\n~/.openclaw/workspace/hi-lite/collections/\n\n\nCreate ~/.openclaw/workspace/hi-lite/highlights/_index.md with this template:\n\n# Hi-Lite Library\n\n**Total books**: 0\n**Total highlights**: 0\n**Last updated**: (never)\n\n## Books\n\n| Book | Author | Highlights | Date Imported |\n|------|--------|------------|---------------|\n\nTell the user setup is complete.\nSuggest they add ~/.openclaw/workspace/hi-lite/highlights to their memorySearch.extraPaths config for semantic vector search across all highlights. This is optional but highly recommended."
      },
      {
        "title": "2. Import & Parse",
        "body": "Trigger: /hi-lite import or \"import my highlights\" or \"parse my clippings\""
      },
      {
        "title": "Steps",
        "body": "Read all files in ~/.openclaw/workspace/hi-lite/raw/.\nDetect the format of each file and parse highlights from it.\nFor each highlight, extract: quote text, book title, author (if available), location (if available), date highlighted (if available).\nGroup highlights by book.\nFor each book, create or update a markdown file at ~/.openclaw/workspace/hi-lite/highlights/books/<slug>.md.\nDeduplicate: if a highlight with identical text already exists in that book's file, skip it.\nUpdate ~/.openclaw/workspace/hi-lite/highlights/_index.md with current totals.\nReport to the user: how many highlights were imported, how many books, how many duplicates skipped."
      },
      {
        "title": "Supported Formats",
        "body": "Amazon \"My Clippings.txt\" — The standard Kindle export format:\n\nBook Title (Author Name)\n- Your Highlight on page 42 | Location 615-618 | Added on Monday, March 15, 2024 3:22:15 PM\n\nThe actual highlighted text goes here.\n==========\n\nEach clipping is separated by ==========. Parse the title/author from the first line, location/date from the second line, and the quote text from the remaining lines before the separator.\n\nAmazon Read Notebook (read.amazon.com) — Copy-pasted text from the Kindle notebook web page. Highlights typically appear as plain text with book titles as headers. Do your best to identify book titles vs highlight text from context.\n\nBookcision JSON — A JSON array of highlights with fields like text, title, author, location. Parse directly.\n\nBookcision text export — Similar to My Clippings but may have different formatting. Adapt parsing accordingly.\n\nHi-Lite fetch JSON — JSON output from the fetch script (identifiable by \"source\": \"amazon-kindle-notebook\"). Contains a books array where each book has title, author, asin, and a highlights array with text, page, note, and color fields. Parse directly using the structured data. Map page to the location metadata line.\n\nFreeform pasted text — If the user pastes raw text that doesn't match any known format, ask them to confirm the book title and author, then treat each paragraph or quote-block as a separate highlight."
      },
      {
        "title": "Book File Format",
        "body": "Each book gets a markdown file at highlights/books/<slug>.md where <slug> is a URL-safe lowercase version of the title (e.g., crime-and-punishment.md).\n\n---\ntitle: Crime and Punishment\nauthor: Fyodor Dostoevsky\ndate_imported: 2026-02-22\nhighlight_count: 12\ntags: []\n---\n\n# Crime and Punishment — Fyodor Dostoevsky\n\n## Highlights\n\n> Pain and suffering are always inevitable for a large intelligence and a deep heart.\n- Location 342 | Highlighted 2024-03-15\n\n> The soul is healed by being with children.\n- Location 1205 | Highlighted 2024-03-20\n\nRules:\n\nYAML frontmatter with title, author, date_imported, highlight_count, and tags (initially empty array).\nEach highlight is a blockquote (>) followed by metadata on the next line (prefixed with - ).\nInclude whatever metadata is available (location, page, date). If none is available, just use the blockquote with no metadata line.\nWhen updating an existing file, append new highlights to the end of the ## Highlights section and update the frontmatter highlight_count.\ndate_imported reflects the first import date for that book. Don't change it on subsequent imports."
      },
      {
        "title": "Index File Format",
        "body": "After every import, regenerate highlights/_index.md:\n\n# Hi-Lite Library\n\n**Total books**: 15\n**Total highlights**: 342\n**Last updated**: 2026-02-22\n\n## Books\n\n| Book | Author | Highlights | Date Imported |\n|------|--------|------------|---------------|\n| Crime and Punishment | Fyodor Dostoevsky | 12 | 2026-02-22 |\n| Antifragile | Nassim Nicholas Taleb | 28 | 2026-02-22 |\n\nSort books alphabetically by title. Compute totals by summing all highlight counts."
      },
      {
        "title": "3. Search",
        "body": "Trigger: /hi-lite search <query> or any natural language search like \"find quotes about perseverance\", \"what did Dostoevsky say about suffering?\""
      },
      {
        "title": "Steps",
        "body": "Preferred method: Use the memory_search tool with the user's query. This performs hybrid vector + BM25 search across all highlight files if memorySearch.extraPaths includes the highlights directory. Return matching quotes with their book title, author, and location.\n\n\nFallback method: If memory_search is not available or doesn't return results, read the highlight files directly from ~/.openclaw/workspace/hi-lite/highlights/books/ and reason over them to find relevant quotes."
      },
      {
        "title": "Response Format",
        "body": "Present results as a clean list:\n\n📖 **Crime and Punishment** — Fyodor Dostoevsky\n> Pain and suffering are always inevitable for a large intelligence and a deep heart.\nLocation 342\n\n📖 **Antifragile** — Nassim Nicholas Taleb\n> Wind extinguishes a candle and energizes fire.\nLocation 89\n\nIf no results are found, say so and suggest alternative search terms."
      },
      {
        "title": "4. Browse",
        "body": "Trigger: /hi-lite browse or \"show me all books\", \"list my highlights\", \"what books do I have?\""
      },
      {
        "title": "Capabilities",
        "body": "\"Show me all books\" — Read _index.md and display the books table.\n\"Show me highlights from [book]\" — Find and read the corresponding book file, display all highlights.\n\"Show me highlights from [author]\" — Find all book files by that author (check frontmatter), display highlights.\n\"Show me highlights from [month/year]\" — Filter highlights by their highlighted date or import date.\n\"Show me my most highlighted books\" — Read _index.md, sort by highlight count descending, display top results.\n\"How many highlights do I have?\" — Read _index.md and report totals."
      },
      {
        "title": "Response Format",
        "body": "Keep responses clean and scannable. Use the books table for listings. When showing highlights from a specific book, show the book title as a header followed by all blockquoted highlights."
      },
      {
        "title": "5. Random Quotes",
        "body": "Trigger: /hi-lite random [count] or \"give me a random quote\", \"surprise me\", \"random highlight\""
      },
      {
        "title": "Steps",
        "body": "List all book files in highlights/books/.\nRead one or more book files (chosen randomly).\nPick random highlights from the loaded files.\nDefault count is 1 if not specified. The user can request any number (e.g., \"give me 5 random quotes\").\nTry to pick from different books for variety when count > 1."
      },
      {
        "title": "Response Format",
        "body": "📖 **Crime and Punishment** — Fyodor Dostoevsky\n> The soul is healed by being with children.\n\nFor multiple quotes, separate each with a blank line."
      },
      {
        "title": "6. Collections",
        "body": "Trigger: /hi-lite collection <name> or \"make a collection about courage\", \"create a [theme] collection\""
      },
      {
        "title": "Steps",
        "body": "Search across all highlights for quotes matching the theme (use memory_search or read files directly).\nCurate a selection of the most relevant quotes.\nSave as ~/.openclaw/workspace/hi-lite/collections/<slug>.md.\nPresent the collection to the user."
      },
      {
        "title": "Collection File Format",
        "body": "---\nname: Quotes About Courage\ncreated: 2026-02-22\nhighlight_count: 8\n---\n\n# Quotes About Courage\n\n> Pain and suffering are always inevitable for a large intelligence and a deep heart.\n— Fyodor Dostoevsky, *Crime and Punishment*\n\n> Wind extinguishes a candle and energizes fire.\n— Nassim Nicholas Taleb, *Antifragile*\n\nEach quote includes full attribution (author and book title) since collections pull from multiple books."
      },
      {
        "title": "Managing Collections",
        "body": "\"Show my collections\" — List all files in collections/.\n\"Show collection [name]\" — Read and display the specified collection.\n\"Add [quote] to [collection]\" — Append a quote to an existing collection and update its count.\n\"Delete collection [name]\" — Remove the collection file (confirm with user first)."
      },
      {
        "title": "7. Fetch from Amazon",
        "body": "Trigger: /hi-lite fetch or \"fetch my highlights from Amazon\" or \"sync my Kindle\""
      },
      {
        "title": "First-Time Setup",
        "body": "Check if Playwright is available by running python3 -c \"from playwright.sync_api import sync_playwright\". If it fails, guide the user:\n\npip install \"playwright>=1.40.0\"\nplaywright install chromium"
      },
      {
        "title": "Execution",
        "body": "When the user triggers a fetch:\n\nWrite the following Python script to ~/.openclaw/workspace/hi-lite/raw/fetch_highlights.py.\nRun it via bash: python3 ~/.openclaw/workspace/hi-lite/raw/fetch_highlights.py (append --amazon-domain amazon.co.uk etc. if the user specifies a non-US domain).\nThe script opens a visible Chromium window. If the user isn't logged in, it waits up to 5 minutes for them to sign in manually (this handles 2FA, CAPTCHA, etc.). Session cookies are saved at ~/.openclaw/workspace/hi-lite/.browser-data/ so future fetches skip login.\nThe script iterates through all annotated books in the sidebar, extracts highlights, and saves a JSON file to ~/.openclaw/workspace/hi-lite/raw/kindle-fetch-{timestamp}.json.\nAfter the script finishes, delete the script file (fetch_highlights.py) from raw/ so it doesn't get parsed as an import.\nThen automatically run the standard import flow (Section 2) on the fetched JSON file.\n\nThe script to write:\n\n#!/usr/bin/env python3\n\"\"\"Fetch Kindle highlights from Amazon's read.amazon.com/notebook page.\"\"\"\n\nimport argparse\nimport json\nimport os\nimport sys\nimport time\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\ntry:\n    from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout\nexcept ImportError:\n    print(\n        \"Playwright is not installed. Run:\\n\"\n        \"  pip install 'playwright>=1.40.0'\\n\"\n        \"  playwright install chromium\"\n    )\n    sys.exit(1)\n\nDEFAULT_BROWSER_DATA = os.path.expanduser(\n    \"~/.openclaw/workspace/hi-lite/.browser-data\"\n)\nDEFAULT_OUTPUT_DIR = os.path.expanduser(\"~/.openclaw/workspace/hi-lite/raw\")\nDEFAULT_DOMAIN = \"amazon.com\"\nLOGIN_TIMEOUT_SEC = 300\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description=\"Fetch Kindle highlights from Amazon\"\n    )\n    parser.add_argument(\n        \"--output-dir\", default=DEFAULT_OUTPUT_DIR,\n        help=\"Directory to save the fetched JSON file\",\n    )\n    parser.add_argument(\n        \"--amazon-domain\", default=DEFAULT_DOMAIN,\n        help=\"Amazon domain, e.g. amazon.co.uk\",\n    )\n    parser.add_argument(\n        \"--browser-data\", default=DEFAULT_BROWSER_DATA,\n        help=\"Path to persistent browser profile\",\n    )\n    return parser.parse_args()\n\n\ndef wait_for_login(page, timeout_sec=LOGIN_TIMEOUT_SEC):\n    print(\"Login required — please sign in to Amazon in the browser window.\")\n    print(f\"Waiting up to {timeout_sec // 60} minutes for login...\")\n    deadline = time.time() + timeout_sec\n    while time.time() < deadline:\n        url = page.url\n        if \"notebook\" in url and \"signin\" not in url and \"ap/signin\" not in url:\n            print(\"Login detected. Continuing...\")\n            return True\n        time.sleep(2)\n    print(\"Login timed out.\")\n    return False\n\n\ndef scroll_to_load_all(page, container_selector, item_selector):\n    previous_count = 0\n    stale_rounds = 0\n    while stale_rounds < 3:\n        items = page.query_selector_all(item_selector)\n        current_count = len(items)\n        if current_count > previous_count:\n            previous_count = current_count\n            stale_rounds = 0\n        else:\n            stale_rounds += 1\n        container = page.query_selector(container_selector)\n        if container:\n            container.evaluate(\"el => el.scrollTop = el.scrollHeight\")\n        else:\n            page.evaluate(\"window.scrollTo(0, document.body.scrollHeight)\")\n        time.sleep(1)\n    return previous_count\n\n\ndef extract_highlights_from_pane(page):\n    highlights = []\n    annotations = page.query_selector_all(\".a-row.a-spacing-base\")\n    for annotation in annotations:\n        header = annotation.query_selector(\"#annotationHighlightHeader\")\n        if not header:\n            continue\n        metadata_text = header.inner_text().strip()\n        color = \"\"\n        page_num = \"\"\n        if \"|\" in metadata_text:\n            parts = [p.strip() for p in metadata_text.split(\"|\")]\n            if parts:\n                color = parts[0].replace(\"highlight\", \"\").strip()\n            if len(parts) > 1 and \":\" in parts[1]:\n                page_num = parts[1].split(\":\", 1)[1].strip()\n        text_el = annotation.query_selector(\"#highlight\")\n        text = text_el.inner_text().strip() if text_el else \"\"\n        note_el = annotation.query_selector(\"#note\")\n        note = note_el.inner_text().strip() if note_el else \"\"\n        if text:\n            highlights.append({\n                \"text\": text, \"page\": page_num,\n                \"note\": note, \"color\": color,\n            })\n    return highlights\n\n\ndef fetch_highlights(args):\n    domain = args.amazon_domain\n    notebook_url = f\"https://read.{domain}/notebook\"\n    output_dir = Path(args.output_dir)\n    output_dir.mkdir(parents=True, exist_ok=True)\n    browser_data = Path(args.browser_data)\n    browser_data.mkdir(parents=True, exist_ok=True)\n\n    with sync_playwright() as pw:\n        context = pw.chromium.launch_persistent_context(\n            user_data_dir=str(browser_data),\n            headless=False,\n            args=[\"--disable-blink-features=AutomationControlled\"],\n            viewport={\"width\": 1280, \"height\": 900},\n        )\n        page = context.pages[0] if context.pages else context.new_page()\n\n        print(f\"Navigating to {notebook_url} ...\")\n        page.goto(notebook_url, wait_until=\"domcontentloaded\", timeout=60000)\n        time.sleep(3)\n\n        if \"signin\" in page.url or \"ap/signin\" in page.url:\n            if not wait_for_login(page):\n                context.close()\n                sys.exit(1)\n            page.goto(notebook_url, wait_until=\"domcontentloaded\", timeout=60000)\n            time.sleep(3)\n\n        print(\"Waiting for notebook to load...\")\n        try:\n            page.wait_for_selector(\"#library-section\", timeout=30000)\n        except PwTimeout:\n            try:\n                page.wait_for_selector(\n                    \".kp-notebook-library-each-book\", timeout=15000\n                )\n            except PwTimeout:\n                print(\"Could not find the book list. The page may have changed.\")\n                context.close()\n                sys.exit(1)\n\n        time.sleep(2)\n\n        print(\"Discovering books in your library...\")\n        scroll_to_load_all(\n            page, \"#library-section\", \".kp-notebook-library-each-book\"\n        )\n\n        book_elements = page.query_selector_all(\n            \".kp-notebook-library-each-book\"\n        )\n        total_books = len(book_elements)\n        print(f\"Found {total_books} annotated books.\")\n\n        if total_books == 0:\n            print(\"No annotated books found.\")\n            context.close()\n            return\n\n        books_data = []\n        for i in range(total_books):\n            book_elements = page.query_selector_all(\n                \".kp-notebook-library-each-book\"\n            )\n            if i >= len(book_elements):\n                break\n            book_el = book_elements[i]\n\n            title_el = book_el.query_selector(\"h2, .kp-notebook-searchable\")\n            sidebar_title = (\n                title_el.inner_text().strip() if title_el else f\"Book {i+1}\"\n            )\n            print(\n                f\"Fetching highlights from {sidebar_title} \"\n                f\"({i+1}/{total_books})...\"\n            )\n\n            book_el.click()\n            time.sleep(2)\n\n            try:\n                page.wait_for_selector(\n                    \"#annotationHighlightHeader\", timeout=10000\n                )\n            except PwTimeout:\n                time.sleep(2)\n\n            title = \"\"\n            author = \"\"\n            asin = \"\"\n\n            title_header = page.query_selector(\n                \".kp-notebook-metadata h3, \"\n                \".kp-notebook-metadata .a-size-base-plus\"\n            )\n            if title_header:\n                title = title_header.inner_text().strip()\n            if not title:\n                title = sidebar_title\n\n            author_el = page.query_selector(\n                \".kp-notebook-metadata .a-color-secondary, \"\n                \".kp-notebook-metadata p\"\n            )\n            if author_el:\n                author = (\n                    author_el.inner_text().strip()\n                    .replace(\"By: \", \"\").replace(\"by: \", \"\").strip()\n                )\n\n            asin_attr = book_el.get_attribute(\"id\") or \"\"\n            if asin_attr.startswith(\"B\"):\n                asin = asin_attr\n\n            scroll_to_load_all(\n                page,\n                \"#annotations-container, .a-row.a-spacing-base\",\n                \"#annotationHighlightHeader\",\n            )\n\n            highlights = extract_highlights_from_pane(page)\n            print(f\"  Found {len(highlights)} highlights.\")\n\n            books_data.append({\n                \"title\": title, \"author\": author,\n                \"asin\": asin, \"highlights\": highlights,\n            })\n\n        context.close()\n\n    timestamp = datetime.now(timezone.utc).strftime(\"%Y-%m-%dT%H:%M:%S\")\n    output = {\n        \"source\": \"amazon-kindle-notebook\",\n        \"fetched_at\": timestamp,\n        \"amazon_domain\": domain,\n        \"books\": books_data,\n    }\n\n    filename = (\n        f\"kindle-fetch-\"\n        f\"{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}.json\"\n    )\n    output_path = output_dir / filename\n    with open(output_path, \"w\", encoding=\"utf-8\") as f:\n        json.dump(output, f, indent=2, ensure_ascii=False)\n\n    total_hl = sum(len(b[\"highlights\"]) for b in books_data)\n    print(f\"\\nDone! Fetched {total_hl} highlights from {len(books_data)} books.\")\n    print(f\"Saved to: {output_path}\")\n\n\nif __name__ == \"__main__\":\n    args = parse_args()\n    fetch_highlights(args)"
      },
      {
        "title": "Re-Fetch",
        "body": "Re-fetching is safe. The import step deduplicates highlights, so running fetch multiple times will not create duplicate entries."
      },
      {
        "title": "Non-US Amazon Domains",
        "body": "For users on non-US Amazon stores, append --amazon-domain <domain> when running the script (e.g., --amazon-domain amazon.co.uk). Ask the user which Amazon store they use if unclear."
      },
      {
        "title": "General Behavior",
        "body": "Always be conversational and helpful. The user is interacting through a chat interface.\nWhen the user's intent is ambiguous, ask a clarifying question rather than guessing wrong.\nIf the workspace doesn't exist yet and the user tries to use any feature, run setup first automatically.\nIf raw/ is empty when the user tries to import, tell them where to place their files: ~/.openclaw/workspace/hi-lite/raw/\nKeep responses concise. Don't dump 50 highlights at once — show 5-10 at a time and offer to show more.\nWhen showing highlights, always include the book title and author for context."
      }
    ],
    "body": "Hi-Lite — Kindle Highlights Skill\n\nYou are the Hi-Lite skill. You help users import, search, browse, and rediscover their Kindle highlights. All data stays local in the user's OpenClaw workspace.\n\nWorkspace Location\n\nAll Hi-Lite data lives at: ~/.openclaw/workspace/hi-lite/\n\nhi-lite/\n├── raw/              # User drops raw Kindle exports here\n├── highlights/\n│   ├── _index.md     # Master index of all books\n│   └── books/        # One markdown file per book\n└── collections/      # User-curated themed collections\n\n1. Setup (First Run)\n\nWhen the user first invokes Hi-Lite or says \"set up hi-lite\":\n\nCheck if ~/.openclaw/workspace/hi-lite/ exists.\nIf not, create the directory structure:\n~/.openclaw/workspace/hi-lite/raw/\n~/.openclaw/workspace/hi-lite/highlights/books/\n~/.openclaw/workspace/hi-lite/collections/\nCreate ~/.openclaw/workspace/hi-lite/highlights/_index.md with this template:\n# Hi-Lite Library\n\n**Total books**: 0\n**Total highlights**: 0\n**Last updated**: (never)\n\n## Books\n\n| Book | Author | Highlights | Date Imported |\n|------|--------|------------|---------------|\n\nTell the user setup is complete.\nSuggest they add ~/.openclaw/workspace/hi-lite/highlights to their memorySearch.extraPaths config for semantic vector search across all highlights. This is optional but highly recommended.\n2. Import & Parse\n\nTrigger: /hi-lite import or \"import my highlights\" or \"parse my clippings\"\n\nSteps\nRead all files in ~/.openclaw/workspace/hi-lite/raw/.\nDetect the format of each file and parse highlights from it.\nFor each highlight, extract: quote text, book title, author (if available), location (if available), date highlighted (if available).\nGroup highlights by book.\nFor each book, create or update a markdown file at ~/.openclaw/workspace/hi-lite/highlights/books/<slug>.md.\nDeduplicate: if a highlight with identical text already exists in that book's file, skip it.\nUpdate ~/.openclaw/workspace/hi-lite/highlights/_index.md with current totals.\nReport to the user: how many highlights were imported, how many books, how many duplicates skipped.\nSupported Formats\n\nAmazon \"My Clippings.txt\" — The standard Kindle export format:\n\nBook Title (Author Name)\n- Your Highlight on page 42 | Location 615-618 | Added on Monday, March 15, 2024 3:22:15 PM\n\nThe actual highlighted text goes here.\n==========\n\n\nEach clipping is separated by ==========. Parse the title/author from the first line, location/date from the second line, and the quote text from the remaining lines before the separator.\n\nAmazon Read Notebook (read.amazon.com) — Copy-pasted text from the Kindle notebook web page. Highlights typically appear as plain text with book titles as headers. Do your best to identify book titles vs highlight text from context.\n\nBookcision JSON — A JSON array of highlights with fields like text, title, author, location. Parse directly.\n\nBookcision text export — Similar to My Clippings but may have different formatting. Adapt parsing accordingly.\n\nHi-Lite fetch JSON — JSON output from the fetch script (identifiable by \"source\": \"amazon-kindle-notebook\"). Contains a books array where each book has title, author, asin, and a highlights array with text, page, note, and color fields. Parse directly using the structured data. Map page to the location metadata line.\n\nFreeform pasted text — If the user pastes raw text that doesn't match any known format, ask them to confirm the book title and author, then treat each paragraph or quote-block as a separate highlight.\n\nBook File Format\n\nEach book gets a markdown file at highlights/books/<slug>.md where <slug> is a URL-safe lowercase version of the title (e.g., crime-and-punishment.md).\n\n---\ntitle: Crime and Punishment\nauthor: Fyodor Dostoevsky\ndate_imported: 2026-02-22\nhighlight_count: 12\ntags: []\n---\n\n# Crime and Punishment — Fyodor Dostoevsky\n\n## Highlights\n\n> Pain and suffering are always inevitable for a large intelligence and a deep heart.\n- Location 342 | Highlighted 2024-03-15\n\n> The soul is healed by being with children.\n- Location 1205 | Highlighted 2024-03-20\n\n\nRules:\n\nYAML frontmatter with title, author, date_imported, highlight_count, and tags (initially empty array).\nEach highlight is a blockquote (>) followed by metadata on the next line (prefixed with - ).\nInclude whatever metadata is available (location, page, date). If none is available, just use the blockquote with no metadata line.\nWhen updating an existing file, append new highlights to the end of the ## Highlights section and update the frontmatter highlight_count.\ndate_imported reflects the first import date for that book. Don't change it on subsequent imports.\nIndex File Format\n\nAfter every import, regenerate highlights/_index.md:\n\n# Hi-Lite Library\n\n**Total books**: 15\n**Total highlights**: 342\n**Last updated**: 2026-02-22\n\n## Books\n\n| Book | Author | Highlights | Date Imported |\n|------|--------|------------|---------------|\n| Crime and Punishment | Fyodor Dostoevsky | 12 | 2026-02-22 |\n| Antifragile | Nassim Nicholas Taleb | 28 | 2026-02-22 |\n\n\nSort books alphabetically by title. Compute totals by summing all highlight counts.\n\n3. Search\n\nTrigger: /hi-lite search <query> or any natural language search like \"find quotes about perseverance\", \"what did Dostoevsky say about suffering?\"\n\nSteps\n\nPreferred method: Use the memory_search tool with the user's query. This performs hybrid vector + BM25 search across all highlight files if memorySearch.extraPaths includes the highlights directory. Return matching quotes with their book title, author, and location.\n\nFallback method: If memory_search is not available or doesn't return results, read the highlight files directly from ~/.openclaw/workspace/hi-lite/highlights/books/ and reason over them to find relevant quotes.\n\nResponse Format\n\nPresent results as a clean list:\n\n📖 **Crime and Punishment** — Fyodor Dostoevsky\n> Pain and suffering are always inevitable for a large intelligence and a deep heart.\nLocation 342\n\n📖 **Antifragile** — Nassim Nicholas Taleb\n> Wind extinguishes a candle and energizes fire.\nLocation 89\n\n\nIf no results are found, say so and suggest alternative search terms.\n\n4. Browse\n\nTrigger: /hi-lite browse or \"show me all books\", \"list my highlights\", \"what books do I have?\"\n\nCapabilities\n\"Show me all books\" — Read _index.md and display the books table.\n\"Show me highlights from [book]\" — Find and read the corresponding book file, display all highlights.\n\"Show me highlights from [author]\" — Find all book files by that author (check frontmatter), display highlights.\n\"Show me highlights from [month/year]\" — Filter highlights by their highlighted date or import date.\n\"Show me my most highlighted books\" — Read _index.md, sort by highlight count descending, display top results.\n\"How many highlights do I have?\" — Read _index.md and report totals.\nResponse Format\n\nKeep responses clean and scannable. Use the books table for listings. When showing highlights from a specific book, show the book title as a header followed by all blockquoted highlights.\n\n5. Random Quotes\n\nTrigger: /hi-lite random [count] or \"give me a random quote\", \"surprise me\", \"random highlight\"\n\nSteps\nList all book files in highlights/books/.\nRead one or more book files (chosen randomly).\nPick random highlights from the loaded files.\nDefault count is 1 if not specified. The user can request any number (e.g., \"give me 5 random quotes\").\nTry to pick from different books for variety when count > 1.\nResponse Format\n📖 **Crime and Punishment** — Fyodor Dostoevsky\n> The soul is healed by being with children.\n\n\nFor multiple quotes, separate each with a blank line.\n\n6. Collections\n\nTrigger: /hi-lite collection <name> or \"make a collection about courage\", \"create a [theme] collection\"\n\nSteps\nSearch across all highlights for quotes matching the theme (use memory_search or read files directly).\nCurate a selection of the most relevant quotes.\nSave as ~/.openclaw/workspace/hi-lite/collections/<slug>.md.\nPresent the collection to the user.\nCollection File Format\n---\nname: Quotes About Courage\ncreated: 2026-02-22\nhighlight_count: 8\n---\n\n# Quotes About Courage\n\n> Pain and suffering are always inevitable for a large intelligence and a deep heart.\n— Fyodor Dostoevsky, *Crime and Punishment*\n\n> Wind extinguishes a candle and energizes fire.\n— Nassim Nicholas Taleb, *Antifragile*\n\n\nEach quote includes full attribution (author and book title) since collections pull from multiple books.\n\nManaging Collections\n\"Show my collections\" — List all files in collections/.\n\"Show collection [name]\" — Read and display the specified collection.\n\"Add [quote] to [collection]\" — Append a quote to an existing collection and update its count.\n\"Delete collection [name]\" — Remove the collection file (confirm with user first).\n7. Fetch from Amazon\n\nTrigger: /hi-lite fetch or \"fetch my highlights from Amazon\" or \"sync my Kindle\"\n\nFirst-Time Setup\n\nCheck if Playwright is available by running python3 -c \"from playwright.sync_api import sync_playwright\". If it fails, guide the user:\n\npip install \"playwright>=1.40.0\"\nplaywright install chromium\n\nExecution\n\nWhen the user triggers a fetch:\n\nWrite the following Python script to ~/.openclaw/workspace/hi-lite/raw/fetch_highlights.py.\nRun it via bash: python3 ~/.openclaw/workspace/hi-lite/raw/fetch_highlights.py (append --amazon-domain amazon.co.uk etc. if the user specifies a non-US domain).\nThe script opens a visible Chromium window. If the user isn't logged in, it waits up to 5 minutes for them to sign in manually (this handles 2FA, CAPTCHA, etc.). Session cookies are saved at ~/.openclaw/workspace/hi-lite/.browser-data/ so future fetches skip login.\nThe script iterates through all annotated books in the sidebar, extracts highlights, and saves a JSON file to ~/.openclaw/workspace/hi-lite/raw/kindle-fetch-{timestamp}.json.\nAfter the script finishes, delete the script file (fetch_highlights.py) from raw/ so it doesn't get parsed as an import.\nThen automatically run the standard import flow (Section 2) on the fetched JSON file.\n\nThe script to write:\n\n#!/usr/bin/env python3\n\"\"\"Fetch Kindle highlights from Amazon's read.amazon.com/notebook page.\"\"\"\n\nimport argparse\nimport json\nimport os\nimport sys\nimport time\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\ntry:\n    from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout\nexcept ImportError:\n    print(\n        \"Playwright is not installed. Run:\\n\"\n        \"  pip install 'playwright>=1.40.0'\\n\"\n        \"  playwright install chromium\"\n    )\n    sys.exit(1)\n\nDEFAULT_BROWSER_DATA = os.path.expanduser(\n    \"~/.openclaw/workspace/hi-lite/.browser-data\"\n)\nDEFAULT_OUTPUT_DIR = os.path.expanduser(\"~/.openclaw/workspace/hi-lite/raw\")\nDEFAULT_DOMAIN = \"amazon.com\"\nLOGIN_TIMEOUT_SEC = 300\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(\n        description=\"Fetch Kindle highlights from Amazon\"\n    )\n    parser.add_argument(\n        \"--output-dir\", default=DEFAULT_OUTPUT_DIR,\n        help=\"Directory to save the fetched JSON file\",\n    )\n    parser.add_argument(\n        \"--amazon-domain\", default=DEFAULT_DOMAIN,\n        help=\"Amazon domain, e.g. amazon.co.uk\",\n    )\n    parser.add_argument(\n        \"--browser-data\", default=DEFAULT_BROWSER_DATA,\n        help=\"Path to persistent browser profile\",\n    )\n    return parser.parse_args()\n\n\ndef wait_for_login(page, timeout_sec=LOGIN_TIMEOUT_SEC):\n    print(\"Login required — please sign in to Amazon in the browser window.\")\n    print(f\"Waiting up to {timeout_sec // 60} minutes for login...\")\n    deadline = time.time() + timeout_sec\n    while time.time() < deadline:\n        url = page.url\n        if \"notebook\" in url and \"signin\" not in url and \"ap/signin\" not in url:\n            print(\"Login detected. Continuing...\")\n            return True\n        time.sleep(2)\n    print(\"Login timed out.\")\n    return False\n\n\ndef scroll_to_load_all(page, container_selector, item_selector):\n    previous_count = 0\n    stale_rounds = 0\n    while stale_rounds < 3:\n        items = page.query_selector_all(item_selector)\n        current_count = len(items)\n        if current_count > previous_count:\n            previous_count = current_count\n            stale_rounds = 0\n        else:\n            stale_rounds += 1\n        container = page.query_selector(container_selector)\n        if container:\n            container.evaluate(\"el => el.scrollTop = el.scrollHeight\")\n        else:\n            page.evaluate(\"window.scrollTo(0, document.body.scrollHeight)\")\n        time.sleep(1)\n    return previous_count\n\n\ndef extract_highlights_from_pane(page):\n    highlights = []\n    annotations = page.query_selector_all(\".a-row.a-spacing-base\")\n    for annotation in annotations:\n        header = annotation.query_selector(\"#annotationHighlightHeader\")\n        if not header:\n            continue\n        metadata_text = header.inner_text().strip()\n        color = \"\"\n        page_num = \"\"\n        if \"|\" in metadata_text:\n            parts = [p.strip() for p in metadata_text.split(\"|\")]\n            if parts:\n                color = parts[0].replace(\"highlight\", \"\").strip()\n            if len(parts) > 1 and \":\" in parts[1]:\n                page_num = parts[1].split(\":\", 1)[1].strip()\n        text_el = annotation.query_selector(\"#highlight\")\n        text = text_el.inner_text().strip() if text_el else \"\"\n        note_el = annotation.query_selector(\"#note\")\n        note = note_el.inner_text().strip() if note_el else \"\"\n        if text:\n            highlights.append({\n                \"text\": text, \"page\": page_num,\n                \"note\": note, \"color\": color,\n            })\n    return highlights\n\n\ndef fetch_highlights(args):\n    domain = args.amazon_domain\n    notebook_url = f\"https://read.{domain}/notebook\"\n    output_dir = Path(args.output_dir)\n    output_dir.mkdir(parents=True, exist_ok=True)\n    browser_data = Path(args.browser_data)\n    browser_data.mkdir(parents=True, exist_ok=True)\n\n    with sync_playwright() as pw:\n        context = pw.chromium.launch_persistent_context(\n            user_data_dir=str(browser_data),\n            headless=False,\n            args=[\"--disable-blink-features=AutomationControlled\"],\n            viewport={\"width\": 1280, \"height\": 900},\n        )\n        page = context.pages[0] if context.pages else context.new_page()\n\n        print(f\"Navigating to {notebook_url} ...\")\n        page.goto(notebook_url, wait_until=\"domcontentloaded\", timeout=60000)\n        time.sleep(3)\n\n        if \"signin\" in page.url or \"ap/signin\" in page.url:\n            if not wait_for_login(page):\n                context.close()\n                sys.exit(1)\n            page.goto(notebook_url, wait_until=\"domcontentloaded\", timeout=60000)\n            time.sleep(3)\n\n        print(\"Waiting for notebook to load...\")\n        try:\n            page.wait_for_selector(\"#library-section\", timeout=30000)\n        except PwTimeout:\n            try:\n                page.wait_for_selector(\n                    \".kp-notebook-library-each-book\", timeout=15000\n                )\n            except PwTimeout:\n                print(\"Could not find the book list. The page may have changed.\")\n                context.close()\n                sys.exit(1)\n\n        time.sleep(2)\n\n        print(\"Discovering books in your library...\")\n        scroll_to_load_all(\n            page, \"#library-section\", \".kp-notebook-library-each-book\"\n        )\n\n        book_elements = page.query_selector_all(\n            \".kp-notebook-library-each-book\"\n        )\n        total_books = len(book_elements)\n        print(f\"Found {total_books} annotated books.\")\n\n        if total_books == 0:\n            print(\"No annotated books found.\")\n            context.close()\n            return\n\n        books_data = []\n        for i in range(total_books):\n            book_elements = page.query_selector_all(\n                \".kp-notebook-library-each-book\"\n            )\n            if i >= len(book_elements):\n                break\n            book_el = book_elements[i]\n\n            title_el = book_el.query_selector(\"h2, .kp-notebook-searchable\")\n            sidebar_title = (\n                title_el.inner_text().strip() if title_el else f\"Book {i+1}\"\n            )\n            print(\n                f\"Fetching highlights from {sidebar_title} \"\n                f\"({i+1}/{total_books})...\"\n            )\n\n            book_el.click()\n            time.sleep(2)\n\n            try:\n                page.wait_for_selector(\n                    \"#annotationHighlightHeader\", timeout=10000\n                )\n            except PwTimeout:\n                time.sleep(2)\n\n            title = \"\"\n            author = \"\"\n            asin = \"\"\n\n            title_header = page.query_selector(\n                \".kp-notebook-metadata h3, \"\n                \".kp-notebook-metadata .a-size-base-plus\"\n            )\n            if title_header:\n                title = title_header.inner_text().strip()\n            if not title:\n                title = sidebar_title\n\n            author_el = page.query_selector(\n                \".kp-notebook-metadata .a-color-secondary, \"\n                \".kp-notebook-metadata p\"\n            )\n            if author_el:\n                author = (\n                    author_el.inner_text().strip()\n                    .replace(\"By: \", \"\").replace(\"by: \", \"\").strip()\n                )\n\n            asin_attr = book_el.get_attribute(\"id\") or \"\"\n            if asin_attr.startswith(\"B\"):\n                asin = asin_attr\n\n            scroll_to_load_all(\n                page,\n                \"#annotations-container, .a-row.a-spacing-base\",\n                \"#annotationHighlightHeader\",\n            )\n\n            highlights = extract_highlights_from_pane(page)\n            print(f\"  Found {len(highlights)} highlights.\")\n\n            books_data.append({\n                \"title\": title, \"author\": author,\n                \"asin\": asin, \"highlights\": highlights,\n            })\n\n        context.close()\n\n    timestamp = datetime.now(timezone.utc).strftime(\"%Y-%m-%dT%H:%M:%S\")\n    output = {\n        \"source\": \"amazon-kindle-notebook\",\n        \"fetched_at\": timestamp,\n        \"amazon_domain\": domain,\n        \"books\": books_data,\n    }\n\n    filename = (\n        f\"kindle-fetch-\"\n        f\"{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}.json\"\n    )\n    output_path = output_dir / filename\n    with open(output_path, \"w\", encoding=\"utf-8\") as f:\n        json.dump(output, f, indent=2, ensure_ascii=False)\n\n    total_hl = sum(len(b[\"highlights\"]) for b in books_data)\n    print(f\"\\nDone! Fetched {total_hl} highlights from {len(books_data)} books.\")\n    print(f\"Saved to: {output_path}\")\n\n\nif __name__ == \"__main__\":\n    args = parse_args()\n    fetch_highlights(args)\n\nRe-Fetch\n\nRe-fetching is safe. The import step deduplicates highlights, so running fetch multiple times will not create duplicate entries.\n\nNon-US Amazon Domains\n\nFor users on non-US Amazon stores, append --amazon-domain <domain> when running the script (e.g., --amazon-domain amazon.co.uk). Ask the user which Amazon store they use if unclear.\n\nGeneral Behavior\nAlways be conversational and helpful. The user is interacting through a chat interface.\nWhen the user's intent is ambiguous, ask a clarifying question rather than guessing wrong.\nIf the workspace doesn't exist yet and the user tries to use any feature, run setup first automatically.\nIf raw/ is empty when the user tries to import, tell them where to place their files: ~/.openclaw/workspace/hi-lite/raw/\nKeep responses concise. Don't dump 50 highlights at once — show 5-10 at a time and offer to show more.\nWhen showing highlights, always include the book title and author for context."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/gofordylan/hi-lite",
    "publisherUrl": "https://clawhub.ai/gofordylan/hi-lite",
    "owner": "gofordylan",
    "version": "1.0.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/hi-lite",
    "downloadUrl": "https://openagent3.xyz/downloads/hi-lite",
    "agentUrl": "https://openagent3.xyz/skills/hi-lite/agent",
    "manifestUrl": "https://openagent3.xyz/skills/hi-lite/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/hi-lite/agent.md"
  }
}