claude-all-hands 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.claude/agents/code-simplifier.md +52 -0
  2. package/.claude/agents/curator.md +189 -245
  3. package/.claude/agents/documentor.md +147 -0
  4. package/.claude/agents/planner.md +123 -166
  5. package/.claude/agents/researcher.md +58 -41
  6. package/.claude/agents/surveyor.md +81 -0
  7. package/.claude/agents/worker.md +74 -0
  8. package/.claude/commands/audit-docs.md +94 -0
  9. package/.claude/commands/continue.md +120 -0
  10. package/.claude/commands/create-docs.md +100 -0
  11. package/.claude/commands/create-skill.md +107 -0
  12. package/.claude/commands/create-specialist.md +111 -0
  13. package/.claude/commands/curator-audit.md +4 -0
  14. package/.claude/commands/debug.md +183 -0
  15. package/.claude/commands/plan.md +199 -102
  16. package/.claude/commands/validate.md +11 -0
  17. package/.claude/commands/whats-next.md +106 -134
  18. package/.claude/envoy/envoy +11 -14
  19. package/.claude/envoy/package-lock.json +1388 -0
  20. package/.claude/envoy/package.json +29 -0
  21. package/.claude/envoy/src/cli.ts +126 -0
  22. package/.claude/envoy/src/commands/base.ts +216 -0
  23. package/.claude/envoy/src/commands/gemini.ts +999 -0
  24. package/.claude/envoy/src/commands/git.ts +639 -0
  25. package/.claude/envoy/src/commands/index.ts +73 -0
  26. package/.claude/envoy/src/commands/knowledge.ts +187 -0
  27. package/.claude/envoy/src/commands/perplexity.ts +129 -0
  28. package/.claude/envoy/src/commands/plan/core.ts +134 -0
  29. package/.claude/envoy/src/commands/plan/findings.ts +446 -0
  30. package/.claude/envoy/src/commands/plan/gates.ts +672 -0
  31. package/.claude/envoy/src/commands/plan/index.ts +135 -0
  32. package/.claude/envoy/src/commands/plan/lifecycle.ts +648 -0
  33. package/.claude/envoy/src/commands/plan/plan-file.ts +138 -0
  34. package/.claude/envoy/src/commands/plan/prompts.ts +285 -0
  35. package/.claude/envoy/src/commands/plan/protocols.ts +166 -0
  36. package/.claude/envoy/src/commands/repomix.ts +99 -0
  37. package/.claude/envoy/src/commands/tavily.ts +220 -0
  38. package/.claude/envoy/src/commands/xai.ts +168 -0
  39. package/.claude/envoy/src/lib/design.ts +41 -0
  40. package/.claude/envoy/src/lib/feedback-schemas.ts +154 -0
  41. package/.claude/envoy/src/lib/findings.ts +215 -0
  42. package/.claude/envoy/src/lib/gates.ts +572 -0
  43. package/.claude/envoy/src/lib/git.ts +132 -0
  44. package/.claude/envoy/src/lib/index.ts +188 -0
  45. package/.claude/envoy/src/lib/knowledge.ts +594 -0
  46. package/.claude/envoy/src/lib/markdown.ts +75 -0
  47. package/.claude/envoy/src/lib/observability.ts +262 -0
  48. package/.claude/envoy/src/lib/paths.ts +130 -0
  49. package/.claude/envoy/src/lib/plan-io.ts +117 -0
  50. package/.claude/envoy/src/lib/prompts.ts +231 -0
  51. package/.claude/envoy/src/lib/protocols.ts +314 -0
  52. package/.claude/envoy/src/lib/repomix.ts +133 -0
  53. package/.claude/envoy/src/lib/retry.ts +138 -0
  54. package/.claude/envoy/src/lib/watcher.ts +167 -0
  55. package/.claude/envoy/tsconfig.json +21 -0
  56. package/.claude/hooks/scripts/scan_agents.py +62 -0
  57. package/.claude/hooks/scripts/scan_commands.py +50 -0
  58. package/.claude/hooks/scripts/scan_skills.py +46 -70
  59. package/.claude/hooks/scripts/validate_artifacts.py +128 -0
  60. package/.claude/hooks/startup.sh +26 -24
  61. package/.claude/protocols/bug-discovery.yaml +55 -0
  62. package/.claude/protocols/debugging.yaml +51 -0
  63. package/.claude/protocols/discovery.yaml +53 -0
  64. package/.claude/protocols/implementation.yaml +84 -0
  65. package/.claude/settings.json +37 -97
  66. package/.claude/skills/brainstorming/SKILL.md +54 -0
  67. package/.claude/skills/commands-development/SKILL.md +630 -0
  68. package/.claude/skills/commands-development/references/arguments.md +252 -0
  69. package/.claude/skills/commands-development/references/patterns.md +796 -0
  70. package/.claude/skills/commands-development/references/tool-restrictions.md +376 -0
  71. package/.claude/skills/discovery-mode/SKILL.md +108 -0
  72. package/.claude/skills/hooks-development/SKILL.md +332 -0
  73. package/.claude/skills/hooks-development/references/command-vs-prompt.md +269 -0
  74. package/.claude/skills/hooks-development/references/examples.md +658 -0
  75. package/.claude/skills/hooks-development/references/hook-types.md +463 -0
  76. package/.claude/skills/hooks-development/references/input-output-schemas.md +469 -0
  77. package/.claude/skills/hooks-development/references/matchers.md +470 -0
  78. package/.claude/skills/hooks-development/references/troubleshooting.md +587 -0
  79. package/.claude/skills/implementation-mode/SKILL.md +171 -0
  80. package/.claude/skills/research-tools/SKILL.md +35 -33
  81. package/.claude/skills/skills-development/SKILL.md +192 -0
  82. package/.claude/skills/skills-development/references/api-security.md +226 -0
  83. package/.claude/skills/skills-development/references/be-clear-and-direct.md +531 -0
  84. package/.claude/skills/skills-development/references/common-patterns.md +595 -0
  85. package/.claude/skills/skills-development/references/core-principles.md +437 -0
  86. package/.claude/skills/skills-development/references/executable-code.md +175 -0
  87. package/.claude/skills/skills-development/references/iteration-and-testing.md +474 -0
  88. package/.claude/skills/skills-development/references/recommended-structure.md +168 -0
  89. package/.claude/skills/skills-development/references/skill-structure.md +372 -0
  90. package/.claude/skills/skills-development/references/use-xml-tags.md +466 -0
  91. package/.claude/skills/skills-development/references/using-scripts.md +113 -0
  92. package/.claude/skills/skills-development/references/using-templates.md +112 -0
  93. package/.claude/skills/skills-development/references/workflows-and-validation.md +510 -0
  94. package/.claude/skills/skills-development/templates/router-skill.md +73 -0
  95. package/.claude/skills/skills-development/templates/simple-skill.md +33 -0
  96. package/.claude/skills/skills-development/workflows/add-reference.md +96 -0
  97. package/.claude/skills/skills-development/workflows/add-script.md +93 -0
  98. package/.claude/skills/skills-development/workflows/add-template.md +74 -0
  99. package/.claude/skills/skills-development/workflows/add-workflow.md +120 -0
  100. package/.claude/skills/skills-development/workflows/audit-skill.md +138 -0
  101. package/.claude/skills/skills-development/workflows/create-domain-expertise-skill.md +605 -0
  102. package/.claude/skills/skills-development/workflows/create-new-skill.md +191 -0
  103. package/.claude/skills/skills-development/workflows/get-guidance.md +121 -0
  104. package/.claude/skills/skills-development/workflows/upgrade-to-router.md +161 -0
  105. package/.claude/skills/skills-development/workflows/verify-skill.md +204 -0
  106. package/.claude/skills/subagents-development/SKILL.md +325 -0
  107. package/.claude/skills/subagents-development/references/context-management.md +567 -0
  108. package/.claude/skills/subagents-development/references/debugging-agents.md +714 -0
  109. package/.claude/skills/subagents-development/references/error-handling-and-recovery.md +502 -0
  110. package/.claude/skills/subagents-development/references/evaluation-and-testing.md +374 -0
  111. package/.claude/skills/subagents-development/references/orchestration-patterns.md +591 -0
  112. package/.claude/skills/subagents-development/references/subagents.md +508 -0
  113. package/.claude/skills/subagents-development/references/writing-subagent-prompts.md +517 -0
  114. package/.claude/statusline.sh +24 -0
  115. package/bin/cli.js +110 -72
  116. package/package.json +1 -1
  117. package/.claude/agents/explorer.md +0 -62
  118. package/.claude/agents/parallel-worker.md +0 -121
  119. package/.claude/commands/curation-fix.md +0 -92
  120. package/.claude/commands/new-branch.md +0 -36
  121. package/.claude/commands/parallel-discovery.md +0 -69
  122. package/.claude/commands/parallel-orchestration.md +0 -99
  123. package/.claude/commands/plan-checkpoint.md +0 -37
  124. package/.claude/envoy/commands/__init__.py +0 -1
  125. package/.claude/envoy/commands/base.py +0 -95
  126. package/.claude/envoy/commands/parallel.py +0 -439
  127. package/.claude/envoy/commands/perplexity.py +0 -86
  128. package/.claude/envoy/commands/plans.py +0 -451
  129. package/.claude/envoy/commands/tavily.py +0 -156
  130. package/.claude/envoy/commands/vertex.py +0 -358
  131. package/.claude/envoy/commands/xai.py +0 -124
  132. package/.claude/envoy/envoy.py +0 -122
  133. package/.claude/envoy/pyrightconfig.json +0 -4
  134. package/.claude/envoy/requirements.txt +0 -2
  135. package/.claude/hooks/capture-queries.sh +0 -3
  136. package/.claude/hooks/scripts/enforce_planning.py +0 -118
  137. package/.claude/hooks/scripts/enforce_rg.py +0 -34
  138. package/.claude/hooks/scripts/validate_skill.py +0 -81
  139. package/.claude/skills/claude-envoy-curation/SKILL.md +0 -162
  140. package/.claude/skills/claude-envoy-usage/SKILL.md +0 -46
  141. package/.claude/skills/command-development/SKILL.md +0 -206
  142. package/.claude/skills/command-development/examples/simple-commands.md +0 -212
  143. package/.claude/skills/command-development/references/frontmatter-reference.md +0 -221
  144. package/.claude/skills/hook-development/SKILL.md +0 -127
  145. package/.claude/skills/hook-development/examples/command-hooks.md +0 -301
  146. package/.claude/skills/hook-development/examples/prompt-hooks.md +0 -114
  147. package/.claude/skills/hook-development/references/event-reference.md +0 -226
  148. package/.claude/skills/repomix-extraction/SKILL.md +0 -91
  149. package/.claude/skills/skill-development/SKILL.md +0 -168
  150. package/.claude/skills/skill-development/examples/complete-skill-examples.md +0 -281
  151. package/.claude/skills/skill-development/references/progressive-disclosure.md +0 -141
  152. package/.claude/skills/skill-development/references/writing-style.md +0 -180
  153. package/.claude/skills/skill-development/scripts/validate-skill.sh +0 -144
  154. package/.claude/skills/specialist-builder/SKILL.md +0 -327
  155. package/.claude/skills/specialist-builder/docs/agent-catalog.md +0 -28
  156. package/.claude/skills/specialist-builder/examples/complete-agent-examples.md +0 -206
  157. package/.claude/skills/specialist-builder/references/system-prompt-patterns.md +0 -281
  158. package/.claude/skills/specialist-builder/references/triggering-examples.md +0 -162
  159. package/.claude/skills/specialist-builder/scripts/validate-agent.sh +0 -137
  160. /package/.claude/{envoy/claude-envoy.py → skills/claude-envoy-patterns/SKILL.md} +0 -0
