newsblur-cli 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.
Files changed (42) hide show
  1. newsblur_cli-0.1.0/.gitignore +95 -0
  2. newsblur_cli-0.1.0/Dockerfile +13 -0
  3. newsblur_cli-0.1.0/PKG-INFO +122 -0
  4. newsblur_cli-0.1.0/README.md +92 -0
  5. newsblur_cli-0.1.0/newsblur_mcp/__init__.py +0 -0
  6. newsblur_cli-0.1.0/newsblur_mcp/__main__.py +10 -0
  7. newsblur_cli-0.1.0/newsblur_mcp/auth.py +106 -0
  8. newsblur_cli-0.1.0/newsblur_mcp/cli/__init__.py +75 -0
  9. newsblur_cli-0.1.0/newsblur_mcp/cli/auth.py +327 -0
  10. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/__init__.py +0 -0
  11. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/account.py +26 -0
  12. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/actions.py +124 -0
  13. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/auth.py +74 -0
  14. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/discover.py +83 -0
  15. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/feeds.py +140 -0
  16. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/intelligence.py +117 -0
  17. newsblur_cli-0.1.0/newsblur_mcp/cli/commands/stories.py +174 -0
  18. newsblur_cli-0.1.0/newsblur_mcp/cli/output.py +392 -0
  19. newsblur_cli-0.1.0/newsblur_mcp/cli/runner.py +50 -0
  20. newsblur_cli-0.1.0/newsblur_mcp/client.py +65 -0
  21. newsblur_cli-0.1.0/newsblur_mcp/prompts/__init__.py +0 -0
  22. newsblur_cli-0.1.0/newsblur_mcp/prompts/prompts.py +85 -0
  23. newsblur_cli-0.1.0/newsblur_mcp/resources/__init__.py +0 -0
  24. newsblur_cli-0.1.0/newsblur_mcp/resources/resources.py +85 -0
  25. newsblur_cli-0.1.0/newsblur_mcp/server.py +78 -0
  26. newsblur_cli-0.1.0/newsblur_mcp/settings.py +29 -0
  27. newsblur_cli-0.1.0/newsblur_mcp/tools/__init__.py +0 -0
  28. newsblur_cli-0.1.0/newsblur_mcp/tools/account.py +51 -0
  29. newsblur_cli-0.1.0/newsblur_mcp/tools/actions.py +172 -0
  30. newsblur_cli-0.1.0/newsblur_mcp/tools/briefing.py +55 -0
  31. newsblur_cli-0.1.0/newsblur_mcp/tools/classifiers.py +115 -0
  32. newsblur_cli-0.1.0/newsblur_mcp/tools/discovery.py +60 -0
  33. newsblur_cli-0.1.0/newsblur_mcp/tools/feeds.py +300 -0
  34. newsblur_cli-0.1.0/newsblur_mcp/tools/notifications.py +53 -0
  35. newsblur_cli-0.1.0/newsblur_mcp/tools/stories.py +282 -0
  36. newsblur_cli-0.1.0/newsblur_mcp/transforms.py +128 -0
  37. newsblur_cli-0.1.0/newsblur_mcp/ui.py +402 -0
  38. newsblur_cli-0.1.0/pyproject.toml +48 -0
  39. newsblur_cli-0.1.0/tests/__init__.py +0 -0
  40. newsblur_cli-0.1.0/tests/conftest.py +42 -0
  41. newsblur_cli-0.1.0/tests/test_transforms.py +74 -0
  42. newsblur_cli-0.1.0/uv.lock +1482 -0
