letswork 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letswork-0.1.0/.claude/settings.json +11 -0
- letswork-0.1.0/.github/workflows/ci.yml +29 -0
- letswork-0.1.0/.github/workflows/publish.yml +29 -0
- letswork-0.1.0/.gitignore +35 -0
- letswork-0.1.0/PKG-INFO +120 -0
- letswork-0.1.0/README.md +104 -0
- letswork-0.1.0/docs/architecture.md +73 -0
- letswork-0.1.0/docs/spec.md +247 -0
- letswork-0.1.0/docs/tasks.md +119 -0
- letswork-0.1.0/pyproject.toml +34 -0
- letswork-0.1.0/src/__init__.py +1 -0
- letswork-0.1.0/src/approval.py +98 -0
- letswork-0.1.0/src/auth.py +12 -0
- letswork-0.1.0/src/cli.py +132 -0
- letswork-0.1.0/src/events.py +80 -0
- letswork-0.1.0/src/filelock.py +37 -0
- letswork-0.1.0/src/launcher.py +45 -0
- letswork-0.1.0/src/remote_client.py +89 -0
- letswork-0.1.0/src/server.py +214 -0
- letswork-0.1.0/src/tui/__init__.py +1 -0
- letswork-0.1.0/src/tui/app.py +265 -0
- letswork-0.1.0/src/tui/approval_panel.py +78 -0
- letswork-0.1.0/src/tui/chat.py +41 -0
- letswork-0.1.0/src/tui/file_tree.py +68 -0
- letswork-0.1.0/src/tui/file_viewer.py +158 -0
- letswork-0.1.0/src/tui/guest_app.py +203 -0
- letswork-0.1.0/src/tui/terminal_panel.py +56 -0
- letswork-0.1.0/src/tunnel.py +38 -0
- letswork-0.1.0/tests/__init__.py +1 -0
- letswork-0.1.0/tests/test_auth.py +19 -0
- letswork-0.1.0/tests/test_filelock.py +52 -0
- letswork-0.1.0/tests/test_server.py +112 -0
- letswork-0.1.0/tests/test_tunnel.py +67 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(git -C /Users/saicharanrajoju/Desktop/LetsWork diff --name-only HEAD)",
|
|
5
|
+
"Bash(git -C /Users/saicharanrajoju/Desktop/LetsWork status)",
|
|
6
|
+
"Bash(git -C /Users/saicharanrajoju/Desktop/LetsWork diff docs/spec.md)",
|
|
7
|
+
"Bash(git -C /Users/saicharanrajoju/Desktop/LetsWork diff tests/__init__.py)",
|
|
8
|
+
"Bash(git -C /Users/saicharanrajoju/Desktop/LetsWork diff src/server.py)"
|
|
9
|
+
]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ${{ matrix.os }}
|
|
10
|
+
strategy:
|
|
11
|
+
matrix:
|
|
12
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: |
|
|
25
|
+
python -m pip install --upgrade pip
|
|
26
|
+
pip install -e ".[dev]"
|
|
27
|
+
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: pytest tests/ -v
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Set up Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
|
|
19
|
+
- name: Install build tools
|
|
20
|
+
run: pip install build twine
|
|
21
|
+
|
|
22
|
+
- name: Build package
|
|
23
|
+
run: python -m build
|
|
24
|
+
|
|
25
|
+
- name: Publish to PyPI
|
|
26
|
+
env:
|
|
27
|
+
TWINE_USERNAME: __token__
|
|
28
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
29
|
+
run: twine upload dist/*
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg-info/
|
|
7
|
+
*.egg
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
venv/
|
|
14
|
+
.venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.vscode/
|
|
19
|
+
.idea/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
*~
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Testing
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.coverage
|
|
31
|
+
htmlcov/
|
|
32
|
+
|
|
33
|
+
# Distribution
|
|
34
|
+
*.tar.gz
|
|
35
|
+
*.whl
|
letswork-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: letswork
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Real-time collaborative coding via MCP — two developers, one codebase
|
|
5
|
+
Author: Sai Charan Rajoju
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: click>=8.1.0
|
|
9
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
10
|
+
Requires-Dist: requests>=2.28.0
|
|
11
|
+
Requires-Dist: textual[syntax]>=0.50.0
|
|
12
|
+
Requires-Dist: websockets>=12.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# LetsWork
|
|
18
|
+
|
|
19
|
+
**Google Docs for AI-assisted coding** — real-time collaboration on a local codebase using two independent Claude subscriptions.
|
|
20
|
+
|
|
21
|
+
## What is LetsWork?
|
|
22
|
+
|
|
23
|
+
LetsWork is an MCP (Model Context Protocol) server that lets two developers work on the same local codebase simultaneously, each using their own Claude. One developer hosts, the other connects — with file-level locking to prevent conflicts.
|
|
24
|
+
|
|
25
|
+
## How It Works
|
|
26
|
+
|
|
27
|
+
1. Developer A (Host) runs `letswork start` in their project folder
|
|
28
|
+
2. A secure HTTPS tunnel is created automatically via Cloudflare
|
|
29
|
+
3. A one-time URL + secret token is generated
|
|
30
|
+
4. Developer A shares both with Developer B (Guest)
|
|
31
|
+
5. Developer B connects: `claude mcp add letswork --transport http <url>`
|
|
32
|
+
6. Both can now read, write, and list files — with lock protection
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### Install
|
|
37
|
+
```bash
|
|
38
|
+
pip install letswork
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Requirements
|
|
42
|
+
|
|
43
|
+
- Python >= 3.10
|
|
44
|
+
- [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) installed and available in PATH
|
|
45
|
+
- Git (recommended for conflict safety)
|
|
46
|
+
|
|
47
|
+
### Host (Developer A)
|
|
48
|
+
```bash
|
|
49
|
+
cd /path/to/your/project
|
|
50
|
+
letswork start
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
You'll see:
|
|
54
|
+
╔══════════════════════════════════════════════════╗
|
|
55
|
+
║ LetsWork Session Active ║
|
|
56
|
+
║ ║
|
|
57
|
+
║ URL: https://abc123.trycloudflare.com ║
|
|
58
|
+
║ Token: a1b2c3d4e5f6... ║
|
|
59
|
+
║ ║
|
|
60
|
+
║ Share both with your collaborator. ║
|
|
61
|
+
║ Press Ctrl+C to end session. ║
|
|
62
|
+
╚══════════════════════════════════════════════════╝
|
|
63
|
+
|
|
64
|
+
Share the URL and token with your collaborator via Slack, Discord, or text.
|
|
65
|
+
|
|
66
|
+
### Guest (Developer B)
|
|
67
|
+
```bash
|
|
68
|
+
claude mcp add letswork --transport http <URL_FROM_HOST>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use the token when prompted. You now have full access to the shared codebase through your own Claude.
|
|
72
|
+
|
|
73
|
+
## MCP Tools Available
|
|
74
|
+
|
|
75
|
+
| Tool | Description |
|
|
76
|
+
|------|-------------|
|
|
77
|
+
| `list_files` | List files and directories with lock status |
|
|
78
|
+
| `read_file` | Read file contents (1MB limit) |
|
|
79
|
+
| `write_file` | Write to a file (auto-locks if needed) |
|
|
80
|
+
| `lock_file` | Lock a file for exclusive editing |
|
|
81
|
+
| `unlock_file` | Release a file lock |
|
|
82
|
+
| `get_status` | Show session info and active locks |
|
|
83
|
+
|
|
84
|
+
## Security
|
|
85
|
+
|
|
86
|
+
- Unguessable tunnel URL (random Cloudflare subdomain)
|
|
87
|
+
- Cryptographic secret token (second auth layer)
|
|
88
|
+
- All traffic encrypted via HTTPS
|
|
89
|
+
- Path traversal prevention (no access outside project root)
|
|
90
|
+
- No accounts, no signup, no persistent credentials
|
|
91
|
+
|
|
92
|
+
## CLI Commands
|
|
93
|
+
|
|
94
|
+
| Command | Description |
|
|
95
|
+
|---------|-------------|
|
|
96
|
+
| `letswork start [--port PORT]` | Start session (default port: 8000) |
|
|
97
|
+
| `letswork stop` | Stop instructions (use Ctrl+C in v1) |
|
|
98
|
+
| `letswork status` | Status instructions (use get_status tool in v1) |
|
|
99
|
+
|
|
100
|
+
## Architecture
|
|
101
|
+
Developer A's Machine:
|
|
102
|
+
[Local Codebase] ← [MCP Server] ← [Cloudflare Tunnel] ← HTTPS URL
|
|
103
|
+
↑
|
|
104
|
+
Developer B connects here
|
|
105
|
+
with secret token
|
|
106
|
+
|
|
107
|
+
## Constraints (v1)
|
|
108
|
+
|
|
109
|
+
- Maximum 2 users per session (Host + Guest)
|
|
110
|
+
- Text files only (no binary support)
|
|
111
|
+
- 1MB file size limit per operation
|
|
112
|
+
- File operations only (no shell access for Guest)
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
Built with the [Model Context Protocol](https://modelcontextprotocol.io).
|
letswork-0.1.0/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# LetsWork
|
|
2
|
+
|
|
3
|
+
**Google Docs for AI-assisted coding** — real-time collaboration on a local codebase using two independent Claude subscriptions.
|
|
4
|
+
|
|
5
|
+
## What is LetsWork?
|
|
6
|
+
|
|
7
|
+
LetsWork is an MCP (Model Context Protocol) server that lets two developers work on the same local codebase simultaneously, each using their own Claude. One developer hosts, the other connects — with file-level locking to prevent conflicts.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
1. Developer A (Host) runs `letswork start` in their project folder
|
|
12
|
+
2. A secure HTTPS tunnel is created automatically via Cloudflare
|
|
13
|
+
3. A one-time URL + secret token is generated
|
|
14
|
+
4. Developer A shares both with Developer B (Guest)
|
|
15
|
+
5. Developer B connects: `claude mcp add letswork --transport http <url>`
|
|
16
|
+
6. Both can now read, write, and list files — with lock protection
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Install
|
|
21
|
+
```bash
|
|
22
|
+
pip install letswork
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Requirements
|
|
26
|
+
|
|
27
|
+
- Python >= 3.10
|
|
28
|
+
- [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) installed and available in PATH
|
|
29
|
+
- Git (recommended for conflict safety)
|
|
30
|
+
|
|
31
|
+
### Host (Developer A)
|
|
32
|
+
```bash
|
|
33
|
+
cd /path/to/your/project
|
|
34
|
+
letswork start
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You'll see:
|
|
38
|
+
╔══════════════════════════════════════════════════╗
|
|
39
|
+
║ LetsWork Session Active ║
|
|
40
|
+
║ ║
|
|
41
|
+
║ URL: https://abc123.trycloudflare.com ║
|
|
42
|
+
║ Token: a1b2c3d4e5f6... ║
|
|
43
|
+
║ ║
|
|
44
|
+
║ Share both with your collaborator. ║
|
|
45
|
+
║ Press Ctrl+C to end session. ║
|
|
46
|
+
╚══════════════════════════════════════════════════╝
|
|
47
|
+
|
|
48
|
+
Share the URL and token with your collaborator via Slack, Discord, or text.
|
|
49
|
+
|
|
50
|
+
### Guest (Developer B)
|
|
51
|
+
```bash
|
|
52
|
+
claude mcp add letswork --transport http <URL_FROM_HOST>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use the token when prompted. You now have full access to the shared codebase through your own Claude.
|
|
56
|
+
|
|
57
|
+
## MCP Tools Available
|
|
58
|
+
|
|
59
|
+
| Tool | Description |
|
|
60
|
+
|------|-------------|
|
|
61
|
+
| `list_files` | List files and directories with lock status |
|
|
62
|
+
| `read_file` | Read file contents (1MB limit) |
|
|
63
|
+
| `write_file` | Write to a file (auto-locks if needed) |
|
|
64
|
+
| `lock_file` | Lock a file for exclusive editing |
|
|
65
|
+
| `unlock_file` | Release a file lock |
|
|
66
|
+
| `get_status` | Show session info and active locks |
|
|
67
|
+
|
|
68
|
+
## Security
|
|
69
|
+
|
|
70
|
+
- Unguessable tunnel URL (random Cloudflare subdomain)
|
|
71
|
+
- Cryptographic secret token (second auth layer)
|
|
72
|
+
- All traffic encrypted via HTTPS
|
|
73
|
+
- Path traversal prevention (no access outside project root)
|
|
74
|
+
- No accounts, no signup, no persistent credentials
|
|
75
|
+
|
|
76
|
+
## CLI Commands
|
|
77
|
+
|
|
78
|
+
| Command | Description |
|
|
79
|
+
|---------|-------------|
|
|
80
|
+
| `letswork start [--port PORT]` | Start session (default port: 8000) |
|
|
81
|
+
| `letswork stop` | Stop instructions (use Ctrl+C in v1) |
|
|
82
|
+
| `letswork status` | Status instructions (use get_status tool in v1) |
|
|
83
|
+
|
|
84
|
+
## Architecture
|
|
85
|
+
Developer A's Machine:
|
|
86
|
+
[Local Codebase] ← [MCP Server] ← [Cloudflare Tunnel] ← HTTPS URL
|
|
87
|
+
↑
|
|
88
|
+
Developer B connects here
|
|
89
|
+
with secret token
|
|
90
|
+
|
|
91
|
+
## Constraints (v1)
|
|
92
|
+
|
|
93
|
+
- Maximum 2 users per session (Host + Guest)
|
|
94
|
+
- Text files only (no binary support)
|
|
95
|
+
- 1MB file size limit per operation
|
|
96
|
+
- File operations only (no shell access for Guest)
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
Built with the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# LetsWork — Architecture Decisions
|
|
2
|
+
|
|
3
|
+
*Record of every architectural decision made during development.
|
|
4
|
+
Read this at the start of every session to avoid contradicting past decisions.*
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Decision: Python as Implementation Language
|
|
9
|
+
Date: Session 1
|
|
10
|
+
Decision: Use Python for the entire project
|
|
11
|
+
Reasoning: The official Anthropic MCP SDK is Python-first with the best documentation and examples. The target audience (developers using Claude) overwhelmingly uses Python. Click provides excellent CLI tooling in Python.
|
|
12
|
+
Alternatives rejected: TypeScript (MCP SDK exists but Python SDK is more mature), Rust (no official MCP SDK), Go (no official MCP SDK)
|
|
13
|
+
Impact: All source files are .py, packaging via PyPI, dependencies are Python packages
|
|
14
|
+
|
|
15
|
+
## Decision: FastMCP as Server Framework
|
|
16
|
+
Date: Session 1
|
|
17
|
+
Decision: Use FastMCP from the official mcp Python SDK
|
|
18
|
+
Reasoning: FastMCP is the high-level API provided by Anthropic's own MCP SDK. It handles protocol details, tool registration, and transport layers. Using the official SDK ensures compatibility with Claude Code and other MCP clients.
|
|
19
|
+
Alternatives rejected: Building raw MCP protocol handling manually (unnecessary complexity), third-party MCP libraries (less maintained than official SDK)
|
|
20
|
+
Impact: Server defined in src/server.py using @app.tool() decorators, transport handled by FastMCP
|
|
21
|
+
|
|
22
|
+
## Decision: Cloudflare Tunnel for Networking
|
|
23
|
+
Date: Session 1
|
|
24
|
+
Decision: Use cloudflared (Cloudflare Tunnel) to expose the local MCP server
|
|
25
|
+
Reasoning: Free tier sufficient, no port forwarding needed, no VPN needed, no DNS configuration, works behind NATs and firewalls, HTTPS by default. The tunnel creates a random subdomain on trycloudflare.com.
|
|
26
|
+
Alternatives rejected: ngrok (requires account, has rate limits on free tier), localtunnel (less reliable), direct port forwarding (requires router config, not user-friendly), Tailscale (requires both users to install and configure)
|
|
27
|
+
Impact: cloudflared is an external dependency that must be pre-installed. src/tunnel.py manages the subprocess lifecycle.
|
|
28
|
+
|
|
29
|
+
## Decision: In-Memory File Locking
|
|
30
|
+
Date: Session 1
|
|
31
|
+
Decision: Use a Python dictionary for file lock tracking (path -> user_id)
|
|
32
|
+
Reasoning: Simple, fast, no external dependencies. Locks only need to persist for the duration of a session. When the session ends, all locks are released automatically since the process exits.
|
|
33
|
+
Alternatives rejected: File-system level locks with fcntl/msvcrt (cross-platform complexity, doesn't track user identity), Redis (overkill external dependency), SQLite (unnecessary persistence)
|
|
34
|
+
Impact: LockManager class in src/filelock.py with a dict. Locks are lost if the process crashes — acceptable for v1 since Git is the safety net.
|
|
35
|
+
|
|
36
|
+
## Decision: Secret Token Authentication
|
|
37
|
+
Date: Session 1
|
|
38
|
+
Decision: Generate a cryptographic random token per session using secrets.token_urlsafe
|
|
39
|
+
Reasoning: Simple, no accounts needed, no persistent credentials. Combined with the unguessable tunnel URL, this provides two layers of security. Token is shared out-of-band (Slack, Discord, text).
|
|
40
|
+
Alternatives rejected: OAuth (massive complexity for a CLI tool), API keys (requires persistent storage), mutual TLS (complex setup for end users)
|
|
41
|
+
Impact: src/auth.py generates and validates tokens. Token is set once at session start and checked on every request.
|
|
42
|
+
|
|
43
|
+
## Decision: Centralized Path Safety via safe_resolve
|
|
44
|
+
Date: Session 4
|
|
45
|
+
Decision: Create a single safe_resolve(path) function used by all MCP tools for path resolution and traversal prevention
|
|
46
|
+
Reasoning: Initially each tool had inline path resolution logic (os.path.join, os.path.abspath, startswith check). This was duplicated across 5 tools. Extracting it into safe_resolve eliminates duplication and ensures a single point of security enforcement.
|
|
47
|
+
Alternatives rejected: Keeping inline checks per tool (duplication, risk of inconsistency), middleware-level path check (FastMCP doesn't natively support pre-tool middleware)
|
|
48
|
+
Impact: All tools call safe_resolve(path) first. Any path outside project_root raises ValueError.
|
|
49
|
+
|
|
50
|
+
## Decision: Click for CLI Framework
|
|
51
|
+
Date: Session 1
|
|
52
|
+
Decision: Use Click for the command-line interface
|
|
53
|
+
Reasoning: Click is the standard Python CLI library — well-documented, widely used, supports groups, commands, options, and help text out of the box. Minimal code for a professional CLI.
|
|
54
|
+
Alternatives rejected: argparse (more verbose, less elegant), Typer (adds a dependency on top of Click), fire (too magic, less control)
|
|
55
|
+
Impact: src/cli.py defines a Click group with start, stop, and status commands
|
|
56
|
+
|
|
57
|
+
## Decision: Streamable HTTP Transport
|
|
58
|
+
Date: Session 4
|
|
59
|
+
Decision: Run the MCP server with transport="streamable-http"
|
|
60
|
+
Reasoning: This is the transport mode required for remote MCP connections over HTTPS tunnels. The Guest connects via HTTP to the Cloudflare tunnel URL, which forwards to the local server.
|
|
61
|
+
Alternatives rejected: stdio transport (only works for local connections, not over network), SSE transport (older protocol, streamable-http is the current standard)
|
|
62
|
+
Impact: app.run() in cli.py uses transport="streamable-http" with host="127.0.0.1"
|
|
63
|
+
|
|
64
|
+
## Decision: v1 Scope Boundaries
|
|
65
|
+
Date: Session 1
|
|
66
|
+
Decision: Limit v1 to exactly 2 users, text files only, 1MB limit, no directory creation/deletion, no shell access for Guest
|
|
67
|
+
Reasoning: Shipping a focused, working tool is more important than feature completeness. Every constraint can be relaxed in v2 based on real user feedback. The core value proposition (two developers, one codebase, real-time) works within these constraints.
|
|
68
|
+
Alternatives rejected: Building multi-user support from the start (complexity), supporting binary files (encoding complexity), adding shell access (security risk)
|
|
69
|
+
Impact: All tools enforce these limits. Future scope documented in spec.md Section 9.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
*Last updated: Session 5 — All architecture decisions documented*
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# LetsWork — Product Specification
|
|
2
|
+
*Version 1.0 — Source of truth for what LetsWork does and how.*
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## 1. Product Summary
|
|
7
|
+
|
|
8
|
+
LetsWork is an MCP (Model Context Protocol) server that enables
|
|
9
|
+
two developers to collaborate on the same local codebase in
|
|
10
|
+
real time, each using their own Claude subscription independently.
|
|
11
|
+
|
|
12
|
+
One developer (the Host) runs LetsWork in their project folder.
|
|
13
|
+
It starts an MCP server, creates a secure Cloudflare tunnel,
|
|
14
|
+
and generates a one-time URL + secret token. A second developer
|
|
15
|
+
(the Guest) adds that URL as an MCP server in their Claude Code.
|
|
16
|
+
Both can now read and write files in the same codebase with
|
|
17
|
+
file-level locking to prevent conflicts.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. Users
|
|
22
|
+
|
|
23
|
+
### Host (Developer A)
|
|
24
|
+
- Has the codebase on their local machine
|
|
25
|
+
- Runs `letswork start` to begin a session
|
|
26
|
+
- Shares the generated URL + token with the Guest
|
|
27
|
+
- Can read, write, list, and lock files
|
|
28
|
+
- Can see which files the Guest has locked
|
|
29
|
+
- Can end the session at any time
|
|
30
|
+
|
|
31
|
+
### Guest (Developer B)
|
|
32
|
+
- Does NOT have the codebase locally
|
|
33
|
+
- Receives the URL + token from the Host
|
|
34
|
+
- Connects via `claude mcp add letswork --transport http <url>`
|
|
35
|
+
- Can read, write, list, and lock files
|
|
36
|
+
- Can see which files the Host has locked
|
|
37
|
+
- Connection ends when the Host stops the session
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 3. Core Features
|
|
42
|
+
|
|
43
|
+
### 3.1 Session Management
|
|
44
|
+
- Host runs `letswork start` in any project directory
|
|
45
|
+
- A local MCP server starts on a random available port
|
|
46
|
+
- A Cloudflare tunnel is created automatically (no config needed)
|
|
47
|
+
- A unique session URL (HTTPS) is generated
|
|
48
|
+
- A one-time secret token is generated for authentication
|
|
49
|
+
- Host sees: URL, token, and session status in terminal
|
|
50
|
+
- Session ends when Host runs `letswork stop` or Ctrl+C
|
|
51
|
+
|
|
52
|
+
### 3.2 File Operations (MCP Tools)
|
|
53
|
+
LetsWork exposes these MCP tools to both Host and Guest Claude:
|
|
54
|
+
|
|
55
|
+
| Tool Name | Parameters | Description |
|
|
56
|
+
|-----------------|-------------------------|-------------------------------------|
|
|
57
|
+
| `read_file` | `path: str` | Read contents of a file |
|
|
58
|
+
| `write_file` | `path: str, content: str` | Write content to a file |
|
|
59
|
+
| `list_files` | `path: str` (optional) | List files in directory (default: root) |
|
|
60
|
+
| `lock_file` | `path: str` | Lock a file for exclusive editing |
|
|
61
|
+
| `unlock_file` | `path: str` | Release lock on a file |
|
|
62
|
+
| `get_locks` | (none) | Show all currently locked files |
|
|
63
|
+
| `get_status` | (none) | Show session info and connected users |
|
|
64
|
+
|
|
65
|
+
### 3.3 File Locking
|
|
66
|
+
- Before writing, a developer must lock the file
|
|
67
|
+
- Only one developer can lock a file at a time
|
|
68
|
+
- Attempting to lock an already-locked file returns an error
|
|
69
|
+
with the name of the holder
|
|
70
|
+
- Locks are released explicitly via `unlock_file` or
|
|
71
|
+
automatically when the session ends
|
|
72
|
+
- `get_locks` shows all active locks with holder identity
|
|
73
|
+
- `write_file` automatically checks lock ownership before writing
|
|
74
|
+
|
|
75
|
+
### 3.4 Authentication
|
|
76
|
+
- On session start, a random secret token is generated
|
|
77
|
+
(cryptographically secure, 32 characters)
|
|
78
|
+
- Every request from the Guest must include this token
|
|
79
|
+
- Invalid tokens are rejected with 401 Unauthorized
|
|
80
|
+
- Token is single-use per session (new token each `letswork start`)
|
|
81
|
+
- No accounts, no signup, no persistent credentials
|
|
82
|
+
|
|
83
|
+
### 3.5 Tunneling
|
|
84
|
+
- Cloudflare Tunnel (via `cloudflared`) creates a public
|
|
85
|
+
HTTPS URL for the local MCP server
|
|
86
|
+
- No port forwarding required
|
|
87
|
+
- No VPN required
|
|
88
|
+
- No DNS configuration required
|
|
89
|
+
- Works behind NATs and firewalls
|
|
90
|
+
- Free tier is sufficient
|
|
91
|
+
- If `cloudflared` is not installed, LetsWork prints
|
|
92
|
+
clear installation instructions and exits
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. CLI Interface
|
|
97
|
+
|
|
98
|
+
### Commands:
|
|
99
|
+
```
|
|
100
|
+
letswork start [--port PORT]
|
|
101
|
+
```
|
|
102
|
+
- Starts MCP server + tunnel in current directory
|
|
103
|
+
- Optional: specify port (default: auto-select available port)
|
|
104
|
+
- Outputs: session URL, secret token, status
|
|
105
|
+
```
|
|
106
|
+
letswork stop
|
|
107
|
+
```
|
|
108
|
+
- Stops the MCP server and closes the tunnel
|
|
109
|
+
- Releases all file locks
|
|
110
|
+
- Ends the session cleanly
|
|
111
|
+
```
|
|
112
|
+
letswork status
|
|
113
|
+
```
|
|
114
|
+
- Shows: running/stopped, connected users, active locks,
|
|
115
|
+
session duration, tunnel URL
|
|
116
|
+
|
|
117
|
+
### Terminal Output on Start:
|
|
118
|
+
```
|
|
119
|
+
╔══════════════════════════════════════════════════╗
|
|
120
|
+
║ LetsWork Session Active ║
|
|
121
|
+
║ ║
|
|
122
|
+
║ URL: https://abc123.trycloudflare.com ║
|
|
123
|
+
║ Token: a1b2c3d4e5f6... ║
|
|
124
|
+
║ ║
|
|
125
|
+
║ Share both with your collaborator. ║
|
|
126
|
+
║ Press Ctrl+C to end session. ║
|
|
127
|
+
╚══════════════════════════════════════════════════╝
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 5. Security Model
|
|
133
|
+
|
|
134
|
+
### Threat Model:
|
|
135
|
+
- The tunnel URL is unguessable (random subdomain from Cloudflare)
|
|
136
|
+
- The secret token adds a second layer of authentication
|
|
137
|
+
- Both are required — URL alone is not enough
|
|
138
|
+
- Token is transmitted out-of-band (Host sends it via
|
|
139
|
+
Slack/Discord/text, not through the tunnel itself)
|
|
140
|
+
- All traffic is encrypted via HTTPS (Cloudflare handles TLS)
|
|
141
|
+
|
|
142
|
+
### What LetsWork does NOT protect against:
|
|
143
|
+
- A malicious Guest who has the valid token
|
|
144
|
+
(they have full read/write access by design)
|
|
145
|
+
- Network-level attacks on Cloudflare's infrastructure
|
|
146
|
+
- Files outside the project directory
|
|
147
|
+
(LetsWork restricts paths to project root — see Section 6)
|
|
148
|
+
|
|
149
|
+
### Path Traversal Prevention:
|
|
150
|
+
- All file paths are resolved relative to the project root
|
|
151
|
+
- Any path containing `..` or resolving outside the project
|
|
152
|
+
directory is rejected with 403 Forbidden
|
|
153
|
+
- Symlinks pointing outside the project directory are rejected
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 6. Constraints & Boundaries
|
|
158
|
+
|
|
159
|
+
### What LetsWork IS:
|
|
160
|
+
- A real-time file collaboration tool for two developers
|
|
161
|
+
- An MCP server that any MCP-compatible client can connect to
|
|
162
|
+
- A lightweight CLI tool with zero configuration
|
|
163
|
+
|
|
164
|
+
### What LetsWork is NOT:
|
|
165
|
+
- Not a version control system (use Git for that)
|
|
166
|
+
- Not a code editor or IDE
|
|
167
|
+
- Not a cloud storage service
|
|
168
|
+
- Not a deployment tool
|
|
169
|
+
- Not a multi-tenant platform (one Host, one Guest per session)
|
|
170
|
+
|
|
171
|
+
### Technical Constraints:
|
|
172
|
+
- Maximum 2 concurrent users per session (Host + Guest)
|
|
173
|
+
- File operations only (no terminal/shell access for Guest)
|
|
174
|
+
- No directory creation/deletion via MCP tools
|
|
175
|
+
(only file read/write/list)
|
|
176
|
+
- No binary file support in v1 (text files only)
|
|
177
|
+
- Maximum file size: 1MB per read/write operation
|
|
178
|
+
- Project root is the directory where `letswork start` was run
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 7. Dependencies
|
|
183
|
+
|
|
184
|
+
| Dependency | Purpose | Required? |
|
|
185
|
+
|------------------|----------------------------|-----------|
|
|
186
|
+
| Python >= 3.10 | Runtime | Yes |
|
|
187
|
+
| mcp SDK | MCP server implementation | Yes |
|
|
188
|
+
| cloudflared | Tunnel creation | Yes (external) |
|
|
189
|
+
| click | CLI framework | Yes |
|
|
190
|
+
| secrets (stdlib) | Token generation | Yes (built-in) |
|
|
191
|
+
| fcntl / msvcrt | File locking | Yes (built-in) |
|
|
192
|
+
| pathlib (stdlib) | Path resolution & safety | Yes (built-in) |
|
|
193
|
+
| git | Conflict safety net | Recommended |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 8. Error Handling Summary
|
|
198
|
+
|
|
199
|
+
| Situation | Behavior |
|
|
200
|
+
|----------------------------------|---------------------------------------|
|
|
201
|
+
| `cloudflared` not installed | Print install instructions, exit |
|
|
202
|
+
| Port already in use | Auto-select next available port |
|
|
203
|
+
| Invalid token from Guest | Reject with 401 Unauthorized |
|
|
204
|
+
| File not found on read | Return clear error message |
|
|
205
|
+
| Path traversal attempt | Reject with 403 Forbidden |
|
|
206
|
+
| File locked by other user | Return error with lock holder info |
|
|
207
|
+
| Write without holding lock | Reject with error |
|
|
208
|
+
| Tunnel connection drops | Attempt reconnect, notify Host |
|
|
209
|
+
| Host stops session | All locks released, Guest disconnected|
|
|
210
|
+
| File exceeds 1MB | Reject with size limit error |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 9. Future Scope (v2+, Not for Current Build)
|
|
215
|
+
|
|
216
|
+
These are explicitly NOT part of v1. Listed here only to
|
|
217
|
+
acknowledge them and prevent scope creep:
|
|
218
|
+
|
|
219
|
+
- Multi-guest support (3+ developers)
|
|
220
|
+
- Directory creation/deletion tools
|
|
221
|
+
- Binary file support
|
|
222
|
+
- Built-in diff/merge viewer
|
|
223
|
+
- Chat between Host and Guest
|
|
224
|
+
- Persistent sessions (survive restarts)
|
|
225
|
+
- Web UI dashboard
|
|
226
|
+
- Access control per file/directory
|
|
227
|
+
- Audit log of all operations
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 10. Success Criteria for v1
|
|
232
|
+
|
|
233
|
+
LetsWork v1 is complete when:
|
|
234
|
+
1. `pip install letswork` works
|
|
235
|
+
2. `letswork start` creates a working MCP server + tunnel
|
|
236
|
+
3. A second developer can connect via the URL + token
|
|
237
|
+
4. Both can read, write, list files through their Claude
|
|
238
|
+
5. File locking prevents simultaneous edits
|
|
239
|
+
6. Path traversal is blocked
|
|
240
|
+
7. Session starts and stops cleanly
|
|
241
|
+
8. Published on PyPI
|
|
242
|
+
9. Listed on the official MCP Registry
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
*Last updated: Session 2 — Full specification written*
|
|
247
|
+
#this is a comment
|