lushly-botcore 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.
- lushly_botcore-0.1.0/.gitignore +28 -0
- lushly_botcore-0.1.0/PKG-INFO +35 -0
- lushly_botcore-0.1.0/pyproject.toml +98 -0
- lushly_botcore-0.1.0/src/botcore/__init__.py +46 -0
- lushly_botcore-0.1.0/src/botcore/commands/__init__.py +70 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/__init__.py +104 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/core.py +256 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/diagnostics.py +179 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/forms.py +207 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/inspection.py +211 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/interaction.py +300 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/launcher.py +198 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp/navigation.py +59 -0
- lushly_botcore-0.1.0/src/botcore/commands/cdp_perf.py +291 -0
- lushly_botcore-0.1.0/src/botcore/commands/dev/__init__.py +48 -0
- lushly_botcore-0.1.0/src/botcore/commands/dev/analysis.py +359 -0
- lushly_botcore-0.1.0/src/botcore/commands/dev/core.py +153 -0
- lushly_botcore-0.1.0/src/botcore/commands/dev/portability.py +206 -0
- lushly_botcore-0.1.0/src/botcore/commands/dev/quality.py +331 -0
- lushly_botcore-0.1.0/src/botcore/commands/docs.py +328 -0
- lushly_botcore-0.1.0/src/botcore/commands/info.py +90 -0
- lushly_botcore-0.1.0/src/botcore/commands/research.py +115 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/__init__.py +28 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/_discovery.py +99 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/adopt.py +64 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/frontmatter.py +137 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/index.py +76 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/lint.py +227 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/list.py +63 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/seed.py +141 -0
- lushly_botcore-0.1.0/src/botcore/commands/skill/status.py +117 -0
- lushly_botcore-0.1.0/src/botcore/commands/spec.py +160 -0
- lushly_botcore-0.1.0/src/botcore/commands/undo.py +88 -0
- lushly_botcore-0.1.0/src/botcore/config.py +211 -0
- lushly_botcore-0.1.0/src/botcore/docs.py +122 -0
- lushly_botcore-0.1.0/src/botcore/plugin.py +111 -0
- lushly_botcore-0.1.0/src/botcore/registry.py +53 -0
- lushly_botcore-0.1.0/src/botcore/server.py +204 -0
- lushly_botcore-0.1.0/src/botcore/skills/__init__.py +0 -0
- lushly_botcore-0.1.0/src/botcore/skills/accessibility/SKILL.md +106 -0
- lushly_botcore-0.1.0/src/botcore/skills/accessibility/references/aria-patterns.md +141 -0
- lushly_botcore-0.1.0/src/botcore/skills/accessibility/references/keyboard-nav.md +128 -0
- lushly_botcore-0.1.0/src/botcore/skills/accessibility/references/wcag-guidelines.md +65 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/README.md +73 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/SKILL.md +130 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/examples/sample-commands/commit.md +51 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/examples/sample-commands/review.md +57 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/references/commands-specification.md +110 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/references/context-injection.md +110 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/references/format.md +69 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/references/hooks.md +112 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/references/namespacing.md +87 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/references/permissions.md +127 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/references/variables.md +95 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/templates/advanced.template.md +91 -0
- lushly_botcore-0.1.0/src/botcore/skills/command-manager/templates/command.template.md +40 -0
- lushly_botcore-0.1.0/src/botcore/skills/doc-management/SKILL.md +138 -0
- lushly_botcore-0.1.0/src/botcore/skills/doc-management/references/docsify-setup.md +119 -0
- lushly_botcore-0.1.0/src/botcore/skills/doc-management/references/index-generation.md +172 -0
- lushly_botcore-0.1.0/src/botcore/skills/documentation/SKILL.md +215 -0
- lushly_botcore-0.1.0/src/botcore/skills/duplicates/SKILL.md +254 -0
- lushly_botcore-0.1.0/src/botcore/skills/humanize-content/SKILL.md +155 -0
- lushly_botcore-0.1.0/src/botcore/skills/humanize-content/references/remediation.md +270 -0
- lushly_botcore-0.1.0/src/botcore/skills/humanize-content/references/syntax.md +233 -0
- lushly_botcore-0.1.0/src/botcore/skills/humanize-content/references/vocabulary.md +184 -0
- lushly_botcore-0.1.0/src/botcore/skills/i18n/SKILL.md +173 -0
- lushly_botcore-0.1.0/src/botcore/skills/infrastructure/SKILL.md +172 -0
- lushly_botcore-0.1.0/src/botcore/skills/licensing/SKILL.md +143 -0
- lushly_botcore-0.1.0/src/botcore/skills/modern-css/SKILL.md +88 -0
- lushly_botcore-0.1.0/src/botcore/skills/modern-css/references/css-frontier.md +216 -0
- lushly_botcore-0.1.0/src/botcore/skills/observability/SKILL.md +141 -0
- lushly_botcore-0.1.0/src/botcore/skills/performance/SKILL.md +96 -0
- lushly_botcore-0.1.0/src/botcore/skills/performance/references/bundle-optimization.md +116 -0
- lushly_botcore-0.1.0/src/botcore/skills/performance/references/core-web-vitals.md +116 -0
- lushly_botcore-0.1.0/src/botcore/skills/performance/references/lazy-loading.md +143 -0
- lushly_botcore-0.1.0/src/botcore/skills/persona-creator/SKILL.md +99 -0
- lushly_botcore-0.1.0/src/botcore/skills/problem-solver/SKILL.md +97 -0
- lushly_botcore-0.1.0/src/botcore/skills/problem-solver/references/debugging-strategies.md +123 -0
- lushly_botcore-0.1.0/src/botcore/skills/problem-solver/references/error-patterns.md +143 -0
- lushly_botcore-0.1.0/src/botcore/skills/project-management/SKILL.md +134 -0
- lushly_botcore-0.1.0/src/botcore/skills/project-management/references/cli-commands.md +76 -0
- lushly_botcore-0.1.0/src/botcore/skills/project-management/references/feature-lifecycle.md +69 -0
- lushly_botcore-0.1.0/src/botcore/skills/project-management/references/issue-templates.md +93 -0
- lushly_botcore-0.1.0/src/botcore/skills/project-management/references/spec-review-workflow.md +106 -0
- lushly_botcore-0.1.0/src/botcore/skills/project-management/references/status-tracking.md +94 -0
- lushly_botcore-0.1.0/src/botcore/skills/project-management/references/wave-orchestration.md +127 -0
- lushly_botcore-0.1.0/src/botcore/skills/researcher/SKILL.md +94 -0
- lushly_botcore-0.1.0/src/botcore/skills/researcher/references/azure.md +115 -0
- lushly_botcore-0.1.0/src/botcore/skills/researcher/references/fast-element.md +68 -0
- lushly_botcore-0.1.0/src/botcore/skills/researcher/references/fluent-ui.md +90 -0
- lushly_botcore-0.1.0/src/botcore/skills/reviewer/SKILL.md +408 -0
- lushly_botcore-0.1.0/src/botcore/skills/security/SKILL.md +327 -0
- lushly_botcore-0.1.0/src/botcore/skills/security/references/owasp-top-10.md +14 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/README.md +61 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/SKILL.md +187 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/proposals/v2.1-upgrades.md +362 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/architecture.md +198 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/descriptions.md +162 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/distributed-skills.md +74 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/format.md +160 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/linter-rules.md +254 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/reference-files.md +242 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/routing.md +173 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/references/skills-specification.md +227 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/templates/reference.template.md +41 -0
- lushly_botcore-0.1.0/src/botcore/skills/skill-manager/templates/skill.template.md +105 -0
- lushly_botcore-0.1.0/src/botcore/skills/spec-writer/SKILL.md +213 -0
- lushly_botcore-0.1.0/src/botcore/skills/testing/SKILL.md +98 -0
- lushly_botcore-0.1.0/src/botcore/skills/testing/references/mocking.md +175 -0
- lushly_botcore-0.1.0/src/botcore/skills/testing/references/playwright-e2e.md +154 -0
- lushly_botcore-0.1.0/src/botcore/skills/testing/references/vitest-patterns.md +129 -0
- lushly_botcore-0.1.0/src/botcore/utils/__init__.py +25 -0
- lushly_botcore-0.1.0/src/botcore/utils/runner.py +93 -0
- lushly_botcore-0.1.0/src/botcore/utils/workspace.py +162 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.venv/
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.egg-info/
|
|
6
|
+
|
|
7
|
+
# Build outputs
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.tsbuildinfo
|
|
11
|
+
.turbo/
|
|
12
|
+
|
|
13
|
+
# Test / coverage
|
|
14
|
+
coverage/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
htmlcov/
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.idea/
|
|
20
|
+
*.sw?
|
|
21
|
+
|
|
22
|
+
# OS
|
|
23
|
+
.DS_Store
|
|
24
|
+
Thumbs.db
|
|
25
|
+
|
|
26
|
+
# Environment
|
|
27
|
+
.env
|
|
28
|
+
.env.local
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lushly-botcore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared bot infrastructure — config, plugin contract, and extracted commands
|
|
5
|
+
Author: Falkicon
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: agent,botcore,cli,mcp,plugins
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: afd>=0.1.0
|
|
17
|
+
Requires-Dist: click>=8.1.0
|
|
18
|
+
Requires-Dist: pydantic>=2.5.0
|
|
19
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
Requires-Dist: botcore[cdp,mcp,quality,research]; extra == 'all'
|
|
22
|
+
Provides-Extra: cdp
|
|
23
|
+
Requires-Dist: httpx>=0.24.0; extra == 'cdp'
|
|
24
|
+
Requires-Dist: playwright>=1.40.0; extra == 'cdp'
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
|
|
32
|
+
Provides-Extra: quality
|
|
33
|
+
Requires-Dist: httpx>=0.24.0; extra == 'quality'
|
|
34
|
+
Provides-Extra: research
|
|
35
|
+
Requires-Dist: google-genai>=0.3.0; extra == 'research'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "lushly-botcore"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Shared bot infrastructure — config, plugin contract, and extracted commands"
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "Falkicon" }]
|
|
8
|
+
keywords = ["botcore", "cli", "mcp", "plugins", "agent"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Environment :: Console",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
dependencies = [
|
|
20
|
+
"afd>=0.1.0",
|
|
21
|
+
"pydantic>=2.5.0",
|
|
22
|
+
"click>=8.1.0",
|
|
23
|
+
"pyyaml>=6.0.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest>=8.0.0",
|
|
29
|
+
"pytest-asyncio>=0.23.0",
|
|
30
|
+
"pytest-cov>=4.0.0",
|
|
31
|
+
"ruff>=0.4.0",
|
|
32
|
+
]
|
|
33
|
+
cdp = [
|
|
34
|
+
"playwright>=1.40.0",
|
|
35
|
+
"httpx>=0.24.0",
|
|
36
|
+
]
|
|
37
|
+
research = [
|
|
38
|
+
"google-genai>=0.3.0",
|
|
39
|
+
]
|
|
40
|
+
quality = [
|
|
41
|
+
"httpx>=0.24.0",
|
|
42
|
+
]
|
|
43
|
+
mcp = [
|
|
44
|
+
"mcp>=1.0.0",
|
|
45
|
+
]
|
|
46
|
+
all = [
|
|
47
|
+
"botcore[cdp,research,quality,mcp]",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.entry-points."botcore.plugins"]
|
|
51
|
+
# Plugins register here, e.g.:
|
|
52
|
+
# lushbot = "lushbot_plugin:LushbotPlugin"
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["hatchling"]
|
|
56
|
+
build-backend = "hatchling.build"
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/botcore"]
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build]
|
|
62
|
+
include = ["src/botcore/**/*.py", "src/botcore/skills/**/*.md"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
66
|
+
target-version = "py311"
|
|
67
|
+
|
|
68
|
+
[tool.ruff.lint]
|
|
69
|
+
select = [
|
|
70
|
+
"E", "F", "I", "UP",
|
|
71
|
+
"C901",
|
|
72
|
+
"PLR0912", "PLR0915", "PLR0913",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
[tool.ruff.lint.mccabe]
|
|
76
|
+
max-complexity = 20
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint.pylint]
|
|
79
|
+
max-args = 8
|
|
80
|
+
max-branches = 20
|
|
81
|
+
max-statements = 80
|
|
82
|
+
|
|
83
|
+
[tool.pytest.ini_options]
|
|
84
|
+
asyncio_mode = "auto"
|
|
85
|
+
testpaths = ["tests"]
|
|
86
|
+
|
|
87
|
+
[tool.coverage.run]
|
|
88
|
+
source = ["src/botcore"]
|
|
89
|
+
branch = true
|
|
90
|
+
omit = ["*/tests/*", "*/__pycache__/*"]
|
|
91
|
+
|
|
92
|
+
[tool.coverage.report]
|
|
93
|
+
show_missing = true
|
|
94
|
+
exclude_lines = [
|
|
95
|
+
"pragma: no cover",
|
|
96
|
+
"if TYPE_CHECKING:",
|
|
97
|
+
"raise NotImplementedError",
|
|
98
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Botcore — shared bot infrastructure for config, plugins, and commands."""
|
|
2
|
+
|
|
3
|
+
from botcore.config import (
|
|
4
|
+
BotCoreConfig,
|
|
5
|
+
EnvConfig,
|
|
6
|
+
PackageOverrideConfig,
|
|
7
|
+
SkillsConfig,
|
|
8
|
+
get_config_for_path,
|
|
9
|
+
load_config,
|
|
10
|
+
)
|
|
11
|
+
from botcore.plugin import (
|
|
12
|
+
BotCorePlugin,
|
|
13
|
+
PluginRegistry,
|
|
14
|
+
discover_plugins,
|
|
15
|
+
)
|
|
16
|
+
from botcore.registry import (
|
|
17
|
+
get_client,
|
|
18
|
+
registry,
|
|
19
|
+
reset_client,
|
|
20
|
+
)
|
|
21
|
+
from botcore.server import build_docs, build_namespace, create_mcp_server
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"__version__",
|
|
27
|
+
# Config
|
|
28
|
+
"BotCoreConfig",
|
|
29
|
+
"SkillsConfig",
|
|
30
|
+
"PackageOverrideConfig",
|
|
31
|
+
"EnvConfig",
|
|
32
|
+
"load_config",
|
|
33
|
+
"get_config_for_path",
|
|
34
|
+
# Plugin
|
|
35
|
+
"PluginRegistry",
|
|
36
|
+
"BotCorePlugin",
|
|
37
|
+
"discover_plugins",
|
|
38
|
+
# Registry
|
|
39
|
+
"registry",
|
|
40
|
+
"get_client",
|
|
41
|
+
"reset_client",
|
|
42
|
+
# Server
|
|
43
|
+
"build_namespace",
|
|
44
|
+
"build_docs",
|
|
45
|
+
"create_mcp_server",
|
|
46
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Botcore command re-exports."""
|
|
2
|
+
|
|
3
|
+
# Dev commands re-exported from subpackage
|
|
4
|
+
from botcore.commands.dev import (
|
|
5
|
+
dev_build,
|
|
6
|
+
dev_check_coverage,
|
|
7
|
+
dev_check_deps,
|
|
8
|
+
dev_check_paths,
|
|
9
|
+
dev_check_size,
|
|
10
|
+
dev_circular_imports,
|
|
11
|
+
dev_dead_code,
|
|
12
|
+
dev_dep_graph,
|
|
13
|
+
dev_lint,
|
|
14
|
+
dev_skill_lint,
|
|
15
|
+
dev_test,
|
|
16
|
+
dev_unused_deps,
|
|
17
|
+
)
|
|
18
|
+
from botcore.commands.docs import docs_check_agents, docs_check_changelog, docs_lint
|
|
19
|
+
from botcore.commands.info import info_env, info_scripts, info_workspace
|
|
20
|
+
from botcore.commands.research import research_query
|
|
21
|
+
from botcore.commands.skill import (
|
|
22
|
+
skill_adopt,
|
|
23
|
+
skill_index,
|
|
24
|
+
skill_lint,
|
|
25
|
+
skill_list,
|
|
26
|
+
skill_seed,
|
|
27
|
+
skill_status,
|
|
28
|
+
)
|
|
29
|
+
from botcore.commands.spec import spec_create, spec_status, spec_validate
|
|
30
|
+
from botcore.commands.undo import undo_clear, undo_status
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
# Info
|
|
34
|
+
"info_workspace",
|
|
35
|
+
"info_env",
|
|
36
|
+
"info_scripts",
|
|
37
|
+
# Dev
|
|
38
|
+
"dev_lint",
|
|
39
|
+
"dev_test",
|
|
40
|
+
"dev_build",
|
|
41
|
+
"dev_skill_lint",
|
|
42
|
+
"dev_check_size",
|
|
43
|
+
"dev_check_coverage",
|
|
44
|
+
"dev_check_deps",
|
|
45
|
+
"dev_dead_code",
|
|
46
|
+
"dev_circular_imports",
|
|
47
|
+
"dev_unused_deps",
|
|
48
|
+
"dev_dep_graph",
|
|
49
|
+
"dev_check_paths",
|
|
50
|
+
# Skill registry
|
|
51
|
+
"skill_seed",
|
|
52
|
+
"skill_list",
|
|
53
|
+
"skill_status",
|
|
54
|
+
"skill_lint",
|
|
55
|
+
"skill_adopt",
|
|
56
|
+
"skill_index",
|
|
57
|
+
# Docs
|
|
58
|
+
"docs_lint",
|
|
59
|
+
"docs_check_changelog",
|
|
60
|
+
"docs_check_agents",
|
|
61
|
+
# Research
|
|
62
|
+
"research_query",
|
|
63
|
+
# Spec
|
|
64
|
+
"spec_create",
|
|
65
|
+
"spec_status",
|
|
66
|
+
"spec_validate",
|
|
67
|
+
# Undo
|
|
68
|
+
"undo_status",
|
|
69
|
+
"undo_clear",
|
|
70
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Botcore CDP commands — Chrome debug utilities.
|
|
2
|
+
|
|
3
|
+
Modules:
|
|
4
|
+
- core.py: Shared utilities, session management, constants
|
|
5
|
+
- launcher.py: Browser launch, attach, close
|
|
6
|
+
- navigation.py: Navigate, wait
|
|
7
|
+
- interaction.py: Click, hover, scroll, drag, type, press
|
|
8
|
+
- inspection.py: Inspect, query, snapshot
|
|
9
|
+
- forms.py: Fill, upload, dialogs, page management
|
|
10
|
+
- diagnostics.py: Screenshot, console, network, eval, emulate
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from botcore.commands.cdp.core import (
|
|
14
|
+
DEEP_QUERY_SINGLE_JS,
|
|
15
|
+
DEFAULT_TIMEOUT_MS,
|
|
16
|
+
CdpSession,
|
|
17
|
+
ConsoleEntry,
|
|
18
|
+
)
|
|
19
|
+
from botcore.commands.cdp.diagnostics import (
|
|
20
|
+
cdp_console,
|
|
21
|
+
cdp_emulate,
|
|
22
|
+
cdp_eval,
|
|
23
|
+
cdp_get_console_message,
|
|
24
|
+
cdp_get_network,
|
|
25
|
+
cdp_list_network,
|
|
26
|
+
cdp_screenshot,
|
|
27
|
+
)
|
|
28
|
+
from botcore.commands.cdp.forms import (
|
|
29
|
+
cdp_close_page,
|
|
30
|
+
cdp_fill,
|
|
31
|
+
cdp_fill_form,
|
|
32
|
+
cdp_handle_dialog,
|
|
33
|
+
cdp_list_pages,
|
|
34
|
+
cdp_new_page,
|
|
35
|
+
cdp_resize,
|
|
36
|
+
cdp_select_page,
|
|
37
|
+
cdp_upload,
|
|
38
|
+
)
|
|
39
|
+
from botcore.commands.cdp.inspection import (
|
|
40
|
+
cdp_inspect,
|
|
41
|
+
cdp_query,
|
|
42
|
+
cdp_snapshot,
|
|
43
|
+
)
|
|
44
|
+
from botcore.commands.cdp.interaction import (
|
|
45
|
+
cdp_click,
|
|
46
|
+
cdp_drag,
|
|
47
|
+
cdp_hover,
|
|
48
|
+
cdp_press,
|
|
49
|
+
cdp_scroll,
|
|
50
|
+
cdp_type,
|
|
51
|
+
)
|
|
52
|
+
from botcore.commands.cdp.launcher import (
|
|
53
|
+
cdp_attach,
|
|
54
|
+
cdp_close,
|
|
55
|
+
cdp_launch,
|
|
56
|
+
)
|
|
57
|
+
from botcore.commands.cdp.navigation import (
|
|
58
|
+
cdp_navigate,
|
|
59
|
+
cdp_wait,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
# Core
|
|
64
|
+
"CdpSession",
|
|
65
|
+
"ConsoleEntry",
|
|
66
|
+
"DEFAULT_TIMEOUT_MS",
|
|
67
|
+
"DEEP_QUERY_SINGLE_JS",
|
|
68
|
+
# Launcher
|
|
69
|
+
"cdp_launch",
|
|
70
|
+
"cdp_attach",
|
|
71
|
+
"cdp_close",
|
|
72
|
+
# Navigation
|
|
73
|
+
"cdp_navigate",
|
|
74
|
+
"cdp_wait",
|
|
75
|
+
# Interaction
|
|
76
|
+
"cdp_click",
|
|
77
|
+
"cdp_hover",
|
|
78
|
+
"cdp_scroll",
|
|
79
|
+
"cdp_drag",
|
|
80
|
+
"cdp_type",
|
|
81
|
+
"cdp_press",
|
|
82
|
+
# Inspection
|
|
83
|
+
"cdp_inspect",
|
|
84
|
+
"cdp_query",
|
|
85
|
+
"cdp_snapshot",
|
|
86
|
+
# Forms
|
|
87
|
+
"cdp_fill",
|
|
88
|
+
"cdp_fill_form",
|
|
89
|
+
"cdp_upload",
|
|
90
|
+
"cdp_handle_dialog",
|
|
91
|
+
"cdp_list_pages",
|
|
92
|
+
"cdp_select_page",
|
|
93
|
+
"cdp_new_page",
|
|
94
|
+
"cdp_close_page",
|
|
95
|
+
"cdp_resize",
|
|
96
|
+
# Diagnostics
|
|
97
|
+
"cdp_screenshot",
|
|
98
|
+
"cdp_console",
|
|
99
|
+
"cdp_get_console_message",
|
|
100
|
+
"cdp_eval",
|
|
101
|
+
"cdp_list_network",
|
|
102
|
+
"cdp_get_network",
|
|
103
|
+
"cdp_emulate",
|
|
104
|
+
]
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""CDP core utilities — shared across all CDP modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import socket
|
|
9
|
+
import subprocess
|
|
10
|
+
from collections.abc import Awaitable, Callable
|
|
11
|
+
from datetime import UTC, datetime
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, TypeVar
|
|
14
|
+
|
|
15
|
+
from afd import CommandResult, error, success
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
|
|
18
|
+
from botcore.utils.workspace import find_workspace
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
SESSION_DIRNAME = ".botcore"
|
|
23
|
+
SESSION_FILENAME = "cdp-session.json"
|
|
24
|
+
PROFILE_DIRNAME = "chrome-profile"
|
|
25
|
+
SCREENSHOTS_DIRNAME = "screenshots"
|
|
26
|
+
DEFAULT_TIMEOUT_MS = 30_000
|
|
27
|
+
|
|
28
|
+
DEEP_QUERY_SINGLE_JS = """
|
|
29
|
+
(selector) => {
|
|
30
|
+
function querySelectorDeep(root, sel) {
|
|
31
|
+
let result = root.querySelector(sel);
|
|
32
|
+
if (result) return result;
|
|
33
|
+
const elements = root.querySelectorAll('*');
|
|
34
|
+
for (const el of elements) {
|
|
35
|
+
if (el.shadowRoot) {
|
|
36
|
+
result = querySelectorDeep(el.shadowRoot, sel);
|
|
37
|
+
if (result) return result;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return querySelectorDeep(document, selector);
|
|
43
|
+
}
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ConsoleEntry(BaseModel):
|
|
48
|
+
"""Captured console log entry."""
|
|
49
|
+
|
|
50
|
+
timestamp: str
|
|
51
|
+
level: str
|
|
52
|
+
text: str
|
|
53
|
+
url: str | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CdpSession(BaseModel):
|
|
57
|
+
"""CDP session metadata stored on disk."""
|
|
58
|
+
|
|
59
|
+
cdp_endpoint: str
|
|
60
|
+
profile_dir: str
|
|
61
|
+
launched_at: str
|
|
62
|
+
pid: int | None = None
|
|
63
|
+
console_log: list[ConsoleEntry] = Field(default_factory=list)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _now_iso() -> str:
|
|
67
|
+
return datetime.now(UTC).isoformat()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _session_root() -> Path:
|
|
71
|
+
return find_workspace() or Path.cwd()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _session_dir(root: Path) -> Path:
|
|
75
|
+
return root / SESSION_DIRNAME
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _session_file(root: Path) -> Path:
|
|
79
|
+
return _session_dir(root) / SESSION_FILENAME
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _ensure_session_dir(root: Path) -> Path:
|
|
83
|
+
session_dir = _session_dir(root)
|
|
84
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
return session_dir
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _default_profile_dir(root: Path) -> Path:
|
|
89
|
+
root_drive = root.drive.rstrip("\\").lower()
|
|
90
|
+
localapp_drive = os.getenv("LOCALAPPDATA", "").split(":")[0].lower()
|
|
91
|
+
if root_drive and localapp_drive and root_drive != localapp_drive:
|
|
92
|
+
profile_root = Path(os.getenv("LOCALAPPDATA", "")) / "botcore"
|
|
93
|
+
profile_root.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
return profile_root / PROFILE_DIRNAME
|
|
95
|
+
return _ensure_session_dir(root) / PROFILE_DIRNAME
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _default_screenshots_dir(root: Path) -> Path:
|
|
99
|
+
return _ensure_session_dir(root) / SCREENSHOTS_DIRNAME
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _load_session(root: Path) -> CdpSession | None:
|
|
103
|
+
session_path = _session_file(root)
|
|
104
|
+
if not session_path.exists():
|
|
105
|
+
return None
|
|
106
|
+
try:
|
|
107
|
+
data = json.loads(session_path.read_text(encoding="utf-8"))
|
|
108
|
+
return CdpSession(**data)
|
|
109
|
+
except Exception:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _save_session(root: Path, session: CdpSession) -> None:
|
|
114
|
+
session_path = _session_file(root)
|
|
115
|
+
_ensure_session_dir(root)
|
|
116
|
+
session_path.write_text(session.model_dump_json(indent=2), encoding="utf-8")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _clear_session(root: Path) -> None:
|
|
120
|
+
session_path = _session_file(root)
|
|
121
|
+
if session_path.exists():
|
|
122
|
+
session_path.unlink()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _get_free_port() -> int:
|
|
126
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
127
|
+
sock.bind(("127.0.0.1", 0))
|
|
128
|
+
return sock.getsockname()[1]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
async def _probe_cdp(
|
|
132
|
+
endpoint: str,
|
|
133
|
+
timeout_s: float = 5.0,
|
|
134
|
+
attempts: int = 20,
|
|
135
|
+
delay_s: float = 0.5,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Probe CDP endpoint until it responds."""
|
|
138
|
+
import httpx
|
|
139
|
+
|
|
140
|
+
url = f"{endpoint.rstrip('/')}/json/version"
|
|
141
|
+
last_exc: Exception | None = None
|
|
142
|
+
for attempt in range(attempts):
|
|
143
|
+
try:
|
|
144
|
+
async with httpx.AsyncClient(timeout=timeout_s) as client:
|
|
145
|
+
response = await client.get(url)
|
|
146
|
+
response.raise_for_status()
|
|
147
|
+
return
|
|
148
|
+
except Exception as exc:
|
|
149
|
+
last_exc = exc
|
|
150
|
+
if attempt < attempts - 1:
|
|
151
|
+
await asyncio.sleep(delay_s)
|
|
152
|
+
if last_exc:
|
|
153
|
+
raise last_exc
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _spawn_chrome(args: list[str]) -> subprocess.Popen[bytes]:
|
|
157
|
+
popen_kwargs: dict[str, Any] = {
|
|
158
|
+
"stdout": subprocess.DEVNULL,
|
|
159
|
+
"stderr": subprocess.DEVNULL,
|
|
160
|
+
}
|
|
161
|
+
if os.name == "nt":
|
|
162
|
+
popen_kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
163
|
+
else:
|
|
164
|
+
popen_kwargs["start_new_session"] = True
|
|
165
|
+
return subprocess.Popen(args, **popen_kwargs)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
async def _get_chromium_executable() -> str:
|
|
169
|
+
from playwright.async_api import async_playwright
|
|
170
|
+
|
|
171
|
+
async with async_playwright() as playwright:
|
|
172
|
+
return playwright.chromium.executable_path
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
async def _connect_over_cdp_with_retry(
|
|
176
|
+
playwright: Any,
|
|
177
|
+
endpoint: str,
|
|
178
|
+
attempts: int = 10,
|
|
179
|
+
delay_s: float = 0.3,
|
|
180
|
+
) -> Any:
|
|
181
|
+
last_exc: Exception | None = None
|
|
182
|
+
for attempt in range(attempts):
|
|
183
|
+
try:
|
|
184
|
+
browser = await playwright.chromium.connect_over_cdp(endpoint)
|
|
185
|
+
return browser
|
|
186
|
+
except Exception as exc:
|
|
187
|
+
last_exc = exc
|
|
188
|
+
if attempt < attempts - 1:
|
|
189
|
+
await asyncio.sleep(delay_s)
|
|
190
|
+
if last_exc:
|
|
191
|
+
raise last_exc
|
|
192
|
+
raise RuntimeError("Failed to connect to CDP endpoint.")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def _with_session_page(
|
|
196
|
+
action: Callable[[Any], Awaitable[T]],
|
|
197
|
+
timeout_ms: int = DEFAULT_TIMEOUT_MS,
|
|
198
|
+
) -> CommandResult[T]:
|
|
199
|
+
"""Connect to active CDP session and run action on current page."""
|
|
200
|
+
from playwright.async_api import async_playwright
|
|
201
|
+
|
|
202
|
+
root = _session_root()
|
|
203
|
+
session = _load_session(root)
|
|
204
|
+
if not session:
|
|
205
|
+
return error(
|
|
206
|
+
"CDP_SESSION_NOT_FOUND",
|
|
207
|
+
"No active CDP session found.",
|
|
208
|
+
suggestion="Run `botcore cdp launch` or `botcore cdp attach` first.",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
console_entries: list[ConsoleEntry] = []
|
|
212
|
+
|
|
213
|
+
def handle_console(msg: Any) -> None:
|
|
214
|
+
console_entries.append(
|
|
215
|
+
ConsoleEntry(
|
|
216
|
+
timestamp=_now_iso(),
|
|
217
|
+
level=msg.type,
|
|
218
|
+
text=msg.text,
|
|
219
|
+
url=msg.location.get("url") if msg.location else None,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
async with async_playwright() as playwright:
|
|
225
|
+
browser = await _connect_over_cdp_with_retry(playwright, session.cdp_endpoint)
|
|
226
|
+
context = browser.contexts[0] if browser.contexts else await browser.new_context()
|
|
227
|
+
page = context.pages[0] if context.pages else await context.new_page()
|
|
228
|
+
page.on("console", handle_console)
|
|
229
|
+
result = await action(page)
|
|
230
|
+
if console_entries:
|
|
231
|
+
session.console_log.extend(console_entries)
|
|
232
|
+
_save_session(root, session)
|
|
233
|
+
return success(data=result)
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
import httpx
|
|
236
|
+
|
|
237
|
+
if isinstance(exc, httpx.HTTPError):
|
|
238
|
+
return error(
|
|
239
|
+
"CDP_CONNECT_FAILED",
|
|
240
|
+
f"Failed to reach CDP endpoint: {exc}",
|
|
241
|
+
suggestion="Ensure Chrome is running or run `botcore cdp launch`.",
|
|
242
|
+
)
|
|
243
|
+
exc_str = str(exc)
|
|
244
|
+
if "Timeout" in exc_str and "Locator" in exc_str:
|
|
245
|
+
suggestion = "Element not found. Use `botcore cdp query` to verify selector."
|
|
246
|
+
elif "Timeout" in exc_str:
|
|
247
|
+
suggestion = "Operation timed out. Check if page is responsive."
|
|
248
|
+
elif "Target closed" in exc_str or "Connection" in exc_str:
|
|
249
|
+
suggestion = "Browser disconnected. Run `botcore cdp launch` to start a new session."
|
|
250
|
+
else:
|
|
251
|
+
suggestion = "Check selector and page state. Use `botcore cdp screenshot` to debug."
|
|
252
|
+
return error(
|
|
253
|
+
"CDP_COMMAND_FAILED",
|
|
254
|
+
f"{type(exc).__name__}: {exc}",
|
|
255
|
+
suggestion=suggestion,
|
|
256
|
+
)
|