navcli 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,88 @@
1
+ """CSS selector parsing and building utilities."""
2
+
3
+ import re
4
+ from typing import Optional
5
+
6
+
7
+ def parse_selector(selector: str) -> dict:
8
+ """Parse a CSS selector into components.
9
+
10
+ Args:
11
+ selector: CSS selector string
12
+
13
+ Returns:
14
+ Dict with tag, id, classes, attrs
15
+ """
16
+ result = {
17
+ 'tag': None,
18
+ 'id': None,
19
+ 'classes': [],
20
+ 'attrs': {}
21
+ }
22
+
23
+ # Match tag
24
+ tag_match = re.match(r'^([a-zA-Z][a-zA-Z0-9-]*)', selector)
25
+ if tag_match:
26
+ result['tag'] = tag_match.group(1)
27
+ selector = selector[tag_match.end():]
28
+
29
+ # Match id
30
+ id_match = re.match(r'#([a-zA-Z0-9_-]+)', selector)
31
+ if id_match:
32
+ result['id'] = id_match.group(1)
33
+ selector = selector[id_match.end():]
34
+
35
+ # Match classes
36
+ class_matches = re.findall(r'\.([a-zA-Z0-9_-]+)', selector)
37
+ result['classes'] = class_matches
38
+
39
+ # Match attributes
40
+ attr_matches = re.findall(r'\[([^\]]+)\]', selector)
41
+ for attr in attr_matches:
42
+ if '=' in attr:
43
+ key, value = attr.split('=', 1)
44
+ result['attrs'][key] = value.strip('"\'')
45
+ else:
46
+ # Attribute without value (e.g., [disabled])
47
+ result['attrs'][attr] = ''
48
+
49
+ return result
50
+
51
+
52
+ def build_selector(
53
+ tag: Optional[str] = None,
54
+ id: Optional[str] = None,
55
+ classes: Optional[list] = None,
56
+ attrs: Optional[dict] = None,
57
+ text: Optional[str] = None
58
+ ) -> str:
59
+ """Build a CSS selector from components.
60
+
61
+ Args:
62
+ tag: HTML tag name
63
+ id: Element id
64
+ classes: List of class names
65
+ attrs: Dict of attributes
66
+ text: Text content (used for :text pseudo-selector)
67
+
68
+ Returns:
69
+ CSS selector string
70
+ """
71
+ parts = []
72
+
73
+ if tag:
74
+ parts.append(tag)
75
+ if id:
76
+ parts.append(f'#{id}')
77
+ if classes:
78
+ parts.extend(f'.{c}' for c in classes)
79
+ if attrs:
80
+ for k, v in attrs.items():
81
+ if v:
82
+ parts.append(f'[{k}="{v}"]')
83
+ else:
84
+ parts.append(f'[{k}]')
85
+ if text:
86
+ parts.append(f':text-is("{text}")')
87
+
88
+ return ''.join(parts) or '*'
navcli/utils/text.py ADDED
@@ -0,0 +1,17 @@
1
+ """Text processing utilities."""
2
+
3
+
4
+ def parse_key_value(s: str, sep: str = '=') -> tuple:
5
+ """Parse a key-value pair from string.
6
+
7
+ Args:
8
+ s: String in format "key=value"
9
+ sep: Separator character
10
+
11
+ Returns:
12
+ Tuple of (key, value)
13
+ """
14
+ if sep in s:
15
+ key, value = s.split(sep, 1)
16
+ return key.strip(), value.strip()
17
+ return s.strip(), ''
navcli/utils/time.py ADDED
@@ -0,0 +1,46 @@
1
+ """Time and timeout utilities."""
2
+
3
+ import time
4
+ from typing import Callable
5
+
6
+
7
+ def wait_for_condition(
8
+ condition: Callable[[], bool],
9
+ timeout: float = 10.0,
10
+ interval: float = 0.1,
11
+ error_msg: str = "Condition not met"
12
+ ) -> bool:
13
+ """Wait for a condition to become true.
14
+
15
+ Args:
16
+ condition: Callable that returns True when condition is met
17
+ timeout: Maximum time to wait in seconds
18
+ interval: Polling interval in seconds
19
+ error_msg: Error message for timeout
20
+
21
+ Returns:
22
+ True if condition was met
23
+
24
+ Raises:
25
+ TimeoutError: If condition is not met within timeout
26
+ """
27
+ start = time.time()
28
+ while time.time() - start < timeout:
29
+ if condition():
30
+ return True
31
+ time.sleep(interval)
32
+ raise TimeoutError(f"{error_msg} within {timeout}s")
33
+
34
+
35
+ def format_timeout(seconds: float) -> str:
36
+ """Format seconds into human readable timeout.
37
+
38
+ Args:
39
+ seconds: Time in seconds
40
+
41
+ Returns:
42
+ Formatted string like "3s" or "500ms"
43
+ """
44
+ if seconds >= 1:
45
+ return f"{int(seconds)}s" if seconds == int(seconds) else f"{seconds}s"
46
+ return f"{int(seconds * 1000)}ms"
navcli/utils/url.py ADDED
@@ -0,0 +1,16 @@
1
+ """URL utilities."""
2
+
3
+
4
+ def normalize_url(url: str) -> str:
5
+ """Normalize a URL.
6
+
7
+ Args:
8
+ url: URL to normalize
9
+
10
+ Returns:
11
+ Normalized URL with scheme
12
+ """
13
+ url = url.strip()
14
+ if not url.startswith(('http://', 'https://')):
15
+ url = 'https://' + url
16
+ return url
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: navcli
3
+ Version: 0.1.0
4
+ Summary: 可交互、可探索的浏览器命令行工具,专为 AI Agent 设计
5
+ Author: NavCLI Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/navcli/navcli
8
+ Project-URL: Repository, https://github.com/navcli/navcli
9
+ Project-URL: Issues, https://github.com/navcli/navcli/issues
10
+ Keywords: browser,cli,automation,ai-agent,playwright
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: playwright>=1.40.0
22
+ Requires-Dist: cmd2>=2.4.0
23
+ Requires-Dist: fastapi>=0.109.0
24
+ Requires-Dist: uvicorn<1.0.0,>=0.25.0
25
+ Requires-Dist: pydantic>=2.5.0
26
+ Requires-Dist: cssify>=1.0.0
27
+ Requires-Dist: aiohttp>=3.9.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
30
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
32
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
33
+
34
+ # NavCLI - 目标与愿景
35
+
36
+ ## 核心目标
37
+
38
+ **让 AI Agent 能够像人类一样浏览网页。**
39
+
40
+ 现有方案(HTTP API、无头浏览器脚本、Playwright MCP)存在局限:
41
+ - 不支持 JS 渲染的 SPA
42
+ - 无会话状态持久化
43
+ - 缺乏交互式探索能力
44
+
45
+ NavCLI 的定位:**可交互、可探索的浏览器命令行工具**
46
+
47
+ ## 核心价值
48
+
49
+ | 特性 | NavCLI 解决的问题 |
50
+ |------|------------------|
51
+ | JS 渲染支持 | 完整支持 SPA 应用 |
52
+ | 会话持久化 | cookies、session 保持 |
53
+ | 交互式 CLI | Agent 可边探索边操作 |
54
+ | Token 优化 | 轻量 elements + 按需 text/html |
55
+
56
+ ## 典型工作流
57
+
58
+ ```bash
59
+ > g https://example.com # 导航
60
+ > elements # 查看可操作元素
61
+ > c .btn-login # 点击登录
62
+ > t #email "test@example.com" # 输入邮箱
63
+ > t #password "123456" # 输入密码
64
+ > c button[type="submit"] # 提交
65
+ > text # 确认结果
66
+ ```
67
+
68
+ Agent 可以:**导航 → 观察 → 交互 → 反馈 → 继续**
69
+
70
+ ## 愿景
71
+
72
+ 成为 AI Agent 的**标准浏览器交互层**,让任何 Agent 都能通过命令式界面控制浏览器,完成:
73
+ - 表单填写、登录认证
74
+ - 信息抓取、内容探索
75
+ - 复杂多步业务流程
76
+
77
+ ## 相关文档
78
+
79
+ - [PRD 产品需求文档](./NAVCLI_PRD.md)
@@ -0,0 +1,39 @@
1
+ navcli/cli/__init__.py,sha256=SE3EeeJ5Hku0o_rbXHVG3cPDxZaLkrLVcH57wFJOtXI,117
2
+ navcli/cli/app.py,sha256=wYEVsUjp4BE_MJDu2v75av1bT5x7EvF4_ssdpH2Q2VQ,3072
3
+ navcli/cli/client.py,sha256=qYdMNMLnXlsMq0bkKJgAiXHpxnqTErehjAF0GRRAG7E,9138
4
+ navcli/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ navcli/cli/commands/base.py,sha256=b8hsB43lUaD0b-5nErtunBGPOFPfKq_sdFEMmynjFRE,1596
6
+ navcli/cli/commands/control.py,sha256=JbAUNXgxZFmEO96TbhtS2CHw2jH2HZUk3EOIU_RwUBY,3253
7
+ navcli/cli/commands/explore.py,sha256=p1p4i2Z-GISmFIikdpQ0mEnpPArSKVA3OIo1Xu42Njg,3664
8
+ navcli/cli/commands/interaction.py,sha256=39CSeWW-Sb_z1w3i2kkmvJ515DL7WLdJPGCwUpri4aQ,3701
9
+ navcli/cli/commands/navigation.py,sha256=ksIyoaCFzkF076C4K8qbl4OI-15UWOjhd47Hm7dGAkI,2265
10
+ navcli/cli/commands/query.py,sha256=zWp-tiCr6xGeIlrsaK1ufoBOTk5L5n8CzGtkIkcnRAs,4630
11
+ navcli/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ navcli/core/models/__init__.py,sha256=-LEFzktT5xBaOMa_UXlNR_c0O9gVTfv5Bp-3IZb43Q4,295
13
+ navcli/core/models/dom.py,sha256=vZzEpzLEOlCnUJWBWx0-gmHj8WBxorsnPsZJgUW3UOE,1160
14
+ navcli/core/models/element.py,sha256=6L8PnY7h_-_MFweq6NU6gdG1g5_r_AHlW1bYTuUBNoM,747
15
+ navcli/core/models/feedback.py,sha256=K67nc7fCiAAfeYKDppNEnHtM41fSdF-Ai7IMhvAv7PY,689
16
+ navcli/core/models/state.py,sha256=iyn6GvGpONGRP6WwiE0PYird4owJleK03Gk-jWExfGg,657
17
+ navcli/server/__init__.py,sha256=l7As1ZOyGRO_BX5agyhirw8XNm8Jc6qDez0ugAwXFoA,1795
18
+ navcli/server/app.py,sha256=zNWYPGRGVWmoyiZX1g9i6LNqSagJirwzEi8eqsYUUqs,1320
19
+ navcli/server/browser.py,sha256=XEqTUFgw7fLQDIjdahYn1dDDu-IlpaggEiUbH5divjk,10604
20
+ navcli/server/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ navcli/server/routes/__init__.py,sha256=LCd1lCcBCqJmFchr9OTvKJvCTQNLETAljdPaou-fd3I,180
22
+ navcli/server/routes/control.py,sha256=ysoszU2OjhVH4BW3oahJ5-28l3yheQ-vhdqI-I8EVEU,1071
23
+ navcli/server/routes/explore.py,sha256=7leV1LgTLurqDnLIOhDJhA5XEEGBxHkUOV8SgHJxYxc,11862
24
+ navcli/server/routes/interaction.py,sha256=Daq-6Rp7FlWjyMK-HHYGKwQLHDBYWzC0GLq1q346Zxw,8562
25
+ navcli/server/routes/navigation.py,sha256=ZDcblrKnOTDn8aeBKwWgZYHQke6Hmrm_NNtTNSC3MQU,3440
26
+ navcli/server/routes/query.py,sha256=Lgp9lA7lUgXsxvI_1G-fymkIkiCwKpTFOW7SB_NWVJk,7699
27
+ navcli/server/routes/session.py,sha256=gzwQKm8CkJTXcm6-5IYfzm44JF50l7Vhk-0SaLAt754,4701
28
+ navcli/utils/__init__.py,sha256=g3CIjq_xybpmanja9A7oyQMtf9emS6qdHJIJ6BJx6t0,521
29
+ navcli/utils/image.py,sha256=e30zNrOd3wi9UWesiQZJ5n62wIOfrGZnJFCMFKnFOD0,681
30
+ navcli/utils/js.py,sha256=-OenTrj4Nh4s-zjUvjJnN3XSncX8b03tNrgY_nhPRxo,334
31
+ navcli/utils/selector.py,sha256=E_8E5bjjL1gDn524l3bH3XzK3egn9IyoYEb7zXsTwTQ,2146
32
+ navcli/utils/text.py,sha256=wCFCKCn6Ibi8GjAeGXBHT5CfS8BCs7X9qVxDq4eR1tE,389
33
+ navcli/utils/time.py,sha256=bAjJ59rp02MkIfoCT1TzmFd_oGaGlOI-5RPiKSqT-kI,1181
34
+ navcli/utils/url.py,sha256=UG71w8ArYJlqjl2XslwAN-Kk9GJOPK3k5fg6CxXE5js,301
35
+ navcli-0.1.0.dist-info/METADATA,sha256=WyNy3vFZJjHwSR5RYa6rQZYbMR5LhYbepmg4rkztBvA,2607
36
+ navcli-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
37
+ navcli-0.1.0.dist-info/entry_points.txt,sha256=J26RwbEYUO5UbuhH0aXF_tvQFCOCatAR80blaSn5oWk,72
38
+ navcli-0.1.0.dist-info/top_level.txt,sha256=dwTN5Lw7STNP3zhL2-RDElX3EslzM2sbYYLfuszQSO4,7
39
+ navcli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ nav = navcli.cli:main
3
+ nav-server = navcli.server:main
@@ -0,0 +1 @@
1
+ navcli