@@ -0,0 +1,95 @@
1
+ logs/*.log
2
+ logs/*.log.*
3
+ logs/*.pid
4
+ logs/celerybeat-schedule.db
5
+ logs/celerybeat-schedule-*.db
6
+ *.pyc
7
+ backup.db
8
+ __pycache__/
9
+ static/*
10
+ local_settings.py
11
+ celerybeat-schedule
12
+ celerybeat.pid
13
+ media/iphone/NewsBlur/build
14
+ media/iphone/build
15
+ build/
16
+ certbot.conf
17
+ .DS_Store
18
+ **/*.perspectivev*
19
+ .vscode/*
20
+ .env
21
+ task_env.py
22
+ app_env.py
23
+ data/
24
+ api/ip_addresses.txt
25
+ .prom_cache
26
+ config/certificates
27
+ **/*.xcuserstate
28
+ UserInterfaceState.xcuserstate
29
+ UserInterfaceState\.xcuserstate
30
+ *.xcuserstate
31
+ xcuserdata
32
+ .xcodeproj/ push.xcodeproj/project.pbxproj
33
+ clients/ios/NewsBlur.xcodeproj/project.xcworkspace/xcshareddata/
34
+ *.mode1v3
35
+ *.pbxuser
36
+ media/maintenance.html
37
+ media/maintenance.html.unused
38
+ config/settings
39
+ static.tgz
40
+ .sass-cache
41
+ media/css/circular/.sass-cache
42
+ media/css/circular/sass/.sass-cache
43
+ media/css/circular
44
+ config/settings
45
+ config/secrets
46
+ ansible/roles/sentry/vars/secrets.yml
47
+ templates/maintenance_on.html
48
+ apps/social/spam.py
49
+ venv*
50
+ /backup
51
+ /backups
52
+ config/mongodb_keyfile.key
53
+
54
+ # Docker Jinja templates
55
+ docker/haproxy/haproxy.consul.cfg
56
+ # docker/haproxy/haproxy.staging.cfg # Staging doesn't use jinja templates, so no need to ignore
57
+ # Conductor workspace files (auto-generated by .conductor/conductor-setup.sh)
58
+ .conductor/docker-compose.*.yml
59
+ .conductor/haproxy/
60
+ # Ignore all subdirs in .conductor
61
+ .conductor/**/*
62
+
63
+ # Worktree development files (auto-generated by worktree-dev.sh)
64
+ .worktree/
65
+ docker/nginx/nginx.consul.conf
66
+ docker/prometheus/prometheus.yml
67
+ docker/redis/redis_replica.conf
68
+ docker/redis/redis_*_replica.conf
69
+ docker/postgres/postgres.conf
70
+
71
+ # Local configuration file (sdk path, etc)
72
+ /originals
73
+ /node/originals
74
+ media/safari/NewsBlur.safariextz
75
+
76
+ # Discovery cache files (generated by discover_* management commands)
77
+ apps/discover/fixtures/*_cache.json
78
+
79
+ # IDE files
80
+ /docker/volumes/*
81
+
82
+ **/node_modules
83
+ *.tfstate*
84
+ .terraform*
85
+ grafana.ini
86
+ apps/api/ip_addresses.txt
87
+ .ansible/
88
+ .dev_session
89
+ .conductor
90
+ .worktree
91
+ docs
92
+ plans
93
+
94
+ # PyPI build artifacts
95
+ newsblur_mcp/dist/
@@ -0,0 +1,13 @@
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /srv/newsblur_mcp
4
+
5
+ RUN pip install --no-cache-dir uv
6
+
7
+ COPY pyproject.toml .
8
+ COPY newsblur_mcp/ newsblur_mcp/
9
+ RUN uv pip install --system --no-cache .
10
+
11
+ EXPOSE 8099
12
+
13
+ CMD ["python", "-m", "newsblur_mcp"]
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: newsblur-cli
3
+ Version: 0.1.0
4
+ Summary: NewsBlur CLI and MCP server - manage feeds, stories, and classifiers
5
+ Project-URL: Homepage, https://newsblur.com/features/mcp-cli
6
+ Project-URL: Repository, https://github.com/samuelclay/NewsBlur
7
+ Project-URL: Issues, https://github.com/samuelclay/NewsBlur/issues
8
+ License-Expression: MIT
9
+ Keywords: cli,feeds,mcp,news,newsblur,rss
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: beautifulsoup4>=4.12.0
21
+ Requires-Dist: fastmcp>=2.0.0
22
+ Requires-Dist: httpx>=0.27.0
23
+ Requires-Dist: rich>=13.0.0
24
+ Requires-Dist: typer>=0.15.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: respx>=0.22; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # NewsBlur CLI
32
+
33
+ A command-line interface for [NewsBlur](https://newsblur.com), the visual intelligence RSS reader. Read feeds, manage stories, train classifiers, and more from your terminal.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install newsblur-cli
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # Log in (opens your browser for OAuth)
45
+ newsblur auth login
46
+
47
+ # Get your daily briefing
48
+ newsblur briefing
49
+
50
+ # List your feeds
51
+ newsblur feeds list
52
+
53
+ # Read stories from a feed
54
+ newsblur stories river --feed 42
55
+
56
+ # Search stories
57
+ newsblur stories search "machine learning"
58
+
59
+ # Save a story
60
+ newsblur save <story-hash>
61
+
62
+ # Mark stories as read
63
+ newsblur read <story-hash>
64
+ ```
65
+
66
+ ## Commands
67
+
68
+ | Command | Description |
69
+ |---------|-------------|
70
+ | `newsblur auth login` | Log in via OAuth |
71
+ | `newsblur auth logout` | Log out and remove credentials |
72
+ | `newsblur auth status` | Show authentication status |
73
+ | `newsblur briefing` | Daily briefing of top stories |
74
+ | `newsblur stories river` | Read stories from feeds or folders |
75
+ | `newsblur stories search` | Search across stories |
76
+ | `newsblur feeds list` | List subscribed feeds |
77
+ | `newsblur feeds add` | Subscribe to a new feed |
78
+ | `newsblur feeds remove` | Unsubscribe from a feed |
79
+ | `newsblur train show` | View intelligence classifiers |
80
+ | `newsblur train set` | Train a classifier |
81
+ | `newsblur discover` | Find new feeds by topic |
82
+ | `newsblur account` | Show account info |
83
+ | `newsblur save` | Save a story |
84
+ | `newsblur read` | Mark a story as read |
85
+
86
+ ## Self-Hosted
87
+
88
+ For self-hosted NewsBlur instances, pass `--server`:
89
+
90
+ ```bash
91
+ newsblur auth login --server https://nb.example.com
92
+ ```
93
+
94
+ The server URL is persisted to `~/.config/newsblur/config.json`.
95
+
96
+ ## Output Formats
97
+
98
+ ```bash
99
+ # Rich terminal output (default)
100
+ newsblur feeds list
101
+
102
+ # JSON output (for scripting)
103
+ newsblur --json feeds list
104
+
105
+ # Raw text output
106
+ newsblur --raw feeds list
107
+ ```
108
+
109
+ ## MCP Server
110
+
111
+ This package also includes an MCP (Model Context Protocol) server for AI assistants. See [NewsBlur MCP](https://newsblur.com/features/mcp-cli) for details.
112
+
113
+ ## Requirements
114
+
115
+ - Python 3.11+
116
+ - A NewsBlur account (premium required for most features)
117
+
118
+ ## Links
119
+
120
+ - [NewsBlur](https://newsblur.com)
121
+ - [GitHub](https://github.com/samuelclay/NewsBlur)
122
+ - [MCP & CLI Features](https://newsblur.com/features/mcp-cli)
@@ -0,0 +1,92 @@
1
+ # NewsBlur CLI
2
+
3
+ A command-line interface for [NewsBlur](https://newsblur.com), the visual intelligence RSS reader. Read feeds, manage stories, train classifiers, and more from your terminal.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install newsblur-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Log in (opens your browser for OAuth)
15
+ newsblur auth login
16
+
17
+ # Get your daily briefing
18
+ newsblur briefing
19
+
20
+ # List your feeds
21
+ newsblur feeds list
22
+
23
+ # Read stories from a feed
24
+ newsblur stories river --feed 42
25
+
26
+ # Search stories
27
+ newsblur stories search "machine learning"
28
+
29
+ # Save a story
30
+ newsblur save <story-hash>
31
+
32
+ # Mark stories as read
33
+ newsblur read <story-hash>
34
+ ```
35
+
36
+ ## Commands
37
+
38
+ | Command | Description |
39
+ |---------|-------------|
40
+ | `newsblur auth login` | Log in via OAuth |
41
+ | `newsblur auth logout` | Log out and remove credentials |
42
+ | `newsblur auth status` | Show authentication status |
43
+ | `newsblur briefing` | Daily briefing of top stories |
44
+ | `newsblur stories river` | Read stories from feeds or folders |
45
+ | `newsblur stories search` | Search across stories |
46
+ | `newsblur feeds list` | List subscribed feeds |
47
+ | `newsblur feeds add` | Subscribe to a new feed |
48
+ | `newsblur feeds remove` | Unsubscribe from a feed |
49
+ | `newsblur train show` | View intelligence classifiers |
50
+ | `newsblur train set` | Train a classifier |
51
+ | `newsblur discover` | Find new feeds by topic |
52
+ | `newsblur account` | Show account info |
53
+ | `newsblur save` | Save a story |
54
+ | `newsblur read` | Mark a story as read |
55
+
56
+ ## Self-Hosted
57
+
58
+ For self-hosted NewsBlur instances, pass `--server`:
59
+
60
+ ```bash
61
+ newsblur auth login --server https://nb.example.com
62
+ ```
63
+
64
+ The server URL is persisted to `~/.config/newsblur/config.json`.
65
+
66
+ ## Output Formats
67
+
68
+ ```bash
69
+ # Rich terminal output (default)
70
+ newsblur feeds list
71
+
72
+ # JSON output (for scripting)
73
+ newsblur --json feeds list
74
+
75
+ # Raw text output
76
+ newsblur --raw feeds list
77
+ ```
78
+
79
+ ## MCP Server
80
+
81
+ This package also includes an MCP (Model Context Protocol) server for AI assistants. See [NewsBlur MCP](https://newsblur.com/features/mcp-cli) for details.
82
+
83
+ ## Requirements
84
+
85
+ - Python 3.11+
86
+ - A NewsBlur account (premium required for most features)
87
+
88
+ ## Links
89
+
90
+ - [NewsBlur](https://newsblur.com)
91
+ - [GitHub](https://github.com/samuelclay/NewsBlur)
92
+ - [MCP & CLI Features](https://newsblur.com/features/mcp-cli)
File without changes
@@ -0,0 +1,10 @@
1
+ """Entry point for `python -m newsblur_mcp`.
2
+
3
+ Imports from the canonical module to avoid the double-import problem
4
+ that occurs when `python -m` loads the module as __main__ while tool
5
+ sub-modules import from newsblur_mcp.server.
6
+ """
7
+
8
+ from newsblur_mcp.server import main
9
+
10
+ main()
@@ -0,0 +1,106 @@
1
+ """NewsBlur OAuth provider for FastMCP.
2
+
3
+ Proxies authentication through Django's existing OAuth2 infrastructure
4
+ so that MCP clients (Claude Code, Codex, etc.) can authenticate via
5
+ standard OAuth flows without any manual token setup.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import time
12
+
13
+ import httpx
14
+ from fastmcp.server.auth import TokenVerifier
15
+ from fastmcp.server.auth.auth import AccessToken
16
+ from fastmcp.server.auth.oauth_proxy import OAuthProxy
17
+ from pydantic import AnyHttpUrl
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ from newsblur_mcp.settings import (
22
+ MCP_OAUTH_BASE_URL,
23
+ MCP_OAUTH_CLIENT_ID,
24
+ MCP_OAUTH_CLIENT_SECRET,
25
+ MCP_OAUTH_INTERNAL_URL,
26
+ MCP_OAUTH_UPSTREAM_URL,
27
+ )
28
+ from newsblur_mcp.ui import patch_fastmcp_ui
29
+
30
+ # Replace FastMCP's generic OAuth pages with NewsBlur-branded ones
31
+ patch_fastmcp_ui()
32
+
33
+
34
+ class NewsBlurTokenVerifier(TokenVerifier):
35
+ """Validates upstream Django OAuth tokens by calling NewsBlur's API."""
36
+
37
+ def __init__(self, upstream_base_url: str, timeout_seconds: int = 10):
38
+ super().__init__(required_scopes=None)
39
+ self.upstream_base_url = upstream_base_url
40
+ self.timeout_seconds = timeout_seconds
41
+
42
+ async def verify_token(self, token: str) -> AccessToken | None:
43
+ """Verify a Django OAuth access token by calling the user info endpoint."""
44
+ try:
45
+ async with httpx.AsyncClient(timeout=self.timeout_seconds) as client:
46
+ response = await client.get(
47
+ f"{self.upstream_base_url}/oauth/user/info/",
48
+ headers={"Authorization": f"Bearer {token}"},
49
+ )
50
+
51
+ if response.status_code != 200:
52
+ logger.warning("Django token verification failed: status=%d url=%s", response.status_code, self.upstream_base_url)
53
+ return None
54
+
55
+ user_info = response.json()
56
+ logger.info("Token verified for user=%s", user_info.get("user_name", "unknown"))
57
+
58
+ return AccessToken(
59
+ token=token,
60
+ client_id=MCP_OAUTH_CLIENT_ID,
61
+ scopes=["read", "write", "mcp"],
62
+ expires_at=int(time.time()) + (60 * 60 * 24 * 365 * 10),
63
+ claims={
64
+ "sub": str(user_info.get("user_id", "")),
65
+ "username": user_info.get("user_name", ""),
66
+ },
67
+ )
68
+ except Exception as e:
69
+ logger.error("Django token verification error: %s", e)
70
+ return None
71
+
72
+
73
+ class NewsBlurOAuthProvider(OAuthProxy):
74
+ """OAuth provider that proxies to Django's OAuth2 infrastructure."""
75
+
76
+ def __init__(
77
+ self,
78
+ *,
79
+ base_url: AnyHttpUrl | str | None = None,
80
+ upstream_url: str | None = None,
81
+ internal_url: str | None = None,
82
+ client_id: str | None = None,
83
+ client_secret: str | None = None,
84
+ ):
85
+ base_url = base_url or MCP_OAUTH_BASE_URL
86
+ upstream_url = upstream_url or MCP_OAUTH_UPSTREAM_URL
87
+ internal_url = internal_url or MCP_OAUTH_INTERNAL_URL
88
+ client_id = client_id or MCP_OAUTH_CLIENT_ID
89
+ client_secret = client_secret or MCP_OAUTH_CLIENT_SECRET
90
+
91
+ token_verifier = NewsBlurTokenVerifier(upstream_base_url=internal_url)
92
+
93
+ super().__init__(
94
+ # Browser redirect — user sees this URL
95
+ upstream_authorization_endpoint=f"{upstream_url}/oauth/authorize/",
96
+ # Server-to-server — bypasses TLS for self-signed certs in dev
97
+ upstream_token_endpoint=f"{internal_url}/oauth/token/",
98
+ upstream_client_id=client_id,
99
+ upstream_client_secret=client_secret,
100
+ token_verifier=token_verifier,
101
+ base_url=base_url,
102
+ issuer_url=base_url,
103
+ require_authorization_consent=False,
104
+ forward_pkce=False,
105
+ valid_scopes=["read", "write", "mcp"],
106
+ )
@@ -0,0 +1,75 @@
1
+ """NewsBlur CLI - read feeds, manage stories, and train classifiers from your terminal."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ app = typer.Typer(
8
+ name="newsblur",
9
+ help="NewsBlur CLI - read feeds, manage stories, and train classifiers from your terminal.",
10
+ no_args_is_help=True,
11
+ )
12
+
13
+
14
+ @app.callback(invoke_without_command=True)
15
+ def main(
16
+ ctx: typer.Context,
17
+ json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
18
+ raw_output: bool = typer.Option(False, "--raw", help="Output unformatted text"),
19
+ version: bool = typer.Option(False, "--version", "-v", help="Show version"),
20
+ server: str = typer.Option(
21
+ None, "--server", "-s",
22
+ help="NewsBlur server URL (default: https://newsblur.com). Persisted to config.",
23
+ ),
24
+ ):
25
+ """NewsBlur CLI - read feeds, manage stories, and train classifiers."""
26
+ ctx.ensure_object(dict)
27
+ ctx.obj["json"] = json_output
28
+ ctx.obj["raw"] = raw_output
29
+ if server:
30
+ from newsblur_mcp.cli.auth import set_server_url
31
+ set_server_url(server)
32
+ if version:
33
+ try:
34
+ from importlib.metadata import version as get_version
35
+
36
+ typer.echo(f"newsblur {get_version('newsblur-cli')}")
37
+ except Exception:
38
+ typer.echo("newsblur 0.1.0")
39
+ raise typer.Exit()
40
+ if ctx.invoked_subcommand is None:
41
+ typer.echo(ctx.get_help())
42
+ raise typer.Exit()
43
+
44
+
45
+ # Register all command groups
46
+ from newsblur_mcp.cli.commands import account as account_commands
47
+ from newsblur_mcp.cli.commands import actions as actions_commands
48
+ from newsblur_mcp.cli.commands import auth as auth_commands
49
+ from newsblur_mcp.cli.commands import discover as discover_commands
50
+ from newsblur_mcp.cli.commands import feeds as feeds_commands
51
+ from newsblur_mcp.cli.commands import intelligence as intelligence_commands
52
+ from newsblur_mcp.cli.commands import stories as stories_commands
53
+
54
+ app.add_typer(auth_commands.app, name="auth", help="Login, logout, and check auth status")
55
+ app.add_typer(stories_commands.app, name="stories", help="Read, search, and browse stories")
56
+ app.add_typer(feeds_commands.app, name="feeds", help="List, add, and manage feed subscriptions")
57
+ app.add_typer(
58
+ actions_commands.app,
59
+ name="actions",
60
+ help="Mark read, save, unsave, share stories",
61
+ )
62
+ app.add_typer(
63
+ intelligence_commands.app,
64
+ name="train",
65
+ help="View and train intelligence classifiers",
66
+ )
67
+ app.add_typer(discover_commands.app, name="discover", help="Find new feeds by topic or similarity")
68
+
69
+ # Top-level shortcuts for common actions
70
+ app.command("account")(account_commands.account_info)
71
+ app.command("briefing")(stories_commands.briefing)
72
+ app.command("read")(actions_commands.mark_read)
73
+ app.command("save")(actions_commands.save)
74
+ app.command("unsave")(actions_commands.unsave)
75
+ app.command("share")(actions_commands.share)