opencode-skills-collection 3.1.23 → 4.0.1
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/bundled-skills/.antigravity-install-manifest.json +24 -1
- package/bundled-skills/007/SKILL.md +2 -2
- package/bundled-skills/10-andruia-skill-smith/SKILL.md +1 -1
- package/bundled-skills/agent-squad/SKILL.md +5 -0
- package/bundled-skills/agent-squad/alex/SKILL.md +3 -0
- package/bundled-skills/agent-squad/aria/SKILL.md +3 -0
- package/bundled-skills/agent-squad/dep/SKILL.md +3 -0
- package/bundled-skills/agent-squad/luna/SKILL.md +4 -1
- package/bundled-skills/agent-squad/mason/SKILL.md +3 -0
- package/bundled-skills/agent-squad/max/SKILL.md +3 -0
- package/bundled-skills/agent-squad/quinn/SKILL.md +3 -0
- package/bundled-skills/agent-squad/rex/SKILL.md +3 -0
- package/bundled-skills/angular/metadata.json +1 -1
- package/bundled-skills/angular-best-practices/metadata.json +1 -1
- package/bundled-skills/angular-state-management/metadata.json +1 -1
- package/bundled-skills/angular-ui-patterns/metadata.json +1 -1
- package/bundled-skills/antigravity-skill-orchestrator/SKILL.md +2 -2
- package/bundled-skills/apify-actor-development/SKILL.md +2 -2
- package/bundled-skills/apple-container/SKILL.md +165 -0
- package/bundled-skills/apple-container/references/commands.md +1593 -0
- package/bundled-skills/apple-container/references/concepts.md +294 -0
- package/bundled-skills/apple-container/references/configuration.md +499 -0
- package/bundled-skills/apple-container/references/workflows.md +498 -0
- package/bundled-skills/ask-copilot/SKILL.md +112 -0
- package/bundled-skills/auto-research/SKILL.md +126 -0
- package/bundled-skills/brooks-lint/SKILL.md +1 -1
- package/bundled-skills/browser-harness/references/install.md +3 -1
- package/bundled-skills/chrome-extension-developer/SKILL.md +1 -1
- package/bundled-skills/clean-code-guard/SKILL.md +184 -0
- package/bundled-skills/clean-code-guard/references/ai-failure-modes.md +345 -0
- package/bundled-skills/clean-code-guard/references/comments-and-formatting.md +115 -0
- package/bundled-skills/clean-code-guard/references/dry-kiss-yagni.md +133 -0
- package/bundled-skills/clean-code-guard/references/naming-and-functions.md +187 -0
- package/bundled-skills/clean-code-guard/references/review-checklist.md +150 -0
- package/bundled-skills/clean-code-guard/references/solid.md +280 -0
- package/bundled-skills/clean-code-guard/references/sources.md +47 -0
- package/bundled-skills/cloudflare-security-audit/SKILL.md +137 -0
- package/bundled-skills/cloudflare-security-audit/references/AI-AND-LLM.md +67 -0
- package/bundled-skills/cloudflare-security-audit/references/ATTACK-CLASSES.md +116 -0
- package/bundled-skills/cloudflare-security-audit/references/CLIENT-SIDE.md +67 -0
- package/bundled-skills/cloudflare-security-audit/references/HUNTING.md +110 -0
- package/bundled-skills/cloudflare-security-audit/references/MEMORY-SAFETY-AND-BINARY.md +58 -0
- package/bundled-skills/cloudflare-security-audit/references/RECONNAISSANCE.md +46 -0
- package/bundled-skills/cloudflare-security-audit/references/VALIDATION-AND-REPORTING.md +109 -0
- package/bundled-skills/cloudflare-security-audit/references/WEB-PROTOCOL-AND-AUTH.md +85 -0
- package/bundled-skills/cloudflare-security-audit/resources/report-schema.json +210 -0
- package/bundled-skills/comfyui-gateway/references/troubleshooting.md +1 -1
- package/bundled-skills/commit/SKILL.md +2 -1
- package/bundled-skills/deepapi/SKILL.md +6 -6
- package/bundled-skills/docs/README.md +1 -1
- package/bundled-skills/docs/contributors/quality-bar.md +1 -1
- package/bundled-skills/docs/contributors/security-guardrails.md +1 -1
- package/bundled-skills/docs/integrations/jetski-cortex.md +5 -5
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +3 -3
- package/bundled-skills/docs/maintainers/ci-drift-fix.md +7 -5
- package/bundled-skills/docs/maintainers/merge-batch.md +24 -7
- package/bundled-skills/docs/maintainers/pr-autonomy.md +89 -0
- package/bundled-skills/docs/maintainers/release-notes-7.2.0.md +1 -1
- package/bundled-skills/docs/maintainers/release-process.md +9 -9
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +5 -5
- package/bundled-skills/docs/maintainers/skills-date-tracking.md +1 -1
- package/bundled-skills/docs/maintainers/skills-update-guide.md +2 -2
- package/bundled-skills/docs/users/{antigravity-awesome-skills-vs-awesome-claude-skills.md → agentic-awesome-skills-vs-awesome-claude-skills.md} +5 -5
- package/bundled-skills/docs/users/ai-agent-skills.md +4 -4
- package/bundled-skills/docs/users/best-claude-code-skills-github.md +3 -3
- package/bundled-skills/docs/users/best-cursor-skills-github.md +3 -3
- package/bundled-skills/docs/users/bundles.md +56 -57
- package/bundled-skills/docs/users/claude-code-skills.md +6 -6
- package/bundled-skills/docs/users/codex-cli-skills.md +5 -5
- package/bundled-skills/docs/users/cursor-skills.md +3 -3
- package/bundled-skills/docs/users/faq.md +27 -15
- package/bundled-skills/docs/users/gemini-cli-skills.md +4 -4
- package/bundled-skills/docs/users/getting-started.md +22 -12
- package/bundled-skills/docs/users/kiro-integration.md +7 -7
- package/bundled-skills/docs/users/plugins.md +8 -9
- package/bundled-skills/docs/users/security-skills.md +1 -1
- package/bundled-skills/docs/users/skills-vs-mcp-tools.md +1 -1
- package/bundled-skills/docs/users/specialized-plugin-roadmap.md +3 -4
- package/bundled-skills/docs/users/usage.md +9 -9
- package/bundled-skills/docs/users/visual-guide.md +7 -7
- package/bundled-skills/docs/users/windows-truncation-recovery.md +1 -1
- package/bundled-skills/docs/users/workflows.md +1 -1
- package/bundled-skills/docs/vietnamese/BUNDLES.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/FAQ.vi.md +15 -8
- package/bundled-skills/docs/vietnamese/GETTING_STARTED.vi.md +12 -5
- package/bundled-skills/docs/vietnamese/QUALITY_BAR.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/README.vi.md +21 -17
- package/bundled-skills/docs/vietnamese/SECURITY_GUARDRAILS.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/SKILLS_README.vi.md +2 -2
- package/bundled-skills/docs/vietnamese/TRANSLATION_PLAN.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/VISUAL_GUIDE.vi.md +8 -8
- package/bundled-skills/docs-guard/SKILL.md +109 -0
- package/bundled-skills/docs-guard/references/code-samples.md +46 -0
- package/bundled-skills/docs-guard/references/docstrings.md +60 -0
- package/bundled-skills/docs-guard/references/review-checklist.md +45 -0
- package/bundled-skills/docs-guard/references/sources.md +22 -0
- package/bundled-skills/docs-guard/references/verification.md +51 -0
- package/bundled-skills/efficient-web-research/SKILL.md +4 -0
- package/bundled-skills/electron-development/SKILL.md +1 -1
- package/bundled-skills/event-staffing-compliance/SKILL.md +3 -0
- package/bundled-skills/event-staffing-ordering/SKILL.md +3 -0
- package/bundled-skills/feature-tracking/SKILL.md +222 -0
- package/bundled-skills/flutter-expert/SKILL.md +6 -1
- package/bundled-skills/flutter-expert/resources/implementation-playbook.md +214 -0
- package/bundled-skills/gemini-deep-research/README.md +246 -0
- package/bundled-skills/gemini-deep-research/SKILL.md +136 -0
- package/bundled-skills/gemini-deep-research/requirements.txt +2 -0
- package/bundled-skills/gemini-deep-research/scripts/research.py +692 -0
- package/bundled-skills/git-pushing/SKILL.md +21 -4
- package/bundled-skills/git-pushing/scripts/smart_commit.sh +206 -11
- package/bundled-skills/go-in-depth/scripts/workflow-script.js +4 -1
- package/bundled-skills/grok-build/SKILL.md +167 -0
- package/bundled-skills/grok-build/references/cli.md +99 -0
- package/bundled-skills/huggingface-tool-builder/references/find_models_by_paper.sh +1 -1
- package/bundled-skills/idea-autopsy/SKILL.md +147 -0
- package/bundled-skills/it-manager-hospital/README.md +1 -1
- package/bundled-skills/it-manager-pro/README.md +1 -1
- package/bundled-skills/itil-expert/README.md +1 -1
- package/bundled-skills/langgraph/SKILL.md +1 -1
- package/bundled-skills/linkedin-content-generator/SKILL.md +1 -1
- package/bundled-skills/lint-and-validate/scripts/lint_runner.py +23 -23
- package/bundled-skills/lint-and-validate/scripts/type_coverage.py +30 -30
- package/bundled-skills/logic-lens/SKILL.md +1 -1
- package/bundled-skills/lore/LICENSE +21 -0
- package/bundled-skills/lore/README.md +386 -0
- package/bundled-skills/lore/README.zh-CN.md +386 -0
- package/bundled-skills/lore/SKILL.md +453 -0
- package/bundled-skills/lore/WORKFLOWS.md +216 -0
- package/bundled-skills/lore/WORKFLOWS.zh-CN.md +216 -0
- package/bundled-skills/lore/references/audit-template.md +60 -0
- package/bundled-skills/lore/references/compatibility.md +228 -0
- package/bundled-skills/lore/references/config.md +125 -0
- package/bundled-skills/lore/references/entry-format.md +70 -0
- package/bundled-skills/lore/references/history-command.md +106 -0
- package/bundled-skills/lore/references/monorepo-detection.md +78 -0
- package/bundled-skills/lore/references/platform-mirrors.md +377 -0
- package/bundled-skills/lore/references/stale-new-markers.md +62 -0
- package/bundled-skills/lore/references/summary-template.md +98 -0
- package/bundled-skills/lore/scripts/README.md +54 -0
- package/bundled-skills/lore/scripts/README.zh-CN.md +54 -0
- package/bundled-skills/lore/scripts/find_duplicates.py +211 -0
- package/bundled-skills/lore/scripts/find_stale.py +116 -0
- package/bundled-skills/lore/scripts/history.py +527 -0
- package/bundled-skills/lore/scripts/id_hash.py +32 -0
- package/bundled-skills/lore/scripts/list_entries.py +183 -0
- package/bundled-skills/lovable-cleanup/SKILL.md +3 -3
- package/bundled-skills/makepad-splash/SKILL.md +1 -1
- package/bundled-skills/mmx-cli/SKILL.md +14 -1
- package/bundled-skills/monopoly/SKILL.md +5 -0
- package/bundled-skills/monopoly/patterns/SKILL.md +4 -0
- package/bundled-skills/monopoly/scale-benchmarks/SKILL.md +4 -0
- package/bundled-skills/monopoly/security-checklist/SKILL.md +4 -0
- package/bundled-skills/monopoly/tech-matrix/SKILL.md +4 -0
- package/bundled-skills/mtls-configuration/SKILL.md +1 -1
- package/bundled-skills/network-101/SKILL.md +4 -5
- package/bundled-skills/nika/SKILL.md +270 -0
- package/bundled-skills/not-a-vibe-coder/SKILL.md +4 -0
- package/bundled-skills/ontoly-software-graph/SKILL.md +160 -0
- package/bundled-skills/openclaw-github-repo-commander/SKILL.md +1 -1
- package/bundled-skills/pilot-protocol/SKILL.md +6 -3
- package/bundled-skills/postgres-readonly-queries/README.md +77 -0
- package/bundled-skills/postgres-readonly-queries/SKILL.md +152 -0
- package/bundled-skills/postgres-readonly-queries/connections.example.json +34 -0
- package/bundled-skills/postgres-readonly-queries/requirements.txt +1 -0
- package/bundled-skills/postgres-readonly-queries/scripts/query.py +262 -0
- package/bundled-skills/pptx-deck-creation/SKILL.md +275 -0
- package/bundled-skills/pptx-deck-creation/references/audit-checklist.md +156 -0
- package/bundled-skills/pptx-deck-creation/references/design-profiles.md +96 -0
- package/bundled-skills/pptx-deck-creation/references/python-snippets.md +150 -0
- package/bundled-skills/pptx-deck-creation/references/reference-deck-analysis.md +91 -0
- package/bundled-skills/pptx-deck-creation/references/visual-asset-adapters.md +158 -0
- package/bundled-skills/privilege-escalation-methods/SKILL.md +1 -1
- package/bundled-skills/product-decision-agent/SKILL.md +94 -0
- package/bundled-skills/product-decision-agent/agents/openai.yaml +7 -0
- package/bundled-skills/product-decision-agent/references/methodology-basis.md +232 -0
- package/bundled-skills/product-decision-agent/references/product-playbooks.md +813 -0
- package/bundled-skills/product-decision-agent/references/reasoning-engine.md +289 -0
- package/bundled-skills/product-decision-agent/references/response-examples.md +279 -0
- package/bundled-skills/product-decision-agent/scripts/quality_gate.py +221 -0
- package/bundled-skills/product-decision-agent/scripts/test_quality_gate.py +94 -0
- package/bundled-skills/pytest-skill/reference/playbook.md +358 -0
- package/bundled-skills/quit-sponsor/SKILL.md +96 -0
- package/bundled-skills/rclone-cli/references/providers/s3.md +1 -1
- package/bundled-skills/security-and-hardening/SKILL.md +1 -1
- package/bundled-skills/semgrep-rule-creator/SKILL.md +6 -6
- package/bundled-skills/skill-creator/README.md +3 -3
- package/bundled-skills/skill-creator/SKILL.md +8 -4
- package/bundled-skills/skill-creator/scripts/quick_validate.py +1 -1
- package/bundled-skills/sql-injection-testing/SKILL.md +4 -4
- package/bundled-skills/subagent-orchestrator/README.md +1 -1
- package/bundled-skills/super-code/bash/SKILL.md +3 -0
- package/bundled-skills/super-code/c/SKILL.md +3 -0
- package/bundled-skills/super-code/cpp/SKILL.md +3 -0
- package/bundled-skills/super-code/csharp/SKILL.md +3 -0
- package/bundled-skills/super-code/dart/SKILL.md +3 -0
- package/bundled-skills/super-code/elixir/SKILL.md +3 -0
- package/bundled-skills/super-code/go/SKILL.md +3 -0
- package/bundled-skills/super-code/java/SKILL.md +3 -0
- package/bundled-skills/super-code/kotlin/SKILL.md +3 -0
- package/bundled-skills/super-code/php/SKILL.md +3 -0
- package/bundled-skills/super-code/python/SKILL.md +3 -0
- package/bundled-skills/super-code/ruby/SKILL.md +3 -0
- package/bundled-skills/super-code/rust/SKILL.md +3 -0
- package/bundled-skills/super-code/scala/SKILL.md +3 -0
- package/bundled-skills/super-code/swift/SKILL.md +3 -0
- package/bundled-skills/super-code/typescript/SKILL.md +3 -0
- package/bundled-skills/telegram-bot-messaging/README.md +185 -0
- package/bundled-skills/telegram-bot-messaging/SKILL.md +101 -0
- package/bundled-skills/telegram-bot-messaging/scripts/telegram.sh +420 -0
- package/bundled-skills/test-guard/SKILL.md +132 -0
- package/bundled-skills/test-guard/references/jest.md +50 -0
- package/bundled-skills/test-guard/references/llm-app-testing.md +40 -0
- package/bundled-skills/test-guard/references/phpunit.md +56 -0
- package/bundled-skills/test-guard/references/pytest.md +65 -0
- package/bundled-skills/trading-ledger/SKILL.md +11 -3
- package/bundled-skills/uizze-ui-research/SKILL.md +93 -0
- package/bundled-skills/vibe-code-auditor/SKILL.md +1 -1
- package/bundled-skills/vibecode-production-qa-validator/SKILL.md +1 -1
- package/bundled-skills/videodb/scripts/ws_listener.py +29 -15
- package/bundled-skills/voice-ai-engine-development/README.md +1 -1
- package/bundled-skills/vulnerability-scanner/SKILL.md +1 -1
- package/bundled-skills/warehouse/SKILL.md +154 -0
- package/bundled-skills/woo-guard/SKILL.md +113 -0
- package/bundled-skills/woo-guard/references/checkout-and-money.md +64 -0
- package/bundled-skills/woo-guard/references/hpos-and-crud.md +87 -0
- package/bundled-skills/woo-guard/references/review-checklist.md +44 -0
- package/bundled-skills/woo-guard/references/sources.md +17 -0
- package/bundled-skills/wp-guard/SKILL.md +119 -0
- package/bundled-skills/wp-guard/references/i18n.md +88 -0
- package/bundled-skills/wp-guard/references/performance.md +70 -0
- package/bundled-skills/wp-guard/references/review-checklist.md +57 -0
- package/bundled-skills/wp-guard/references/security.md +100 -0
- package/bundled-skills/wp-guard/references/sources.md +27 -0
- package/bundled-skills/writing-skills/gotchas.md +1 -1
- package/bundled-skills/writing-skills/references/standards/metadata-standard.md +1 -1
- package/bundled-skills/x-twitter-scraper/SKILL.md +14 -32
- package/bundled-skills/xss-html-injection/SKILL.md +3 -3
- package/bundled-skills/youtube-seo-optimizer/SKILL.md +1 -1
- package/bundled-skills/youtube-summarizer/CHANGELOG.md +2 -2
- package/package.json +1 -1
- package/skills_index.json +273 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Gemini Deep Research Skill - Execute autonomous multi-step research tasks.
|
|
4
|
+
|
|
5
|
+
Uses Google's Deep Research Agent for comprehensive, cited research reports.
|
|
6
|
+
Research tasks take 2-10 minutes but produce detailed analysis.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, AsyncIterator, Callable, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import httpx
|
|
21
|
+
except ImportError:
|
|
22
|
+
print("Error: httpx not installed. Run: pip install httpx", file=sys.stderr)
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from dotenv import load_dotenv
|
|
27
|
+
load_dotenv()
|
|
28
|
+
except ImportError:
|
|
29
|
+
pass # dotenv is optional
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DeepResearchError(Exception):
|
|
33
|
+
"""Custom exception for Deep Research API errors."""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class HistoryManager:
|
|
38
|
+
"""Manage local research history cache."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, cache_dir: Optional[str] = None):
|
|
41
|
+
default_dir = os.path.expanduser("~/.cache/deep-research")
|
|
42
|
+
self.cache_dir = Path(cache_dir or os.getenv("DEEP_RESEARCH_CACHE_DIR", default_dir))
|
|
43
|
+
self.history_file = self.cache_dir / "history.json"
|
|
44
|
+
self._ensure_cache_dir()
|
|
45
|
+
|
|
46
|
+
def _ensure_cache_dir(self):
|
|
47
|
+
"""Create cache directory if it doesn't exist."""
|
|
48
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
|
|
50
|
+
def _load_history(self) -> Dict:
|
|
51
|
+
"""Load history from file."""
|
|
52
|
+
if self.history_file.exists():
|
|
53
|
+
try:
|
|
54
|
+
return json.loads(self.history_file.read_text())
|
|
55
|
+
except (json.JSONDecodeError, IOError):
|
|
56
|
+
return {"interactions": []}
|
|
57
|
+
return {"interactions": []}
|
|
58
|
+
|
|
59
|
+
def _save_history(self, history: Dict):
|
|
60
|
+
"""Save history to file."""
|
|
61
|
+
self.history_file.write_text(json.dumps(history, indent=2))
|
|
62
|
+
|
|
63
|
+
def add_interaction(self, interaction_id: str, query: str, status: str = "started"):
|
|
64
|
+
"""Add or update an interaction in history."""
|
|
65
|
+
history = self._load_history()
|
|
66
|
+
|
|
67
|
+
# Check if interaction already exists
|
|
68
|
+
for item in history["interactions"]:
|
|
69
|
+
if item["id"] == interaction_id:
|
|
70
|
+
item["status"] = status
|
|
71
|
+
item["updated_at"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
72
|
+
self._save_history(history)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# Add new interaction
|
|
76
|
+
history["interactions"].insert(0, {
|
|
77
|
+
"id": interaction_id,
|
|
78
|
+
"query": query[:200] + "..." if len(query) > 200 else query,
|
|
79
|
+
"started_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
80
|
+
"status": status
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
# Keep only last 50 interactions
|
|
84
|
+
history["interactions"] = history["interactions"][:50]
|
|
85
|
+
self._save_history(history)
|
|
86
|
+
|
|
87
|
+
def update_status(self, interaction_id: str, status: str):
|
|
88
|
+
"""Update the status of an interaction."""
|
|
89
|
+
history = self._load_history()
|
|
90
|
+
for item in history["interactions"]:
|
|
91
|
+
if item["id"] == interaction_id:
|
|
92
|
+
item["status"] = status
|
|
93
|
+
item["updated_at"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
94
|
+
if status == "completed":
|
|
95
|
+
item["completed_at"] = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
96
|
+
break
|
|
97
|
+
self._save_history(history)
|
|
98
|
+
|
|
99
|
+
def get_recent(self, limit: int = 10) -> List[Dict]:
|
|
100
|
+
"""Get recent interactions."""
|
|
101
|
+
history = self._load_history()
|
|
102
|
+
return history["interactions"][:limit]
|
|
103
|
+
|
|
104
|
+
def get_interaction(self, interaction_id: str) -> Optional[Dict]:
|
|
105
|
+
"""Get a specific interaction by ID."""
|
|
106
|
+
history = self._load_history()
|
|
107
|
+
for item in history["interactions"]:
|
|
108
|
+
if item["id"] == interaction_id:
|
|
109
|
+
return item
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class DeepResearchClient:
|
|
114
|
+
"""Client for Gemini Deep Research API."""
|
|
115
|
+
|
|
116
|
+
BASE_URL = "https://generativelanguage.googleapis.com/v1beta/interactions"
|
|
117
|
+
AGENT = "deep-research-pro-preview-12-2025"
|
|
118
|
+
|
|
119
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
120
|
+
self.api_key = api_key or os.getenv("GEMINI_API_KEY")
|
|
121
|
+
if not self.api_key:
|
|
122
|
+
raise DeepResearchError(
|
|
123
|
+
"GEMINI_API_KEY not set. Set it in .env or environment variables."
|
|
124
|
+
)
|
|
125
|
+
self._client: Optional[httpx.AsyncClient] = None
|
|
126
|
+
self.timeout = int(os.getenv("DEEP_RESEARCH_TIMEOUT", "600"))
|
|
127
|
+
self.poll_interval = int(os.getenv("DEEP_RESEARCH_POLL_INTERVAL", "10"))
|
|
128
|
+
self.history = HistoryManager()
|
|
129
|
+
|
|
130
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
131
|
+
"""Get or create the HTTP client."""
|
|
132
|
+
if self._client is None or self._client.is_closed:
|
|
133
|
+
self._client = httpx.AsyncClient(timeout=60.0)
|
|
134
|
+
return self._client
|
|
135
|
+
|
|
136
|
+
async def close(self):
|
|
137
|
+
"""Close the HTTP client."""
|
|
138
|
+
if self._client and not self._client.is_closed:
|
|
139
|
+
await self._client.aclose()
|
|
140
|
+
|
|
141
|
+
def _build_prompt(self, query: str, format_spec: Optional[str] = None) -> str:
|
|
142
|
+
"""Build the research prompt with optional format specification."""
|
|
143
|
+
prompt = query
|
|
144
|
+
|
|
145
|
+
if format_spec:
|
|
146
|
+
prompt += f"\n\nFormat the output with the following structure:\n{format_spec}"
|
|
147
|
+
|
|
148
|
+
return prompt
|
|
149
|
+
|
|
150
|
+
async def start_research(
|
|
151
|
+
self,
|
|
152
|
+
query: str,
|
|
153
|
+
format_spec: Optional[str] = None,
|
|
154
|
+
previous_interaction_id: Optional[str] = None
|
|
155
|
+
) -> str:
|
|
156
|
+
"""
|
|
157
|
+
Start a research task.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
query: The research query
|
|
161
|
+
format_spec: Optional output format specification
|
|
162
|
+
previous_interaction_id: Optional ID to continue from previous research
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Interaction ID for polling results
|
|
166
|
+
"""
|
|
167
|
+
prompt = self._build_prompt(query, format_spec)
|
|
168
|
+
client = await self._get_client()
|
|
169
|
+
|
|
170
|
+
payload: Dict[str, Any] = {
|
|
171
|
+
"input": prompt,
|
|
172
|
+
"agent": self.AGENT,
|
|
173
|
+
"background": True
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if previous_interaction_id:
|
|
177
|
+
payload["previous_interaction_id"] = previous_interaction_id
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
response = await client.post(
|
|
181
|
+
self.BASE_URL,
|
|
182
|
+
headers={
|
|
183
|
+
"Content-Type": "application/json",
|
|
184
|
+
"x-goog-api-key": str(self.api_key)
|
|
185
|
+
},
|
|
186
|
+
json=payload
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if response.status_code != 200:
|
|
190
|
+
error_text = response.text
|
|
191
|
+
raise DeepResearchError(f"API error {response.status_code}: {error_text}")
|
|
192
|
+
|
|
193
|
+
data = response.json()
|
|
194
|
+
interaction_id = data.get("id") or data.get("name", "").split("/")[-1]
|
|
195
|
+
|
|
196
|
+
if not interaction_id:
|
|
197
|
+
raise DeepResearchError("No interaction ID returned from API")
|
|
198
|
+
|
|
199
|
+
# Track in history
|
|
200
|
+
self.history.add_interaction(interaction_id, query, "started")
|
|
201
|
+
|
|
202
|
+
return interaction_id
|
|
203
|
+
|
|
204
|
+
except httpx.HTTPError as e:
|
|
205
|
+
raise DeepResearchError(f"HTTP error: {e}")
|
|
206
|
+
|
|
207
|
+
async def get_status(self, interaction_id: str) -> Dict[str, Any]:
|
|
208
|
+
"""
|
|
209
|
+
Get the status of a research task.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dict with 'status' and optionally 'result' or 'error'
|
|
213
|
+
"""
|
|
214
|
+
client = await self._get_client()
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
response = await client.get(
|
|
218
|
+
f"{self.BASE_URL}/{interaction_id}",
|
|
219
|
+
headers={"x-goog-api-key": str(self.api_key)},
|
|
220
|
+
timeout=30.0
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if response.status_code != 200:
|
|
224
|
+
return {"status": "error", "error": f"API error: {response.status_code}"}
|
|
225
|
+
|
|
226
|
+
data = response.json()
|
|
227
|
+
status = data.get("status", "unknown")
|
|
228
|
+
|
|
229
|
+
if status == "completed":
|
|
230
|
+
outputs = data.get("outputs", [])
|
|
231
|
+
if outputs:
|
|
232
|
+
text = outputs[-1].get("text", "")
|
|
233
|
+
return {"status": "completed", "result": text, "raw": data}
|
|
234
|
+
return {"status": "completed", "result": None, "raw": data}
|
|
235
|
+
elif status == "failed":
|
|
236
|
+
return {"status": "failed", "error": data.get("error", "Unknown error")}
|
|
237
|
+
else:
|
|
238
|
+
return {"status": status, "raw": data}
|
|
239
|
+
|
|
240
|
+
except httpx.HTTPError as e:
|
|
241
|
+
return {"status": "error", "error": str(e)}
|
|
242
|
+
|
|
243
|
+
async def wait_for_completion(
|
|
244
|
+
self,
|
|
245
|
+
interaction_id: str,
|
|
246
|
+
timeout: Optional[int] = None,
|
|
247
|
+
poll_interval: Optional[int] = None,
|
|
248
|
+
progress_callback: Optional[Callable[[int, float, str], None]] = None
|
|
249
|
+
) -> Dict[str, Any]:
|
|
250
|
+
"""
|
|
251
|
+
Wait for research to complete with polling.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
interaction_id: The interaction ID to poll
|
|
255
|
+
timeout: Maximum wait time in seconds
|
|
256
|
+
poll_interval: Seconds between polls
|
|
257
|
+
progress_callback: Optional callback for progress updates
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Dict with research result or error
|
|
261
|
+
"""
|
|
262
|
+
timeout = timeout or self.timeout
|
|
263
|
+
poll_interval = poll_interval or self.poll_interval
|
|
264
|
+
start_time = asyncio.get_event_loop().time()
|
|
265
|
+
poll_count = 0
|
|
266
|
+
|
|
267
|
+
while True:
|
|
268
|
+
elapsed = asyncio.get_event_loop().time() - start_time
|
|
269
|
+
if elapsed > timeout:
|
|
270
|
+
self.history.update_status(interaction_id, "timeout")
|
|
271
|
+
return {"status": "timeout", "error": f"Research timed out after {timeout}s"}
|
|
272
|
+
|
|
273
|
+
result = await self.get_status(interaction_id)
|
|
274
|
+
poll_count += 1
|
|
275
|
+
|
|
276
|
+
if progress_callback:
|
|
277
|
+
progress_callback(poll_count, elapsed, result.get("status", "unknown"))
|
|
278
|
+
|
|
279
|
+
if result["status"] == "completed":
|
|
280
|
+
self.history.update_status(interaction_id, "completed")
|
|
281
|
+
return result
|
|
282
|
+
elif result["status"] in ["failed", "error"]:
|
|
283
|
+
self.history.update_status(interaction_id, "failed")
|
|
284
|
+
return result
|
|
285
|
+
|
|
286
|
+
await asyncio.sleep(poll_interval)
|
|
287
|
+
|
|
288
|
+
async def stream_research(
|
|
289
|
+
self,
|
|
290
|
+
query: str,
|
|
291
|
+
format_spec: Optional[str] = None
|
|
292
|
+
) -> AsyncIterator[Dict[str, Any]]:
|
|
293
|
+
"""
|
|
294
|
+
Stream research progress in real-time.
|
|
295
|
+
|
|
296
|
+
Yields:
|
|
297
|
+
Dict with event type and content
|
|
298
|
+
"""
|
|
299
|
+
prompt = self._build_prompt(query, format_spec)
|
|
300
|
+
client = await self._get_client()
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
async with client.stream(
|
|
304
|
+
"POST",
|
|
305
|
+
f"{self.BASE_URL}?alt=sse",
|
|
306
|
+
headers={
|
|
307
|
+
"Content-Type": "application/json",
|
|
308
|
+
"x-goog-api-key": str(self.api_key)
|
|
309
|
+
},
|
|
310
|
+
json={
|
|
311
|
+
"input": prompt,
|
|
312
|
+
"agent": self.AGENT,
|
|
313
|
+
"background": True,
|
|
314
|
+
"stream": True,
|
|
315
|
+
"agent_config": {
|
|
316
|
+
"type": "deep-research",
|
|
317
|
+
"thinking_summaries": "auto"
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
timeout=None # No timeout for streaming
|
|
321
|
+
) as response:
|
|
322
|
+
interaction_id = None
|
|
323
|
+
buffer = ""
|
|
324
|
+
|
|
325
|
+
async for chunk in response.aiter_text():
|
|
326
|
+
buffer += chunk
|
|
327
|
+
|
|
328
|
+
# Parse SSE events
|
|
329
|
+
while "\n\n" in buffer:
|
|
330
|
+
event_str, buffer = buffer.split("\n\n", 1)
|
|
331
|
+
|
|
332
|
+
if not event_str.strip():
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
# Parse SSE format
|
|
336
|
+
data_line = None
|
|
337
|
+
for line in event_str.split("\n"):
|
|
338
|
+
if line.startswith("data: "):
|
|
339
|
+
data_line = line[6:]
|
|
340
|
+
break
|
|
341
|
+
|
|
342
|
+
if not data_line:
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
event = json.loads(data_line)
|
|
347
|
+
except json.JSONDecodeError:
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
event_type = event.get("event_type", "")
|
|
351
|
+
|
|
352
|
+
if event_type == "interaction.start":
|
|
353
|
+
interaction_id = event.get("interaction", {}).get("id")
|
|
354
|
+
if interaction_id:
|
|
355
|
+
self.history.add_interaction(interaction_id, query, "streaming")
|
|
356
|
+
yield {"type": "start", "interaction_id": interaction_id}
|
|
357
|
+
|
|
358
|
+
elif event_type == "content.delta":
|
|
359
|
+
delta = event.get("delta", {})
|
|
360
|
+
delta_type = delta.get("type")
|
|
361
|
+
|
|
362
|
+
if delta_type == "text":
|
|
363
|
+
yield {"type": "text", "content": delta.get("text", "")}
|
|
364
|
+
elif delta_type == "thought_summary":
|
|
365
|
+
content = delta.get("content", {})
|
|
366
|
+
yield {"type": "thought", "content": content.get("text", "")}
|
|
367
|
+
|
|
368
|
+
elif event_type == "interaction.complete":
|
|
369
|
+
if interaction_id:
|
|
370
|
+
self.history.update_status(interaction_id, "completed")
|
|
371
|
+
yield {"type": "complete"}
|
|
372
|
+
|
|
373
|
+
elif event_type == "error":
|
|
374
|
+
if interaction_id:
|
|
375
|
+
self.history.update_status(interaction_id, "failed")
|
|
376
|
+
yield {"type": "error", "error": event.get("error", "Unknown error")}
|
|
377
|
+
|
|
378
|
+
except httpx.HTTPError as e:
|
|
379
|
+
yield {"type": "error", "error": str(e)}
|
|
380
|
+
|
|
381
|
+
def parse_result(self, text: str) -> Optional[Dict]:
|
|
382
|
+
"""
|
|
383
|
+
Parse research result text into structured data if JSON.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
text: Raw text output from Gemini
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Parsed JSON data or None if not JSON
|
|
390
|
+
"""
|
|
391
|
+
if not text:
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
# Try direct JSON parsing
|
|
395
|
+
try:
|
|
396
|
+
return json.loads(text)
|
|
397
|
+
except json.JSONDecodeError:
|
|
398
|
+
pass
|
|
399
|
+
|
|
400
|
+
# Try extracting JSON from markdown code blocks
|
|
401
|
+
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, re.DOTALL)
|
|
402
|
+
if json_match:
|
|
403
|
+
try:
|
|
404
|
+
return json.loads(json_match.group(1))
|
|
405
|
+
except json.JSONDecodeError:
|
|
406
|
+
pass
|
|
407
|
+
|
|
408
|
+
# Try finding JSON object pattern
|
|
409
|
+
json_match = re.search(r'\{[^{}]*"[^"]+"\s*:[^{}]*\}', text, re.DOTALL)
|
|
410
|
+
if json_match:
|
|
411
|
+
try:
|
|
412
|
+
return json.loads(json_match.group(0))
|
|
413
|
+
except json.JSONDecodeError:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def print_progress(poll_count: int, elapsed: float, status: str):
|
|
420
|
+
"""Print progress update during polling."""
|
|
421
|
+
mins = int(elapsed // 60)
|
|
422
|
+
secs = int(elapsed % 60)
|
|
423
|
+
print(f"\r[{mins:02d}:{secs:02d}] Poll #{poll_count} - Status: {status}", end="", flush=True)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
async def cmd_research(args):
|
|
427
|
+
"""Execute a research query."""
|
|
428
|
+
client = DeepResearchClient()
|
|
429
|
+
|
|
430
|
+
try:
|
|
431
|
+
if args.stream:
|
|
432
|
+
# Streaming mode
|
|
433
|
+
print(f"Starting streaming research...\n")
|
|
434
|
+
full_text = ""
|
|
435
|
+
|
|
436
|
+
async for event in client.stream_research(args.query, args.format):
|
|
437
|
+
if event["type"] == "start":
|
|
438
|
+
print(f"Interaction ID: {event['interaction_id']}\n")
|
|
439
|
+
elif event["type"] == "thought":
|
|
440
|
+
print(f"\n[Thinking] {event['content']}\n", file=sys.stderr)
|
|
441
|
+
elif event["type"] == "text":
|
|
442
|
+
print(event["content"], end="", flush=True)
|
|
443
|
+
full_text += event["content"]
|
|
444
|
+
elif event["type"] == "complete":
|
|
445
|
+
print("\n\n[Research Complete]")
|
|
446
|
+
elif event["type"] == "error":
|
|
447
|
+
print(f"\n[Error] {event['error']}", file=sys.stderr)
|
|
448
|
+
return 1
|
|
449
|
+
|
|
450
|
+
# Output JSON if requested
|
|
451
|
+
if args.json and full_text:
|
|
452
|
+
parsed = client.parse_result(full_text)
|
|
453
|
+
if parsed:
|
|
454
|
+
print("\n\n--- Parsed JSON ---")
|
|
455
|
+
print(json.dumps(parsed, indent=2))
|
|
456
|
+
else:
|
|
457
|
+
# Polling mode
|
|
458
|
+
previous_id = args.continue_from if hasattr(args, 'continue_from') else None
|
|
459
|
+
|
|
460
|
+
print(f"Starting research task...")
|
|
461
|
+
interaction_id = await client.start_research(
|
|
462
|
+
args.query,
|
|
463
|
+
args.format,
|
|
464
|
+
previous_id
|
|
465
|
+
)
|
|
466
|
+
print(f"Interaction ID: {interaction_id}")
|
|
467
|
+
print(f"Estimated time: 2-10 minutes\n")
|
|
468
|
+
|
|
469
|
+
if args.no_wait:
|
|
470
|
+
print(f"Research started. Check status with: --status {interaction_id}")
|
|
471
|
+
return 0
|
|
472
|
+
|
|
473
|
+
print("Waiting for completion (Ctrl+C to cancel)...")
|
|
474
|
+
result = await client.wait_for_completion(
|
|
475
|
+
interaction_id,
|
|
476
|
+
progress_callback=print_progress
|
|
477
|
+
)
|
|
478
|
+
print() # New line after progress
|
|
479
|
+
|
|
480
|
+
if result["status"] == "completed":
|
|
481
|
+
text = result.get("result", "")
|
|
482
|
+
|
|
483
|
+
if args.json:
|
|
484
|
+
parsed = client.parse_result(text)
|
|
485
|
+
if parsed:
|
|
486
|
+
print(json.dumps(parsed, indent=2))
|
|
487
|
+
else:
|
|
488
|
+
print(json.dumps({"text": text}, indent=2))
|
|
489
|
+
elif args.raw:
|
|
490
|
+
print(json.dumps(result.get("raw", {}), indent=2))
|
|
491
|
+
else:
|
|
492
|
+
print("\n--- Research Result ---\n")
|
|
493
|
+
print(text)
|
|
494
|
+
|
|
495
|
+
return 0
|
|
496
|
+
else:
|
|
497
|
+
print(f"\nResearch failed: {result.get('error', 'Unknown error')}")
|
|
498
|
+
return 1
|
|
499
|
+
|
|
500
|
+
except DeepResearchError as e:
|
|
501
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
502
|
+
return 1
|
|
503
|
+
except KeyboardInterrupt:
|
|
504
|
+
print("\n\nCancelled by user.")
|
|
505
|
+
return 130
|
|
506
|
+
finally:
|
|
507
|
+
await client.close()
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
async def cmd_status(args):
|
|
511
|
+
"""Check status of a research task."""
|
|
512
|
+
client = DeepResearchClient()
|
|
513
|
+
|
|
514
|
+
try:
|
|
515
|
+
result = await client.get_status(args.interaction_id)
|
|
516
|
+
|
|
517
|
+
if args.json:
|
|
518
|
+
print(json.dumps(result, indent=2))
|
|
519
|
+
else:
|
|
520
|
+
print(f"Status: {result['status']}")
|
|
521
|
+
|
|
522
|
+
if result["status"] == "completed":
|
|
523
|
+
text = result.get("result", "")
|
|
524
|
+
if text:
|
|
525
|
+
print(f"\n--- Result Preview ---\n{text[:500]}...")
|
|
526
|
+
elif result["status"] in ["failed", "error"]:
|
|
527
|
+
print(f"Error: {result.get('error', 'Unknown')}")
|
|
528
|
+
|
|
529
|
+
return 0 if result["status"] != "error" else 1
|
|
530
|
+
|
|
531
|
+
except DeepResearchError as e:
|
|
532
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
533
|
+
return 1
|
|
534
|
+
finally:
|
|
535
|
+
await client.close()
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
async def cmd_wait(args):
|
|
539
|
+
"""Wait for a research task to complete."""
|
|
540
|
+
client = DeepResearchClient()
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
print(f"Waiting for {args.interaction_id}...")
|
|
544
|
+
result = await client.wait_for_completion(
|
|
545
|
+
args.interaction_id,
|
|
546
|
+
progress_callback=print_progress
|
|
547
|
+
)
|
|
548
|
+
print()
|
|
549
|
+
|
|
550
|
+
if result["status"] == "completed":
|
|
551
|
+
text = result.get("result", "")
|
|
552
|
+
|
|
553
|
+
if args.json:
|
|
554
|
+
parsed = client.parse_result(text)
|
|
555
|
+
if parsed:
|
|
556
|
+
print(json.dumps(parsed, indent=2))
|
|
557
|
+
else:
|
|
558
|
+
print(json.dumps({"text": text}, indent=2))
|
|
559
|
+
else:
|
|
560
|
+
print("\n--- Research Result ---\n")
|
|
561
|
+
print(text)
|
|
562
|
+
|
|
563
|
+
return 0
|
|
564
|
+
else:
|
|
565
|
+
print(f"Research failed: {result.get('error', 'Unknown error')}")
|
|
566
|
+
return 1
|
|
567
|
+
|
|
568
|
+
except DeepResearchError as e:
|
|
569
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
570
|
+
return 1
|
|
571
|
+
except KeyboardInterrupt:
|
|
572
|
+
print("\n\nCancelled by user.")
|
|
573
|
+
return 130
|
|
574
|
+
finally:
|
|
575
|
+
await client.close()
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
async def cmd_list(args):
|
|
579
|
+
"""List recent research tasks."""
|
|
580
|
+
history = HistoryManager()
|
|
581
|
+
interactions = history.get_recent(args.limit)
|
|
582
|
+
|
|
583
|
+
if not interactions:
|
|
584
|
+
print("No research history found.")
|
|
585
|
+
return 0
|
|
586
|
+
|
|
587
|
+
if args.json:
|
|
588
|
+
print(json.dumps(interactions, indent=2))
|
|
589
|
+
else:
|
|
590
|
+
print(f"Recent research tasks (last {len(interactions)}):\n")
|
|
591
|
+
for item in interactions:
|
|
592
|
+
status_icon = {
|
|
593
|
+
"completed": "[ok]",
|
|
594
|
+
"failed": "[!!]",
|
|
595
|
+
"started": "[..]",
|
|
596
|
+
"streaming": "[>>]",
|
|
597
|
+
"timeout": "[to]"
|
|
598
|
+
}.get(item.get("status", ""), "[??]")
|
|
599
|
+
|
|
600
|
+
print(f"{status_icon} {item['id'][:12]}...")
|
|
601
|
+
print(f" Query: {item['query'][:60]}{'...' if len(item['query']) > 60 else ''}")
|
|
602
|
+
print(f" Started: {item.get('started_at', 'N/A')}")
|
|
603
|
+
if item.get("completed_at"):
|
|
604
|
+
print(f" Completed: {item['completed_at']}")
|
|
605
|
+
print()
|
|
606
|
+
|
|
607
|
+
return 0
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def main():
|
|
611
|
+
parser = argparse.ArgumentParser(
|
|
612
|
+
description="Gemini Deep Research - Autonomous multi-step research agent",
|
|
613
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
614
|
+
epilog="""
|
|
615
|
+
Examples:
|
|
616
|
+
# Start a research task and wait for results
|
|
617
|
+
%(prog)s --query "Research the history of Kubernetes"
|
|
618
|
+
|
|
619
|
+
# With structured output format
|
|
620
|
+
%(prog)s --query "Compare Python web frameworks" \\
|
|
621
|
+
--format "1. Executive Summary\\n2. Comparison Table\\n3. Recommendations"
|
|
622
|
+
|
|
623
|
+
# Stream progress in real-time
|
|
624
|
+
%(prog)s --query "Analyze EV battery market" --stream
|
|
625
|
+
|
|
626
|
+
# Start without waiting
|
|
627
|
+
%(prog)s --query "Research topic" --no-wait
|
|
628
|
+
|
|
629
|
+
# Check status of running research
|
|
630
|
+
%(prog)s --status abc123
|
|
631
|
+
|
|
632
|
+
# Continue from previous research
|
|
633
|
+
%(prog)s --query "Elaborate on point 2" --continue abc123
|
|
634
|
+
|
|
635
|
+
# List recent research
|
|
636
|
+
%(prog)s --list
|
|
637
|
+
|
|
638
|
+
Note: Research tasks typically take 2-10 minutes and cost $2-5 per task.
|
|
639
|
+
"""
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Main commands (mutually exclusive)
|
|
643
|
+
cmd_group = parser.add_mutually_exclusive_group(required=True)
|
|
644
|
+
cmd_group.add_argument("--query", "-q", help="Research query to execute")
|
|
645
|
+
cmd_group.add_argument("--status", "-s", dest="interaction_id", metavar="ID",
|
|
646
|
+
help="Check status of a research task")
|
|
647
|
+
cmd_group.add_argument("--wait", "-w", dest="wait_id", metavar="ID",
|
|
648
|
+
help="Wait for a research task to complete")
|
|
649
|
+
cmd_group.add_argument("--list", "-l", action="store_true",
|
|
650
|
+
help="List recent research tasks")
|
|
651
|
+
|
|
652
|
+
# Query options
|
|
653
|
+
parser.add_argument("--format", "-f", metavar="SPEC",
|
|
654
|
+
help="Output format specification")
|
|
655
|
+
parser.add_argument("--continue", dest="continue_from", metavar="ID",
|
|
656
|
+
help="Continue from previous research interaction")
|
|
657
|
+
parser.add_argument("--stream", action="store_true",
|
|
658
|
+
help="Stream progress in real-time")
|
|
659
|
+
parser.add_argument("--no-wait", action="store_true",
|
|
660
|
+
help="Start research without waiting for completion")
|
|
661
|
+
|
|
662
|
+
# Output options
|
|
663
|
+
parser.add_argument("--json", "-j", action="store_true",
|
|
664
|
+
help="Output results as JSON")
|
|
665
|
+
parser.add_argument("--raw", "-r", action="store_true",
|
|
666
|
+
help="Output raw API response")
|
|
667
|
+
|
|
668
|
+
# List options
|
|
669
|
+
parser.add_argument("--limit", type=int, default=10,
|
|
670
|
+
help="Number of recent tasks to show (default: 10)")
|
|
671
|
+
|
|
672
|
+
args = parser.parse_args()
|
|
673
|
+
|
|
674
|
+
# Route to appropriate command
|
|
675
|
+
if args.query:
|
|
676
|
+
exit_code = asyncio.run(cmd_research(args))
|
|
677
|
+
elif args.interaction_id:
|
|
678
|
+
exit_code = asyncio.run(cmd_status(args))
|
|
679
|
+
elif args.wait_id:
|
|
680
|
+
args.interaction_id = args.wait_id
|
|
681
|
+
exit_code = asyncio.run(cmd_wait(args))
|
|
682
|
+
elif args.list:
|
|
683
|
+
exit_code = asyncio.run(cmd_list(args))
|
|
684
|
+
else:
|
|
685
|
+
parser.print_help()
|
|
686
|
+
exit_code = 1
|
|
687
|
+
|
|
688
|
+
sys.exit(exit_code)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
if __name__ == "__main__":
|
|
692
|
+
main()
|