luckiest-co 1.0.47 → 1.0.49
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/ab-testing/CHANGELOG.md +5 -0
- package/skills/ab-testing/SKILL.md +12 -9
- package/skills/ab-testing/evals/evals.json +18 -7
- package/skills/ab-testing/references/sample-size-guide.md +2 -2
- package/skills/ads/CHANGELOG.md +5 -0
- package/skills/ads/SKILL.md +4 -4
- package/skills/ai-seo/CHANGELOG.md +7 -0
- package/skills/ai-seo/SKILL.md +27 -20
- package/skills/analytics/CHANGELOG.md +6 -0
- package/skills/analytics/SKILL.md +4 -3
- package/skills/analytics/evals/evals.json +27 -0
- package/skills/analytics/references/ga4-implementation.md +7 -3
- package/skills/analytics/references/gtm-implementation.md +8 -2
- package/skills/aso/CHANGELOG.md +5 -0
- package/skills/aso/SKILL.md +4 -4
- package/skills/aso/evals/evals.json +15 -2
- package/skills/aso/references/benchmarks.md +4 -2
- package/skills/aso/references/google-play-specs.md +5 -1
- package/skills/brainstorm/SKILL.md +59 -16
- package/skills/brainstorm/builder-check-prompt.md +23 -0
- package/skills/churn-prevention/CHANGELOG.md +5 -0
- package/skills/churn-prevention/SKILL.md +3 -3
- package/skills/churn-prevention/evals/evals.json +14 -0
- package/skills/churn-prevention/references/dunning-playbook.md +6 -1
- package/skills/coder-brainstorming/CHANGELOG.md +10 -0
- package/skills/coder-brainstorming/SKILL.md +62 -20
- package/skills/coder-brainstorming/builder-check-prompt.md +23 -0
- package/skills/coder-brainstorming/scripts/server.cjs +1 -1
- package/skills/coder-debugging/CHANGELOG.md +4 -0
- package/skills/coder-debugging/SKILL.md +5 -5
- package/skills/coder-requesting-code-review/CHANGELOG.md +4 -0
- package/skills/coder-requesting-code-review/SKILL.md +4 -4
- package/skills/coder-subagent-driven-development/CHANGELOG.md +4 -0
- package/skills/coder-subagent-driven-development/SKILL.md +3 -3
- package/skills/coder-subagent-driven-development/scripts/sdd-workspace +3 -1
- package/skills/cold-email/CHANGELOG.md +7 -0
- package/skills/cold-email/SKILL.md +27 -7
- package/skills/cold-email/evals/evals.json +30 -0
- package/skills/cold-email/references/benchmarks.md +15 -11
- package/skills/cold-email/references/deliverability.md +110 -0
- package/skills/cold-email/references/linkedin-outreach.md +91 -0
- package/skills/cold-email/references/multichannel-cadence.md +79 -0
- package/skills/cold-email/references/outbound-plays.md +88 -0
- package/skills/cold-email/references/personalization.md +11 -0
- package/skills/cold-email/references/reply-handling.md +65 -0
- package/skills/competitor-profiling/CHANGELOG.md +5 -0
- package/skills/competitor-profiling/SKILL.md +4 -4
- package/skills/content-strategy/CHANGELOG.md +5 -0
- package/skills/content-strategy/SKILL.md +5 -3
- package/skills/copy-editing/CHANGELOG.md +5 -0
- package/skills/copy-editing/SKILL.md +5 -5
- package/skills/copy-editing/references/content-refresh.md +33 -0
- package/skills/cro/CHANGELOG.md +5 -0
- package/skills/cro/SKILL.md +14 -3
- package/skills/cro/references/form.md +1 -1
- package/skills/directory-submissions/CHANGELOG.md +5 -0
- package/skills/directory-submissions/SKILL.md +13 -4
- package/skills/directory-submissions/evals/evals.json +15 -1
- package/skills/directory-submissions/references/directory-list.md +2 -4
- package/skills/emails/CHANGELOG.md +5 -0
- package/skills/emails/SKILL.md +14 -4
- package/skills/launch/CHANGELOG.md +7 -0
- package/skills/launch/SKILL.md +8 -4
- package/skills/launch/evals/evals.json +29 -0
- package/skills/launch/references/shipped-changes.md +84 -0
- package/skills/launch/references/site-launch-qa.md +55 -0
- package/skills/lead-magnets/CHANGELOG.md +5 -0
- package/skills/lead-magnets/SKILL.md +6 -4
- package/skills/lead-magnets/evals/evals.json +18 -0
- package/skills/lead-magnets/references/content-to-skill-corpus-design.md +89 -0
- package/skills/lead-magnets/references/content-to-skill-release.md +71 -0
- package/skills/lead-magnets/references/content-to-skill.md +108 -0
- package/skills/marketing-plan/CHANGELOG.md +5 -0
- package/skills/marketing-plan/SKILL.md +3 -3
- package/skills/marketing-plan/evals/evals.json +13 -0
- package/skills/marketing-plan/references/budget-planning.md +26 -17
- package/skills/model-router/CHANGELOG.md +6 -0
- package/skills/model-router/SKILL.md +3 -3
- package/skills/model-router/scripts/route.py +1 -1
- package/skills/model-router/scripts/test_route.py +5 -0
- package/skills/pricing/CHANGELOG.md +5 -0
- package/skills/pricing/SKILL.md +4 -4
- package/skills/pricing/evals/evals.json +12 -0
- package/skills/pricing/references/research-methods.md +8 -4
- package/skills/programmatic-seo/CHANGELOG.md +5 -0
- package/skills/programmatic-seo/SKILL.md +8 -4
- package/skills/programmatic-seo/evals/evals.json +13 -0
- package/skills/programmatic-seo/references/implementation-platforms.md +37 -0
- package/skills/prospecting/CHANGELOG.md +7 -0
- package/skills/prospecting/SKILL.md +27 -13
- package/skills/prospecting/evals/evals.json +41 -0
- package/skills/prospecting/references/account-research.md +59 -0
- package/skills/prospecting/references/b2b-prospecting.md +1 -1
- package/skills/prospecting/references/compliance.md +44 -6
- package/skills/prospecting/references/data-sources.md +13 -15
- package/skills/prospecting/references/enrichment-playbook.md +61 -0
- package/skills/prospecting/references/saas-prospecting.md +1 -1
- package/skills/prospecting/references/signal-plays.md +99 -0
- package/skills/prospecting/references/sourcing-and-enrichment.md +89 -0
- package/skills/referrals/CHANGELOG.md +5 -0
- package/skills/referrals/SKILL.md +10 -4
- package/skills/referrals/evals/evals.json +99 -0
- package/skills/referrals/references/affiliate-operations.md +64 -0
- package/skills/referrals/references/affiliate-programs.md +2 -2
- package/skills/referrals/references/program-examples.md +14 -6
- package/skills/referrals/references/review-platform-rules.md +36 -0
- package/skills/referrals/references/review-program-operations.md +62 -0
- package/skills/referrals/references/review-programs.md +96 -0
- package/skills/revops/CHANGELOG.md +5 -0
- package/skills/revops/SKILL.md +10 -6
- package/skills/revops/evals/evals.json +13 -0
- package/skills/revops/references/automation-playbooks.md +1 -1
- package/skills/revops/references/inbound-routing.md +43 -0
- package/skills/revops/references/lifecycle-definitions.md +31 -0
- package/skills/sales-enablement/CHANGELOG.md +5 -0
- package/skills/sales-enablement/SKILL.md +6 -4
- package/skills/sales-enablement/evals/evals.json +14 -0
- package/skills/sales-enablement/references/cold-call-scripts.md +68 -0
- package/skills/schema/CHANGELOG.md +5 -0
- package/skills/schema/SKILL.md +20 -8
- package/skills/schema/evals/evals.json +40 -0
- package/skills/schema/references/schema-examples.md +9 -15
- package/skills/seo-audit/CHANGELOG.md +5 -0
- package/skills/seo-audit/SKILL.md +10 -16
- package/skills/seo-audit/evals/evals.json +68 -0
- package/skills/seo-audit/references/international-seo.md +9 -11
- package/skills/seo-audit/references/local-seo-pages-and-measurement.md +39 -0
- package/skills/seo-audit/references/local-seo-profile-and-citations.md +41 -0
- package/skills/seo-audit/references/local-seo.md +86 -0
- package/skills/seo-audit/references/rankings-push.md +62 -0
- package/skills/seo-audit/references/title-tags.md +89 -0
- package/skills/signup/CHANGELOG.md +5 -0
- package/skills/signup/SKILL.md +7 -3
- package/skills/site-architecture/CHANGELOG.md +5 -0
- package/skills/site-architecture/SKILL.md +5 -4
- package/skills/site-architecture/evals/evals.json +25 -0
- package/skills/site-architecture/references/platforms-and-migration.md +84 -0
- package/skills/social/CHANGELOG.md +5 -0
- package/skills/social/SKILL.md +4 -4
- package/skills/trends/.skillignore +3 -0
- package/skills/trends/CHANGELOG.md +20 -0
- package/skills/trends/SKILL.md +12 -8
- package/skills/trends/references/agent-mode.md +36 -0
- package/skills/trends/references/category-peers.md +38 -0
- package/skills/trends/references/comparison.md +227 -0
- package/skills/trends/references/competitors.md +29 -0
- package/skills/trends/references/discovery.md +90 -0
- package/skills/trends/references/engine-contract.md +111 -1889
- package/skills/trends/references/followups.md +112 -0
- package/skills/trends/references/grok-bot-x.md +55 -0
- package/skills/trends/references/hiring-signals.md +19 -0
- package/skills/trends/references/library-queue.md +33 -0
- package/skills/trends/references/recommendations.md +86 -0
- package/skills/trends/references/research-runbook.md +781 -0
- package/skills/trends/references/runtime.md +165 -0
- package/skills/trends/references/save-html-brief.md +39 -20
- package/skills/trends/references/setup-wizard.md +253 -0
- package/skills/trends/references/synthesis.md +289 -0
- package/skills/trends/scripts/box_chrome_login.py +209 -0
- package/skills/trends/scripts/briefing.py +9 -17
- package/skills/trends/scripts/last30days.py +3247 -280
- package/skills/trends/scripts/lib/agentcookie.py +196 -0
- package/skills/trends/scripts/lib/amazon.py +877 -0
- package/skills/trends/scripts/lib/arxiv.py +42 -8
- package/skills/trends/scripts/lib/backends.py +769 -0
- package/skills/trends/scripts/lib/bird_x.py +340 -57
- package/skills/trends/scripts/lib/bluesky.py +84 -10
- package/skills/trends/scripts/lib/bounded_get.py +156 -0
- package/skills/trends/scripts/lib/brightdata.py +291 -0
- package/skills/trends/scripts/lib/categories.py +7 -1
- package/skills/trends/scripts/lib/chrome_cdp.py +455 -0
- package/skills/trends/scripts/lib/chrome_cookies.py +132 -39
- package/skills/trends/scripts/lib/cluster.py +31 -54
- package/skills/trends/scripts/lib/competitors.py +10 -1
- package/skills/trends/scripts/lib/cookie_extract.py +221 -44
- package/skills/trends/scripts/lib/cookie_paths.py +27 -0
- package/skills/trends/scripts/lib/corpus.py +439 -0
- package/skills/trends/scripts/lib/devto.py +313 -0
- package/skills/trends/scripts/lib/discovery_handoff.py +988 -0
- package/skills/trends/scripts/lib/doctor.py +2166 -0
- package/skills/trends/scripts/lib/dripstack.py +203 -0
- package/skills/trends/scripts/lib/entity_extract.py +37 -0
- package/skills/trends/scripts/lib/env.py +1092 -138
- package/skills/trends/scripts/lib/fanout.py +11 -1
- package/skills/trends/scripts/lib/feed.py +61 -0
- package/skills/trends/scripts/lib/fetch_memo.py +135 -0
- package/skills/trends/scripts/lib/freshness.py +566 -0
- package/skills/trends/scripts/lib/fusion.py +314 -28
- package/skills/trends/scripts/lib/github.py +438 -24
- package/skills/trends/scripts/lib/grok_x.py +1237 -0
- package/skills/trends/scripts/lib/grounding.py +129 -11
- package/skills/trends/scripts/lib/hackernews.py +61 -4
- package/skills/trends/scripts/lib/health.py +373 -0
- package/skills/trends/scripts/lib/hosted.py +333 -0
- package/skills/trends/scripts/lib/html_publish.py +34 -0
- package/skills/trends/scripts/lib/html_render.py +169 -3
- package/skills/trends/scripts/lib/http.py +1215 -70
- package/skills/trends/scripts/lib/instagram.py +132 -9
- package/skills/trends/scripts/lib/jobs.py +4 -2
- package/skills/trends/scripts/lib/library.py +308 -0
- package/skills/trends/scripts/lib/library_index.py +477 -0
- package/skills/trends/scripts/lib/linkedin.py +271 -24
- package/skills/trends/scripts/lib/log.py +5 -2
- package/skills/trends/scripts/lib/meta_ads.py +982 -0
- package/skills/trends/scripts/lib/normalize.py +382 -28
- package/skills/trends/scripts/lib/parallel_mcp.py +234 -0
- package/skills/trends/scripts/lib/permission_preflight.py +42 -3
- package/skills/trends/scripts/lib/perplexity.py +1080 -468
- package/skills/trends/scripts/lib/pipeline.py +5079 -211
- package/skills/trends/scripts/lib/planner.py +396 -53
- package/skills/trends/scripts/lib/polymarket.py +285 -23
- package/skills/trends/scripts/lib/prescriptions.py +432 -0
- package/skills/trends/scripts/lib/private_files.py +21 -0
- package/skills/trends/scripts/lib/providers.py +231 -10
- package/skills/trends/scripts/lib/quality_nudge.py +122 -37
- package/skills/trends/scripts/lib/reddit.py +177 -12
- package/skills/trends/scripts/lib/reddit_arctic.py +146 -5
- package/skills/trends/scripts/lib/reddit_keyless.py +121 -60
- package/skills/trends/scripts/lib/reddit_listing.py +215 -14
- package/skills/trends/scripts/lib/reddit_public.py +12 -13
- package/skills/trends/scripts/lib/reddit_search.py +304 -0
- package/skills/trends/scripts/lib/reddit_shreddit.py +51 -7
- package/skills/trends/scripts/lib/registers.py +134 -0
- package/skills/trends/scripts/lib/relevance.py +17 -0
- package/skills/trends/scripts/lib/render.py +2161 -271
- package/skills/trends/scripts/lib/rerank.py +623 -55
- package/skills/trends/scripts/lib/resolve.py +50 -1
- package/skills/trends/scripts/lib/safari_cookies.py +42 -15
- package/skills/trends/scripts/lib/schema.py +782 -1
- package/skills/trends/scripts/lib/setup_wizard.py +779 -120
- package/skills/trends/scripts/lib/signals.py +206 -7
- package/skills/trends/scripts/lib/stocktwits.py +374 -0
- package/skills/trends/scripts/lib/subproc.py +307 -53
- package/skills/trends/scripts/lib/techmeme.py +89 -64
- package/skills/trends/scripts/lib/telegram.py +317 -0
- package/skills/trends/scripts/lib/tiktok.py +13 -9
- package/skills/trends/scripts/lib/topic_shape.py +340 -0
- package/skills/trends/scripts/lib/transcribe.py +12 -3
- package/skills/trends/scripts/lib/trustpilot.py +256 -5
- package/skills/trends/scripts/lib/truthsocial.py +9 -1
- package/skills/trends/scripts/lib/ui.py +6 -0
- package/skills/trends/scripts/lib/usage.py +132 -0
- package/skills/trends/scripts/lib/web_search_keyless.py +63 -4
- package/skills/trends/scripts/lib/windows_job.py +138 -0
- package/skills/trends/scripts/lib/x_api.py +824 -0
- package/skills/trends/scripts/lib/x_envelope.py +733 -0
- package/skills/trends/scripts/lib/x_judge.py +292 -0
- package/skills/trends/scripts/lib/xai_x.py +26 -3
- package/skills/trends/scripts/lib/xquik.py +89 -15
- package/skills/trends/scripts/lib/xurl_x.py +284 -91
- package/skills/trends/scripts/lib/youtube_yt.py +570 -119
- package/skills/trends/scripts/setup-keychain.sh +12 -4
- package/skills/trends/scripts/setup-pass.sh +3 -0
- package/skills/trends/scripts/store.py +444 -50
- package/skills/trends/scripts/watchlist.py +38 -11
- package/skills/trends/scripts/x_lane_parity.py +107 -0
- package/skills/video-studio/CHANGELOG.md +4 -0
- package/skills/video-studio/SKILL.md +4 -4
- package/skills/video-studio/references/styles/product-flight.md +45 -0
- package/skills/video-studio/references/styles.md +3 -1
- package/skills/video-studio-motion/ATTRIBUTION.md +1 -1
- package/skills/video-studio-motion/CHANGELOG.md +4 -0
- package/skills/video-studio-motion/SKILL.md +8 -5
- package/skills/youtube-planner/ATTRIBUTION.md +25 -0
- package/skills/youtube-planner/CHANGELOG.md +12 -0
- package/skills/youtube-planner/SKILL.md +191 -0
- package/skills/youtube-planner/references/plan-template.md +57 -0
- package/skills/youtube-planner/references/stages.md +119 -0
- package/skills/youtube-planner/scripts/floor_ceiling.py +132 -0
- package/skills/coder-brainstorming/spec-document-reviewer-prompt.md +0 -49
- package/skills/trends/scripts/lib/reddit_rss.py +0 -224
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Opt-in Parallel Search MCP adapter using stdlib Streamable HTTP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import urllib.error
|
|
7
|
+
import urllib.request
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
|
|
11
|
+
from . import dates, http, usage
|
|
12
|
+
|
|
13
|
+
PARALLEL_MCP_URL = "https://search.parallel.ai/mcp"
|
|
14
|
+
_PROTOCOL_VERSION = "2025-03-26"
|
|
15
|
+
_MAX_RESPONSE_BYTES = 4 * 1024 * 1024
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
19
|
+
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
20
|
+
# The endpoint is fixed. Never forward credentials, session IDs, or
|
|
21
|
+
# search data to a redirect target, including an HTTPS downgrade.
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _matching_response(payload: bytes, request_id: int | None) -> dict[str, Any] | None:
|
|
26
|
+
value = json.loads(payload)
|
|
27
|
+
# The negotiated 2025-03-26 protocol permits batched SSE messages.
|
|
28
|
+
for message in value if isinstance(value, list) else [value]:
|
|
29
|
+
if not isinstance(message, dict) or message.get("jsonrpc") != "2.0":
|
|
30
|
+
raise RuntimeError("Parallel MCP returned an invalid JSON-RPC response")
|
|
31
|
+
if message.get("id") == request_id and ("result" in message or "error" in message):
|
|
32
|
+
return message
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _read_response(response: Any, request_id: int | None) -> dict[str, Any]:
|
|
37
|
+
if "text/event-stream" not in response.headers.get("Content-Type", "").lower():
|
|
38
|
+
payload = response.read(_MAX_RESPONSE_BYTES + 1)
|
|
39
|
+
if len(payload) > _MAX_RESPONSE_BYTES:
|
|
40
|
+
raise RuntimeError("Parallel MCP response exceeded 4 MiB")
|
|
41
|
+
if request_id is None and not payload:
|
|
42
|
+
return {}
|
|
43
|
+
result = _matching_response(payload, request_id) if payload else None
|
|
44
|
+
if result is not None:
|
|
45
|
+
return result
|
|
46
|
+
else:
|
|
47
|
+
data = []
|
|
48
|
+
total = 0
|
|
49
|
+
while True:
|
|
50
|
+
line = response.readline(_MAX_RESPONSE_BYTES - total + 1)
|
|
51
|
+
total += len(line)
|
|
52
|
+
if total > _MAX_RESPONSE_BYTES:
|
|
53
|
+
raise RuntimeError("Parallel MCP response exceeded 4 MiB")
|
|
54
|
+
if not line:
|
|
55
|
+
break
|
|
56
|
+
line = line.rstrip(b"\r\n")
|
|
57
|
+
if not line and data:
|
|
58
|
+
result = _matching_response(b"\n".join(data), request_id)
|
|
59
|
+
if result is not None:
|
|
60
|
+
# A valid response completes the request, even if the
|
|
61
|
+
# server keeps the stream open or sends more notifications.
|
|
62
|
+
return result
|
|
63
|
+
data = []
|
|
64
|
+
elif line.startswith(b"data:"):
|
|
65
|
+
value = line[5:]
|
|
66
|
+
data.append(value[1:] if value.startswith(b" ") else value)
|
|
67
|
+
raise RuntimeError("Parallel MCP response is missing the requested JSON-RPC result")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _request(
|
|
71
|
+
message: dict[str, Any] | None, api_key: str | None, session_id: str | None = None,
|
|
72
|
+
) -> tuple[dict[str, Any], str | None]:
|
|
73
|
+
headers = {
|
|
74
|
+
"Accept": "application/json, text/event-stream",
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
"User-Agent": http.USER_AGENT,
|
|
77
|
+
}
|
|
78
|
+
if api_key:
|
|
79
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
80
|
+
if session_id:
|
|
81
|
+
headers["Mcp-Session-Id"] = session_id
|
|
82
|
+
if message is None or message.get("method") != "initialize":
|
|
83
|
+
headers["MCP-Protocol-Version"] = _PROTOCOL_VERSION
|
|
84
|
+
request = urllib.request.Request(
|
|
85
|
+
PARALLEL_MCP_URL,
|
|
86
|
+
data=json.dumps(message, separators=(",", ":")).encode("utf-8") if message is not None else None,
|
|
87
|
+
headers=headers,
|
|
88
|
+
method="POST" if message is not None else "DELETE",
|
|
89
|
+
)
|
|
90
|
+
if api_key and message is not None and message.get("method") == "tools/call":
|
|
91
|
+
usage.begin("parallel")
|
|
92
|
+
with urllib.request.build_opener(_NoRedirect()).open(request, timeout=http.DEFAULT_TIMEOUT) as response:
|
|
93
|
+
result = _read_response(response, message.get("id")) if message is not None else {}
|
|
94
|
+
return result, response.headers.get("Mcp-Session-Id") or session_id
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _result(response: dict[str, Any]) -> dict[str, Any]:
|
|
98
|
+
if response.get("error"):
|
|
99
|
+
error = response["error"]
|
|
100
|
+
detail = error.get("message") if isinstance(error, dict) else str(error)
|
|
101
|
+
raise RuntimeError(f"Parallel MCP error: {detail}")
|
|
102
|
+
result = response.get("result")
|
|
103
|
+
if not isinstance(result, dict):
|
|
104
|
+
raise RuntimeError("Parallel MCP response is missing its result")
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _search_rows(tool_result: dict[str, Any]) -> list[dict[str, Any]]:
|
|
109
|
+
payloads = [tool_result.get("structuredContent")]
|
|
110
|
+
for content in tool_result.get("content") or []:
|
|
111
|
+
if not isinstance(content, dict) or content.get("type") != "text":
|
|
112
|
+
continue
|
|
113
|
+
try:
|
|
114
|
+
payloads.append(json.loads(content.get("text") or ""))
|
|
115
|
+
except (TypeError, ValueError):
|
|
116
|
+
continue
|
|
117
|
+
for candidates in payloads:
|
|
118
|
+
if isinstance(candidates, dict) and "data" in candidates and "results" not in candidates:
|
|
119
|
+
candidates = candidates["data"]
|
|
120
|
+
if isinstance(candidates, dict):
|
|
121
|
+
candidates = candidates.get("results")
|
|
122
|
+
if isinstance(candidates, list):
|
|
123
|
+
return [row for row in candidates if isinstance(row, dict)]
|
|
124
|
+
raise RuntimeError("Parallel MCP web_search returned no results array")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def search(
|
|
128
|
+
query: str, date_range: tuple[str, str], api_key: str | None = None, count: int = 5,
|
|
129
|
+
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
130
|
+
"""Discover and invoke hosted ``web_search`` after explicit dispatcher opt-in."""
|
|
131
|
+
initialized, session_id = _request(
|
|
132
|
+
{
|
|
133
|
+
"jsonrpc": "2.0",
|
|
134
|
+
"id": 1,
|
|
135
|
+
"method": "initialize",
|
|
136
|
+
"params": {
|
|
137
|
+
"protocolVersion": _PROTOCOL_VERSION,
|
|
138
|
+
"capabilities": {},
|
|
139
|
+
"clientInfo": {"name": "last30days-skill", "version": "3"},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
api_key,
|
|
143
|
+
)
|
|
144
|
+
try:
|
|
145
|
+
if _result(initialized).get("protocolVersion") != _PROTOCOL_VERSION:
|
|
146
|
+
raise RuntimeError("Parallel MCP negotiated an unsupported protocol version")
|
|
147
|
+
_request(
|
|
148
|
+
{"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}},
|
|
149
|
+
api_key,
|
|
150
|
+
session_id,
|
|
151
|
+
)
|
|
152
|
+
request_id = 2
|
|
153
|
+
params = {}
|
|
154
|
+
seen_cursors = set()
|
|
155
|
+
while True:
|
|
156
|
+
tools_response, _ = _request(
|
|
157
|
+
{"jsonrpc": "2.0", "id": request_id, "method": "tools/list", "params": params},
|
|
158
|
+
api_key,
|
|
159
|
+
session_id,
|
|
160
|
+
)
|
|
161
|
+
request_id += 1
|
|
162
|
+
page = _result(tools_response)
|
|
163
|
+
tools = page.get("tools") or []
|
|
164
|
+
if any(isinstance(tool, dict) and tool.get("name") == "web_search" for tool in tools):
|
|
165
|
+
break
|
|
166
|
+
cursor = page.get("nextCursor")
|
|
167
|
+
if not isinstance(cursor, str) or not cursor or cursor in seen_cursors or len(seen_cursors) >= 20:
|
|
168
|
+
raise RuntimeError("Parallel MCP did not advertise web_search")
|
|
169
|
+
seen_cursors.add(cursor)
|
|
170
|
+
params = {"cursor": cursor}
|
|
171
|
+
objective = f"Find useful public web evidence about {query} from {date_range[0]} through {date_range[1]}."
|
|
172
|
+
called, _ = _request(
|
|
173
|
+
{
|
|
174
|
+
"jsonrpc": "2.0",
|
|
175
|
+
"id": request_id,
|
|
176
|
+
"method": "tools/call",
|
|
177
|
+
"params": {
|
|
178
|
+
"name": "web_search",
|
|
179
|
+
"arguments": {"objective": objective, "search_queries": [query]},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
api_key,
|
|
183
|
+
session_id,
|
|
184
|
+
)
|
|
185
|
+
finally:
|
|
186
|
+
if session_id:
|
|
187
|
+
try:
|
|
188
|
+
_request(None, api_key, session_id)
|
|
189
|
+
except urllib.error.HTTPError as error:
|
|
190
|
+
error.close()
|
|
191
|
+
except (OSError, RuntimeError, ValueError):
|
|
192
|
+
# Cleanup is best-effort: unsupported DELETEs or a network
|
|
193
|
+
# failure must not hide search results or the original error.
|
|
194
|
+
pass
|
|
195
|
+
tool_result = _result(called)
|
|
196
|
+
if tool_result.get("isError"):
|
|
197
|
+
raise RuntimeError("Parallel MCP web_search reported an error")
|
|
198
|
+
items = []
|
|
199
|
+
for row in _search_rows(tool_result):
|
|
200
|
+
if len(items) >= count:
|
|
201
|
+
break
|
|
202
|
+
url = str(row.get("url") or "")
|
|
203
|
+
try:
|
|
204
|
+
parsed_url = urlparse(url)
|
|
205
|
+
except ValueError:
|
|
206
|
+
continue
|
|
207
|
+
if parsed_url.scheme not in ("http", "https") or not parsed_url.netloc:
|
|
208
|
+
continue
|
|
209
|
+
raw_date = row.get("publish_date")
|
|
210
|
+
parsed_date = dates.parse_date(raw_date[:10]) if isinstance(raw_date, str) else None
|
|
211
|
+
pub_date = parsed_date.date().isoformat() if parsed_date else None
|
|
212
|
+
# Match the paid grounding backends: only dated evidence inside the
|
|
213
|
+
# requested window qualifies, including when --as-of is historical.
|
|
214
|
+
if not pub_date or not date_range[0] <= pub_date <= date_range[1]:
|
|
215
|
+
continue
|
|
216
|
+
excerpts = row.get("excerpts") or []
|
|
217
|
+
if isinstance(excerpts, str):
|
|
218
|
+
excerpts = [excerpts]
|
|
219
|
+
snippet = "\n".join(str(value) for value in excerpts if value) if isinstance(excerpts, list) else ""
|
|
220
|
+
items.append({
|
|
221
|
+
"id": f"WPM{len(items) + 1}",
|
|
222
|
+
"title": row.get("title") or parsed_url.netloc,
|
|
223
|
+
"url": url,
|
|
224
|
+
"source_domain": parsed_url.netloc.strip().lower(),
|
|
225
|
+
"snippet": snippet[:500],
|
|
226
|
+
"date": pub_date,
|
|
227
|
+
"relevance": 0.8,
|
|
228
|
+
"why_relevant": "Parallel Search MCP result",
|
|
229
|
+
})
|
|
230
|
+
return items, {
|
|
231
|
+
"label": "parallel-mcp",
|
|
232
|
+
"webSearchQueries": [query],
|
|
233
|
+
"resultCount": len(items),
|
|
234
|
+
}
|
|
@@ -4,12 +4,15 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
+
from . import env, providers as provider_endpoints
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
ENDPOINT_OVERRIDE_KEYS = {
|
|
9
11
|
"BSKY_SEARCH_HOST",
|
|
10
12
|
"LAST30DAYS_SEARXNG_URL",
|
|
11
13
|
"LAST30DAYS_YOUTUBE_SSH_HOST",
|
|
12
14
|
"OPENAI_BASE_URL",
|
|
15
|
+
"OPENROUTER_BASE_URL",
|
|
13
16
|
"XAI_BASE_URL",
|
|
14
17
|
"XIAOHONGSHU_API_BASE",
|
|
15
18
|
}
|
|
@@ -22,6 +25,10 @@ PROVIDER_CREDENTIALS = {
|
|
|
22
25
|
"perplexity": "Perplexity API key",
|
|
23
26
|
"scrapecreators": "ScrapeCreators API key",
|
|
24
27
|
"github": "GitHub token or gh auth",
|
|
28
|
+
# X API v2 app-only bearer (X_BEARER_TOKEN). Presence is computed from
|
|
29
|
+
# config inside build(), never through diagnose.providers, whose key set
|
|
30
|
+
# is frozen by tests/test_diagnose_compat.py.
|
|
31
|
+
"x_bearer": "X API bearer token",
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
|
|
@@ -100,12 +107,28 @@ def build(
|
|
|
100
107
|
"label": PROVIDER_CREDENTIALS["scrapecreators"],
|
|
101
108
|
},
|
|
102
109
|
"github": {"present": bool(diagnose.get("has_github")), "label": PROVIDER_CREDENTIALS["github"]},
|
|
110
|
+
"x_bearer": {
|
|
111
|
+
"present": bool(str(config.get("X_BEARER_TOKEN") or "").strip()),
|
|
112
|
+
"label": PROVIDER_CREDENTIALS["x_bearer"],
|
|
113
|
+
},
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
active_endpoint_overrides =
|
|
106
|
-
|
|
116
|
+
active_endpoint_overrides: list[str] = []
|
|
117
|
+
rejected_provider_overrides: list[str] = []
|
|
118
|
+
for key in sorted(ENDPOINT_OVERRIDE_KEYS):
|
|
119
|
+
value = str(config.get(key) or "").strip()
|
|
120
|
+
if not value:
|
|
121
|
+
continue
|
|
122
|
+
if (
|
|
123
|
+
key in provider_endpoints.PROVIDER_BASE_URL_KEYS
|
|
124
|
+
and not provider_endpoints.allowed_base_url_override(value)
|
|
125
|
+
):
|
|
126
|
+
rejected_provider_overrides.append(key)
|
|
127
|
+
else:
|
|
128
|
+
active_endpoint_overrides.append(key)
|
|
129
|
+
ignored_endpoint_overrides = sorted(
|
|
130
|
+
set(diagnose.get("ignored_endpoint_overrides") or []) | set(rejected_provider_overrides)
|
|
107
131
|
)
|
|
108
|
-
ignored_endpoint_overrides = sorted(diagnose.get("ignored_endpoint_overrides") or [])
|
|
109
132
|
external_commands = {
|
|
110
133
|
name: {"status": _status(bool(available))}
|
|
111
134
|
for name, available in sorted((diagnose.get("external_commands") or {}).items())
|
|
@@ -114,6 +137,22 @@ def build(
|
|
|
114
137
|
action_items: list[str] = []
|
|
115
138
|
if ignored_project_config:
|
|
116
139
|
action_items.append("Project config was ignored; set LAST30DAYS_TRUST_PROJECT_CONFIG=1 to trust it.")
|
|
140
|
+
# get_config() already emptied these, so the provider flags above read them
|
|
141
|
+
# as absent. Name them anyway: the user's setup is broken in a way the
|
|
142
|
+
# presence booleans alone describe as "nothing configured".
|
|
143
|
+
templated_keys = env.templated_config_keys(config)
|
|
144
|
+
if templated_keys:
|
|
145
|
+
action_items.append(
|
|
146
|
+
"Unsubstituted config template(s) count as unset: "
|
|
147
|
+
+ _format_names(templated_keys)
|
|
148
|
+
+ ". Replace each with a real value or remove it."
|
|
149
|
+
)
|
|
150
|
+
if rejected_provider_overrides:
|
|
151
|
+
action_items.append(
|
|
152
|
+
"Provider endpoint override(s) ignored: "
|
|
153
|
+
+ _format_names(rejected_provider_overrides)
|
|
154
|
+
+ ". Use HTTPS, or HTTP on loopback."
|
|
155
|
+
)
|
|
117
156
|
|
|
118
157
|
return {
|
|
119
158
|
"status": "action_needed" if action_items else "ready",
|