@@ -1,358 +0,0 @@
1
- """Vertex AI (Gemini) commands."""
2
- # pyright: reportAttributeAccessIssue=false
3
-
4
- from __future__ import annotations
5
-
6
- import json
7
- import os
8
- import re
9
- import subprocess
10
- from pathlib import Path
11
- from typing import Optional
12
-
13
- from .base import BaseCommand
14
- from .plans import get_plan_dir, get_branch, is_direct_mode_branch
15
-
16
-
17
- def get_base_branch() -> str:
18
- """Auto-detect base branch using merge-base."""
19
- # Try common base branches in order
20
- for base in ["main", "master", "develop", "staging", "production"]:
21
- result = subprocess.run(
22
- ["git", "merge-base", base, "HEAD"],
23
- capture_output=True,
24
- text=True,
25
- )
26
- if result.returncode == 0:
27
- return base
28
- return "main"
29
-
30
-
31
- class VertexAskCommand(BaseCommand):
32
- """Raw Gemini inference - thin wrapper, no system prompt."""
33
-
34
- name = "ask"
35
- description = "Raw Gemini inference"
36
-
37
- def add_arguments(self, parser) -> None:
38
- parser.add_argument("query", help="Query for Gemini")
39
- parser.add_argument("--files", nargs="+", help="Files to include as context")
40
- parser.add_argument("--context", help="Additional context")
41
- parser.add_argument("--model", default="gemini-2.0-flash", help="Model to use")
42
-
43
- def execute(
44
- self,
45
- *,
46
- query: str,
47
- model: str = "gemini-2.0-flash",
48
- files: Optional[list[str]] = None,
49
- context: Optional[str] = None,
50
- **kwargs,
51
- ) -> dict:
52
- api_key = os.environ.get("VERTEX_API_KEY")
53
- if not api_key:
54
- return self.error("auth_error", "VERTEX_API_KEY not set")
55
-
56
- parts = []
57
- if context:
58
- parts.append(context)
59
- if files:
60
- file_contents = self.read_files(files)
61
- if file_contents:
62
- file_context = "\n\n".join(f"### {path}\n```\n{content}\n```" for path, content in file_contents.items())
63
- parts.append(file_context)
64
- parts.append(query)
65
- prompt = "\n\n".join(parts)
66
-
67
- try:
68
- response, duration_ms = self.timed_execute(self._call_api, api_key, model, prompt)
69
- return self.success(
70
- {"content": response},
71
- {"model": model, "command": "vertex ask", "duration_ms": duration_ms},
72
- )
73
- except Exception as e:
74
- return self.error("api_error", str(e))
75
-
76
- def _call_api(self, api_key: str, model: str, prompt: str) -> str:
77
- from google import genai
78
-
79
- client = genai.Client(vertexai=True, api_key=api_key)
80
- response = client.models.generate_content(model=model, contents=prompt)
81
- return response.text
82
-
83
-
84
- class VertexValidateCommand(BaseCommand):
85
- """Validate plan against user requirements."""
86
-
87
- name = "validate"
88
- description = "Validate plan against requirements (anti-overengineering)"
89
-
90
- SYSTEM_PROMPT = """You are a plan validator ensuring implementations are NOT over-engineered.
91
-
92
- Given user requirements and a proposed plan, evaluate:
93
- - Does plan exceed what user actually asked for?
94
- - Are there unnecessary abstractions/features?
95
- - Is complexity justified by requirements?
96
- - Are there gaps, risks, or architectural concerns?
97
-
98
- Be thorough and direct. Only ask questions for user discretion you can't answer yourself.
99
-
100
- IMPORTANT: This is SYSTEM VALIDATION only. User approval is a separate step after validation passes.
101
-
102
- Validation result: "valid" or "invalid" (binary).
103
- - "valid": Plan is acceptable and ready for user approval.
104
- - "invalid": Plan has issues (over-engineered, unclear requirements, missing details, etc.)
105
-
106
- The verdict_context MUST explain the reasoning - whether over-engineered, what's missing, lack of user clarification, or why it passes.
107
-
108
- CRITICAL: If user_questions is non-empty, validation_result MUST be "invalid". A "valid" result with pending questions is not allowed.
109
-
110
- Output JSON:
111
- {
112
- "validation_result": "valid" | "invalid",
113
- "verdict_context": "Specific reasoning - what's wrong, over-engineering issues, missing clarification, or why it passes",
114
- "recommended_edits": ["Edits to fix issues (required if invalid), or minor improvements (optional if valid)"],
115
- "user_questions": ["Questions needing user input - MUST be empty if validation_result is valid"]
116
- }"""
117
-
118
- def add_arguments(self, parser) -> None:
119
- parser.add_argument("--queries", help="Queries file path (optional)")
120
- parser.add_argument("--context", help="Additional context")
121
-
122
- def execute(self, *, queries: Optional[str] = None, context: Optional[str] = None, **kwargs) -> dict:
123
- api_key = os.environ.get("VERTEX_API_KEY")
124
- if not api_key:
125
- return self.error("auth_error", "VERTEX_API_KEY not set")
126
-
127
- if is_direct_mode_branch(get_branch()):
128
- return self.error("direct_mode", "No plan in direct mode")
129
-
130
- plan_path = get_plan_dir()
131
- plan_file = plan_path / "plan.md"
132
- queries_file = queries or plan_path / "queries.jsonl"
133
-
134
- plan_content = self.read_file(str(plan_file))
135
- if not plan_content:
136
- return self.error("file_not_found", f"Plan file not found: {plan_file}")
137
-
138
- queries_content = ""
139
- if queries_file and Path(queries_file).exists():
140
- with open(queries_file) as f:
141
- queries_list = [json.loads(line).get("prompt", "") for line in f if line.strip()]
142
- queries_content = "\n".join(f"- {q}" for q in queries_list)
143
-
144
- additional = f"\n\n## Additional Context\n{context}" if context else ""
145
- full_prompt = f"""{self.SYSTEM_PROMPT}
146
-
147
- ## User Requirements/Queries
148
- {queries_content or "(No queries captured)"}
149
-
150
- ## Plan
151
- {plan_content}
152
- {additional}
153
-
154
- Respond with JSON only."""
155
-
156
- try:
157
- response, duration_ms = self.timed_execute(self._call_api, api_key, full_prompt)
158
- parsed = self._parse_json_response(response)
159
- return self.success(parsed, {"command": "vertex validate", "duration_ms": duration_ms})
160
- except Exception as e:
161
- return self.error("api_error", str(e))
162
-
163
- def _call_api(self, api_key: str, prompt: str) -> str:
164
- from google import genai
165
-
166
- client = genai.Client(vertexai=True, api_key=api_key)
167
- response = client.models.generate_content(model="gemini-2.0-flash", contents=prompt)
168
- return response.text
169
-
170
- def _parse_json_response(self, response: str) -> dict:
171
- json_match = re.search(r"\{[\s\S]*\}", response)
172
- if json_match:
173
- return json.loads(json_match.group())
174
- return {"raw_response": response}
175
-
176
-
177
- class VertexReviewCommand(BaseCommand):
178
- """Review implementation against plan."""
179
-
180
- name = "review"
181
- description = "Review implementation against plan (uses git diff)"
182
-
183
- SYSTEM_PROMPT = """You are an implementation reviewer for a lean development team.
184
-
185
- Core philosophy: Keep it simple. Favor pragmatic shortcuts over elaborate abstractions. This is likely an early-stage or small-team product where shipping fast matters more than architectural perfection.
186
-
187
- Given a plan and git diff of changes, evaluate:
188
- - Does implementation match plan intent?
189
- - Is there over-engineering? (unnecessary abstractions, premature optimization, excessive indirection)
190
- - Are there simpler alternatives that would achieve the same goal?
191
- - Code quality issues that actually matter?
192
-
193
- Flag anything that adds complexity without clear immediate value.
194
-
195
- Output JSON:
196
- {
197
- "verdict": "approved" | "needs_work" | "off_track",
198
- "verdict_context": "Explanation with specific reasoning",
199
- "over_engineering": ["Any unnecessary complexity spotted"],
200
- "simplification_opportunities": ["Ways to reduce complexity"],
201
- "issues": ["Actionable issues to address"],
202
- "user_questions": ["Questions if clarification needed"]
203
- }"""
204
-
205
- def add_arguments(self, parser) -> None:
206
- parser.add_argument("--last-commit", action="store_true", help="Diff against last commit (for step reviews)")
207
- parser.add_argument("--context", help="Additional context")
208
-
209
- def execute(self, *, last_commit: bool = False, context: Optional[str] = None, **kwargs) -> dict:
210
- api_key = os.environ.get("VERTEX_API_KEY")
211
- if not api_key:
212
- return self.error("auth_error", "VERTEX_API_KEY not set")
213
-
214
- if is_direct_mode_branch(get_branch()):
215
- return self.error("direct_mode", "No plan in direct mode")
216
-
217
- plan_path = get_plan_dir()
218
- plan_file = plan_path / "plan.md"
219
- plan_content = self.read_file(str(plan_file))
220
- if not plan_content:
221
- return self.error("file_not_found", f"Plan file not found: {plan_file}")
222
-
223
- diff_ref = "HEAD~1" if last_commit else get_base_branch()
224
- diff_result = subprocess.run(["git", "diff", diff_ref], capture_output=True, text=True)
225
-
226
- # Fallback to empty tree if ref doesn't exist (fresh repo)
227
- if diff_result.returncode != 0:
228
- empty_tree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" # git's empty tree hash
229
- diff_ref = "empty tree"
230
- diff_result = subprocess.run(["git", "diff", empty_tree], capture_output=True, text=True)
231
- if diff_result.returncode != 0:
232
- return self.error("git_error", f"Failed to get diff: {diff_result.stderr}")
233
-
234
- diff_content = diff_result.stdout or "(No changes)"
235
-
236
- additional = f"\n\n## Additional Context\n{context}" if context else ""
237
- full_prompt = f"""{self.SYSTEM_PROMPT}
238
-
239
- ## Plan
240
- {plan_content}
241
-
242
- ## Git Diff (against {diff_ref})
243
- ```diff
244
- {diff_content}
245
- ```
246
- {additional}
247
-
248
- Respond with JSON only."""
249
-
250
- try:
251
- response, duration_ms = self.timed_execute(self._call_api, api_key, full_prompt)
252
- parsed = self._parse_json_response(response)
253
- return self.success(parsed, {"command": "vertex review", "duration_ms": duration_ms})
254
- except Exception as e:
255
- return self.error("api_error", str(e))
256
-
257
- def _call_api(self, api_key: str, prompt: str) -> str:
258
- from google import genai
259
-
260
- client = genai.Client(vertexai=True, api_key=api_key)
261
- response = client.models.generate_content(model="gemini-2.0-flash", contents=prompt)
262
- return response.text
263
-
264
- def _parse_json_response(self, response: str) -> dict:
265
- json_match = re.search(r"\{[\s\S]*\}", response)
266
- if json_match:
267
- return json.loads(json_match.group())
268
- return {"raw_response": response}
269
-
270
-
271
- class VertexArchitectCommand(BaseCommand):
272
- """Solutions architecture guidance."""
273
-
274
- name = "architect"
275
- description = "Solutions architecture for complex features"
276
-
277
- SYSTEM_PROMPT = """You are a solutions architect for complex software systems.
278
-
279
- Given a feature request and optional codebase context:
280
- 1. Identify architectural decisions needed
281
- 2. Propose approaches with trade-offs
282
- 3. Recommend implementation strategy
283
- 4. Flag risks and unknowns
284
-
285
- Output JSON:
286
- {
287
- "complexity_assessment": "simple" | "moderate" | "complex" | "system_integration",
288
- "architectural_decisions": [{
289
- "decision": "string",
290
- "options": [{"option": "string", "pros": ["string"], "cons": ["string"]}],
291
- "recommendation": "string",
292
- "rationale": "string"
293
- }],
294
- "implementation_strategy": {
295
- "approach": "string",
296
- "phases": [{"phase": "string", "deliverables": ["string"]}],
297
- "dependencies": ["string"]
298
- },
299
- "risks": [{"risk": "string", "mitigation": "string", "severity": "low"|"medium"|"high"}],
300
- "questions_for_user": ["string"]
301
- }"""
302
-
303
- def add_arguments(self, parser) -> None:
304
- parser.add_argument("query", help="Feature/system description")
305
- parser.add_argument("--files", nargs="+", help="Relevant code files")
306
- parser.add_argument("--context", help="Additional context or constraints")
307
-
308
- def execute(self, *, query: str, files: Optional[list[str]] = None, context: Optional[str] = None, **kwargs) -> dict:
309
- api_key = os.environ.get("VERTEX_API_KEY")
310
- if not api_key:
311
- return self.error("auth_error", "VERTEX_API_KEY not set")
312
-
313
- file_context = ""
314
- if files:
315
- file_contents = self.read_files(files)
316
- if file_contents:
317
- file_context = "\n\n## Existing Code\n" + "\n\n".join(
318
- f"### {path}\n```\n{content}\n```" for path, content in file_contents.items()
319
- )
320
-
321
- additional = f"\n\n## Additional Context\n{context}" if context else ""
322
-
323
- full_prompt = f"""{self.SYSTEM_PROMPT}
324
-
325
- ## Feature Request
326
- {query}
327
- {file_context}
328
- {additional}
329
-
330
- Respond with JSON only."""
331
-
332
- try:
333
- response, duration_ms = self.timed_execute(self._call_api, api_key, full_prompt)
334
- parsed = self._parse_json_response(response)
335
- return self.success(parsed, {"command": "vertex architect", "duration_ms": duration_ms})
336
- except Exception as e:
337
- return self.error("api_error", str(e))
338
-
339
- def _call_api(self, api_key: str, prompt: str) -> str:
340
- from google import genai
341
-
342
- client = genai.Client(vertexai=True, api_key=api_key)
343
- response = client.models.generate_content(model="gemini-2.0-flash", contents=prompt)
344
- return response.text
345
-
346
- def _parse_json_response(self, response: str) -> dict:
347
- json_match = re.search(r"\{[\s\S]*\}", response)
348
- if json_match:
349
- return json.loads(json_match.group())
350
- return {"raw_response": response}
351
-
352
-
353
- COMMANDS = {
354
- "ask": VertexAskCommand,
355
- "validate": VertexValidateCommand,
356
- "review": VertexReviewCommand,
357
- "architect": VertexArchitectCommand,
358
- }
@@ -1,124 +0,0 @@
1
- """xAI Grok API commands - X search for technology research."""
2
-
3
- import os
4
- from typing import Optional
5
-
6
- import requests
7
- from .base import BaseCommand
8
-
9
-
10
- SYSTEM_PROMPT = """You are a technology research assistant. Search X (Twitter) for posts about the given technology, tool, or concept.
11
-
12
- Find and synthesize:
13
- - Developer opinions and experiences
14
- - Comparisons with alternatives
15
- - Common issues or gotchas
16
- - Recent developments or announcements
17
- - Community sentiment
18
-
19
- Return a structured summary with key findings and notable posts."""
20
-
21
- CHALLENGER_PROMPT = """You are a critical research challenger. Given research findings, search X to:
22
-
23
- 1. CHALLENGE: Find contradicting opinions, failed implementations, known issues
24
- 2. ALTERNATIVES: Surface newer/better tools the research may have missed
25
- 3. TRENDS: Identify emerging patterns that could affect the recommendations
26
- 4. SENTIMENT: Gauge real developer satisfaction vs marketing claims
27
- 5. DISCUSSIONS: Find where the best practitioners are discussing this topic
28
-
29
- Be skeptical. Surface what the research missed or got wrong. Focus on recent posts (last 6 months)."""
30
-
31
-
32
- class XaiSearchCommand(BaseCommand):
33
- """Search X for technology insights using Grok with X Search tool."""
34
-
35
- name = "search"
36
- description = "Search X for technology opinions, alternatives, and community insights"
37
-
38
- def add_arguments(self, parser) -> None:
39
- parser.add_argument("query", help="Technology/topic to research on X")
40
- parser.add_argument("--context", help="Previous research findings to build upon")
41
- parser.add_argument("--results-to-challenge", help="Research results to challenge (enables challenger mode)")
42
-
43
- def execute(self, query: str, context: Optional[str] = None, results_to_challenge: Optional[str] = None, **kwargs) -> dict:
44
- api_key = os.environ.get("X_AI_API_KEY")
45
- if not api_key:
46
- return self.error("auth_error", "X_AI_API_KEY not set")
47
-
48
- # Determine mode and build prompt
49
- if results_to_challenge:
50
- system_prompt = CHALLENGER_PROMPT
51
- user_prompt = f"""Original query: {query}
52
-
53
- Research findings to challenge:
54
- {results_to_challenge}
55
-
56
- Search X to challenge these findings."""
57
- elif context:
58
- system_prompt = SYSTEM_PROMPT
59
- user_prompt = f"""Previous research findings:
60
- {context}
61
-
62
- Now search X for additional insights about: {query}
63
-
64
- Focus on opinions, alternatives, and community discussions that complement the existing findings."""
65
- else:
66
- system_prompt = SYSTEM_PROMPT
67
- user_prompt = f"Search X for developer opinions, experiences, and alternatives regarding: {query}"
68
-
69
- try:
70
- response, duration_ms = self.timed_execute(
71
- self._call_api, api_key, user_prompt, system_prompt
72
- )
73
-
74
- content = response["choices"][0]["message"]["content"]
75
- citations = response.get("citations", [])
76
- usage = response.get("usage", {})
77
- tool_usage = response.get("server_side_tool_usage", {})
78
-
79
- return self.success(
80
- {
81
- "content": content,
82
- "citations": citations,
83
- },
84
- {
85
- "model": "grok-4-1-fast",
86
- "command": "xai search",
87
- "duration_ms": duration_ms,
88
- "input_tokens": usage.get("prompt_tokens"),
89
- "output_tokens": usage.get("completion_tokens"),
90
- "reasoning_tokens": usage.get("reasoning_tokens"),
91
- "x_search_calls": tool_usage.get("SERVER_SIDE_TOOL_X_SEARCH", 0),
92
- },
93
- )
94
-
95
- except requests.exceptions.Timeout:
96
- return self.error("timeout", f"Request timed out after {self.timeout_ms}ms")
97
- except requests.exceptions.RequestException as e:
98
- return self.error("api_error", str(e))
99
- except (KeyError, IndexError) as e:
100
- return self.error("parse_error", f"Unexpected response format: {e}")
101
-
102
- def _call_api(self, api_key: str, user_prompt: str, system_prompt: str) -> dict:
103
- response = requests.post(
104
- "https://api.x.ai/v1/chat/completions",
105
- headers={
106
- "Authorization": f"Bearer {api_key}",
107
- "Content-Type": "application/json",
108
- },
109
- json={
110
- "model": "grok-4-1-fast",
111
- "messages": [
112
- {"role": "system", "content": system_prompt},
113
- {"role": "user", "content": user_prompt},
114
- ],
115
- },
116
- timeout=self.timeout_ms / 1000,
117
- )
118
- response.raise_for_status()
119
- return response.json()
120
-
121
-
122
- COMMANDS = {
123
- "search": XaiSearchCommand,
124
- }
@@ -1,122 +0,0 @@
1
- #!/usr/bin/env python3
2
- """claude-envoy: CLI for agent-scoped external tool access.
3
-
4
- Commands are auto-discovered from the commands/ directory.
5
- Each command module registers itself via COMMANDS dict.
6
- """
7
-
8
- import argparse
9
- import importlib
10
- import json
11
- import os
12
- import sys
13
- from pathlib import Path
14
-
15
-
16
- def discover_commands() -> dict:
17
- """Auto-discover command modules from commands/ directory.
18
-
19
- Each module should have a COMMANDS dict mapping subcommand names to classes.
20
- Returns: {group: {subcommand: CommandClass}}
21
- """
22
- commands_dir = Path(__file__).parent / "commands"
23
- all_commands = {}
24
-
25
- for module_file in commands_dir.glob("*.py"):
26
- if module_file.name.startswith("_"):
27
- continue
28
-
29
- module_name = module_file.stem
30
- try:
31
- module = importlib.import_module(f"commands.{module_name}")
32
- if hasattr(module, "COMMANDS"):
33
- all_commands[module_name] = module.COMMANDS
34
- except ImportError as e:
35
- # Skip modules with missing dependencies
36
- print(f"Warning: Could not load {module_name}: {e}", file=sys.stderr)
37
-
38
- return all_commands
39
-
40
-
41
- def build_parser(commands: dict) -> argparse.ArgumentParser:
42
- """Build argparse parser from discovered commands."""
43
- parser = argparse.ArgumentParser(
44
- prog="envoy",
45
- description="CLI for agent-scoped external tool access",
46
- )
47
- subparsers = parser.add_subparsers(dest="command", help="Available commands")
48
-
49
- for group_name, subcommands in commands.items():
50
- group = subparsers.add_parser(group_name, help=f"{group_name.title()} tools")
51
- group_sub = group.add_subparsers(dest="subcommand")
52
-
53
- for subcmd_name, cmd_class in subcommands.items():
54
- cmd = cmd_class()
55
- subcmd = group_sub.add_parser(subcmd_name, help=cmd.description)
56
- cmd.add_arguments(subcmd)
57
-
58
- # Built-in info command
59
- subparsers.add_parser("info", help="Show available commands and API status")
60
-
61
- return parser
62
-
63
-
64
- def run_command(args: argparse.Namespace, commands: dict) -> dict:
65
- """Route to appropriate command handler."""
66
- if args.command == "info":
67
- return get_info(commands)
68
-
69
- if args.command in commands:
70
- subcommands = commands[args.command]
71
- if args.subcommand in subcommands:
72
- cmd = subcommands[args.subcommand]()
73
- # Convert args namespace to dict, excluding command/subcommand
74
- kwargs = {k: v for k, v in vars(args).items() if k not in ("command", "subcommand")}
75
- return cmd.execute(**kwargs)
76
-
77
- return {"status": "error", "error": {"type": "invalid_command", "message": "Unknown command"}}
78
-
79
-
80
- def get_info(commands: dict) -> dict:
81
- """Return info about available commands and API status."""
82
- cmd_list = ["info"]
83
- for group, subcommands in commands.items():
84
- for subcmd in subcommands:
85
- cmd_list.append(f"{group} {subcmd}")
86
-
87
- return {
88
- "status": "success",
89
- "data": {
90
- "version": "0.1.0",
91
- "commands": sorted(cmd_list),
92
- "api_keys": {
93
- "PERPLEXITY_API_KEY": "set" if os.environ.get("PERPLEXITY_API_KEY") else "missing",
94
- "TAVILY_API_KEY": "set" if os.environ.get("TAVILY_API_KEY") else "missing",
95
- "VERTEX_API_KEY": "set" if os.environ.get("VERTEX_API_KEY") else "missing",
96
- },
97
- "timeout_ms": os.environ.get("ENVOY_TIMEOUT_MS", "120000"),
98
- },
99
- }
100
-
101
-
102
- def main():
103
- # Add commands directory to path
104
- sys.path.insert(0, str(Path(__file__).parent))
105
-
106
- commands = discover_commands()
107
- parser = build_parser(commands)
108
- args = parser.parse_args()
109
-
110
- if not args.command:
111
- parser.print_help()
112
- sys.exit(1)
113
-
114
- if args.command in commands and not getattr(args, "subcommand", None):
115
- parser.parse_args([args.command, "-h"])
116
-
117
- result = run_command(args, commands)
118
- print(json.dumps(result, indent=2))
119
-
120
-
121
- if __name__ == "__main__":
122
- main()
@@ -1,4 +0,0 @@
1
- {
2
- "venvPath": ".",
3
- "venv": ".venv"
4
- }
@@ -1,2 +0,0 @@
1
- requests>=2.28.0
2
- google-genai>=0.8.0
@@ -1,3 +0,0 @@
1
- #!/bin/bash
2
- # Thin shim for UserPromptSubmit hook - pipes stdin to envoy
3
- "$CLAUDE_PROJECT_DIR/.claude/envoy/envoy" plans capture