├── .DS_Store
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SETUP_GUIDE.MD
├── cookbook
└── python
│ ├── README.md
│ ├── claude-mcp
│ ├── README.md
│ ├── image.png
│ └── linkedin.py
│ ├── knowledge-graph
│ ├── .gitignore
│ ├── README.md
│ ├── app.py
│ ├── helpers.py
│ ├── lib
│ │ ├── bindings
│ │ │ └── utils.js
│ │ ├── tom-select
│ │ │ ├── tom-select.complete.min.js
│ │ │ └── tom-select.css
│ │ └── vis-9.1.2
│ │ │ ├── vis-network.css
│ │ │ └── vis-network.min.js
│ └── requirements.txt
│ └── streamlit-chatbot
│ ├── .gitignore
│ ├── .streamlit
│ └── secrets_template.toml
│ ├── README.md
│ ├── app.py
│ ├── helpers.py
│ └── requirements.txt
├── desktop
├── .editorconfig
├── .erb
│ ├── configs
│ │ ├── .eslintrc
│ │ ├── webpack.config.base.ts
│ │ ├── webpack.config.eslint.ts
│ │ ├── webpack.config.main.prod.ts
│ │ ├── webpack.config.preload.dev.ts
│ │ ├── webpack.config.renderer.dev.dll.ts
│ │ ├── webpack.config.renderer.dev.ts
│ │ ├── webpack.config.renderer.prod.ts
│ │ └── webpack.paths.ts
│ ├── img
│ │ ├── erb-banner.svg
│ │ ├── erb-logo.png
│ │ └── palette-sponsor-banner.svg
│ ├── mocks
│ │ └── fileMock.js
│ ├── release
│ │ └── app
│ │ │ └── dist
│ │ │ └── renderer
│ │ │ └── index.html
│ └── scripts
│ │ ├── .eslintrc
│ │ ├── check-build-exists.ts
│ │ ├── check-native-dep.js
│ │ ├── check-node-env.js
│ │ ├── check-port-in-use.js
│ │ ├── clean.js
│ │ ├── delete-source-maps.js
│ │ ├── electron-rebuild.js
│ │ ├── link-modules.ts
│ │ └── notarize.js
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── 1-Bug_report.md
│ │ ├── 2-Question.md
│ │ └── 3-Feature_request.md
│ ├── config.yml
│ ├── stale.yml
│ └── workflows
│ │ ├── codeql-analysis.yml
│ │ ├── publish.yml
│ │ └── test.yml
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── ADD_PLATFORMS.md
├── HELPER_FUNCTIONS.md
├── README.md
├── assets
│ ├── Surfer.png
│ ├── SurferApp.png
│ ├── SurferDemo.mp4
│ ├── SurferDiagram.png
│ ├── assets.d.ts
│ ├── current_db.py
│ ├── entitlements.mac.plist
│ ├── error.png
│ ├── icon.icns
│ ├── icon.png
│ ├── icon.svg
│ ├── imessage_mac.py
│ ├── imessage_windows.py
│ ├── password-prompt.html
│ ├── requirements.txt
│ ├── search_vector_db.py
│ ├── vectorize.py
│ └── windows_requirements.txt
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── release
│ └── app
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ └── pnpm-lock.yaml
├── src
│ ├── main
│ │ ├── helpers
│ │ │ ├── imessage.ts
│ │ │ ├── menu.ts
│ │ │ ├── network.ts
│ │ │ ├── platforms.ts
│ │ │ ├── python.ts
│ │ │ └── util.ts
│ │ ├── main.ts
│ │ ├── platforms
│ │ │ ├── Apple
│ │ │ │ ├── imessage.js
│ │ │ │ ├── imessage.json
│ │ │ │ └── imessage.md
│ │ │ ├── Google
│ │ │ │ ├── gmail.js
│ │ │ │ ├── gmail.json
│ │ │ │ ├── gmail.md
│ │ │ │ ├── youtube.js
│ │ │ │ ├── youtube.json
│ │ │ │ └── youtube.md
│ │ │ ├── Microsoft
│ │ │ │ ├── connections.js
│ │ │ │ ├── connections.json
│ │ │ │ ├── connections.md
│ │ │ │ └── connections_with_profile.txt
│ │ │ ├── Notion
│ │ │ │ ├── notion.js
│ │ │ │ ├── notion.json
│ │ │ │ └── notion.md
│ │ │ ├── OpenAI
│ │ │ │ ├── chatgpt.js
│ │ │ │ ├── chatgpt.json
│ │ │ │ └── chatgpt.md
│ │ │ └── X Corp
│ │ │ │ ├── bookmarks.js
│ │ │ │ ├── bookmarks.json
│ │ │ │ ├── bookmarks.md
│ │ │ │ ├── twitter.js
│ │ │ │ ├── twitter.json
│ │ │ │ └── twitter.md
│ │ ├── preload.ts
│ │ ├── preloadElectron.js
│ │ ├── preloadFunctions.js
│ │ └── preloadWebview.js
│ └── renderer
│ │ ├── App.tsx
│ │ ├── Surfer.tsx
│ │ ├── components
│ │ ├── CodeBlock.jsx
│ │ ├── Header.jsx
│ │ ├── Layout.tsx
│ │ ├── PlatformDashboard.jsx
│ │ ├── RunDetails.jsx
│ │ ├── Webview.tsx
│ │ └── ui
│ │ │ ├── accordion.tsx
│ │ │ ├── alert-dialog.tsx
│ │ │ ├── alert.tsx
│ │ │ ├── aspect-ratio.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── badge.tsx
│ │ │ ├── breadcrumb.tsx
│ │ │ ├── button.tsx
│ │ │ ├── calendar.tsx
│ │ │ ├── card.tsx
│ │ │ ├── carousel.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── cn.js
│ │ │ ├── collapsible.tsx
│ │ │ ├── command.tsx
│ │ │ ├── context-menu.tsx
│ │ │ ├── dialog.tsx
│ │ │ ├── drawer.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── form.tsx
│ │ │ ├── hover-card.tsx
│ │ │ ├── input-otp.tsx
│ │ │ ├── input.tsx
│ │ │ ├── label.tsx
│ │ │ ├── menubar.tsx
│ │ │ ├── navigation-menu.tsx
│ │ │ ├── pagination.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── radio-group.tsx
│ │ │ ├── resizable.tsx
│ │ │ ├── scroll-area.tsx
│ │ │ ├── select.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── sheet.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── slider.tsx
│ │ │ ├── sonner.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── table.tsx
│ │ │ ├── tabs.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── theme-provider.tsx
│ │ │ ├── toast.tsx
│ │ │ ├── toaster.tsx
│ │ │ ├── toggle-group.tsx
│ │ │ ├── toggle.tsx
│ │ │ ├── toggleColor.tsx
│ │ │ ├── tooltip.tsx
│ │ │ └── use-toast.ts
│ │ ├── helpers.ts
│ │ ├── index.ejs
│ │ ├── index.tsx
│ │ ├── pages
│ │ ├── Home.jsx
│ │ └── Platform.jsx
│ │ ├── preload.d.ts
│ │ ├── state
│ │ ├── actions.ts
│ │ ├── initialStates.ts
│ │ ├── reducers.ts
│ │ └── store.js
│ │ ├── styles
│ │ └── globals.css
│ │ └── types
│ │ └── interfaces.ts
├── tailwind.config.js
└── tsconfig.json
├── docs
├── .eslintrc.json
├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── jsconfig.json
├── mdx-components.jsx
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.js
├── prettier.config.js
├── src
│ ├── app
│ │ ├── about
│ │ │ └── page.mdx
│ │ ├── claude
│ │ │ └── page.mdx
│ │ ├── community-projects
│ │ │ └── page.mdx
│ │ ├── contributing
│ │ │ └── page.mdx
│ │ ├── cookbook
│ │ │ └── python
│ │ │ │ └── page.mdx
│ │ ├── desktop
│ │ │ ├── installation
│ │ │ │ └── page.mdx
│ │ │ ├── page.mdx
│ │ │ └── platforms
│ │ │ │ ├── chatgpt
│ │ │ │ └── page.mdx
│ │ │ │ ├── gmail
│ │ │ │ └── page.mdx
│ │ │ │ ├── imessage
│ │ │ │ └── page.mdx
│ │ │ │ ├── linkedin
│ │ │ │ └── page.mdx
│ │ │ │ ├── notion
│ │ │ │ └── page.mdx
│ │ │ │ ├── page.mdx
│ │ │ │ └── twitter
│ │ │ │ └── page.mdx
│ │ ├── faq
│ │ │ └── page.mdx
│ │ ├── favicon.ico
│ │ ├── layout.jsx
│ │ ├── not-found.jsx
│ │ ├── page.mdx
│ │ ├── providers.jsx
│ │ └── sdk
│ │ │ └── python
│ │ │ └── page.mdx
│ ├── components
│ │ ├── Button.jsx
│ │ ├── Code.jsx
│ │ ├── Desktop.jsx
│ │ ├── DownloadButtons.jsx
│ │ ├── EditPage.jsx
│ │ ├── Feedback.jsx
│ │ ├── Footer.jsx
│ │ ├── GridPattern.jsx
│ │ ├── Header.jsx
│ │ ├── Heading.jsx
│ │ ├── HeroPattern.jsx
│ │ ├── Image.jsx
│ │ ├── Layout.jsx
│ │ ├── Libraries.jsx
│ │ ├── Logo.jsx
│ │ ├── MobileNavigation.jsx
│ │ ├── Navigation.jsx
│ │ ├── Prose.jsx
│ │ ├── Resources.jsx
│ │ ├── Search.jsx
│ │ ├── SectionProvider.jsx
│ │ ├── Tag.jsx
│ │ ├── ThemeToggle.jsx
│ │ ├── icons
│ │ │ ├── BellIcon.jsx
│ │ │ ├── BoltIcon.jsx
│ │ │ ├── BookIcon.jsx
│ │ │ ├── CalendarIcon.jsx
│ │ │ ├── CartIcon.jsx
│ │ │ ├── ChatBubbleIcon.jsx
│ │ │ ├── CheckIcon.jsx
│ │ │ ├── ChevronRightLeftIcon.jsx
│ │ │ ├── ClipboardIcon.jsx
│ │ │ ├── CogIcon.jsx
│ │ │ ├── CopyIcon.jsx
│ │ │ ├── DocumentIcon.jsx
│ │ │ ├── EnvelopeIcon.jsx
│ │ │ ├── FaceSmileIcon.jsx
│ │ │ ├── FolderIcon.jsx
│ │ │ ├── LinkIcon.jsx
│ │ │ ├── ListIcon.jsx
│ │ │ ├── MagnifyingGlassIcon.jsx
│ │ │ ├── MapPinIcon.jsx
│ │ │ ├── PackageIcon.jsx
│ │ │ ├── PaperAirplaneIcon.jsx
│ │ │ ├── PaperClipIcon.jsx
│ │ │ ├── ShapesIcon.jsx
│ │ │ ├── ShirtIcon.jsx
│ │ │ ├── SquaresPlusIcon.jsx
│ │ │ ├── TagIcon.jsx
│ │ │ ├── UserIcon.jsx
│ │ │ └── UsersIcon.jsx
│ │ └── mdx.jsx
│ ├── helpers.js
│ ├── images
│ │ ├── claude-mcp.png
│ │ ├── claude_finished.png
│ │ ├── cover_image.png
│ │ ├── knowledge-graph.png
│ │ ├── logos
│ │ │ ├── go.svg
│ │ │ ├── node.svg
│ │ │ ├── php.svg
│ │ │ ├── python.svg
│ │ │ └── ruby.svg
│ │ └── streamlit-chatbot.png
│ ├── lib
│ │ └── remToPx.js
│ ├── mdx
│ │ ├── recma.mjs
│ │ ├── rehype.mjs
│ │ ├── remark.mjs
│ │ └── search.mjs
│ └── styles
│ │ └── tailwind.css
├── tailwind.config.js
└── typography.js
├── landing
├── .firebase
│ └── hosting.ZGlzdA.cache
├── .firebaserc
├── .gitignore
├── README.md
├── eslint.config.js
├── firebase.json
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
│ ├── App.jsx
│ ├── Business.jsx
│ ├── assets
│ │ ├── cover_image.png
│ │ ├── favicon.ico
│ │ └── logo.svg
│ ├── index.css
│ └── main.jsx
├── tailwind.config.js
└── vite.config.js
├── sdk
└── python
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── pyproject.toml
│ ├── requirements.txt
│ ├── setup.py
│ └── surfer_protocol
│ ├── __init__.py
│ └── client.py
└── surfer-mcp
├── .python-version
├── README.md
├── pyproject.toml
├── src
└── surfer_mcp
│ ├── __init__.py
│ ├── __pycache__
│ ├── __init__.cpython-313.pyc
│ └── server.cpython-313.pyc
│ └── server.py
└── uv.lock
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/.DS_Store
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to the Surfer Protocol! We welcome contributions from the community to help improve and expand this project.
4 |
5 | ## Types of Contributions
6 |
7 | - Adding new platforms in the desktop app and documentation (see [Guide to Adding New Platforms](ADD_PLATFORMS.md))
8 | - Building applications off of Surfer Protocol (see [Python SDK](sdk/python/README.md) and [Cookbook](cookbook/README.md))
9 | - Helping with the documentation website (see [Docs](docs/README.md))
10 | - General Bug Fixes
11 |
12 | ## Setup
13 |
14 | Please see our [Setup Guide](SETUP_GUIDE.md) for more information on how to set up the development environment.
15 |
16 | ## Contribution Guidelines
17 |
18 | 1. **Reporting Bugs**: If you encounter any bugs or issues, please open a new issue in the [GitHub repository](https://github.com/Surfer-Org/Protocol/issues/new?labels=bug&template=bug-report---.md). Provide a clear and detailed description of the problem, along with any relevant steps to reproduce the issue.
19 |
20 | 2. **Suggesting Features**: If you have an idea for a new feature or improvement, please open a new issue in the [GitHub repository](https://github.com/Surfer-Org/Protocol/issues/new?labels=enhancement&template=feature-request---.md). Describe the feature in detail, explain its benefits, and provide any additional context that might be helpful.
21 |
22 | 3. **Submitting Pull Requests**: If you would like to contribute code changes to the project, follow these steps:
23 | - Fork the repository
24 | - Create a new branch for your feature or bug fix
25 | - Make your changes
26 | - Test your changes thoroughly
27 | - Submit a pull request to the main repository, providing a clear description of your changes and the problem they solve
28 |
29 | We appreciate all contributions, whether they are bug reports, feature suggestions, or code changes. By working together, we can make Surfer a better and more useful tool for everyone.
30 |
31 | ## Contact
32 |
33 | If you have any questions or need further assistance, please feel free to reach out to the project maintainers:
34 |
35 | [Surfer Discord Server](https://discord.gg/Tjg7pjcFNP) - [@SahilLalani0](https://x.com/SahilLalani0) - [@JackBlair87](https://x.com/JackBlair87) - [@T0M_3D](https://x.com/T0M_3D)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Surfer Protocol
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SETUP_GUIDE.MD:
--------------------------------------------------------------------------------
1 | ## Surfer Protocol Setup Guide
2 |
3 | Before you can start contributing, please follow these steps to set up the development environment:
4 |
5 | 1. Fork the repository:
6 |
7 | 2. Clone the forked repository:
8 | ```
9 | git clone https://github.com/YOUR_GITHUB_USERNAME/Protocol.git
10 | ```
11 |
12 | ## Desktop App
13 |
14 | 1. Install the required dependencies:
15 | ```
16 | cd Surfer-Protocol/desktop
17 | npm install
18 | ```
19 |
20 | 2. Start the development server:
21 | ```
22 | npm start
23 | ```
24 |
25 | ## Python SDK
26 |
27 | 1. Navigate to the Python SDK directory:
28 | ```bash
29 | cd Surfer-Protocol/sdk/python
30 | ```
31 |
32 | 2. Create and activate a virtual environment:
33 | ```bash
34 | python -m venv venv
35 | source venv/bin/activate # On Windows, use: venv\Scripts\activate
36 | ```
37 |
38 | 3. Install development dependencies:
39 | ```bash
40 | pip install -r requirements.txt
41 | pip install -e .
42 | ```
43 |
44 | 4. Test the package:
45 | ```bash
46 | python -m surfer_protocol
47 | ```
48 |
49 | 5. You can now import and use the package in your Python code:
50 | ```python
51 | from surfer_protocol import SurferClient
52 |
53 | client = SurferClient()
54 | # Make sure the desktop app is running before testing
55 | data = client.get("bookmarks-001")
56 | ```
57 |
58 | ## Docs
59 |
60 | 1. Install the required dependencies:
61 | ```
62 | cd Surfer-Protocol/docs
63 | npm install
64 | ```
65 |
66 | 2. Start the development server:
67 | ```
68 | npm run dev
69 | ```
70 |
71 | ## Landing Page
72 |
73 | 1. Install the required dependencies:
74 | ```
75 | cd Surfer-Protocol/landing
76 | npm install
77 | ```
78 |
79 | 2. Start the development server:
80 | ```
81 | npm run dev
82 | ```
83 |
84 | This will run the application in development mode, allowing you to test your changes and see the results in real-time.
85 |
--------------------------------------------------------------------------------
/cookbook/python/README.md:
--------------------------------------------------------------------------------
1 | # Surfer Protocol Python Cookbook
2 |
3 | This cookbook contains examples of how to use the Surfer Protocol Python SDK to build applications.
4 |
5 | ## Current Recipes
6 |
7 | - [Streamlit Chatbot](streamlit-chatbot/app.py)
8 | - [Knowledge Graph Maker](knowledge-graph/app.py)
9 |
10 | ## How to Contribute
11 |
12 | We welcome contributions to the cookbook! Please see our [CONTRIBUTING.md](../../CONTRIBUTING.md) for more information on how to contribute to the Surfer Protocol.
13 |
--------------------------------------------------------------------------------
/cookbook/python/claude-mcp/README.md:
--------------------------------------------------------------------------------
1 | # Claude MCP
2 |
3 | 1. Install the Claude Desktop App
4 |
5 | 3. Install uv. This is recommended for managing model context protocol servers. More details can be found here.
6 |
7 | 4. If not already there, create a `claude_desktop_config.json` file in the app data folder of the Claude desktop app.
8 |
9 | Mac Path: `~/Library/Application Support/Claude/claude_desktop_config.json`
10 |
11 | Windows Path: `C:/Users/[your-username]/AppData/Roaming/Claude/claude_desktop_config.json`
12 |
13 | 5. Add the following content to the file and save it:
14 |
15 |
16 | ```json
17 | {
18 | "mcpServers": {
19 | "surfer-mcp": {
20 | "command": "uvx",
21 | "args": [
22 | "surfer-mcp"
23 | ]
24 | }
25 | }
26 | }
27 | ```
28 |
29 | 6. Close the Claude Desktop app and run it again. If you see a plug and tool icon in the chat window, you should be good to go!
30 |
--------------------------------------------------------------------------------
/cookbook/python/claude-mcp/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/cookbook/python/claude-mcp/image.png
--------------------------------------------------------------------------------
/cookbook/python/claude-mcp/linkedin.py:
--------------------------------------------------------------------------------
1 | import json
2 | from datetime import datetime
3 | from collections import defaultdict
4 |
5 | # Load the JSON data
6 | with open('your-file-here.json', 'r') as file:
7 | data = json.load(file)
8 |
9 | # Initialize a dictionary to store the counts
10 | connections_by_year_month = defaultdict(lambda: defaultdict(int))
11 |
12 | # Iterate through the connections
13 | for connection in data['content']:
14 | created_at_timestamp = connection.get('created_at')
15 | if created_at_timestamp:
16 | # Convert timestamp to datetime
17 | created_date = datetime.fromtimestamp(created_at_timestamp / 1000) # Convert ms to seconds
18 | year = created_date.year
19 | month = created_date.strftime('%B') # Get month name
20 | connections_by_year_month[year][month] += 1
21 |
22 | # Print the results
23 | for year, months in sorted(connections_by_year_month.items()):
24 | print(f"\nYear: {year}")
25 | for month, count in sorted(months.items()):
26 | print(f" {month}: {count} connections")
--------------------------------------------------------------------------------
/cookbook/python/knowledge-graph/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | .env
3 | *.json
4 | *.png
5 | *.html
--------------------------------------------------------------------------------
/cookbook/python/knowledge-graph/requirements.txt:
--------------------------------------------------------------------------------
1 | networkx
2 | matplotlib
3 | pyvis
4 | surfer-protocol
--------------------------------------------------------------------------------
/cookbook/python/streamlit-chatbot/.gitignore:
--------------------------------------------------------------------------------
1 | .streamlit/secrets.toml
2 | __pycache__/
--------------------------------------------------------------------------------
/cookbook/python/streamlit-chatbot/.streamlit/secrets_template.toml:
--------------------------------------------------------------------------------
1 | **Rename this file to `secrets.toml` and add your API keys**
2 |
3 | WEAVIATE_URL = "add-your-weaviate-url-here"
4 | WEAVIATE_API_KEY = "add-your-weaviate-api-key-here"
5 | OPENAI_API_KEY = "add-your-openai-api-key-here"
--------------------------------------------------------------------------------
/cookbook/python/streamlit-chatbot/helpers.py:
--------------------------------------------------------------------------------
1 | def chunk_object(obj, chunk_size=1000, overlap=200):
2 | """
3 | Chunks the text property of an object into smaller pieces with overlap.
4 |
5 | Args:
6 | obj (dict): The input object containing properties including 'text'
7 | chunk_size (int): Maximum size of each chunk
8 | overlap (int): Number of characters to overlap between chunks
9 |
10 | Returns:
11 | list: List of objects with the same properties but chunked text
12 | """
13 | # If there's no text property or text is shorter than chunk_size, return original
14 | if 'text' not in obj or len(obj['text']) <= chunk_size:
15 | return [obj]
16 |
17 | text = obj['text']
18 | chunks = []
19 | start = 0
20 |
21 | while start < len(text):
22 | # Get chunk of text
23 | end = start + chunk_size
24 |
25 | # If this isn't the first chunk, include the overlap from the previous chunk
26 | if start > 0:
27 | start = start - overlap
28 |
29 | # Get the chunk
30 | chunk = text[start:end]
31 |
32 | # Create new object with same properties but chunked text
33 | new_obj = obj.copy()
34 | new_obj['text'] = chunk
35 |
36 |
37 | chunks.append(new_obj)
38 |
39 | # Move start position for next chunk
40 | start = end
41 |
42 | return chunks
--------------------------------------------------------------------------------
/cookbook/python/streamlit-chatbot/requirements.txt:
--------------------------------------------------------------------------------
1 | streamlit
2 | weaviate-client
3 | openai
4 | surfer-protocol
--------------------------------------------------------------------------------
/desktop/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/desktop/.erb/configs/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/desktop/.erb/configs/webpack.config.eslint.ts:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 |
3 | module.exports = require('./webpack.config.renderer.dev').default;
4 |
--------------------------------------------------------------------------------
/desktop/.erb/configs/webpack.config.renderer.dev.dll.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds the DLL for development electron renderer process
3 | */
4 |
5 | import webpack from 'webpack';
6 | import path from 'path';
7 | import { merge } from 'webpack-merge';
8 | import baseConfig from './webpack.config.base';
9 | import webpackPaths from './webpack.paths';
10 | import { dependencies } from '../../package.json';
11 | import checkNodeEnv from '../scripts/check-node-env';
12 |
13 | checkNodeEnv('development');
14 |
15 | const dist = webpackPaths.dllPath;
16 |
17 | const configuration: webpack.Configuration = {
18 | context: webpackPaths.rootPath,
19 |
20 | devtool: 'eval',
21 |
22 | mode: 'development',
23 |
24 | target: 'electron-renderer',
25 |
26 | externals: ['fsevents', 'crypto-browserify'],
27 |
28 | /**
29 | * Use `module` from `webpack.config.renderer.dev.js`
30 | */
31 | module: require('./webpack.config.renderer.dev').default.module,
32 |
33 | entry: {
34 | renderer: Object.keys(dependencies || {}).filter((it) => it !== 'langchain').filter((it) => it !== '@langchain/community'),
35 | },
36 |
37 | output: {
38 | path: dist,
39 | filename: '[name].dev.dll.js',
40 | library: {
41 | name: 'renderer',
42 | type: 'var',
43 | },
44 | },
45 |
46 | plugins: [
47 | new webpack.DllPlugin({
48 | path: path.join(dist, '[name].json'),
49 | name: '[name]',
50 | }),
51 |
52 | /**
53 | * Create global constants which can be configured at compile time.
54 | *
55 | * Useful for allowing different behaviour between development builds and
56 | * release builds
57 | *
58 | * NODE_ENV should be production so that modules do not perform certain
59 | * development checks
60 | */
61 | new webpack.EnvironmentPlugin({
62 | NODE_ENV: 'development',
63 | }),
64 |
65 | new webpack.LoaderOptionsPlugin({
66 | debug: true,
67 | options: {
68 | context: webpackPaths.srcPath,
69 | output: {
70 | path: webpackPaths.dllPath,
71 | },
72 | },
73 | }),
74 | ],
75 | };
76 |
77 | export default merge(baseConfig, configuration);
78 |
--------------------------------------------------------------------------------
/desktop/.erb/configs/webpack.paths.ts:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const rootPath = path.join(__dirname, '../..');
4 |
5 | const dllPath = path.join(__dirname, '../dll');
6 |
7 | const srcPath = path.join(rootPath, 'src');
8 | const srcMainPath = path.join(srcPath, 'main');
9 | const srcRendererPath = path.join(srcPath, 'renderer');
10 |
11 | const releasePath = path.join(rootPath, 'release');
12 | const appPath = path.join(releasePath, 'app');
13 | const appPackagePath = path.join(appPath, 'package.json');
14 | const appNodeModulesPath = path.join(appPath, 'node_modules');
15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules');
16 |
17 | const distPath = path.join(appPath, 'dist');
18 | const distMainPath = path.join(distPath, 'main');
19 | const distRendererPath = path.join(distPath, 'renderer');
20 |
21 | const buildPath = path.join(releasePath, 'build');
22 |
23 | export default {
24 | rootPath,
25 | dllPath,
26 | srcPath,
27 | srcMainPath,
28 | srcRendererPath,
29 | releasePath,
30 | appPath,
31 | appPackagePath,
32 | appNodeModulesPath,
33 | srcNodeModulesPath,
34 | distPath,
35 | distMainPath,
36 | distRendererPath,
37 | buildPath,
38 | };
39 |
--------------------------------------------------------------------------------
/desktop/.erb/img/erb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/.erb/img/erb-logo.png
--------------------------------------------------------------------------------
/desktop/.erb/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/desktop/.erb/release/app/dist/renderer/index.html:
--------------------------------------------------------------------------------
1 |
Surfer
2 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off",
6 | "import/no-extraneous-dependencies": "off"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/check-build-exists.ts:
--------------------------------------------------------------------------------
1 | // Check if the renderer and main bundles are built
2 | import path from 'path';
3 | import chalk from 'chalk';
4 | import fs from 'fs';
5 | import webpackPaths from '../configs/webpack.paths';
6 |
7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.ts');
8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js');
9 |
10 | if (!fs.existsSync(mainPath)) {
11 | throw new Error(
12 | chalk.whiteBright.bgRed.bold(
13 | 'The main process is not built yet. Build it by running "npm run build:main"',
14 | ),
15 | );
16 | }
17 |
18 | if (!fs.existsSync(rendererPath)) {
19 | throw new Error(
20 | chalk.whiteBright.bgRed.bold(
21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"',
22 | ),
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/check-native-dep.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import chalk from 'chalk';
3 | import { execSync } from 'child_process';
4 | import { dependencies } from '../../package.json';
5 |
6 | if (dependencies) {
7 | const dependenciesKeys = Object.keys(dependencies);
8 | const nativeDeps = fs
9 | .readdirSync('node_modules')
10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
11 | if (nativeDeps.length === 0) {
12 | process.exit(0);
13 | }
14 | try {
15 | // Find the reason for why the dependency is installed. If it is installed
16 | // because of a devDependency then that is okay. Warn when it is installed
17 | // because of a dependency
18 | const { dependencies: dependenciesObject } = JSON.parse(
19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString(),
20 | );
21 | const rootDependencies = Object.keys(dependenciesObject);
22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
23 | dependenciesKeys.includes(rootDependency),
24 | );
25 | if (filteredRootDependencies.length > 0) {
26 | const plural = filteredRootDependencies.length > 1;
27 | console.log(`
28 | ${chalk.whiteBright.bgYellow.bold(
29 | 'Webpack does not work with native dependencies.',
30 | )}
31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${
32 | plural ? 'are native dependencies' : 'is a native dependency'
33 | } and should be installed inside of the "./release/app" folder.
34 | First, uninstall the packages from "./package.json":
35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}
36 | ${chalk.bold(
37 | 'Then, instead of installing the package to the root "./package.json":',
38 | )}
39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')}
40 | ${chalk.bold('Install the package to "./release/app/package.json"')}
41 | ${chalk.whiteBright.bgGreen.bold(
42 | 'cd ./release/app && npm install your-package',
43 | )}
44 | Read more about native dependencies at:
45 | ${chalk.bold(
46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure',
47 | )}
48 | `);
49 | process.exit(1);
50 | }
51 | } catch (e) {
52 | console.log('Native dependencies could not be checked');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/check-node-env.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 |
3 | export default function checkNodeEnv(expectedEnv) {
4 | if (!expectedEnv) {
5 | throw new Error('"expectedEnv" not set');
6 | }
7 |
8 | if (process.env.NODE_ENV !== expectedEnv) {
9 | console.log(
10 | chalk.whiteBright.bgRed.bold(
11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`,
12 | ),
13 | );
14 | process.exit(2);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/check-port-in-use.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import detectPort from 'detect-port';
3 |
4 | const port = process.env.PORT || '1212';
5 |
6 | detectPort(port, (_err, availablePort) => {
7 | if (port !== String(availablePort)) {
8 | throw new Error(
9 | chalk.whiteBright.bgRed.bold(
10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`,
11 | ),
12 | );
13 | } else {
14 | process.exit(0);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/clean.js:
--------------------------------------------------------------------------------
1 | import { rimrafSync } from 'rimraf';
2 | import fs from 'fs';
3 | import webpackPaths from '../configs/webpack.paths';
4 |
5 | const foldersToRemove = [
6 | webpackPaths.distPath,
7 | webpackPaths.buildPath,
8 | webpackPaths.dllPath,
9 | ];
10 |
11 | foldersToRemove.forEach((folder) => {
12 | if (fs.existsSync(folder)) rimrafSync(folder);
13 | });
14 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/delete-source-maps.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import { rimrafSync } from 'rimraf';
4 | import webpackPaths from '../configs/webpack.paths';
5 |
6 | export default function deleteSourceMaps() {
7 | if (fs.existsSync(webpackPaths.distMainPath))
8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), {
9 | glob: true,
10 | });
11 | if (fs.existsSync(webpackPaths.distRendererPath))
12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), {
13 | glob: true,
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/electron-rebuild.js:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process';
2 | import fs from 'fs';
3 | import { dependencies } from '../../release/app/package.json';
4 | import webpackPaths from '../configs/webpack.paths';
5 |
6 | if (
7 | Object.keys(dependencies || {}).length > 0 &&
8 | fs.existsSync(webpackPaths.appNodeModulesPath)
9 | ) {
10 | const electronRebuildCmd =
11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .';
12 | const cmd =
13 | process.platform === 'win32'
14 | ? electronRebuildCmd.replace(/\//g, '\\')
15 | : electronRebuildCmd;
16 | execSync(cmd, {
17 | cwd: webpackPaths.appPath,
18 | stdio: 'inherit',
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/link-modules.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import webpackPaths from '../configs/webpack.paths';
3 |
4 | const { srcNodeModulesPath } = webpackPaths;
5 | const { appNodeModulesPath } = webpackPaths;
6 |
7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');
9 | }
10 |
--------------------------------------------------------------------------------
/desktop/.erb/scripts/notarize.js:
--------------------------------------------------------------------------------
1 | const { notarize } = require('@electron/notarize');
2 | require('dotenv').config();
3 | const { build } = require('../../package.json');
4 |
5 | exports.default = async function notarizeMacos(context) {
6 | const { electronPlatformName, appOutDir } = context;
7 | if (electronPlatformName !== 'darwin') {
8 | return;
9 | }
10 |
11 | if (process.env.CI !== 'true') {
12 | console.warn('Skipping notarizing step. Packaging is not running in CI');
13 | return;
14 | }
15 |
16 | if (
17 | !('APPLE_ID' in process.env && 'APPLE_APP_SPECIFIC_PASSWORD' in process.env)
18 | ) {
19 | console.warn(
20 | 'Skipping notarizing step. APPLE_ID and APPLE_APP_SPECIFIC_PASSWORD env variables must be set',
21 | );
22 | return;
23 | }
24 |
25 | const appName = context.packager.appInfo.productFilename;
26 |
27 | console.log(`Notarizing ${appName}`);
28 | console.log(` appBundleId: ${build.appId}`);
29 | console.log(` appPath: ${appOutDir}/${appName}.app`);
30 | console.log(` appleId: ${process.env.APPLE_ID}`);
31 | console.log(` teamId: ${process.env.APPLE_TEAM_ID}`);
32 | console.log(` appleIdPassword: ${process.env.APPLE_APP_SPECIFIC_PASSWORD}`);
33 | console.log();
34 |
35 | try {
36 | await notarize({
37 | appBundleId: build.appId,
38 | appPath: `${appOutDir}/${appName}.app`,
39 | appleId: process.env.APPLE_ID,
40 | appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
41 | teamId: process.env.APPLE_TEAM_ID,
42 | });
43 | } catch (error) {
44 | console.log(`Failed to notarize ${appName}`);
45 | console.log(` appBundleId: ${build.appId}`);
46 | console.error(`Failed to notarize ${appName}`, error);
47 | console.error(error)
48 | }
49 |
50 | console.log('Notarization complete');
51 | };
52 |
--------------------------------------------------------------------------------
/desktop/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage directory used by tools like istanbul
11 | coverage
12 | .eslintcache
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # OSX
19 | .DS_Store
20 |
21 | release/app/dist
22 | release/build
23 | .erb/dll
24 |
25 | .idea
26 | npm-debug.log.*
27 | *.css.d.ts
28 | *.sass.d.ts
29 | *.scss.d.ts
30 |
31 | # eslint ignores hidden directories by default:
32 | # https://github.com/eslint/eslint/issues/8429
33 | !.erb
34 |
--------------------------------------------------------------------------------
/desktop/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'erb',
3 | plugins: ['@typescript-eslint'],
4 | rules: {
5 | // A temporary hack related to IDE not resolving correct package.json
6 | 'import/no-extraneous-dependencies': 'off',
7 | 'react/react-in-jsx-scope': 'off',
8 | 'react/jsx-filename-extension': 'off',
9 | 'import/extensions': 'off',
10 | 'import/no-unresolved': 'off',
11 | 'import/no-import-module-exports': 'off',
12 | 'no-shadow': 'off',
13 | '@typescript-eslint/no-shadow': 'error',
14 | 'no-unused-vars': 'off',
15 | '@typescript-eslint/no-unused-vars': 'error',
16 | {
17 | test: /\.(sass|less|css)$/,
18 | use: ['style-loader', 'css-loader', 'less-loader']
19 | }
20 | },
21 | parserOptions: {
22 | ecmaVersion: 2022,
23 | sourceType: 'module',
24 | },
25 | settings: {
26 | 'import/resolver': {
27 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
28 | node: {},
29 | webpack: {
30 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'),
31 | },
32 | typescript: {},
33 | },
34 | 'import/parsers': {
35 | '@typescript-eslint/parser': ['.ts', '.tsx'],
36 | },
37 | },
38 | };
39 |
--------------------------------------------------------------------------------
/desktop/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.exe binary
3 | *.png binary
4 | *.jpg binary
5 | *.jpeg binary
6 | *.ico binary
7 | *.icns binary
8 | *.eot binary
9 | *.otf binary
10 | *.ttf binary
11 | *.woff binary
12 | *.woff2 binary
13 |
--------------------------------------------------------------------------------
/desktop/.github/ISSUE_TEMPLATE/1-Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: You're having technical issues. 🐞
4 | labels: 'bug'
5 | ---
6 |
7 |
8 |
9 | ## Prerequisites
10 |
11 |
12 |
13 | - [ ] Using npm
14 | - [ ] Using an up-to-date [`main` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/main)
15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/)
16 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400)
17 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true npm run build && npm start`
18 |
19 | ## Expected Behavior
20 |
21 |
22 |
23 | ## Current Behavior
24 |
25 |
26 |
27 | ## Steps to Reproduce
28 |
29 |
30 |
31 |
32 | 1.
33 |
34 | 2.
35 |
36 | 3.
37 |
38 | 4.
39 |
40 | ## Possible Solution (Not obligatory)
41 |
42 |
43 |
44 | ## Context
45 |
46 |
47 |
48 |
49 |
50 | ## Your Environment
51 |
52 |
53 |
54 | - Node version :
55 | - electron-react-boilerplate version or branch :
56 | - Operating System and version :
57 | - Link to your project :
58 |
59 |
68 |
--------------------------------------------------------------------------------
/desktop/.github/ISSUE_TEMPLATE/2-Question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question.❓
4 | labels: 'question'
5 | ---
6 |
7 | ## Summary
8 |
9 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/desktop/.github/ISSUE_TEMPLATE/3-Feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: You want something added to the boilerplate. 🎉
4 | labels: 'enhancement'
5 | ---
6 |
7 |
16 |
--------------------------------------------------------------------------------
/desktop/.github/config.yml:
--------------------------------------------------------------------------------
1 | requiredHeaders:
2 | - Prerequisites
3 | - Expected Behavior
4 | - Current Behavior
5 | - Possible Solution
6 | - Your Environment
7 |
--------------------------------------------------------------------------------
/desktop/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - discussion
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/desktop/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | publish:
10 | # To enable auto publishing to github, update your electron publisher
11 | # config in package.json > "build" and remove the conditional below
12 | if: ${{ github.repository_owner == 'electron-react-boilerplate' }}
13 |
14 | runs-on: ${{ matrix.os }}
15 |
16 | strategy:
17 | matrix:
18 | os: [macos-latest]
19 |
20 | steps:
21 | - name: Checkout git repo
22 | uses: actions/checkout@v3
23 |
24 | - name: Install Node and NPM
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: 18
28 | cache: npm
29 |
30 | - name: Install and build
31 | run: |
32 | npm install
33 | npm run postinstall
34 | npm run build
35 |
36 | - name: Publish releases
37 | env:
38 | # These values are used for auto updates signing
39 | APPLE_ID: ${{ secrets.APPLE_ID }}
40 | APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASS }}
41 | CSC_LINK: ${{ secrets.CSC_LINK }}
42 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
43 | # This is used for uploading release assets to github
44 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 | run: |
46 | npm exec electron-builder -- --publish always --win --mac --linux
47 |
--------------------------------------------------------------------------------
/desktop/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | matrix:
11 | os: [macos-latest, windows-latest, ubuntu-latest]
12 |
13 | steps:
14 | - name: Check out Git repository
15 | uses: actions/checkout@v3
16 |
17 | - name: Install Node.js and NPM
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: 18
21 | cache: npm
22 |
23 | - name: npm install
24 | run: |
25 | npm install
26 |
27 | - name: npm test
28 | env:
29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | run: |
31 | npm run package
32 | npm run lint
33 | npm exec tsc
34 | npm test
35 |
--------------------------------------------------------------------------------
/desktop/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # chrome data
6 | src/firebase.js
7 | config.json
8 | edge_data.json
9 | chrome_data.json
10 | inputNodes.json
11 | axTree.json
12 | flattenedDOM.json
13 | ChromeWebData.db
14 | screenshot.pdf
15 | screenshot.png
16 | before_interaction.png
17 | after_interaction.png
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 | .eslintcache
27 |
28 | # Dependency directory
29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
30 | node_modules
31 | languages
32 |
33 | # OSX
34 | .DS_Store
35 |
36 | release/app/dist
37 | release/build
38 | .erb/dll
39 |
40 | .idea
41 | npm-debug.log.*
42 | *.css.d.ts
43 | *.sass.d.ts
44 | *.scss.d.ts
45 | *.llamafile
46 |
47 | # Added src/models directory
48 | src/models
49 |
50 | # Added .env file
51 | .env
52 | *.p12
53 |
54 |
55 | languages/Mac/python
56 | languages/Windows/python
57 | [0-9a-f]{10}*.json
58 |
59 | unpacked-app
60 | unpacked_asar
61 | #Any .pyc files
62 | *.pyc
63 | *.cpython-311.pyc
64 | /lancedb
65 | assets/chrome_windows.py
66 | ignoredAXTree.json
67 |
68 |
--------------------------------------------------------------------------------
/desktop/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"]
3 | }
4 |
--------------------------------------------------------------------------------
/desktop/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Electron: Main",
6 | "type": "node",
7 | "request": "launch",
8 | "protocol": "inspector",
9 | "runtimeExecutable": "npm",
10 | "runtimeArgs": ["run", "start"],
11 | "env": {
12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223"
13 | }
14 | },
15 | {
16 | "name": "Electron: Renderer",
17 | "type": "chrome",
18 | "request": "attach",
19 | "port": 9223,
20 | "webRoot": "${workspaceFolder}",
21 | "timeout": 15000
22 | }
23 | ],
24 | "compounds": [
25 | {
26 | "name": "Electron: All",
27 | "configurations": ["Electron: Main", "Electron: Renderer"]
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/desktop/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | ".eslintrc": "jsonc",
4 | ".prettierrc": "jsonc",
5 | ".eslintignore": "ignore"
6 | },
7 |
8 | "eslint.validate": [
9 | "javascript",
10 | "javascriptreact",
11 | "html",
12 | "typescriptreact"
13 | ],
14 |
15 | "javascript.validate.enable": false,
16 | "javascript.format.enable": false,
17 | "typescript.format.enable": false,
18 |
19 | "search.exclude": {
20 | ".git": true,
21 | ".eslintcache": true,
22 | ".erb/dll": true,
23 | "release/{build,app/dist}": true,
24 | "node_modules": true,
25 | "npm-debug.log.*": true,
26 | "test/**/__snapshots__": true,
27 | "package-lock.json": true,
28 | "*.{css,sass,scss}.d.ts": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/desktop/HELPER_FUNCTIONS.md:
--------------------------------------------------------------------------------
1 | # Helper Functions Documentation
2 |
3 | Surfer uses various helper functions to streamline the scraping process. Here's an overview of key helpers defined in `preloadFunctions.js`:
4 |
5 | 1. `customConsoleLog(...args)`:
6 | - Sends console log messages to the renderer process via IPC.
7 | - Converts objects to strings to avoid cloning issues.
8 |
9 | 2. `waitForElement(id, selector, elementName, multipleElements = false, timeout = 10000)`:
10 | - Waits for an element to appear in the DOM.
11 | - Parameters:
12 | - `id`: ID of the run.
13 | - `selector`: CSS selector for the element.
14 | - `elementName`: Name of the element for logging.
15 | - `multipleElements`: Set to true if waiting for multiple elements.
16 | - `timeout`: Maximum wait time in milliseconds.
17 | - Returns a Promise that resolves with the element(s) or null if timed out.
18 |
19 | 3. `wait(seconds)`:
20 | - Creates a delay for the specified number of seconds.
21 | - Returns a Promise that resolves after the delay.
22 |
23 | These helper functions are designed to assist with common tasks in the scraping process, such as waiting for elements to load, cleaning HTML, and managing asynchronous operations. Use them in your platform-specific modules to maintain consistency and improve code readability.
24 |
--------------------------------------------------------------------------------
/desktop/assets/Surfer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/Surfer.png
--------------------------------------------------------------------------------
/desktop/assets/SurferApp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/SurferApp.png
--------------------------------------------------------------------------------
/desktop/assets/SurferDemo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/SurferDemo.mp4
--------------------------------------------------------------------------------
/desktop/assets/SurferDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/SurferDiagram.png
--------------------------------------------------------------------------------
/desktop/assets/assets.d.ts:
--------------------------------------------------------------------------------
1 | type Styles = Record;
2 |
3 | declare module '*.svg' {
4 | import React = require('react');
5 |
6 | export const ReactComponent: React.FC>;
7 |
8 | const content: string;
9 | export default content;
10 | }
11 |
12 | declare module '*.png' {
13 | const content: string;
14 | export default content;
15 | }
16 |
17 | declare module '*.jpg' {
18 | const content: string;
19 | export default content;
20 | }
21 |
22 | declare module '*.scss' {
23 | const content: Styles;
24 | export default content;
25 | }
26 |
27 | declare module '*.sass' {
28 | const content: Styles;
29 | export default content;
30 | }
31 |
32 | declare module '*.css' {
33 | const content: Styles;
34 | export default content;
35 | }
36 |
--------------------------------------------------------------------------------
/desktop/assets/current_db.py:
--------------------------------------------------------------------------------
1 | import chromadb
2 | import os
3 | import sys
4 | import json
5 |
6 | user_data_path = sys.argv[1]
7 |
8 |
9 | # Initialize ChromaDB client with persistent storage
10 | persistent_dir = os.path.join(user_data_path, "vector_db")
11 | client = chromadb.PersistentClient(path=persistent_dir)
12 |
13 | # Get collection
14 | collection = client.get_collection("surfer_collection")
15 |
16 | existing_chunks = collection.get()
17 | names_array = list(set(chunk['name'] for chunk in existing_chunks['metadatas']))
18 | print(names_array)
--------------------------------------------------------------------------------
/desktop/assets/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-unsigned-executable-memory
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.cs.disable-library-validation
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/desktop/assets/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/error.png
--------------------------------------------------------------------------------
/desktop/assets/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/icon.icns
--------------------------------------------------------------------------------
/desktop/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/icon.png
--------------------------------------------------------------------------------
/desktop/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/desktop/assets/password-prompt.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Enter password
5 |
53 |
54 |
55 |
56 |
Enter the password for your iPhone backup:
57 |
58 |
59 |
60 |
68 |
69 |
--------------------------------------------------------------------------------
/desktop/assets/requirements.txt:
--------------------------------------------------------------------------------
1 | chromadb
2 |
--------------------------------------------------------------------------------
/desktop/assets/search_vector_db.py:
--------------------------------------------------------------------------------
1 | import chromadb
2 | import os
3 | import sys
4 | import json
5 |
6 | user_data_path = sys.argv[1]
7 | query = sys.argv[2]
8 | platform = sys.argv[3]
9 |
10 | try:
11 | # Initialize ChromaDB client with persistent storage
12 | persistent_dir = os.path.join(user_data_path, "vector_db")
13 | client = chromadb.PersistentClient(path=persistent_dir)
14 |
15 | # Get collection
16 | collection = client.get_collection("surfer_collection")
17 |
18 | # Perform search
19 | regular_results = collection.query(
20 | query_texts=[query],
21 | n_results=5,
22 | where={"name": platform}
23 | )
24 |
25 | # full_text_results = collection.query(
26 | # query_texts=[query],
27 | # n_results=5,
28 | # where={"name": platform},
29 | # where_document={"$contains":query}
30 | # )
31 |
32 | # full_results = {
33 | # "regular_results": regular_results,
34 | # "full_text_results": full_text_results
35 | # }
36 |
37 | # Format results for output
38 | formatted_results = {
39 | "documents": regular_results["documents"][0],
40 | "distances": regular_results["distances"][0],
41 | "ids": regular_results["ids"][0],
42 | "metadata": regular_results["metadatas"][0]
43 | }
44 |
45 | # Print as JSON for easy parsing
46 | print(json.dumps(formatted_results))
47 |
48 | except Exception as e:
49 | print(json.dumps({"error": str(e)}), file=sys.stderr)
50 | sys.exit(1)
51 |
52 |
--------------------------------------------------------------------------------
/desktop/assets/windows_requirements.txt:
--------------------------------------------------------------------------------
1 | iphone_backup_decrypt
2 | fastpbkdf2
3 | chromadb
--------------------------------------------------------------------------------
/desktop/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [require('tailwindcss'), require('autoprefixer')],
3 | };
4 |
--------------------------------------------------------------------------------
/desktop/release/app/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "surfer",
3 | "version": "1.0.4",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "surfer",
9 | "version": "1.0.4",
10 | "hasInstallScript": true
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/desktop/release/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Surfer is a desktop app that allows you to export your personal data.",
3 | "productName": "Surfer",
4 | "name": "surfer",
5 | "version": "1.0.4",
6 | "keywords": [
7 | "electron",
8 | "ai",
9 | "chat",
10 | "interface",
11 | "local",
12 | "personalized",
13 | "llms",
14 | "chatGPT",
15 | "gpt3",
16 | "gpt4",
17 | "chatbot"
18 | ],
19 | "repository": "https://github.com/Surfer-Org/Protocol/tree/main/desktop",
20 | "publish": {
21 | "provider": "github",
22 | "releaseType": "release"
23 | },
24 | "author": {
25 | "name": "Sahil Lalani, Jack Blair, Thomas Stahura",
26 | "email": "lihas1002@gmail.com",
27 | "url": "https://surferprotocol.org"
28 | },
29 | "main": "./dist/main/main.js",
30 | "scripts": {
31 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js",
32 | "postinstall": "npm run rebuild && npm run link-modules",
33 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/desktop/release/app/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .: {}
10 |
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Apple/imessage.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer } = require('electron');
2 | const { customConsoleLog } = require('../../preloadFunctions');
3 |
4 | async function exportiMessage(id, platformId, filename, company, name) {
5 | customConsoleLog(id, 'Exporting iMessages');
6 | const messageData = await ipcRenderer.invoke(
7 | 'get-imessage-data',
8 | company,
9 | name,
10 | id,
11 | );
12 | return 'NOTHING';
13 | }
14 |
15 | module.exports = exportiMessage;
16 |
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Apple/imessage.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iMessage",
3 | "description": "Exports all your iMessages.",
4 | "needsConnection": false,
5 | "vectorize_config": {
6 | "documents": "text"
7 | }
8 | }
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Apple/imessage.md:
--------------------------------------------------------------------------------
1 | https://github.com/saubury/paranoid_text_LLM/
2 |
3 | https://simon-aubury.medium.com/my-data-your-llm-paranoid-analysis-of-imessage-chats-with-openai-llamaindex-duckdb-60e5eb9e23e3#:~:text=INSTALL%20sqlite_scanner;,Load%20messages%20into%20table
4 |
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Google/gmail.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Gmail",
3 | "description": "Exports all emails.",
4 | "isUpdated": true,
5 | "connectURL": "https://takeout.google.com/u/0/settings/takeout/custom/gmail",
6 | "connectSelector": "button[aria-label='Next step']",
7 | "exportFrequency": "hourly",
8 | "vectorize_config": {
9 | "documents": "body"
10 | }
11 | }
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Google/gmail.md:
--------------------------------------------------------------------------------
1 | # Gmail
2 |
3 | This module is responsible for scraping email data from a user's Gmail account.
4 |
5 | ## Functionality
6 |
7 | The `exportGmail()` function in the `gmail.js` file performs the following tasks:
8 |
9 | 1. Checks if this is the first export or a subsequent one.
10 | 2. For the first export:
11 | - Initiates a Google Takeout process to download all emails.
12 | - Navigates to Gmail to wait for the Takeout email.
13 | - Downloads the Takeout data.
14 | 3. For subsequent exports:
15 | - Checks if the user is connected to their Gmail account.
16 | - Collects new email content and stores it in an array.
17 | - Navigates through emails and checks for duplicates.
18 | - Updates the local storage with new emails.
19 |
20 | ## Implementation
21 |
22 | The Gmail export functionality is implemented in the `gmail.js` file. Here's an overview of the implementation:
23 |
24 | 1. The `exportGmail()` function is the main entry point.
25 | 2. It uses helper functions like `checkIfEmailExists()` to avoid duplicates.
26 | 3. For first-time exports, it uses Google Takeout.
27 | 4. For subsequent exports, it scrapes emails directly from the Gmail interface.
28 | 5. The process uses IPC messages to communicate with the main process for various operations.
29 |
30 | ## Platform-specific Considerations
31 |
32 | 1. Navigation: The module uses specific selectors to navigate the Gmail interface, which may need updating if the UI changes.
33 | 2. Google Takeout: The first export uses Google Takeout, which may have rate limits or change its interface.
34 | 3. Email Content Extraction: The module extracts email content by targeting specific HTML elements, which may require updates if Gmail's structure changes.
35 |
36 | ## Future Improvements
37 |
38 | 1. **Handling Multiple Email Threads**: Incorporate thread-level data for a more comprehensive view of the user's email history.
39 | 2. **Optimizing Incremental Data Collection**: Further refine the process of collecting only new emails to improve efficiency.
40 | 3. **Enhanced Error Handling**: Add more robust error handling and recovery mechanisms.
41 | 4. **User Settings**: Implement options for users to customize the export process (e.g., date ranges, specific folders).
42 | 5. **Performance Optimization**: Improve the speed of data collection, especially for accounts with large numbers of emails.
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Google/youtube.js:
--------------------------------------------------------------------------------
1 | const {
2 | customConsoleLog ,
3 | wait,
4 | waitForElement,
5 | } = require('../../preloadFunctions');
6 | const { ipcRenderer } = require('electron');
7 |
8 | async function exportYoutube(id, platformId, filename, company, name) {
9 | if (!window.location.href.includes('youtube.com')) {
10 | customConsoleLog(id, 'Navigating to YouTube');
11 | window.location.assign('https://www.youtube.com/');
12 | }
13 |
14 | await wait(10);
15 |
16 | if (document.querySelector('a[aria-label="Sign in"]')) {
17 | customConsoleLog(id, 'YOU NEED TO SIGN IN (click the eye in the top right)!');
18 | ipcRenderer.send('connect-website', id);
19 | return 'CONNECT_WEBSITE';
20 | }
21 | const videoData = [];
22 |
23 | // Extract video information
24 | customConsoleLog(id, 'Waiting for Video elements');
25 | const videoElements = await waitForElement(
26 | id,
27 | 'ytd-rich-grid-media',
28 | 'Video elements',
29 | true,
30 | );
31 |
32 | if (videoElements && videoElements.length > 0) {
33 | customConsoleLog(id, 'Got Video elements');
34 | for (const videoElement of videoElements) {
35 | const titleElement = videoElement.querySelector(
36 | 'yt-formatted-string#video-title',
37 | );
38 |
39 | const linkElement = videoElement.querySelector(
40 | '#video-title-link'
41 | )
42 | const channelElement = videoElement.querySelector(
43 | 'a.yt-simple-endpoint.style-scope.yt-formatted-string[href^="/@"]',
44 | );
45 | const viewCountElement = videoElement.querySelector(
46 | 'span.inline-metadata-item',
47 | );
48 |
49 | if (titleElement && channelElement && viewCountElement) {
50 | videoData.push({
51 | title: titleElement.textContent.trim(),
52 | url: linkElement.href,
53 | channel: channelElement.textContent.trim(),
54 | viewCount: viewCountElement.textContent.trim(),
55 | });
56 | }
57 | }
58 | } else {
59 | customConsoleLog(id, 'No video elements found');
60 | }
61 |
62 | customConsoleLog(id, 'Video data collected:', videoData.length);
63 |
64 | return videoData;
65 | }
66 |
67 | module.exports = exportYoutube;
68 |
69 |
70 |
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Google/youtube.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "YouTube",
3 | "description": "Exports the videos in your recommended feed.",
4 | "connectURL": "https://www.youtube.com",
5 | "connectSelector": "img[alt='Avatar image']"
6 | }
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Google/youtube.md:
--------------------------------------------------------------------------------
1 | # YouTube
2 |
3 | This module is responsible for scraping video data from the YouTube home page.
4 |
5 | ## Functionality
6 |
7 | The `exportYoutube()` function in the `youtube.js` file performs the following tasks:
8 |
9 | 1. Navigates to the YouTube home page if not already there.
10 | 2. Checks if the user is signed in and handles the sign-in process if needed.
11 | 3. Waits for video elements to load on the page.
12 | 4. Collects video information (title, URL, channel name, and view count) from the home page.
13 | 5. Stores the collected data in an array.
14 | 6. Returns the collected video data.
15 |
16 | ## Implementation
17 |
18 | The YouTube export functionality is implemented in the `youtube.js` file. Here's an overview of the implementation:
19 |
20 | 1. The `exportYoutube()` function is defined and exported as a module.
21 | 2. It uses helper functions like `customConsoleLog`, `wait`, `waitForElement` or other helper functions for various tasks.
22 | 3. The function handles navigation, sign-in checks, and data extraction.
23 | 4. It uses Electron's `ipcRenderer` to communicate with the main process.
24 |
25 | ## Platform-specific Considerations
26 |
27 | 1. Element Selection: The module uses specific CSS selectors to target video information elements, which may need updating if YouTube's HTML structure changes.
28 | 2. Home Page Focus: The current implementation only extracts data from the home page.
29 | 3. Sign-in Handling: The module checks for a sign-in button and handles the sign-in process if needed.
30 |
31 | ## Future Improvements
32 |
33 | 1. **Expand Data Collection**: Include additional metadata such as video duration and upload date.
34 | 2. **User-specific Data**: Implement functionality to extract data from user's playlists, subscriptions, or watch history.
35 | 3. **Pagination Handling**: Add the ability to navigate through multiple pages of video results.
36 | 4. **Enhanced Error Handling**: Implement more robust error handling and recovery mechanisms.
37 | 5. **Rate Limiting**: Implement rate limiting to avoid potential issues with YouTube's anti-scraping measures.
38 | 6. **Configurable Export**: Allow users to specify the number of videos to export or set other export parameters.
39 |
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Microsoft/connections.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LinkedIn Connections",
3 | "id": "connections-001",
4 | "description": "Exports your connections.",
5 | "logoURL": "https://logo.clearbit.com/linkedin.com",
6 | "connectURL": "https://linkedin.com/login",
7 | "connectSelector": "img[alt*='Photo of']",
8 | "exportFrequency": "daily",
9 | "vectorize_config": {
10 | "documents": "headline"
11 | }
12 | }
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Microsoft/connections.md:
--------------------------------------------------------------------------------
1 | # LinkedIn
2 |
3 | This module provides functionality to export a user's LinkedIn profile data.
4 |
5 | ## Functionality
6 |
7 | The module consists of one main function:
8 |
9 | 1. `exportLinkedin()`: Navigates to the user's profile page and exports specific profile data.
10 |
11 | ## Implementation
12 |
13 | The export process follows these steps:
14 |
15 | 1. Navigate to LinkedIn if not already there.
16 | 2. Check for user authentication and prompt for login if necessary.
17 | 3. Click on the profile button and then the contact info button.
18 | 4. Extract specific profile data including:
19 | - Name
20 | - Subheading
21 | - About section
22 | - Profile URL
23 | - Experience
24 | - Email address
25 | 5. Structure the extracted data as an object.
26 | 6. Return the structured data for further processing.
27 |
28 | ## Platform-specific Considerations
29 |
30 | 1. DOM Manipulation: The module relies on specific selectors to find and interact with page elements. These may need updates if LinkedIn's HTML structure changes.
31 | 2. Timing: The module uses wait functions to account for page load times, which may need adjustment based on network conditions.
32 | 3. Authentication: The script checks for authentication status and can prompt for login if needed.
33 |
34 | ## Future Improvements
35 |
36 | 1. Data Enrichment: Expand data extraction to include additional profile sections like education, skills, and recommendations.
37 | 2. Error Handling: Implement more robust error handling and recovery mechanisms.
38 | 3. Progressive Loading: Handle LinkedIn's progressive loading behavior to ensure all profile data is captured, especially for profiles with extensive content.
39 | 4. Rate Limiting: Implement respect for LinkedIn's rate limits to prevent potential blocking.
40 | 5. API Integration: Consider using LinkedIn's API for data retrieval instead of web scraping, which could be faster and more reliable.
41 |
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Notion/notion.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Notion",
3 | "description": "Exports your entire workspace.",
4 | "connectURL": "https://www.notion.so/login",
5 | "connectSelector": ".notion-sidebar-switcher",
6 | "exportFrequency": "daily",
7 | "vectorize_config": {
8 | "documents": "text"
9 | }
10 | }
--------------------------------------------------------------------------------
/desktop/src/main/platforms/Notion/notion.md:
--------------------------------------------------------------------------------
1 | # Notion
2 |
3 | This module exports all workspace content from a user's Notion account.
4 |
5 | ## Functionality
6 |
7 | The `exportNotion()` function performs these tasks:
8 | 1. Checks if the user is connected to Notion.
9 | 2. Navigates through the Notion interface to reach the export dialog.
10 | 3. Initiates the export of all workspace content.
11 |
12 | ## Implementation
13 |
14 | The Notion export process is implemented as follows:
15 | 1. The function checks if the current page is Notion, navigating to it if not.
16 | 2. It verifies user authentication, prompting for sign-in if needed.
17 | 3. The function navigates through the Notion interface by:
18 | - Clicking the workspace dropdown
19 | - Accessing Settings through two separate button clicks
20 | - Selecting "Export all workspace content"
21 | - Confirming the export
22 | 4. The process uses `waitForElement()` to ensure UI elements are present before interacting.
23 | 5. Error handling is implemented throughout the process.
24 |
25 | ## Platform-specific Considerations
26 |
27 | 1. DOM Manipulation: The module relies on specific class names, roles, and text content to navigate the Notion interface.
28 | 2. Timing: The `wait()` function is used to account for page load and animation times.
29 | 3. Scrolling: Elements are scrolled into view before clicking to ensure visibility.
30 | 4. IPC Communication: The module uses Electron's IPC to communicate with the main process for actions like connecting to the website.
31 |
32 | ## Future Improvements
33 |
34 | 1. Error Handling: While error handling is implemented, it could be further enhanced for each step of the process.
35 | 2. Dynamic Waiting: Replace fixed waits with more dynamic waiting for elements to appear or change.
36 | 3. Selective Export: Add options to export specific pages or sections instead of the entire workspace.
37 | 4. Progress Tracking: Implement a way to track and report the export progress beyond the current step logging.
38 |
39 |
--------------------------------------------------------------------------------
/desktop/src/main/platforms/OpenAI/chatgpt.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ChatGPT",
3 | "description": "Exports your entire ChatGPT history.",
4 | "connectURL": "https://chat.openai.com",
5 | "connectSelector": "img[alt='User']",
6 | "exportFrequency": "daily",
7 | "vectorize_config": {
8 | "documents": "title"
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/desktop/src/main/platforms/X Corp/bookmarks.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Twitter Bookmarks",
3 | "description": "Exports your Twitter bookmarks",
4 | "logoURL": "https://logo.clearbit.com/twitter.com",
5 | "isUpdated": true,
6 | "connectURL": "https://twitter.com",
7 | "connectSelector": "img.css-9pa8cd",
8 | "exportFrequency": "daily",
9 | "vectorize_config": {
10 | "documents": "text"
11 | }
12 | }
--------------------------------------------------------------------------------
/desktop/src/main/platforms/X Corp/twitter.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Twitter Posts",
3 | "description": "Exports all your tweets.",
4 | "isUpdated": true,
5 | "logoURL": "https://logo.clearbit.com/twitter.com",
6 | "connectURL": "https://twitter.com",
7 | "connectSelector": "img.css-9pa8cd"
8 | }
--------------------------------------------------------------------------------
/desktop/src/main/preload.ts:
--------------------------------------------------------------------------------
1 | const {
2 | contextBridge,
3 | ipcRenderer,
4 | IpcRendererEvent,
5 | app,
6 | } = require('electron');
7 |
8 | const state = {
9 | workspaceID: '',
10 | };
11 |
12 | const electronHandler = {
13 | ipcRenderer: {
14 | send(channel, ...args) {
15 | ipcRenderer.send(channel, ...args);
16 | },
17 | on(channel, func) {
18 | const subscription = (_event, ...args) => func(...args);
19 | ipcRenderer.on(channel, subscription);
20 |
21 | return () => {
22 | ipcRenderer.removeListener(channel, subscription);
23 | };
24 | },
25 | once(channel, func) {
26 | ipcRenderer.once(channel, (_event, ...args) => func(...args));
27 | },
28 | removeAllListeners(channel) {
29 | ipcRenderer.removeAllListeners(channel);
30 | },
31 | removeListener(channel, listener) {
32 | ipcRenderer.removeListener(channel, listener);
33 | },
34 | off(channel, func) {
35 | const subscription = (_event, ...args) => func(...args);
36 | ipcRenderer.removeListener(channel, subscription);
37 | },
38 | invoke(channel, ...args) {
39 | return ipcRenderer.invoke(channel, ...args);
40 | },
41 | },
42 | };
43 |
44 | contextBridge.exposeInMainWorld('electron', electronHandler);
45 |
--------------------------------------------------------------------------------
/desktop/src/main/preloadElectron.js:
--------------------------------------------------------------------------------
1 | const electronHandler = {
2 | ipcRenderer: {
3 | send(channel, ...args) {
4 | ipcRenderer.send(channel, ...args);
5 | },
6 | on(channel, func) {
7 | const subscription = (_event, ...args) => func(...args);
8 | ipcRenderer.on(channel, subscription);
9 |
10 | return () => {
11 | ipcRenderer.removeListener(channel, subscription);
12 | };
13 | },
14 | once(channel, func) {
15 | ipcRenderer.once(channel, (_event, ...args) => func(...args));
16 | },
17 | removeAllListeners(channel) {
18 | ipcRenderer.removeAllListeners(channel);
19 | },
20 | off(channel, func) {
21 | const subscription = (_event, ...args) => func(...args);
22 | ipcRenderer.removeListener(channel, subscription);
23 | },
24 | invoke(channel, ...args) {
25 | return ipcRenderer.invoke(channel, ...args);
26 | },
27 | },
28 | };
29 |
30 | module.exports = electronHandler
--------------------------------------------------------------------------------
/desktop/src/main/preloadFunctions.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer } = require('electron')
2 |
3 | function customConsoleLog(id, ...args) {
4 | // Convert arguments to strings to avoid cloning issues
5 | const stringArgs = args.map((arg) =>
6 | typeof arg === 'object' ? JSON.stringify(arg) : arg,
7 | );
8 | ipcRenderer.sendToHost('console-log', id, ...stringArgs);
9 | };
10 |
11 | function waitForElement(
12 | id,
13 | selector,
14 | elementName,
15 | multipleElements = false,
16 | timeout = 10000,
17 | ) {
18 |
19 | if (!multipleElements){
20 | customConsoleLog(id, `Waiting for ${elementName}`);
21 | }
22 |
23 | return new Promise((resolve) => {
24 | const startTime = Date.now();
25 |
26 | const checkElement = () => {
27 | const element = multipleElements
28 | ? document.querySelectorAll(selector)
29 | : document.querySelector(selector);
30 | if (element) {
31 | if (!multipleElements){
32 | customConsoleLog(id, `Found ${elementName}`);
33 | }
34 | resolve(element);
35 | } else if (Date.now() - startTime >= timeout) {
36 | customConsoleLog(id, `Timeout waiting for ${elementName}`);
37 | resolve(null);
38 | } else {
39 | setTimeout(checkElement, 100);
40 | }
41 | };
42 |
43 | checkElement();
44 | });
45 | }
46 |
47 | async function wait(seconds) {
48 | return new Promise((resolve) => {
49 | setTimeout(resolve, seconds * 1000);
50 | });
51 | }
52 |
53 | async function waitForContentToStabilize() {
54 | return new Promise((resolve) => {
55 | let timeout;
56 | const observer = new MutationObserver(() => {
57 | clearTimeout(timeout);
58 | timeout = setTimeout(() => {
59 | observer.disconnect();
60 | console.log('Content has stabilized');
61 | resolve();
62 | }, 100); // Adjust this delay as needed
63 | });
64 |
65 | observer.observe(document.body, {
66 | childList: true,
67 | subtree: true,
68 | attributes: true,
69 | });
70 |
71 | // Fallback in case the page never stabilizes
72 | setTimeout(() => {
73 | observer.disconnect();
74 | console.log('Timed out waiting for content to stabilize');
75 | resolve();
76 | }, 5000); // Adjust this timeout as needed
77 | });
78 | }
79 |
80 |
81 |
82 | module.exports = { customConsoleLog,waitForElement, wait, waitForContentToStabilize }
--------------------------------------------------------------------------------
/desktop/src/main/preloadWebview.js:
--------------------------------------------------------------------------------
1 | if (window.trustedTypes && window.trustedTypes.createPolicy) {
2 | window.trustedTypes.createPolicy('default', {
3 | createHTML: (string, sink) => string,
4 | });
5 | }
6 |
7 | const { ipcRenderer } = require('electron');
8 | const { customConsoleLog } = require('./preloadFunctions');
9 |
10 |
11 | ipcRenderer.on('export-platform', async (event, runID, platformId, filename, company, name, isUpdated) => {
12 | const platform = require(`./platforms/${company}/${filename}.js`);
13 |
14 | const data = await platform(runID, platformId, filename, company, name);
15 | console.log('data', data);
16 |
17 | if (data === 'CONNECT_WEBSITE') {
18 | console.log('CONNECT_WEBSITE');
19 | }
20 |
21 | else if (data === 'NOTHING') {
22 | console.log('NOTHING')
23 | }
24 |
25 | else if (data === 'DOWNLOADING') {
26 | customConsoleLog(runID, 'Downloading export (will take some time!');
27 | }
28 |
29 | else if (data === 'HANDLE_UPDATE_COMPLETE') {
30 | customConsoleLog(runID, 'Finishing updating data!');
31 | }
32 |
33 | else if (data) {
34 | ipcRenderer.send(
35 | 'handle-export',
36 | runID,
37 | platformId,
38 | filename,
39 | company,
40 | name,
41 | data,
42 | isUpdated,
43 | );
44 | customConsoleLog(runID, 'Got data, need to export now');
45 | }
46 |
47 | else {
48 | console.log(
49 | runID,
50 | "Something might've gone wrong (click the Eye icon to view the run)",
51 | );
52 | }
53 | });
--------------------------------------------------------------------------------
/desktop/src/renderer/App.tsx:
--------------------------------------------------------------------------------
1 | import { Provider } from 'react-redux';
2 | import store from './state/store';
3 | import { ThemeProvider } from './components/ui/theme-provider';
4 | import './styles/globals.css';
5 | import Surfer from './Surfer';
6 |
7 | export default function App() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/CodeBlock.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Button } from "./ui/button";
3 | import { Copy, Check } from 'lucide-react';
4 | import MonacoEditor from '@monaco-editor/react';
5 | import { getLanguageFromFilename } from '../helpers';
6 |
7 | const CodeBlock = ({ run, code, filename }) => {
8 | const [showCheck, setShowCheck] = useState(false);
9 |
10 | const detectedLanguage = filename
11 | ? getLanguageFromFilename(filename)
12 | : 'plaintext';
13 |
14 | const handleCopyCode = () => {
15 | navigator.clipboard.writeText(code);
16 | setShowCheck(true);
17 | setTimeout(() => setShowCheck(false), 500);
18 | };
19 |
20 | let formattedCode;
21 |
22 | if (detectedLanguage === 'python') {
23 | formattedCode = code.replace('platform-001', run.platformId);
24 | }
25 | else if (detectedLanguage === 'markdown') {
26 | formattedCode = code.replace('[insert-folder-path-of-your-choice-here]', run.exportPath);
27 | }
28 | else {
29 | formattedCode = code;
30 | }
31 |
32 | return (
33 |
34 |
35 |
36 |
48 |
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | export default CodeBlock;
67 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Outlet } from 'react-router-dom';
3 | import { Header } from './Header';
4 | import Webview from './Webview';
5 |
6 | interface LayoutProps {
7 | webviewRefs: { [key: string]: React.RefObject };
8 | getWebviewRef: (runId: string) => React.RefObject;
9 | contentScale: number;
10 | onHomeClick: () => void;
11 | children: React.ReactNode;
12 | }
13 |
14 | const Layout: React.FC = ({
15 | webviewRefs,
16 | getWebviewRef,
17 | contentScale,
18 | onHomeClick,
19 | children
20 | }) => {
21 | return (
22 |
23 |
24 |
25 |
34 |
35 | {children}
36 |
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default Layout;
48 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
5 | import { ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "./cn"
8 |
9 | const Accordion = AccordionPrimitive.Root
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | AccordionItem.displayName = "AccordionItem"
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 | svg]:rotate-180",
32 | className
33 | )}
34 | {...props}
35 | >
36 | {children}
37 |
38 |
39 |
40 | ))
41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42 |
43 | const AccordionContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, children, ...props }, ref) => (
47 |
52 | {children}
53 |
54 | ))
55 |
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
59 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "./cn"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | moreDestructive:
15 | "bg-red-700 text-white border-red-500 dark:border-red-500 [&>svg]:text-white",
16 | },
17 | },
18 | defaultVariants: {
19 | variant: "default",
20 | },
21 | }
22 | )
23 |
24 | const Alert = React.forwardRef<
25 | HTMLDivElement,
26 | React.HTMLAttributes & VariantProps
27 | >(({ className, variant, ...props }, ref) => (
28 |
34 | ))
35 | Alert.displayName = "Alert"
36 |
37 | const AlertTitle = React.forwardRef<
38 | HTMLParagraphElement,
39 | React.HTMLAttributes
40 | >(({ className, ...props }, ref) => (
41 |
46 | ))
47 | AlertTitle.displayName = "AlertTitle"
48 |
49 | const AlertDescription = React.forwardRef<
50 | HTMLParagraphElement,
51 | React.HTMLAttributes
52 | >(({ className, ...props }, ref) => (
53 |
58 | ))
59 | AlertDescription.displayName = "AlertDescription"
60 |
61 | export { Alert, AlertTitle, AlertDescription }
62 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "./cn"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "./cn"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "./cn"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "./cn"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "./cn"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/cn.js:
--------------------------------------------------------------------------------
1 | import { clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5 |
6 | import { cn } from "./cn"
7 |
8 | const HoverCard = HoverCardPrimitive.Root
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
26 | ))
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28 |
29 | export { HoverCard, HoverCardTrigger, HoverCardContent }
30 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "./cn"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, onChange, ...props }, ref) => {
10 | const handleKeyDown = (event: React.KeyboardEvent) => {
11 | if (event.key === 'Escape') {
12 | const target = event.currentTarget;
13 | target.value = '';
14 |
15 | // Create a custom event
16 | const customEvent = new CustomEvent('input', {
17 | bubbles: true,
18 | cancelable: true,
19 | detail: { value: '' }
20 | });
21 |
22 | // Dispatch the custom event
23 | target.dispatchEvent(customEvent);
24 |
25 | // Call the onChange handler with a synthesized React event
26 | onChange && onChange({
27 | target,
28 | currentTarget: target,
29 | type: 'input',
30 | } as React.ChangeEvent);
31 | }
32 | // Call the original onKeyDown if it exists
33 | props.onKeyDown?.(event);
34 | };
35 |
36 | return (
37 |
48 | )
49 | }
50 | )
51 | Input.displayName = "Input"
52 |
53 | export { Input }
54 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "./cn"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "./cn"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "./cn"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5 | import { Circle } from "lucide-react"
6 |
7 | import { cn } from "./cn"
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | )
20 | })
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43 |
44 | export { RadioGroup, RadioGroupItem }
45 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/resizable.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { GripVertical } from "lucide-react"
4 | import * as ResizablePrimitive from "react-resizable-panels"
5 |
6 | import { cn } from "./cn"
7 |
8 | const ResizablePanelGroup = ({
9 | className,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
19 | )
20 |
21 | const ResizablePanel = ResizablePrimitive.Panel
22 |
23 | const ResizableHandle = ({
24 | withHandle,
25 | className,
26 | ...props
27 | }: React.ComponentProps & {
28 | withHandle?: boolean
29 | }) => (
30 | div]:rotate-90",
33 | className
34 | )}
35 | {...props}
36 | >
37 | {withHandle && (
38 |
39 |
40 |
41 | )}
42 |
43 | )
44 |
45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
46 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "./cn"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "./cn"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "./cn"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "./cn"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useTheme } from "next-themes"
4 | import { Toaster as Sonner } from "sonner"
5 |
6 | type ToasterProps = React.ComponentProps
7 |
8 | const Toaster = ({ ...props }: ToasterProps) => {
9 | const { theme = "system" } = useTheme()
10 |
11 | return (
12 |
28 | )
29 | }
30 |
31 | export { Toaster }
32 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "./cn"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "./cn"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from "react"
2 |
3 | type Theme = "dark" | "light" | "system"
4 |
5 | type ThemeProviderProps = {
6 | children: React.ReactNode
7 | defaultTheme?: Theme
8 | storageKey?: string
9 | }
10 |
11 | type ThemeProviderState = {
12 | theme: Theme
13 | setTheme: (theme: Theme) => void
14 | }
15 |
16 | const initialState: ThemeProviderState = {
17 | theme: "dark",
18 | setTheme: () => null,
19 | }
20 |
21 | const ThemeProviderContext = createContext(initialState)
22 |
23 | export function ThemeProvider({
24 | children,
25 | defaultTheme = "dark",
26 | storageKey = "vite-ui-theme",
27 | ...props
28 | }: ThemeProviderProps) {
29 | const [theme, setTheme] = useState(
30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
31 | )
32 |
33 | useEffect(() => {
34 | const root = window.document.documentElement
35 |
36 | root.classList.remove("light", "dark")
37 |
38 | if (theme === "system") {
39 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
40 | .matches
41 | ? "dark"
42 | : "light"
43 |
44 | root.classList.add(systemTheme)
45 | return
46 | }
47 |
48 | root.classList.add(theme)
49 | }, [theme])
50 |
51 | const value = {
52 | theme,
53 | setTheme: (theme: Theme) => {
54 | localStorage.setItem(storageKey, theme)
55 | setTheme(theme)
56 | },
57 | }
58 |
59 | return (
60 |
61 | {children}
62 |
63 | )
64 | }
65 |
66 | export const useTheme = () => {
67 | const context = useContext(ThemeProviderContext)
68 |
69 | if (context === undefined)
70 | throw new Error("useTheme must be used within a ThemeProvider")
71 |
72 | return context
73 | }
74 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "./toast"
11 | import { useToast } from "./use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
5 | import { VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "./cn"
8 | import { toggleVariants } from "@/registry/default/ui/toggle"
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default",
15 | })
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ))
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext)
41 |
42 | return (
43 |
54 | {children}
55 |
56 | )
57 | })
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
60 |
61 | export { ToggleGroup, ToggleGroupItem }
62 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TogglePrimitive from "@radix-ui/react-toggle"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "./cn"
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
17 | },
18 | size: {
19 | default: "h-10 px-3",
20 | sm: "h-9 px-2.5",
21 | lg: "h-11 px-5",
22 | },
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default",
27 | },
28 | }
29 | )
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ))
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName
44 |
45 | export { Toggle, toggleVariants }
46 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/toggleColor.tsx:
--------------------------------------------------------------------------------
1 | import { Moon, Sun } from "lucide-react"
2 |
3 | import { Button } from "./button"
4 | import { useTheme } from "./theme-provider"
5 |
6 | import { DropdownMenu,
7 | DropdownMenuContent,
8 | DropdownMenuItem,
9 | DropdownMenuTrigger } from "./dropdown-menu"
10 |
11 | export function ModeToggle() {
12 | const { setTheme } = useTheme()
13 |
14 | return (
15 |
16 |
17 |
22 |
23 |
24 | setTheme("light")}>
25 | Light
26 |
27 | setTheme("dark")}>
28 | Dark
29 |
30 | setTheme("system")}>
31 | System
32 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/desktop/src/renderer/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "./cn"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/desktop/src/renderer/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 | Surfer
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/desktop/src/renderer/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 | import App from './App';
3 |
4 | const container = document.getElementById('root') as HTMLElement;
5 | const root = createRoot(container);
6 | root.render();
7 |
--------------------------------------------------------------------------------
/desktop/src/renderer/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { setCurrentRoute, updateBreadcrumb } from '../state/actions';
4 | import DataExtractionTable from '../components/PlatformDashboard';
5 |
6 | const Home = () => {
7 | const dispatch = useDispatch();
8 | const handlePlatformClick = (platform) => {
9 | dispatch(setCurrentRoute(`/platform/${platform.id}`, { platform }));
10 | dispatch(updateBreadcrumb([
11 | { text: 'Home', link: '/home' },
12 | { text: platform.name, link: `/platform/${platform.id}` },
13 | ]));
14 | };
15 |
16 | return (
17 |
18 |
21 |
22 | );
23 | };
24 |
25 | export default Home;
26 |
--------------------------------------------------------------------------------
/desktop/src/renderer/preload.d.ts:
--------------------------------------------------------------------------------
1 | import { ElectronHandler } from '../main/preload';
2 |
3 | declare global {
4 | // eslint-disable-next-line no-unused-vars
5 | interface Window {
6 | electron: ElectronHandler;
7 | }
8 | }
9 |
10 | export {};
11 |
--------------------------------------------------------------------------------
/desktop/src/renderer/state/initialStates.ts:
--------------------------------------------------------------------------------
1 | import { IAppState, IPreferences } from '../types/interfaces';
2 |
3 | export const initialPreferencesState: IPreferences = {
4 | contentScale: 1,
5 | };
6 |
7 | export const initialAppState: IAppState = {
8 | preferences: initialPreferencesState,
9 | app: {
10 | route: '/',
11 | activeRunIndex: 0,
12 | isFullScreen: false,
13 | isMac: false,
14 | isRunLayerVisible: false,
15 | breadcrumb: [{ text: 'Home', link: '/' }],
16 | runs: [],
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/desktop/src/renderer/state/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import rootReducer from './reducers';
3 | import { initialAppState } from './initialStates';
4 |
5 | // Load saved state from localStorage
6 | let savedState = localStorage.getItem('userState');
7 |
8 | const preloadedState = savedState ? JSON.parse(savedState) : initialAppState;
9 |
10 | // Ensure isRunLayerVisible is false on application start
11 | if (preloadedState) {
12 | preloadedState.isRunLayerVisible = false;
13 | }
14 |
15 | // Create a custom middleware to log actions
16 | const loggerMiddleware = (store) => (next) => (action) => {
17 | return next(action);
18 | };
19 |
20 | const store = configureStore({
21 | reducer: rootReducer,
22 | preloadedState: preloadedState,
23 | middleware: (getDefaultMiddleware) =>
24 | getDefaultMiddleware().concat(loggerMiddleware),
25 | });
26 |
27 | // Subscribe to store changes to save the state in localStorage
28 | store.subscribe(() => {
29 | const currentState = store.getState();
30 | localStorage.setItem('userState', JSON.stringify(currentState));
31 | });
32 |
33 | export default store;
34 |
--------------------------------------------------------------------------------
/desktop/src/renderer/types/interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface IPreferences {
2 | contentScale: number;
3 | }
4 |
5 | export interface IAppState {
6 | preferences: IPreferences;
7 | app: {
8 | route: string;
9 | activeRunIndex: number;
10 | isFullScreen: boolean;
11 | isMac: boolean;
12 | isRunLayerVisible: boolean;
13 | breadcrumb: { text: string; link: string }[];
14 | runs: IRun[];
15 | };
16 | }
17 |
18 | export const initialState: IAppState = {
19 | preferences: {
20 | contentScale: 1,
21 | },
22 | app: {
23 | route: '/',
24 | activeRunIndex: 0,
25 | isFullScreen: false,
26 | isMac: false,
27 | isRunLayerVisible: false,
28 | breadcrumb: [{ text: 'Home', link: '/' }],
29 | runs: [],
30 | },
31 | };
32 |
33 |
34 | export interface IRun {
35 | id: string;
36 | platformId: string;
37 | filename: string;
38 | isConnected: boolean;
39 | startDate: string;
40 | endDate?: string;
41 | status: 'pending' | 'running' | 'success' | 'error' | 'stopped' | 'vectorizing';
42 | url: string;
43 | exportSize?: number;
44 | exportPath?: string;
45 | company: string;
46 | name: string;
47 | currentStep?: string;
48 | isUpdated?: boolean;
49 | vectorize_config?: any;
50 | vectorization_progress?: any;
51 | logs?: string;
52 | vectorizationProgress?: {
53 | current: number;
54 | total: number;
55 | percentage: number;
56 | };
57 | }
--------------------------------------------------------------------------------
/desktop/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "incremental": true,
4 | "target": "es2022",
5 | "module": "commonjs",
6 | "lib": ["dom", "es2022"],
7 | "jsx": "react-jsx",
8 | "strict": true,
9 | "sourceMap": true,
10 | "moduleResolution": "node",
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "resolveJsonModule": true,
14 | "allowJs": true,
15 | "outDir": ".erb/dll",
16 | "baseUrl": ".",
17 | "paths": {
18 | "@/*": ["./*"]
19 | }
20 | },
21 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"]
22 | }
23 |
--------------------------------------------------------------------------------
/docs/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
37 | .vercel
38 |
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 2024-11-14
4 |
5 | Docs site started.
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Protocol
2 |
3 | Protocol is a [Tailwind UI](https://tailwindui.com) site template built using [Tailwind CSS](https://tailwindcss.com) and [Next.js](https://nextjs.org).
4 |
5 | ## Getting started
6 |
7 | To get started with this template, first install the npm dependencies:
8 |
9 | ```bash
10 | npm install
11 | ```
12 |
13 | Next, run the development server:
14 |
15 | ```bash
16 | npm run dev
17 | ```
18 |
19 | Finally, open [http://localhost:3000](http://localhost:3000) in your browser to view the website.
20 |
21 | ## Customizing
22 |
23 | You can start editing this template by modifying the files in the `/src` folder. The site will auto-update as you edit these files.
24 |
25 | ## Global search
26 |
27 | This template includes a global search that's powered by the [FlexSearch](https://github.com/nextapps-de/flexsearch) library. It's available by clicking the search input or by using the `⌘K` shortcut.
28 |
29 | This feature requires no configuration, and works out of the box by automatically scanning your documentation pages to build its index. You can adjust the search parameters by editing the `/src/mdx/search.mjs` file.
30 |
31 | ## License
32 |
33 | This site template is a commercial product and is licensed under the [Tailwind UI license](https://tailwindui.com/license).
34 |
35 | ## Learn more
36 |
37 | To learn more about the technologies used in this site template, see the following resources:
38 |
39 | - [Tailwind CSS](https://tailwindcss.com/docs) - the official Tailwind CSS documentation
40 | - [Next.js](https://nextjs.org/docs) - the official Next.js documentation
41 | - [Headless UI](https://headlessui.dev) - the official Headless UI documentation
42 | - [Framer Motion](https://www.framer.com/docs/) - the official Framer Motion documentation
43 | - [MDX](https://mdxjs.com/) - the official MDX documentation
44 | - [Algolia Autocomplete](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/) - the official Algolia Autocomplete documentation
45 | - [FlexSearch](https://github.com/nextapps-de/flexsearch) - the official FlexSearch documentation
46 | - [Zustand](https://docs.pmnd.rs/zustand/getting-started/introduction) - the official Zustand documentation
47 |
--------------------------------------------------------------------------------
/docs/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/docs/mdx-components.jsx:
--------------------------------------------------------------------------------
1 | import * as mdxComponents from '@/components/mdx'
2 |
3 | export function useMDXComponents(components) {
4 | return {
5 | ...components,
6 | ...mdxComponents,
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/docs/next.config.mjs:
--------------------------------------------------------------------------------
1 | import nextMDX from '@next/mdx'
2 |
3 | import { recmaPlugins } from './src/mdx/recma.mjs'
4 | import { rehypePlugins } from './src/mdx/rehype.mjs'
5 | import { remarkPlugins } from './src/mdx/remark.mjs'
6 | import withSearch from './src/mdx/search.mjs'
7 |
8 | const withMDX = nextMDX({
9 | options: {
10 | remarkPlugins,
11 | rehypePlugins,
12 | recmaPlugins,
13 | },
14 | })
15 |
16 | /** @type {import('next').NextConfig} */
17 | const nextConfig = {
18 | pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'],
19 | experimental: {
20 | outputFileTracingIncludes: {
21 | '/**/*': ['./src/app/**/*.mdx'],
22 | },
23 | },
24 | }
25 |
26 | export default withSearch(withMDX(nextConfig))
27 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "browserslist": "defaults, not ie <= 11",
12 | "dependencies": {
13 | "@algolia/autocomplete-core": "^1.7.3",
14 | "@headlessui/react": "^2.1.0",
15 | "@headlessui/tailwindcss": "^0.2.0",
16 | "@mdx-js/loader": "^3.0.0",
17 | "@mdx-js/react": "^3.0.0",
18 | "@next/mdx": "^14.0.4",
19 | "@sindresorhus/slugify": "^2.1.1",
20 | "@tailwindcss/typography": "^0.5.10",
21 | "acorn": "^8.8.1",
22 | "autoprefixer": "^10.4.7",
23 | "axios": "^1.7.7",
24 | "clsx": "^2.1.0",
25 | "fast-glob": "^3.3.0",
26 | "flexsearch": "^0.7.31",
27 | "framer-motion": "^10.18.0",
28 | "lucide-react": "^0.460.0",
29 | "mdast-util-to-string": "^4.0.0",
30 | "mdx-annotations": "^0.1.1",
31 | "next": "^14.0.4",
32 | "next-themes": "^0.2.1",
33 | "raw-loader": "^4.0.2",
34 | "react": "^18.2.0",
35 | "react-dom": "^18.2.0",
36 | "react-highlight-words": "^0.20.0",
37 | "remark": "^15.0.1",
38 | "remark-gfm": "^4.0.0",
39 | "remark-mdx": "^3.0.0",
40 | "shiki": "^0.14.7",
41 | "simple-functional-loader": "^1.2.1",
42 | "tailwindcss": "^3.4.1",
43 | "unist-util-filter": "^5.0.1",
44 | "unist-util-visit": "^5.0.0",
45 | "zustand": "^4.3.2"
46 | },
47 | "devDependencies": {
48 | "eslint": "^8.56.0",
49 | "eslint-config-next": "^14.0.4",
50 | "file-loader": "^6.2.0",
51 | "prettier": "^3.3.2",
52 | "prettier-plugin-tailwindcss": "^0.6.5",
53 | "sharp": "0.33.1"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/docs/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/docs/prettier.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('prettier').Options} */
2 | module.exports = {
3 | singleQuote: true,
4 | semi: false,
5 | plugins: ['prettier-plugin-tailwindcss'],
6 | }
7 |
--------------------------------------------------------------------------------
/docs/src/app/about/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'About',
3 | description:
4 | 'Learn more about Surfer Protocol.',
5 | }
6 |
7 | import Image from '@/components/Image'
8 | import cover_image from '@/images/cover_image.png'
9 |
10 | # About Surfer Protocol
11 |
12 | ## Our Mission
13 |
14 | Surfer Protocol is an open-source framework dedicated to solving the challenge of personal data portability. We believe that your personal data shouldn't be trapped in silos across hundreds of platforms. Our mission is to make it easy to export, understand, and build applications with your personal data.
15 |
16 | ## Our Story
17 |
18 |
19 |
20 | Founded by Sahil Lalani and Jack Blair, Surfer Protocol emerged from years of experience building AI agents, autonomous web browsers, and digital clones. We recognized that while AI technologies have become increasingly accessible, accessing and utilizing personal data remains a significant challenge.
21 |
22 | ## Technology
23 |
24 | Surfer Protocol consists of three main components:
25 | - A Desktop Application for data export
26 | - Comprehensive Documentation
27 | - Python SDK for developers
28 |
29 | ## Get Involved
30 |
31 | We welcome contributions from the community! You can:
32 | - Join our Discord
33 | - Contribute to our GitHub repositories
34 | - Help expand platform support
35 | - Build applications using our SDK
36 |
37 | Together, we're building the future of personal data portability.
--------------------------------------------------------------------------------
/docs/src/app/claude/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Claude Model Context Protocol',
3 | description:
4 | 'Connect your Surfer data to Claude using the Model Context Protocol.',
5 | }
6 |
7 | import Image from '@/components/Image'
8 | import claude_mcp from '@/images/claude-mcp.png'
9 | import finished_mcp from '@/images/claude_finished.png'
10 |
11 | ### Claude Model Context Protocol (only works on macOS for now, check out this issue for more details)
12 |
13 | Source code
14 |
15 | More details from Anthropic
16 |
17 | Connect your Surfer data to Claude using the Model Context Protocol.
18 |
19 | Step 1: Install or run the Surfer Desktop App locally, connect your accounts, and export your data.
20 |
21 | Step 2: Install the Claude Desktop App
22 |
23 | Step 3: Install uv. This is recommended for managing model context protocol servers. More details can be found here.
24 |
25 | Step 4: If not already there, create a `claude_desktop_config.json` file in the app data folder of the Claude desktop app.
26 |
27 | Mac Path: `~/Library/Application Support/Claude/claude_desktop_config.json`
28 |
29 | Windows Path: `C:/Users/[your-username]/AppData/Roaming/Claude/claude_desktop_config.json`
30 |
31 | Step 5: Add the following content to the file and save it:
32 |
33 |
34 | ```json
35 | {
36 | "mcpServers": {
37 | "surfer-mcp": {
38 | "command": "uvx",
39 | "args": [
40 | "surfer-mcp"
41 | ]
42 | }
43 | }
44 | }
45 | ```
46 |
47 | Step 6: Close the Claude Desktop app and run it again. If you see the following, you should be good to go!
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/src/app/community-projects/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Community Projects',
3 | description:
4 | 'Overview of community projects.',
5 | }
6 |
7 | # Community Projects
8 |
9 | Coming soon!
--------------------------------------------------------------------------------
/docs/src/app/contributing/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Contributing',
3 | description:
4 | 'Learn how to contribute to Surfer Protocol.',
5 | }
6 |
7 | # Contributing to Surfer Protocol
8 |
9 | We welcome contributions from the community! Here's how you can help:
10 |
11 | ## Getting Started
12 |
13 | 1. Fork the repository
14 | 2. Clone your fork
15 | 3. Create a new branch
16 | 4. Make your changes
17 | 5. Submit a pull request
18 |
19 | ## Where to Contribute
20 |
21 | - Adding platforms for export
22 | - Documentation improvements
23 | - Building applications with Surfer Protocol
24 | - Bug fixes
25 |
--------------------------------------------------------------------------------
/docs/src/app/cookbook/python/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Python Cookbook',
3 | description:
4 | 'Example applications built with the Surfer Protocol Python SDK.',
5 | }
6 |
7 | import Image from '@/components/Image'
8 | import streamlit_chatbot from '@/images/streamlit-chatbot.png'
9 | import knowledge_graph from '@/images/knowledge-graph.png'
10 | import claude_mcp from '@/images/claude-mcp.png'
11 |
12 | ## Prerequisites
13 |
14 | 1. **Desktop Application**: The Surfer desktop application must be running in the background for the SDK to work.
15 | - Follow the installation instructions on the [Desktop Application Installation](/desktop/installation) page
16 | - Connect your platforms and export your data to use with the SDK.
17 |
18 | > Note: The desktop app runs a local server on port 2024 that the SDK communicates with. Make sure the server is running before using the SDK.
19 |
20 | ### Claude Model Context Protocol
21 |
22 | Check out the Claude Model Context Protocol page for more details.
23 |
24 | ### Streamlit Chatbot
25 |
26 | Full code: Streamlit Chatbot
27 |
28 | Create a Streamlit chatbot that uses Weaviate and OpenAI to analyze your Surfer data:
29 |
30 |
31 |
32 | ### Knowledge Graph
33 |
34 | Turn your Surfer data into a knowledge graph.
35 |
36 | Full code: Knowledge Graph
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/installation/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Desktop Application Installation',
3 | description:
4 | 'Overview of Surfer Protocol Desktop application.',
5 | }
6 |
7 | import DownloadButtons from '@/components/DownloadButtons'
8 |
9 | # Desktop Application Installation
10 |
11 |
12 |
13 | ## Download Surfer Protocol (Recommended)
14 |
15 | Get started quickly by downloading the latest version for your operating system:
16 |
17 |
18 |
19 | ---
20 |
21 | ## Run Locally (Optional)
22 |
23 | If you're a developer interested in running the application locally, follow these steps:
24 |
25 | Step 1: Clone the repository
26 |
27 | ```bash
28 | git clone https://github.com/Surfer-Org/Protocol.git
29 | ```
30 |
31 | Step 2: cd into the desktop directory
32 |
33 | ```bash
34 | cd Protocol/desktop
35 | ```
36 |
37 | Step 3: Install dependencies
38 |
39 | ```bash
40 | npm install
41 | ```
42 |
43 | Step 4: Run the application
44 |
45 | ```bash
46 | npm start
47 | ```
48 |
49 | Step 5 (optional): Build the application
50 |
51 | ```bash
52 | npm run package
53 | ```
54 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Desktop Application',
3 | description:
4 | 'Overview of Surfer Protocol Desktop application.',
5 | }
6 |
7 |
8 | # Desktop Application
9 |
10 | [Install Now →](/desktop/installation)
11 |
12 |
13 | The Surfer Protocol Desktop application is a cross-platform application that allows you to easily export your data.
14 |
15 | ## Supported Platforms
16 |
17 | The Desktop Application currently supports the following platforms:
18 |
19 | - [Twitter](/desktop/platforms/twitter)
20 | - [ChatGPT](/desktop/platforms/chatgpt)
21 | - [Notion](/desktop/platforms/notion)
22 | - [Gmail](/desktop/platforms/gmail)
23 | - [iMessage](/desktop/platforms/imessage)
24 | - [LinkedIn](/desktop/platforms/linkedin)
25 |
26 | ## Schema Format
27 |
28 | The exported data for the supported platforms is in JSON format like this:
29 |
30 | ```json
31 | {
32 | "company": "Company Name",
33 | "name": "Platform Name",
34 | "runID": "platform-run-id",
35 | "timestamp": 1234567890,
36 | "content": [
37 | {
38 | "key_1": "value_1",
39 | "key_2": "value_2",
40 | "key_3": "value_3"
41 | },
42 | ...
43 | ]
44 | }
45 | ```
--------------------------------------------------------------------------------
/docs/src/app/desktop/platforms/chatgpt/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'ChatGPT',
3 | description:
4 | 'Overview of Surfer Protocol ChatGPT platform.',
5 | }
6 |
7 | # ChatGPT
8 |
9 | ## Schema
10 |
11 | File: `1_parsed_conversations.json`
12 |
13 | ```json
14 | {
15 | "company": "OpenAI",
16 | "name": "ChatGPT",
17 | "runID": "chatgpt-001",
18 | "timestamp": 1731700918119,
19 | "content": [
20 | {
21 | "title": "Title of the page",
22 | "messages": [
23 | {
24 | "text": "Content of the message",
25 | "type": "human" | "ai",
26 | "timestamp": 1731599217.951566
27 | },
28 | ...
29 | ]
30 | },
31 | ...
32 | ]
33 | }
34 | ```
35 |
36 | ## Export Process
37 |
38 | Steps:
39 | - The app goes to https://chatgpt.com/#settings/DataControls.
40 | - The app then clicks the `Export` button.
41 | - The app then goes to the user's gmail inbox and waits for the email from ChatGPT.
42 | - Once the email is received, the app opens the email and downloads the zip file.
43 | - The app then parses the `conversations.json` file for better readability.
44 |
45 | Files for reference:
46 | - `chatgpt.js`
47 | - `platforms.ts`
48 | - `main.ts`
49 |
50 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/platforms/gmail/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Gmail',
3 | description:
4 | 'Overview of Surfer Protocol Gmail platform.',
5 | }
6 |
7 | # Gmail
8 |
9 | ## Schema
10 |
11 | File: `gmail-001.json`
12 |
13 | ```json
14 | {
15 | "company": "Google",
16 | "name": "Gmail",
17 | "runID": "gmail-001",
18 | "timestamp": 1731700918119,
19 | "content": [
20 | {
21 | "accountID": "0",
22 | "from": "From email address",
23 | "to": "To email address",
24 | "subject": "Subject of email",
25 | "timestamp": "Timestamp of email",
26 | "body": "Body of email",
27 | "added_to_db": "Timestamp of when email was added to database"
28 | },
29 | ...
30 | ]
31 | }
32 | ```
33 |
34 | ## Export Process
35 |
36 | Steps:
37 | - For the first export, the app goes to Google Takeout and exports the Gmail data.
38 | - The app then goes to the email and waits for the email from takeout.
39 | - Once the email is received, the app opens the email and downlaods the zip file.
40 | - The app then parses the zip file and converts the MBOX format to JSON using the `mbox-parser` library.
41 | - For future exports, the app goes to the inbox and clicks and scrapes each email that isn't already in the database.
42 |
43 | Files for reference:
44 | - `gmail.js`
45 | - `platforms.ts`
46 | - `main.ts`
47 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/platforms/imessage/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'iMessage',
3 | description:
4 | 'Overview of Surfer Protocol iMessage platform.',
5 | }
6 |
7 | # iMessage
8 |
9 | ## Schema
10 |
11 | File: `imessage-001.json`
12 |
13 | ```json
14 | {
15 | "company": "Apple",
16 | "name": "iMessage",
17 | "runID": "imessage-001-1731702239127",
18 | "timestamp": 1731702239127,
19 | "content": [
20 | {
21 | "id": 349530,
22 | "text": "Message text",
23 | "timestamp": "Timestamp of message",
24 | "contact": "Name of contact",
25 | "is_from_me": true
26 | },
27 | ...
28 | ]
29 | }
30 | ```
31 |
32 | ## Export Process
33 |
34 | #### Mac:
35 |
36 | Steps:
37 | - The app uses the 'chat.db' file on the Mac to extract the messages, as well as the 'AddressBook-v22.abcddb' file to extract the contacts.
38 | - The app runs SQL queries on these files to extract the messages and contacts and merge them into a single JSON file.
39 |
40 | Files for reference:
41 | - `imessage.ts`
42 | - `imessage_mac.py`
43 |
44 | #### Windows:
45 |
46 | Steps:
47 | - The app takes in the path of the iPhone backup file and requires a password to decrypt it.
48 | - The app uses the `iphone_backup_decrypt` library to decrypt the backup file.
49 | - The app runs SQL queries on the decrypted backup file to extract the messages and contacts and merge them into a single JSON file.
50 |
51 | Files for reference:
52 | - `imessage.ts`
53 | - `imessage_windows.py`
54 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/platforms/linkedin/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'LinkedIn',
3 | description:
4 | 'Overview of Surfer Protocol LinkedIn platform.',
5 | }
6 |
7 | # LinkedIn
8 |
9 | ## Schema
10 |
11 | File: `connections-001.json`
12 |
13 | ```json
14 | {
15 | "company": "Microsoft",
16 | "name": "LinkedIn Connections",
17 | "runID": "connections-001-1731552035370",
18 | "timestamp": 1731552041372,
19 | "content": [
20 | {
21 | "first_name": "First name of connection",
22 | "last_name": "Last name of connection",
23 | "headline": "Headline of connection",
24 | "created_at": "Timestamp of when connection was created",
25 | "added_to_db": "Timestamp of when connection was added to database"
26 | },
27 | ...
28 | ]
29 | }
30 | ```
31 |
32 | ## Export Process
33 |
34 | Steps:
35 | - The app goes to the LinkedIn homepage.
36 | - If the user is logged in, the app intercepts a LinkedIn API request and extracts the `csrf-token` and `cookie` tokens.
37 | - The app makes a request to the following endpoint: `https://www.linkedin.com/voyager/api/relationships/dash/connections?${params}` to start the export process.
38 | - The parameters can be found in the `connections.js` file in the Desktop repository.
39 |
40 | Files for reference:
41 | - `connections.js`
42 | - `network.ts`
43 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/platforms/notion/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Notion',
3 | description:
4 | 'Overview of Surfer Protocol Notion platform.',
5 | }
6 |
7 | # Notion
8 |
9 | ## Schema
10 |
11 | File: `notion-001-[timestamp].json`
12 |
13 | ```json
14 | {
15 | "company": "Notion",
16 | "name": "Notion",
17 | "runID": "notion-001",
18 | "timestamp": 1731700918119,
19 | "content": [
20 | {
21 | "title": "Title of the page",
22 | "text": "Content of the page"
23 | },
24 | ...
25 | ]
26 | }
27 | ```
28 |
29 | ## Export Process
30 |
31 | Steps:
32 | - The app goes to the Notion homepage.
33 | - If the user is logged in, the app intercepts a Notion API request and extracts the `x-notion-space-id` and `cookie` tokens.
34 | - The app makes a request to the following endpoint: `https://www.notion.so/api/v3/enqueueTask` to start the export process.
35 | - To get the status of the export and the final file, the app makes a request to the following endpoint: `https://www.notion.so/api/v3/getTasks`
36 |
37 | Files for reference:
38 | - `notion.js`
39 | - `network.ts`
40 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/platforms/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Desktop Application Platforms',
3 | description:
4 | 'Overview of Surfer Protocol Desktop application platforms.',
5 | }
6 |
7 | # Desktop Application Platforms
8 |
9 | - [Twitter](/desktop/platforms/twitter)
10 | - [ChatGPT](/desktop/platforms/chatgpt)
11 | - [Notion](/desktop/platforms/notion)
12 | - [Gmail](/desktop/platforms/gmail)
13 | - [iMessage](/desktop/platforms/imessage)
14 | - [LinkedIn](/desktop/platforms/linkedin)
15 |
--------------------------------------------------------------------------------
/docs/src/app/desktop/platforms/twitter/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Twitter',
3 | description:
4 | 'Overview of Surfer Protocol Twitter platform.',
5 | }
6 |
7 | # Twitter
8 |
9 | ## Schema
10 |
11 | File: `bookmarks-001.json`
12 |
13 | ```json
14 | {
15 | "company": "X Corp",
16 | "name": "Twitter Bookmarks",
17 | "runID": "bookmarks-001-1731366667383",
18 | "timestamp": 1731366675031,
19 | "content": [
20 | {
21 | "id": "tweet-1855399792379134176",
22 | "text": "there used to be 50,000 pay toilets in cities across America until a group of college students in 1970 decided it wasn't fair to have to pay for a natural bodily function and successfully lobbied to get them banned in 12 states and now you can't find public toilets fuckin https://t.co/YE0voWYl7d",
23 | "timestamp": "Sat Nov 09 23:59:16 +0000 2024",
24 | "media": {
25 | "type": "photo",
26 | "source": "https://pbs.twimg.com/media/Gb-0R29XsAA_OR6.jpg"
27 | },
28 | "username": "hyperdiscogirl",
29 | "added_to_db": "2024-11-11T23:11:15.032Z"
30 | },
31 | ...
32 | ]
33 | }
34 | ```
35 |
36 | ## Export Process
37 |
38 | Steps:
39 | - The app goes to the Twitter homepage.
40 | - If the user is logged in, the app intercepts the bookmarks API request and extracts the `bookmarksApiId`, `auth`, `cookie`, and `csrf` tokens.
41 | - The app makes a request to the following endpoint: `https://x.com/i/api/graphql/[bookmarksApiId]/Bookmarks?variables={endpoint_variables}`
42 | - These endpoint variables can be found in the `bookmarks.js` file in the Desktop repository.
43 | - The app then uses these to export the user's bookmarks.
44 |
45 | Files for reference:
46 | - `bookmarks.js`
47 | - `network.ts`
48 |
--------------------------------------------------------------------------------
/docs/src/app/faq/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'FAQ',
3 | description:
4 | 'Frequently Asked Questions about Surfer Protocol.',
5 | }
6 |
7 | # Frequently Asked Questions
8 |
9 | ## General Questions
10 |
11 | ### What is Surfer Protocol?
12 | Surfer Protocol is an open-source framework that allows you to export your personal data from various platforms.
13 |
14 | ### Is Surfer Protocol open source?
15 | Yes, our core protocol is open source and available on GitHub.
16 |
17 | ### How can I get started?
18 | There are several ways to get started:
19 | - Install our [Desktop application](/desktop/installation) to export your data
20 | - Explore our [Python SDK](/sdk/python) for building applications
21 | - Check out our [Cookbook](https://github.com/Surfer-Org/Protocol/tree/main/cookbook/python) for inspiration
22 | - Join our Discord community
23 |
24 | ## Technical Questions
25 |
26 | ### What are the core components of Surfer Protocol?
27 | Surfer Protocol consists of three main components:
28 | - Desktop Application for data export built with Electron JS
29 | - Python SDK for developers
30 | - Cookbook for example applications
31 |
32 | ### What platforms are supported?
33 | We currently support:
34 | - iMessage
35 | - Twitter
36 | - Notion
37 | - ChatGPT
38 | - Gmail
39 | - LinkedIn
40 |
41 |
42 | ### What data formats do you use?
43 | We provide standardized, well-documented JSON schemas for consistent data handling across all supported platforms.
44 |
45 | ## Community & Contribution
46 |
47 | ### How can I contribute to Surfer Protocol?
48 | There are several ways to contribute:
49 | - Join our [Discord](https://discord.gg/Tjg7pjcFNP) community
50 | - Contribute to our [GitHub repositories](https://github.com/Surfer-Org/Protocol)
51 | - Help expand platform support
52 | - Build applications using our SDK
53 |
54 | ### Where can I find community projects?
55 | You can explore community projects in our [Community Projects](/community-projects) section to see what others are building with Surfer Protocol.
56 |
57 | ### Where can I get help?
58 | - Join our [Discord community](https://discord.gg/Tjg7pjcFNP) for direct support
59 | - Explore our comprehensive documentation
60 | - Check out our [Python SDK](/sdk/python) documentation for development questions
--------------------------------------------------------------------------------
/docs/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/docs/src/app/favicon.ico
--------------------------------------------------------------------------------
/docs/src/app/layout.jsx:
--------------------------------------------------------------------------------
1 | import glob from 'fast-glob'
2 |
3 | import { Providers } from '@/app/providers'
4 | import { Layout } from '@/components/Layout'
5 |
6 | import '@/styles/tailwind.css'
7 |
8 | export const metadata = {
9 | title: {
10 | template: '%s - Surfer Protocol',
11 | default: 'Surfer Protocol',
12 | },
13 | }
14 |
15 | export default async function RootLayout({ children }) {
16 | let pages = await glob('**/*.mdx', { cwd: 'src/app' })
17 | let allSectionsEntries = await Promise.all(
18 | pages.map(async (filename) => [
19 | '/' + filename.replace(/(^|\/)page\.mdx$/, ''),
20 | (await import(`./${filename}`)).sections,
21 | ]),
22 | )
23 | let allSections = Object.fromEntries(allSectionsEntries)
24 |
25 | return (
26 |
27 |
28 |
29 |
30 | {children}
31 |
32 |
33 |
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/docs/src/app/not-found.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button'
2 | import { HeroPattern } from '@/components/HeroPattern'
3 |
4 | export default function NotFound() {
5 | return (
6 | <>
7 |
8 |
9 |
10 | 404
11 |
12 |
13 | Page not found
14 |
15 |
16 | Sorry, we couldn’t find the page you’re looking for.
17 |
18 |
21 |
22 | >
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/docs/src/app/page.mdx:
--------------------------------------------------------------------------------
1 | export const metadata = {
2 | title: 'Welcome to Surfer Protocol',
3 | description:
4 | 'Learn everything there is to know about Surfer Protocol and how to get started.',
5 | }
6 |
7 | # Welcome to Surfer Protocol
8 |
9 | Surfer Protocol is an open-source framework that allows you to export your personal data from various platforms.
10 |
11 | ## Why Surfer Protocol?
12 |
13 | - **Data Liberation**: Export your personal data from platforms like iMessage, Gmail, Notion, Twitter, and more.
14 | - **Standardized Formats**: Well-documented schemas for consistent data handling
15 | - **Developer Tools**: Comprehensive SDK and APIs for building applications
16 | - **Community Driven**: Open-source with active community contributions
17 |
18 | ## Get Started
19 |
20 | Choose your path to begin:
21 |
22 | - 🖥️ [Install Desktop App](/desktop/installation) - Export your data
23 | - 🐍 [Python SDK](/sdk/python) - Build applications with our SDK
24 | - 🧑🍳 [Cookbook](/cookbook/python) - Look at example projects
25 | - 🤝 [Join Community](https://discord.gg/Tjg7pjcFNP) - Connect with other builders
26 |
27 | [Get Started →](/desktop/installation)
28 |
29 | ## Open Source
30 |
31 | Surfer Protocol is proudly open source. Contribute on [GitHub](https://github.com/Surfer-Org/Protocol) or join our [Discord](https://discord.gg/Tjg7pjcFNP) to get involved.
--------------------------------------------------------------------------------
/docs/src/app/providers.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useEffect } from 'react'
4 | import { ThemeProvider, useTheme } from 'next-themes'
5 |
6 | function ThemeWatcher() {
7 | let { resolvedTheme, setTheme } = useTheme()
8 |
9 | useEffect(() => {
10 | let media = window.matchMedia('(prefers-color-scheme: dark)')
11 |
12 | function onMediaChange() {
13 | let systemTheme = media.matches ? 'dark' : 'light'
14 | if (resolvedTheme === systemTheme) {
15 | setTheme('system')
16 | }
17 | }
18 |
19 | onMediaChange()
20 | media.addEventListener('change', onMediaChange)
21 |
22 | return () => {
23 | media.removeEventListener('change', onMediaChange)
24 | }
25 | }, [resolvedTheme, setTheme])
26 |
27 | return null
28 | }
29 |
30 | export function Providers({ children }) {
31 | return (
32 |
33 |
34 | {children}
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/docs/src/components/Desktop.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button'
2 | import { Heading } from '@/components/Heading'
3 |
4 | const desktop_links = [
5 | {
6 | href: '/authentication',
7 | name: 'Authentication',
8 | description: 'Learn how to authenticate your API requests.',
9 | },
10 | {
11 | href: '/pagination',
12 | name: 'Pagination',
13 | description: 'Understand how to work with paginated responses.',
14 | },
15 | {
16 | href: '/errors',
17 | name: 'Errors',
18 | description:
19 | 'Read about the different types of errors returned by the API.',
20 | },
21 | {
22 | href: '/webhooks',
23 | name: 'Webhooks',
24 | description:
25 | 'Learn how to programmatically configure webhooks for your app.',
26 | },
27 | ]
28 |
29 | export function Desktop() {
30 | return (
31 |
32 |
33 | Desktop
34 |
35 |
36 | {desktop_links.map((link) => (
37 |
38 |
39 | {link.name}
40 |
41 |
42 | {link.description}
43 |
44 |
45 |
48 |
49 |
50 | ))}
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/docs/src/components/EditPage.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Button } from './Button'
4 | import { Pencil } from 'lucide-react'
5 |
6 | export function EditPage({ filepath }) {
7 | // Base GitHub repo URL - replace with your repository details
8 | const GITHUB_REPO = 'https://github.com/Surfer-Org/Protocol'
9 | const GITHUB_BRANCH = 'main' // or whatever your default branch is
10 |
11 | const editUrl = `${GITHUB_REPO}/tree/${GITHUB_BRANCH}/docs/src/app/${filepath}`
12 |
13 | return (
14 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/docs/src/components/GridPattern.jsx:
--------------------------------------------------------------------------------
1 | import { useId } from 'react'
2 |
3 | export function GridPattern({ width, height, x, y, squares, ...props }) {
4 | let patternId = useId()
5 |
6 | return (
7 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/docs/src/components/HeroPattern.jsx:
--------------------------------------------------------------------------------
1 | import { GridPattern } from '@/components/GridPattern'
2 |
3 | export function HeroPattern() {
4 | return (
5 |
6 |
7 |
8 |
21 |
22 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/docs/src/components/Image.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | import Image from 'next/image'
4 |
5 | export default function CoverImage(props) {
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/docs/src/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from 'next/link'
4 | import { usePathname } from 'next/navigation'
5 | import { motion } from 'framer-motion'
6 |
7 | import { Footer } from '@/components/Footer'
8 | import { Header } from '@/components/Header'
9 | import { Logo } from '@/components/Logo'
10 | import { Navigation } from '@/components/Navigation'
11 | import { SectionProvider } from '@/components/SectionProvider'
12 |
13 | export function Layout({ children, allSections }) {
14 | let pathname = usePathname()
15 |
16 | return (
17 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {children}
35 |
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/docs/src/components/Prose.jsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 |
3 | export function Prose({ as, className, ...props }) {
4 | let Component = as ?? 'div'
5 |
6 | return (
7 | *)` is used to select all direct children without an increase in specificity like you'd get from just `& > *`
12 | '[html_:where(&>*)]:mx-auto [html_:where(&>*)]:max-w-2xl [html_:where(&>*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(&>*)]:lg:max-w-3xl',
13 | )}
14 | {...props}
15 | />
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/Tag.jsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 |
3 | const variantStyles = {
4 | small: '',
5 | medium: 'rounded-lg px-1.5 ring-1 ring-inset',
6 | }
7 |
8 | const colorStyles = {
9 | blue: {
10 | small: 'text-blue-500 dark:text-blue-400',
11 | medium:
12 | 'ring-blue-300 dark:ring-blue-400/30 bg-blue-400/10 text-blue-500 dark:text-blue-400',
13 | },
14 | sky: {
15 | small: 'text-sky-500',
16 | medium:
17 | 'ring-sky-300 bg-sky-400/10 text-sky-500 dark:ring-sky-400/30 dark:bg-sky-400/10 dark:text-sky-400',
18 | },
19 | amber: {
20 | small: 'text-amber-500',
21 | medium:
22 | 'ring-amber-300 bg-amber-400/10 text-amber-500 dark:ring-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400',
23 | },
24 | rose: {
25 | small: 'text-red-500 dark:text-rose-500',
26 | medium:
27 | 'ring-rose-200 bg-rose-50 text-red-500 dark:ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-400',
28 | },
29 | zinc: {
30 | small: 'text-zinc-400 dark:text-zinc-500',
31 | medium:
32 | 'ring-zinc-200 bg-zinc-50 text-zinc-500 dark:ring-zinc-500/20 dark:bg-zinc-400/10 dark:text-zinc-400',
33 | },
34 | }
35 |
36 | const valueColorMap = {
37 | GET: 'blue',
38 | POST: 'sky',
39 | PUT: 'amber',
40 | DELETE: 'rose',
41 | }
42 |
43 | export function Tag({
44 | children,
45 | variant = 'medium',
46 | color = valueColorMap[children] ?? 'blue',
47 | }) {
48 | return (
49 |
56 | {children}
57 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/docs/src/components/ThemeToggle.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { useTheme } from 'next-themes'
3 |
4 | function SunIcon(props) {
5 | return (
6 |
7 |
8 |
12 |
13 | )
14 | }
15 |
16 | function MoonIcon(props) {
17 | return (
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | export function ThemeToggle() {
25 | let { resolvedTheme, setTheme } = useTheme()
26 | let otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark'
27 | let [mounted, setMounted] = useState(false)
28 |
29 | useEffect(() => {
30 | setMounted(true)
31 | }, [])
32 |
33 | return (
34 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/docs/src/components/icons/BellIcon.jsx:
--------------------------------------------------------------------------------
1 | export function BellIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/BoltIcon.jsx:
--------------------------------------------------------------------------------
1 | export function BoltIcon(props) {
2 | return (
3 |
4 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/docs/src/components/icons/BookIcon.jsx:
--------------------------------------------------------------------------------
1 | export function BookIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/CalendarIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CalendarIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/docs/src/components/icons/CartIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CartIcon(props) {
2 | return (
3 |
4 |
8 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/docs/src/components/icons/ChatBubbleIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ChatBubbleIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/CheckIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CheckIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/ChevronRightLeftIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ChevronRightLeftIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/ClipboardIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ClipboardIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/CogIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CogIcon(props) {
2 | return (
3 |
4 |
10 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/docs/src/components/icons/CopyIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CopyIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/DocumentIcon.jsx:
--------------------------------------------------------------------------------
1 | export function DocumentIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/EnvelopeIcon.jsx:
--------------------------------------------------------------------------------
1 | export function EnvelopeIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/FaceSmileIcon.jsx:
--------------------------------------------------------------------------------
1 | export function FaceSmileIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/FolderIcon.jsx:
--------------------------------------------------------------------------------
1 | export function FolderIcon(props) {
2 | return (
3 |
4 |
10 |
14 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/docs/src/components/icons/LinkIcon.jsx:
--------------------------------------------------------------------------------
1 | export function LinkIcon(props) {
2 | return (
3 |
4 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/docs/src/components/icons/ListIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ListIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/MagnifyingGlassIcon.jsx:
--------------------------------------------------------------------------------
1 | export function MagnifyingGlassIcon(props) {
2 | return (
3 |
4 |
5 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/docs/src/components/icons/MapPinIcon.jsx:
--------------------------------------------------------------------------------
1 | export function MapPinIcon(props) {
2 | return (
3 |
4 |
10 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/docs/src/components/icons/PackageIcon.jsx:
--------------------------------------------------------------------------------
1 | export function PackageIcon(props) {
2 | return (
3 |
4 |
8 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/docs/src/components/icons/PaperAirplaneIcon.jsx:
--------------------------------------------------------------------------------
1 | export function PaperAirplaneIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/PaperClipIcon.jsx:
--------------------------------------------------------------------------------
1 | export function PaperClipIcon(props) {
2 | return (
3 |
4 |
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/docs/src/components/icons/ShapesIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ShapesIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/ShirtIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ShirtIcon(props) {
2 | return (
3 |
4 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/docs/src/components/icons/SquaresPlusIcon.jsx:
--------------------------------------------------------------------------------
1 | export function SquaresPlusIcon(props) {
2 | return (
3 |
4 |
9 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/docs/src/components/icons/TagIcon.jsx:
--------------------------------------------------------------------------------
1 | export function TagIcon(props) {
2 | return (
3 |
4 |
10 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/docs/src/components/icons/UserIcon.jsx:
--------------------------------------------------------------------------------
1 | export function UserIcon(props) {
2 | return (
3 |
4 |
10 |
16 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/docs/src/components/icons/UsersIcon.jsx:
--------------------------------------------------------------------------------
1 | export function UsersIcon(props) {
2 | return (
3 |
4 |
10 |
15 |
21 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/docs/src/helpers.js:
--------------------------------------------------------------------------------
1 | export async function getRawCode(url) {
2 | const rawUrl = url
3 | .replace('github.com', 'raw.githubusercontent.com')
4 | .replace('/blob/', '/')
5 | const response = await fetch(rawUrl)
6 | const text = await response.text()
7 | return text
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/docs/src/images/claude-mcp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/docs/src/images/claude-mcp.png
--------------------------------------------------------------------------------
/docs/src/images/claude_finished.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/docs/src/images/claude_finished.png
--------------------------------------------------------------------------------
/docs/src/images/cover_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/docs/src/images/cover_image.png
--------------------------------------------------------------------------------
/docs/src/images/knowledge-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/docs/src/images/knowledge-graph.png
--------------------------------------------------------------------------------
/docs/src/images/logos/go.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/docs/src/images/logos/node.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/src/images/logos/php.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/src/images/logos/python.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/src/images/logos/ruby.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/src/images/streamlit-chatbot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/docs/src/images/streamlit-chatbot.png
--------------------------------------------------------------------------------
/docs/src/lib/remToPx.js:
--------------------------------------------------------------------------------
1 | export function remToPx(remValue) {
2 | let rootFontSize =
3 | typeof window === 'undefined'
4 | ? 16
5 | : parseFloat(window.getComputedStyle(document.documentElement).fontSize)
6 |
7 | return remValue * rootFontSize
8 | }
9 |
--------------------------------------------------------------------------------
/docs/src/mdx/recma.mjs:
--------------------------------------------------------------------------------
1 | import { mdxAnnotations } from 'mdx-annotations'
2 |
3 | export const recmaPlugins = [mdxAnnotations.recma]
4 |
--------------------------------------------------------------------------------
/docs/src/mdx/remark.mjs:
--------------------------------------------------------------------------------
1 | import { mdxAnnotations } from 'mdx-annotations'
2 | import remarkGfm from 'remark-gfm'
3 |
4 | export const remarkPlugins = [mdxAnnotations.remark, remarkGfm]
5 |
--------------------------------------------------------------------------------
/docs/src/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @layer base {
2 | :root {
3 | --shiki-color-text: theme('colors.white');
4 | --shiki-token-constant: theme('colors.blue.300');
5 | --shiki-token-string: theme('colors.blue.300');
6 | --shiki-token-comment: theme('colors.zinc.500');
7 | --shiki-token-keyword: theme('colors.sky.300');
8 | --shiki-token-parameter: theme('colors.pink.300');
9 | --shiki-token-function: theme('colors.violet.300');
10 | --shiki-token-string-expression: theme('colors.blue.300');
11 | --shiki-token-punctuation: theme('colors.zinc.200');
12 | }
13 |
14 | [inert] ::-webkit-scrollbar {
15 | display: none;
16 | }
17 | }
18 |
19 | @tailwind base;
20 | @tailwind components;
21 | @tailwind utilities;
22 |
--------------------------------------------------------------------------------
/docs/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const headlessuiPlugin = require('@headlessui/tailwindcss')
2 | const typographyPlugin = require('@tailwindcss/typography')
3 |
4 | const typographyStyles = require('./typography')
5 |
6 | /** @type {import('tailwindcss').Config} */
7 | module.exports = {
8 | content: ['./src/**/*.{js,mjs,jsx,ts,tsx,mdx}'],
9 | darkMode: 'selector',
10 | theme: {
11 | fontSize: {
12 | '2xs': ['0.75rem', { lineHeight: '1.25rem' }],
13 | xs: ['0.8125rem', { lineHeight: '1.5rem' }],
14 | sm: ['0.875rem', { lineHeight: '1.5rem' }],
15 | base: ['1rem', { lineHeight: '1.75rem' }],
16 | lg: ['1.125rem', { lineHeight: '1.75rem' }],
17 | xl: ['1.25rem', { lineHeight: '1.75rem' }],
18 | '2xl': ['1.5rem', { lineHeight: '2rem' }],
19 | '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
20 | '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
21 | '5xl': ['3rem', { lineHeight: '1' }],
22 | '6xl': ['3.75rem', { lineHeight: '1' }],
23 | '7xl': ['4.5rem', { lineHeight: '1' }],
24 | '8xl': ['6rem', { lineHeight: '1' }],
25 | '9xl': ['8rem', { lineHeight: '1' }],
26 | },
27 | typography: typographyStyles,
28 | extend: {
29 | boxShadow: {
30 | glow: '0 0 4px rgb(0 0 0 / 0.1)',
31 | },
32 | maxWidth: {
33 | lg: '33rem',
34 | '2xl': '40rem',
35 | '3xl': '50rem',
36 | '5xl': '66rem',
37 | },
38 | opacity: {
39 | 1: '0.01',
40 | 2.5: '0.025',
41 | 7.5: '0.075',
42 | 15: '0.15',
43 | },
44 | },
45 | },
46 | plugins: [typographyPlugin, headlessuiPlugin],
47 | }
48 |
--------------------------------------------------------------------------------
/landing/.firebase/hosting.ZGlzdA.cache:
--------------------------------------------------------------------------------
1 | index.html,1735083721672,bf91ff7d195d56aeda846084d615193a4b1c536d665f9adfd6e62c0282db48a6
2 | assets/logo-D_UoNh_H.svg,1735083721672,fb5c7766fb59e4317aa9104bed5bcb87a1f46cacb54b4f7c3beadb31977e8398
3 | assets/index-C9mreshC.css,1735083721672,7dac228bc0a4a338985c08e27a22184bb2e9b6b7e8b5c4d989585f3307bc530d
4 | assets/index-CRukJyXq.js,1735083721672,39e81793bc8b3ff4a4eb1651e4b587e051e2fba811f043589d4b4ed62553218c
5 | assets/favicon-js4TQEI6.ico,1735083721672,d79ff597800369d8fcf0f19873b02dabada5e609e4e348a8360540c73e99965b
6 | assets/cover_image-NWsoxT96.png,1735083721673,be01924e74c0d814233ade99e60df1a28c8a8ab676fdb53c2adeff237c293d28
7 |
--------------------------------------------------------------------------------
/landing/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "surferprotocol"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/landing/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/landing/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/landing/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import react from 'eslint-plugin-react'
4 | import reactHooks from 'eslint-plugin-react-hooks'
5 | import reactRefresh from 'eslint-plugin-react-refresh'
6 |
7 | export default [
8 | { ignores: ['dist'] },
9 | {
10 | files: ['**/*.{js,jsx}'],
11 | languageOptions: {
12 | ecmaVersion: 2020,
13 | globals: globals.browser,
14 | parserOptions: {
15 | ecmaVersion: 'latest',
16 | ecmaFeatures: { jsx: true },
17 | sourceType: 'module',
18 | },
19 | },
20 | settings: { react: { version: '18.3' } },
21 | plugins: {
22 | react,
23 | 'react-hooks': reactHooks,
24 | 'react-refresh': reactRefresh,
25 | },
26 | rules: {
27 | ...js.configs.recommended.rules,
28 | ...react.configs.recommended.rules,
29 | ...react.configs['jsx-runtime'].rules,
30 | ...reactHooks.configs.recommended.rules,
31 | 'react/jsx-no-target-blank': 'off',
32 | 'react-refresh/only-export-components': [
33 | 'warn',
34 | { allowConstantExport: true },
35 | ],
36 | },
37 | },
38 | ]
39 |
--------------------------------------------------------------------------------
/landing/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "dist",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/landing/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Surfer Protocol
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/landing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "protocol-landing",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "lucide-react": "^0.460.0",
14 | "react": "^18.3.1",
15 | "react-dom": "^18.3.1",
16 | "react-router-dom": "^6.28.0",
17 | "react-slick": "^0.30.2",
18 | "react-tweet": "^3.2.1",
19 | "slick-carousel": "^1.8.1"
20 | },
21 | "devDependencies": {
22 | "@eslint/js": "^9.13.0",
23 | "@types/react": "^18.3.12",
24 | "@types/react-dom": "^18.3.1",
25 | "@vitejs/plugin-react-swc": "^3.5.0",
26 | "autoprefixer": "^10.4.20",
27 | "eslint": "^9.13.0",
28 | "eslint-plugin-react": "^7.37.2",
29 | "eslint-plugin-react-hooks": "^5.0.0",
30 | "eslint-plugin-react-refresh": "^0.4.14",
31 | "globals": "^15.11.0",
32 | "postcss": "^8.4.49",
33 | "tailwindcss": "^3.4.15",
34 | "vite": "^5.4.10"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/landing/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/landing/src/assets/cover_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/landing/src/assets/cover_image.png
--------------------------------------------------------------------------------
/landing/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/landing/src/assets/favicon.ico
--------------------------------------------------------------------------------
/landing/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | hr {
7 | @apply border-t border-gray-600/20 my-0;
8 | }
9 | }
--------------------------------------------------------------------------------
/landing/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import './index.css'
4 | import App from './App.jsx'
5 | import Business from './Business.jsx'
6 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
7 |
8 | createRoot(document.getElementById('root')).render(
9 |
10 |
11 |
12 | } />
13 | } />
14 |
15 |
16 | ,
17 | )
18 |
--------------------------------------------------------------------------------
/landing/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {
9 | colors: {
10 | blue: {
11 | 50: '#f0f9ff',
12 | 100: '#e0f2fe',
13 | 500: '#0ea5e9',
14 | 600: '#0284c7',
15 | 700: '#0369a1',
16 | }
17 | }
18 | },
19 | },
20 | plugins: [],
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/landing/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/sdk/python/.gitignore:
--------------------------------------------------------------------------------
1 | # Python
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | *.so
6 | .Python
7 | build/
8 | develop-eggs/
9 | dist/
10 | downloads/
11 | eggs/
12 | .eggs/
13 | lib/
14 | lib64/
15 | parts/
16 | sdist/
17 | var/
18 | wheels/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 |
23 | # Virtual Environment
24 | venv/
25 | env/
26 | ENV/
27 |
28 | # IDE
29 | .idea/
30 | .vscode/
31 | *.swp
32 | *.swo
--------------------------------------------------------------------------------
/sdk/python/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Surfer Protocol
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/sdk/python/README.md:
--------------------------------------------------------------------------------
1 | # Surfer Protocol Python SDK
2 |
3 | A Python SDK for interacting with the Surfer Protocol desktop application. Easily access and export data from various platforms like Twitter, Gmail, iMessage, and more.
4 |
5 | ## Prerequisites
6 |
7 | The Surfer desktop application must be running in the background for the SDK to work. [Download here](https://docs.surferprotocol.org/desktop/installation).
8 |
9 | ## Installation
10 |
11 | ```bash
12 | pip install surfer-protocol
13 | ```
14 |
15 | ## Quick Start
16 |
17 | ```python
18 | from surfer_protocol import SurferClient
19 |
20 | # Initialize the client
21 | client = SurferClient()
22 |
23 | # Get data for a specific platform
24 | data = client.get("bookmarks-001")
25 |
26 | # Export data for a platform
27 | export_result = client.export("bookmarks-001")
28 | ```
29 |
30 | ## Examples
31 |
32 | For examples of how to use the Surfer Protocol Python SDK to build applications, please see the [Cookbook](../../cookbook/python/README.md).
33 |
34 | ## Basic Usage
35 |
36 | The SDK provides two main methods:
37 | - `get(platform_id)`: Retrieve the most recent data for a platform
38 | - `export(platform_id)`: Trigger a new export for a platform
39 |
40 | ## Supported Platforms
41 |
42 | - Twitter Bookmarks (`bookmarks-001`)
43 | - Gmail (`gmail-001`)
44 | - iMessage (`imessage-001`)
45 | - LinkedIn Connections (`connections-001`)
46 | - Notion (`notion-001`)
47 | - ChatGPT (`chatgpt-001`)
48 |
49 | ## Documentation
50 |
51 | For complete documentation, including detailed API reference, response schemas, example applications, and best practices, visit our [official documentation](https://docs.surferprotocol.com/sdk/python).
--------------------------------------------------------------------------------
/sdk/python/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools>=42", "wheel"]
3 | build-backend = "setuptools.build_meta"
--------------------------------------------------------------------------------
/sdk/python/requirements.txt:
--------------------------------------------------------------------------------
1 | requests>=2.25.1
--------------------------------------------------------------------------------
/sdk/python/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | with open("README.md", "r", encoding="utf-8") as fh:
4 | long_description = fh.read()
5 |
6 | setup(
7 | name="surfer-protocol",
8 | version="0.1.2",
9 | packages=find_packages(),
10 | install_requires=[
11 | "requests>=2.25.1",
12 | ],
13 | python_requires=">=3.7",
14 | url="https://github.com/Surfer-Org/Protocol/tree/main/sdk/python",
15 | license="MIT",
16 | classifiers=[
17 | "Development Status :: 3 - Alpha",
18 | "Intended Audience :: Developers",
19 | "License :: OSI Approved :: MIT License",
20 | "Programming Language :: Python :: 3",
21 | "Programming Language :: Python :: 3.7",
22 | "Programming Language :: Python :: 3.8",
23 | "Programming Language :: Python :: 3.9",
24 | "Programming Language :: Python :: 3.10",
25 | ],
26 | author="Sahil Lalani",
27 | author_email="lihas1002@gmail.com",
28 | description="Python client for the Surfer Protocol desktop app",
29 | long_description=long_description,
30 | long_description_content_type="text/markdown",
31 | keywords="surfer-protocol, client, local-first, ai-agent, ai-agent-framework, langchain, langsmith, streamlit, ollama, vector-database, weaviate, personal-data, personal-data-warehouse",
32 | )
--------------------------------------------------------------------------------
/sdk/python/surfer_protocol/__init__.py:
--------------------------------------------------------------------------------
1 | from .client import SurferClient
2 |
3 | __all__ = ['SurferClient']
4 |
--------------------------------------------------------------------------------
/surfer-mcp/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/surfer-mcp/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "surfer-mcp"
3 | version = "0.1.3"
4 | description = "This is the official MCP server for Surfer"
5 | readme = "README.md"
6 | requires-python = ">=3.13"
7 | dependencies = [
8 | "chromadb>=0.5.23",
9 | "mcp>=1.1.2",
10 | ]
11 | [[project.authors]]
12 | name = "Sahil Lalani"
13 | email = "99920148+sahil-lalani@users.noreply.github.com"
14 |
15 | [build-system]
16 | requires = [ "hatchling",]
17 | build-backend = "hatchling.build"
18 |
19 | [project.scripts]
20 | surfer-mcp = "surfer_mcp:main"
21 |
--------------------------------------------------------------------------------
/surfer-mcp/src/surfer_mcp/__init__.py:
--------------------------------------------------------------------------------
1 | from . import server
2 | import asyncio
3 |
4 | def main():
5 | """Main entry point for the package."""
6 | asyncio.run(server.main())
7 |
8 | # Optionally expose other important items at package level
9 | __all__ = ['main', 'server']
--------------------------------------------------------------------------------
/surfer-mcp/src/surfer_mcp/__pycache__/__init__.cpython-313.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/surfer-mcp/src/surfer_mcp/__pycache__/__init__.cpython-313.pyc
--------------------------------------------------------------------------------
/surfer-mcp/src/surfer_mcp/__pycache__/server.cpython-313.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/surfer-mcp/src/surfer_mcp/__pycache__/server.cpython-313.pyc
--------------------------------------------------------------------------------