noscroll 0.1.1__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 (49) hide show
  1. noscroll-0.1.1/.env.example +70 -0
  2. noscroll-0.1.1/.gitignore +22 -0
  3. noscroll-0.1.1/LICENSE +21 -0
  4. noscroll-0.1.1/PKG-INFO +64 -0
  5. noscroll-0.1.1/README.md +45 -0
  6. noscroll-0.1.1/automation/.env.example +4 -0
  7. noscroll-0.1.1/automation/README.md +134 -0
  8. noscroll-0.1.1/automation/__init__.py +4 -0
  9. noscroll-0.1.1/automation/__main__.py +6 -0
  10. noscroll-0.1.1/automation/adapters/__init__.py +98 -0
  11. noscroll-0.1.1/automation/agents.py +253 -0
  12. noscroll-0.1.1/automation/config.py +39 -0
  13. noscroll-0.1.1/automation/evals/__init__.py +110 -0
  14. noscroll-0.1.1/automation/latest +1 -0
  15. noscroll-0.1.1/automation/loop.py +419 -0
  16. noscroll-0.1.1/automation/prompts/diagnostic.txt +24 -0
  17. noscroll-0.1.1/automation/prompts/executor.txt +32 -0
  18. noscroll-0.1.1/automation/prompts/fixer.txt +29 -0
  19. noscroll-0.1.1/automation/tasks/__init__.py +95 -0
  20. noscroll-0.1.1/prompts/system.txt +90 -0
  21. noscroll-0.1.1/pyproject.toml +37 -0
  22. noscroll-0.1.1/src/noscroll/__init__.py +3 -0
  23. noscroll-0.1.1/src/noscroll/__main__.py +5 -0
  24. noscroll-0.1.1/src/noscroll/cli.py +1120 -0
  25. noscroll-0.1.1/src/noscroll/config.py +189 -0
  26. noscroll-0.1.1/src/noscroll/crawler.py +676 -0
  27. noscroll-0.1.1/src/noscroll/duration.py +312 -0
  28. noscroll-0.1.1/src/noscroll/fetch.py +36 -0
  29. noscroll-0.1.1/src/noscroll/hackernews.py +737 -0
  30. noscroll-0.1.1/src/noscroll/llm.py +741 -0
  31. noscroll-0.1.1/src/noscroll/opml.py +76 -0
  32. noscroll-0.1.1/src/noscroll/rss.py +161 -0
  33. noscroll-0.1.1/src/noscroll/runner.py +359 -0
  34. noscroll-0.1.1/src/noscroll/utils.py +88 -0
  35. noscroll-0.1.1/subscriptions/subscriptions.toml +184 -0
  36. noscroll-0.1.1/tests/integration/__init__.py +1 -0
  37. noscroll-0.1.1/tests/integration/conftest.py +47 -0
  38. noscroll-0.1.1/tests/integration/test_cli_config.py +46 -0
  39. noscroll-0.1.1/tests/integration/test_cli_run.py +215 -0
  40. noscroll-0.1.1/tests/integration/test_real_run.py +164 -0
  41. noscroll-0.1.1/tests/test_cli.py +600 -0
  42. noscroll-0.1.1/tests/test_config.py +273 -0
  43. noscroll-0.1.1/tests/test_duration.py +323 -0
  44. noscroll-0.1.1/tests/test_hackernews.py +531 -0
  45. noscroll-0.1.1/tests/test_llm.py +412 -0
  46. noscroll-0.1.1/tests/test_opml.py +293 -0
  47. noscroll-0.1.1/tests/test_rss.py +223 -0
  48. noscroll-0.1.1/tests/test_runner.py +497 -0
  49. noscroll-0.1.1/tests/test_utils.py +182 -0
