{"id":17446089,"url":"https://github.com/markus1189/hocket","last_synced_at":"2025-08-03T20:32:20.573Z","repository":{"id":24878468,"uuid":"28294400","full_name":"markus1189/hocket","owner":"markus1189","description":"Hocket the Haskell pocket client","archived":false,"fork":false,"pushed_at":"2024-10-30T03:37:12.000Z","size":693,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-10-30T06:25:00.130Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/markus1189.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-12-21T09:33:14.000Z","updated_at":"2024-10-30T03:37:16.000Z","dependencies_parsed_at":"2024-04-03T06:38:18.570Z","dependency_job_id":"19abf74b-a8ac-41de-b0d9-ada35fe46d11","html_url":"https://github.com/markus1189/hocket","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markus1189%2Fhocket","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markus1189%2Fhocket/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markus1189%2Fhocket/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markus1189%2Fhocket/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/markus1189","download_url":"https://codeload.github.com/markus1189/hocket/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228564404,"owners_count":17937583,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-17T18:22:31.024Z","updated_at":"2025-08-03T20:32:20.558Z","avatar_url":"https://github.com/markus1189.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hocket\n\nA minimalistic terminal user interface for [Raindrop.io](https://raindrop.io/) written in Haskell.\n\n\n## Overview\n\nHocket provides a keyboard-driven terminal interface for efficiently managing your Raindrop.io bookmarks. It's designed for users who prefer command-line tools and want to quickly process their bookmark collection without leaving the terminal.\n\n![Hocket Interface](pics/hocket.png)\n\n## Features\n\n### Core Functionality\n- **Single-pane interface with action flags** - View all items with visual indicators for pending actions\n- **Real-time synchronization** - Fetch latest bookmarks from Raindrop.io\n- **Batch operations** - Archive multiple items and manage reminders at once\n- **Smart reminder management** - Set and remove reminders with intelligent toggling\n- **Smart updates** - Only fetch items modified since last sync\n- **Favorite indicators** - Visual markers (★) for favorite bookmarks\n- **Rich item display** - Shows dates, titles, URLs, notes, excerpts, and reminder dates\n\n### Command Line Interface\n- **Add bookmarks from CLI** - Add bookmarks directly from terminal\n- **Automatic metadata extraction** - Raindrop.io parses title and description automatically\n- **Collection targeting** - Specify which collection to add bookmarks to\n- **Tag support** - Add multiple tags when creating bookmarks\n- **Robust error handling** - Automatic retries with exponential backoff\n\n### Workflow Support\n- **Browser integration** - Open bookmarks in your default browser\n- **Archive management** - Move items to a designated archive collection\n- **Reminder scheduling** - Set reminders for next day at 7:00 AM in your local timezone\n- **Keyboard navigation** - Efficient Vi-style navigation\n- **Status tracking** - Visual feedback for all operations\n\n## Installation\n\n### Using Nix (Recommended)\n\n```bash\n# Build the project\nnix build\n\n# Run directly\nnix run -- tui\n\n# Install to your profile\nnix profile install\n```\n\n### Using Cabal\n\n```bash\n# Clone the repository\ngit clone https://github.com/markus1189/hocket.git\ncd hocket\n\n# Build and install\ncabal build\ncabal install\n```\n\n## Configuration\n\n### XDG Base Directory Support\n\nHocket follows the XDG Base Directory Specification for configuration files:\n\n**Current config location**: `~/.config/hocket/config.dhall`\n\nCreate your configuration file:\n\n```dhall\n{\n  _raindropToken = \"your-raindrop-test-token-here\",\n  _archiveCollectionId = 12345\n}\n: ./schema.dhall\n```\n\n### Legacy Support\n\nFor backward compatibility, Hocket will still use `./config.dhall` if it exists in your working directory. However, you'll see a warning message encouraging migration to the XDG location.\n\n### Migration from Legacy Config\n\nIf you have an existing `config.dhall` in your working directory:\n\n1. Create the XDG config directory: `mkdir -p ~/.config/hocket`\n2. Move your config: `mv ./config.dhall ~/.config/hocket/config.dhall`\n3. Move the schema: `mv ./schema.dhall ~/.config/hocket/schema.dhall`\n\nThe application will automatically create the schema file if it doesn't exist.\n\n### Getting Your Raindrop.io Token\n\n1. Visit [Raindrop.io Settings → Integrations](https://app.raindrop.io/settings/integrations)\n2. Click **\"+ Create new app\"**\n3. Name your app (e.g., \"Hocket\")\n4. Click **\"Create\"**\n5. Click on your new app\n6. Copy the **\"Test token\"**\n\n### Finding Your Archive Collection ID\n\n1. Go to [Raindrop.io](https://app.raindrop.io)\n2. Create or navigate to your desired archive collection\n3. Note the collection ID from the URL (e.g., `app.raindrop.io/my/12345`)\n\n**Security Note:** Keep your token secure and never commit it to version control.\n\n## Usage\n\n### Commands\n\n#### Terminal User Interface\n```bash\n# Run the interactive TUI (config is loaded from ~/.config/hocket/config.dhall)\nhocket tui\n```\n\n#### Add Bookmarks from Command Line\n```bash\n# Add a bookmark to the unsorted collection\nhocket add https://example.com\n\n# Add a bookmark to a specific collection\nhocket add https://example.com --collection 12345\n\n# Add a bookmark with tags\nhocket add https://example.com --tag programming --tag tutorial\n\n# Add a bookmark with collection and tags\nhocket add https://example.com --collection 12345 --tag rust --tag cli\n```\n\n**Add Command Features:**\n- **Automatic metadata extraction** - Title, description, and other metadata are fetched automatically\n- **Default collection** - Uses unsorted collection (-1) if not specified\n- **Multiple tags** - Add multiple `--tag` flags for multiple tags\n- **Retry logic** - Automatically retries failed requests with exponential backoff\n- **Error handling** - Clear error messages for configuration and API issues\n\n### Keyboard Controls\n\n#### Navigation\n- `↑/↓` or `j/k` - Move up/down in the list\n- `J/K` - Jump to next/previous flagged item (skip unflagged items)\n- `q` - Quit application\n\n#### Item Actions\n- `Space` - Open selected item in browser\n- `Enter` - Open item in browser AND mark for archiving\n- `a` - Flag selected item for archiving\n- `s` - Smart reminder toggle: flag for reminder setting (unscheduled items) or removal (scheduled items)\n- `u` - Remove flags from selected item (archive, reminder, or removal flags)\n\n#### Bulk Operations\n- `r` - Refresh/fetch latest items from Raindrop.io\n- `U` - Clear all flags from all items\n- `X` - Execute all flagged operations (archive items, set reminders, remove reminders)\n- `S` - Toggle showing/hiding items with future reminders\n\n### Interface Layout\n\n```\n┌─ Hocket: (15|2|1|1) (3) ───────────────────────────────────────────┐\n│   2025-01-15: ★ Important Article Title         reddit.com/r/... │\n│   2025-01-14:   Regular Bookmark                github.com/...   │\n│ A 2025-01-13:   Item flagged for archive        example.com/...  │\n│ R 2025-01-12:   Item flagged for reminder       example.com/...  │\n│ r 2025-01-11:   Item flagged to remove reminder news.ycombinator...│\n│   2025-01-10:   Item with existing reminder     stackoverflow.com/│\n├──────────────────────────────────────────────────────────────────┤\n└─ REMINDER 2025-01-12 EXCERPT: This is a sample excerpt ──────────┘\n│                                                    Last: 14:32:18 │\n│ Status: setting reminders                                         │\n└────────────────────────────────────────────────────────────────────┘\n```\n\n#### Visual Elements\n- **A** - Flag indicating item is pending archive action\n- **R** - Flag indicating item is pending reminder setting (blue color)\n- **r** - Flag indicating item is pending reminder removal (red color)\n- **★** - Indicates favorite bookmarks\n- **Header counts** - `(normal|archive|remind_set|remind_remove) (existing_reminders)`\n- **Date** - When the bookmark was created (shows reminder date when present)\n- **Title** - Bookmark title or URL if no title available\n- **Domain** - Truncated URL showing the domain and path\n- **Bottom section** - Shows notes, reminder dates, and excerpts for the selected item\n- **Status bar** - Last update time and current operation status\n\n## Reminder Management\n\n### Smart Reminder Toggle\n\nThe `s` key provides intelligent reminder management based on the item's current state:\n\n- **Unscheduled items**: Press `s` to flag for reminder setting (shows blue \"R \")\n- **Items with existing reminders**: Press `s` to flag for reminder removal (shows red \"r \")\n\n### Reminder Scheduling\n\n- **Default time**: All reminders are set for the next day at 7:00 AM in your local timezone\n- **Automatic conversion**: Times are converted to UTC for API storage\n- **Visual feedback**: Items with reminders show the reminder date instead of creation date\n\n### Workflow Example\n\n```bash\n# Set reminders for important items\n1. Navigate to an unscheduled item\n2. Press 's' - item shows blue \"R \" flag\n3. Repeat for other items\n4. Press 'X' - executes all flagged operations\n\n# Remove unwanted reminders\n1. Navigate to an item with existing reminder\n2. Press 's' - item shows red \"r \" flag  \n3. Press 'X' - removes the reminder\n\n# Mixed operations\n1. Flag some items for archive with 'a'\n2. Flag some items for reminders with 's'\n3. Flag some existing reminders for removal with 's'\n4. Press 'X' - executes all operations at once\n```\n\n### Display Features\n\n- **Color coding**: Blue for setting reminders, red for removing them\n- **Status tracking**: Real-time count of flagged operations in header\n- **Jump navigation**: `J/K` keys jump between all flagged items (archive and reminder)\n- **Toggle visibility**: `S` key shows/hides items with future reminders\n\n## Technical Details\n\n### Architecture\n- **Language**: Haskell (GHC 9.6+)\n- **UI Framework**: [Brick](https://github.com/jtdaugherty/brick) (terminal UI library)\n- **HTTP Client**: [Wreq](http://www.serpentine.com/wreq/) for API communication\n- **Configuration**: [Dhall](https://dhall-lang.org/) functional configuration language\n- **Concurrency**: Async operations for non-blocking UI\n\n### API Integration\n- **Raindrop.io REST API v1** - Full integration with bookmark and reminder management\n- **Individual reminder operations** - Each reminder uses separate API calls as required by Raindrop.io\n- **Smart pagination** - Efficiently handles large bookmark collections  \n- **Rate limiting** - Respects API limits with exponential backoff retry logic\n- **Error handling** - Graceful degradation on network issues\n- **Automatic metadata parsing** - Uses Raindrop's `pleaseParse` feature for rich bookmark data\n\n### Data Management\n- **Local state** - In-memory bookmark cache with smart updates\n- **Conflict resolution** - Handles concurrent modifications\n- **Performance** - Optimized for collections with thousands of items\n\n## Development\n\n### Building from Source\n\n```bash\n# Install dependencies\ncabal update\n\n# Build the project\ncabal build\n\n# Run tests\ncabal test\n\n# Development build with warnings\ncabal build --ghc-options=\"-Wall -Werror\"\n```\n\n### Project Structure\n\n```\nhocket/\n├── main/                   # Main application\n│   ├── hocket.hs          # Entry point and UI logic\n│   └── Events.hs          # Event system definitions\n├── src/Network/\n│   ├── Raindrop.hs        # API client implementation\n│   └── Bookmark/\n│       ├── Types.hs       # Data types and JSON instances\n│       └── Ui/\n│           ├── State.hs   # Application state management\n│           └── Widgets.hs # UI helper functions\n└── test/                  # Test suite\n```\n\n### Contributing\n\nContributions are welcome! Please:\n\n1. Follow the existing code style (see `CONVENTIONS.md`)\n2. Add tests for new functionality\n3. Update documentation as needed\n4. Ensure `cabal test` passes\n\n#### Code Style Guidelines\n- Use `lens` for record access (avoid `_` prefixed accessors)\n- Prefer `wreq` for HTTP operations\n- Use `tasty` for testing\n- Follow Brick patterns for UI components\n\n## Limitations\n\nCurrent limitations that may be addressed in future versions:\n\n- **Single collection focus** - Primarily works with unsorted bookmarks\n- **Browser dependency** - Requires external browser for opening links\n- **Terminal only** - No GUI version available\n- **Limited search** - No built-in search functionality\n\n## Troubleshooting\n\n### Common Issues\n\n**\"Authentication failed\"**\n- Verify your Raindrop.io token in `~/.config/hocket/config.dhall`\n- Ensure the token has not expired\n- Check your internet connection\n\n**\"Collection not found\"**\n- Verify the `archiveCollectionId` in your config\n- Ensure the collection exists and is accessible\n\n**\"Items not appearing\"**\n- Press `r` to refresh/fetch latest items\n- Check that items exist in your Raindrop.io unsorted collection\n- Verify your account has bookmarks\n\n**\"Reminders not working\"**\n- Ensure you have proper permissions to modify bookmarks\n- Check that the reminder time (next day 7:00 AM) is in the future\n- Verify your system timezone is configured correctly\n- Press `r` to refresh and see updated reminder states\n\n**\"Reminder flags showing incorrectly\"**\n- Press `S` to toggle reminder visibility and refresh the display\n- Use `r` to refresh from Raindrop.io to sync latest reminder states\n\n### Getting Help\n\n- Check existing [GitHub Issues](https://github.com/markus1189/hocket/issues)\n- Review the `CONVENTIONS.md` file for development guidelines\n- Consult the [Brick documentation](https://github.com/jtdaugherty/brick/blob/master/docs/guide.rst) for UI-related questions\n\n## License\n\nBSD3 - See [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\n- [Raindrop.io](https://raindrop.io/) for providing an excellent bookmark service with a great API\n- [Brick](https://github.com/jtdaugherty/brick) for making terminal UI development in Haskell approachable\n- The Haskell community for excellent libraries and tooling","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkus1189%2Fhocket","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkus1189%2Fhocket","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkus1189%2Fhocket/lists"}