@@ -0,0 +1,70 @@
1
+ # NoScroll Configuration
2
+ # All environment variables can also be set via CLI arguments.
3
+ # Priority: CLI > Environment Variable > Config File > Default
4
+
5
+ # =============================================================================
6
+ # LLM Configuration (also available as CLI: --llm-api-url, --llm-api-key, etc.)
7
+ # =============================================================================
8
+
9
+ # LLM API Settings
10
+ LLM_API_URL="https://api.openai.com/v1" # CLI: --llm-api-url
11
+ LLM_API_KEY="" # CLI: --llm-api-key
12
+ LLM_MODEL="gpt-4o" # CLI: --llm-model
13
+ LLM_SUMMARY_MODEL="gpt-4o-mini" # CLI: --llm-summary-model
14
+ LLM_API_MODE="chat" # CLI: --llm-api-mode (chat/completions/responses)
15
+
16
+ # LLM Timeout & Concurrency
17
+ LLM_TIMEOUT_MS=600000 # CLI: --llm-timeout
18
+ LLM_GLOBAL_CONCURRENCY=5 # CLI: --llm-concurrency
19
+
20
+ # =============================================================================
21
+ # Paths (also available as CLI arguments)
22
+ # =============================================================================
23
+
24
+ SUBSCRIPTIONS_PATH="subscriptions/subscriptions.toml" # CLI: --subscriptions
25
+ SYSTEM_PROMPT_PATH="prompts/system.txt" # CLI: --system-prompt
26
+ LLM_LOG_PATH="logs/llm-trace.log" # CLI: --llm-log
27
+ FEED_LOG_PATH="logs/feed-items.log" # CLI: --feed-log
28
+ OUTPUT_DIR="outputs"
29
+
30
+ # Config file path (optional - defaults to ~/.config/noscroll/config.toml)
31
+ # NOSCROLL_CONFIG="/path/to/config.toml"
32
+
33
+ # =============================================================================
34
+ # Proxy (uses standard environment variables)
35
+ # =============================================================================
36
+ # NoScroll automatically uses standard proxy environment variables:
37
+ # HTTPS_PROXY, HTTP_PROXY, ALL_PROXY
38
+ # Example:
39
+ # export HTTPS_PROXY="http://127.0.0.1:7890"
40
+ # export ALL_PROXY="socks5://127.0.0.1:1080"
41
+
42
+ # =============================================================================
43
+ # CLI-Specific Options (NOSCROLL_* prefix)
44
+ # These map directly to `noscroll run` arguments
45
+ # =============================================================================
46
+
47
+ # Time Window
48
+ # NOSCROLL_LAST="10d" # CLI: --last (e.g., 10d, 36h, 2w)
49
+ # NOSCROLL_FROM="" # CLI: --from (RFC3339 or YYYY-MM-DD)
50
+ # NOSCROLL_TO="" # CLI: --to (default: now)
51
+
52
+ # Output Splitting
53
+ # NOSCROLL_BUCKET="" # CLI: --bucket (day, hour, or duration)
54
+ # NOSCROLL_NAME_TEMPLATE="{start:%Y-%m-%d}.md" # CLI: --name-template
55
+
56
+ # Output
57
+ # NOSCROLL_OUT="./noscroll.md" # CLI: --out
58
+ # NOSCROLL_FORMAT="markdown" # CLI: --format (markdown/json)
59
+
60
+ # Source Filtering
61
+ # NOSCROLL_SOURCE_TYPES="rss,web,hn" # CLI: --source-types
62
+
63
+ # LLM Request Options
64
+ # NOSCROLL_SERIAL="false" # CLI: --serial
65
+ # NOSCROLL_DELAY=0 # CLI: --delay (ms)
66
+ # NOSCROLL_LANG="en" # CLI: --lang (en, zh, ja, etc.)
67
+
68
+ # Debug
69
+ # NOSCROLL_DEBUG="false" # CLI: --debug
70
+ # DEBUG="false" # Alternative debug flag
@@ -0,0 +1,22 @@
1
+ .env
2
+ node_modules/
3
+ output.json
4
+
5
+ # VS Code
6
+ .vscode/settings.json
7
+
8
+ # Python
9
+ .venv/
10
+ __pycache__/
11
+ *.pyc
12
+ *.pyo
13
+ *.egg-info/
14
+ .pytest_cache/
15
+ htmlcov/
16
+ .coverage
17
+
18
+ # Logs and crawled content
19
+ logs/
20
+ crawled/
21
+ test_outputs/
22
+ outputs/
noscroll-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yuxin
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.
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: noscroll
3
+ Version: 0.1.1
4
+ Summary: Pull, don't scroll. RSS aggregator with LLM-powered summarization.
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: feedparser>=6.0.0
8
+ Requires-Dist: httpx[socks]>=0.27.0
9
+ Requires-Dist: platformdirs>=4.0.0
10
+ Requires-Dist: python-dotenv>=1.0.0
11
+ Requires-Dist: pyyaml>=6.0.0
12
+ Provides-Extra: crawler
13
+ Requires-Dist: crawl4ai>=0.3.0; extra == 'crawler'
14
+ Requires-Dist: pydantic>=2.0.0; extra == 'crawler'
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
17
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # NoScroll - Pull, don't scroll
21
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
22
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
23
+ [![CLI](https://img.shields.io/badge/interface-CLI-black.svg)](https://github.com/zhuanyongxigua/noscroll)
24
+ [![Sources](https://img.shields.io/badge/sources-RSS%20%7C%20Web%20%7C%20HN-orange.svg)](https://github.com/zhuanyongxigua/noscroll)
25
+
26
+ ## What is NoScroll
27
+ NoScroll is a Python CLI that pulls information from RSS feeds, web pages, and Hacker News, then uses an LLM to summarize and rank the most useful items.
28
+
29
+ It is designed for a pull-based reading workflow: define sources once, run on schedule, read only the high-signal digest.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ pipx install noscroll
35
+ ```
36
+
37
+ ## Ask Command
38
+
39
+ Use natural language directly:
40
+
41
+ ```bash
42
+ noscroll --env-file .env ask "Collect content from the past five days, one file per day"
43
+ ```
44
+
45
+ This will generate daily digest files in `outputs/`.
46
+
47
+ Example generated text:
48
+
49
+ ```markdown
50
+ ## AI (3)
51
+ 1) Off Grid: Running text/image/vision models offline on mobile | Value: 4/5 | Type: Practice
52
+ - Conclusion: This open-source project demonstrates on-device multimodal inference on smartphones, with strong privacy and offline usability.
53
+ - Why it matters: On-device AI can reduce privacy risk and cloud inference cost, and is a good fit for offline-first products.
54
+ - Evidence links: https://github.com/alichherawalla/off-grid-mobile
55
+
56
+ ## Other News (2)
57
+ 4) uBlock rule: hide YouTube Shorts with one click | Value: 4/5 | Domain: Tech
58
+
59
+ ## Life & Health (2)
60
+ 6) AI avatars for rural healthcare support | Value: 3/5 | Domain: Health
61
+ ```
62
+
63
+ ## License
64
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,45 @@
1
+ # NoScroll - Pull, don't scroll
2
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
3
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
4
+ [![CLI](https://img.shields.io/badge/interface-CLI-black.svg)](https://github.com/zhuanyongxigua/noscroll)
5
+ [![Sources](https://img.shields.io/badge/sources-RSS%20%7C%20Web%20%7C%20HN-orange.svg)](https://github.com/zhuanyongxigua/noscroll)
6
+
7
+ ## What is NoScroll
8
+ NoScroll is a Python CLI that pulls information from RSS feeds, web pages, and Hacker News, then uses an LLM to summarize and rank the most useful items.
9
+
10
+ It is designed for a pull-based reading workflow: define sources once, run on schedule, read only the high-signal digest.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pipx install noscroll
16
+ ```
17
+
18
+ ## Ask Command
19
+
20
+ Use natural language directly:
21
+
22
+ ```bash
23
+ noscroll --env-file .env ask "Collect content from the past five days, one file per day"
24
+ ```
25
+
26
+ This will generate daily digest files in `outputs/`.
27
+
28
+ Example generated text:
29
+
30
+ ```markdown
31
+ ## AI (3)
32
+ 1) Off Grid: Running text/image/vision models offline on mobile | Value: 4/5 | Type: Practice
33
+ - Conclusion: This open-source project demonstrates on-device multimodal inference on smartphones, with strong privacy and offline usability.
34
+ - Why it matters: On-device AI can reduce privacy risk and cloud inference cost, and is a good fit for offline-first products.
35
+ - Evidence links: https://github.com/alichherawalla/off-grid-mobile
36
+
37
+ ## Other News (2)
38
+ 4) uBlock rule: hide YouTube Shorts with one click | Value: 4/5 | Domain: Tech
39
+
40
+ ## Life & Health (2)
41
+ 6) AI avatars for rural healthcare support | Value: 3/5 | Domain: Health
42
+ ```
43
+
44
+ ## License
45
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,4 @@
1
+ ANTHROPIC_API_KEY=sk-ant-xxx
2
+ ANTHROPIC_BASE_URL="https://<third-party-anthropic-compatible-endpoint>"
3
+ ANTHROPIC_MODEL="claude-3-100k"
4
+ ANTHROPIC_DEFAULT_HAIKU_MODEL="claude-3-100k"
@@ -0,0 +1,134 @@
1
+ # NoScroll Automation Harness
2
+
3
+ Automated **run → test → eval → fix** loop using Claude Agent SDK.
4
+
5
+ This is an **agent harness** layer, separate from business logic in `src/`.
6
+
7
+ ## Directory Structure
8
+
9
+ ```
10
+ automation/
11
+ ├── __init__.py # Package entry
12
+ ├── __main__.py # python -m automation
13
+ ├── loop.py # Main loop: run → test → eval → fix → repeat
14
+ ├── config.py # Configuration
15
+ ├── agents.py # Claude Agent SDK agents (executor, diagnostic, fixer)
16
+ ├── tasks/ # Task/scenario definitions
17
+ │ └── __init__.py # Predefined tasks
18
+ ├── evals/ # Evaluation logic
19
+ │ └── __init__.py # Output validation
20
+ ├── prompts/ # System prompts (file-based for easy editing)
21
+ │ ├── executor.txt # Executor agent prompt
22
+ │ ├── diagnostic.txt # Diagnostic agent prompt
23
+ │ └── fixer.txt # Fixer agent prompt
24
+ ├── adapters/ # External tool adapters
25
+ │ └── __init__.py # pytest, ruff, git adapters
26
+ └── artifacts/ # Loop outputs: logs, diffs, reports
27
+ ```
28
+
29
+ ## The Loop
30
+
31
+ ```
32
+ ┌─────────────────────────────────────────────────────────────┐
33
+ │ Start Task │
34
+ └─────────────────────────────────────────────────────────────┘
35
+
36
+
37
+ ┌─────────────────────────────────────────────────────────────┐
38
+ │ 1. Execute: Translate instruction → Run noscroll command │
39
+ └─────────────────────────────────────────────────────────────┘
40
+
41
+
42
+ ┌─────────────────────────────────────────────────────────────┐
43
+ │ 2. Evaluate: Check output files, validate content │
44
+ └─────────────────────────────────────────────────────────────┘
45
+
46
+
47
+ ┌───────────────┐
48
+ │ Passed? │
49
+ └───────────────┘
50
+ │ │
51
+ Yes │ │ No
52
+ ▼ ▼
53
+ ┌─────────────┐ ┌─────────────────────────────────┐
54
+ │ SUCCESS │ │ 3. Diagnose: Analyze failure │
55
+ │ (break) │ │ (describe phenomena only) │
56
+ └─────────────┘ └─────────────────────────────────┘
57
+
58
+
59
+ ┌─────────────────────────────────┐
60
+ │ 4. Fix: Apply code changes │
61
+ └─────────────────────────────────┘
62
+
63
+
64
+ ┌───────────────┐
65
+ │ Max loops? │
66
+ └───────────────┘
67
+ │ │
68
+ Yes │ │ No
69
+ ▼ ▼
70
+ ┌─────────┐ (back to 1)
71
+ │ FAIL │
72
+ └─────────┘
73
+ ```
74
+
75
+ ## Stop Conditions
76
+
77
+ 1. **Success**: Evaluation passes ✓
78
+ 2. **Max loops**: Reached iteration limit (default: 3)
79
+ 3. **Fix failed**: Fixer couldn't apply changes
80
+
81
+ ## Installation
82
+
83
+ ```bash
84
+ # Claude Agent SDK requires Claude Code CLI
85
+ pip install claude-agent-sdk
86
+ ```
87
+
88
+ ## Usage
89
+
90
+ ```bash
91
+ # List available tasks
92
+ python -m automation --list
93
+
94
+ # Run a specific task
95
+ python -m automation --task basic_run_5d
96
+
97
+ # Run a suite
98
+ python -m automation --suite basic
99
+
100
+ # Custom task
101
+ python -m automation --custom "运行 noscroll,获取过去 3 天的 HN 内容"
102
+
103
+ # Options
104
+ python -m automation --task basic_run_5d --max-loops 5
105
+ python -m automation --task basic_run_5d --quiet
106
+ ```
107
+
108
+ ## Artifacts
109
+
110
+ Each run produces artifacts in `automation/artifacts/<task_name>/`:
111
+
112
+ - `loop_result.json` - Full iteration history
113
+ - Output files from the noscroll command
114
+
115
+ ## Design Principles
116
+
117
+ 1. **Separation of concerns**: This is a harness layer, not business logic
118
+ 2. **File-based prompts**: Easy to edit and version control
119
+ 3. **Adapters for tools**: Clean interface to pytest, git, etc.
120
+ 4. **Artifacts tracking**: Every run produces traceable outputs
121
+
122
+ ## Why Not in `src/`?
123
+
124
+ The `src/` layout convention keeps only publishable package code in `src/`.
125
+ Automation tools, scripts, and harnesses belong at the project root level:
126
+
127
+ ```
128
+ project/
129
+ ├── src/noscroll/ # Business logic (pip installable)
130
+ ├── tests/ # Unit/integration tests
131
+ └── automation/ # Agent harness (development tool)
132
+ ```
133
+
134
+ This keeps import paths clean and separates runtime code from development tooling.
@@ -0,0 +1,4 @@
1
+ """Automation harness for NoScroll using Claude Agent SDK.
2
+
3
+ This package provides an automated run → test → eval → fix loop.
4
+ """
@@ -0,0 +1,6 @@
1
+ """Allow running automation as a module: python -m automation"""
2
+
3
+ from .loop import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,98 @@
1
+ """Adapters for external tools (pytest, git, etc.)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+
11
+ @dataclass
12
+ class CommandResult:
13
+ """Result from running an external command."""
14
+ command: str
15
+ return_code: int
16
+ stdout: str
17
+ stderr: str
18
+ success: bool
19
+
20
+
21
+ def run_command(
22
+ command: list[str],
23
+ cwd: Optional[Path] = None,
24
+ timeout: int = 300,
25
+ ) -> CommandResult:
26
+ """Run a shell command and capture output."""
27
+ cmd_str = " ".join(command)
28
+ try:
29
+ result = subprocess.run(
30
+ command,
31
+ cwd=str(cwd) if cwd else None,
32
+ capture_output=True,
33
+ text=True,
34
+ timeout=timeout,
35
+ )
36
+ return CommandResult(
37
+ command=cmd_str,
38
+ return_code=result.returncode,
39
+ stdout=result.stdout,
40
+ stderr=result.stderr,
41
+ success=result.returncode == 0,
42
+ )
43
+ except subprocess.TimeoutExpired:
44
+ return CommandResult(
45
+ command=cmd_str,
46
+ return_code=-1,
47
+ stdout="",
48
+ stderr=f"Command timed out after {timeout}s",
49
+ success=False,
50
+ )
51
+ except Exception as e:
52
+ return CommandResult(
53
+ command=cmd_str,
54
+ return_code=-1,
55
+ stdout="",
56
+ stderr=str(e),
57
+ success=False,
58
+ )
59
+
60
+
61
+ def run_pytest(
62
+ test_path: Optional[Path] = None,
63
+ cwd: Optional[Path] = None,
64
+ args: Optional[list[str]] = None,
65
+ ) -> CommandResult:
66
+ """Run pytest with optional arguments."""
67
+ command = ["python", "-m", "pytest"]
68
+ if test_path:
69
+ command.append(str(test_path))
70
+ if args:
71
+ command.extend(args)
72
+ return run_command(command, cwd=cwd)
73
+
74
+
75
+ def run_ruff(
76
+ path: Optional[Path] = None,
77
+ cwd: Optional[Path] = None,
78
+ fix: bool = False,
79
+ ) -> CommandResult:
80
+ """Run ruff linter."""
81
+ command = ["python", "-m", "ruff", "check"]
82
+ if fix:
83
+ command.append("--fix")
84
+ if path:
85
+ command.append(str(path))
86
+ else:
87
+ command.append(".")
88
+ return run_command(command, cwd=cwd)
89
+
90
+
91
+ def git_diff(cwd: Optional[Path] = None) -> CommandResult:
92
+ """Get git diff of current changes."""
93
+ return run_command(["git", "diff"], cwd=cwd)
94
+
95
+
96
+ def git_status(cwd: Optional[Path] = None) -> CommandResult:
97
+ """Get git status."""
98
+ return run_command(["git", "status", "--porcelain"], cwd=cwd)