@khaentertainment/grok-swarm 1.0.0
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/LICENSE +21 -0
- package/README.md +294 -0
- package/dist/LICENSE +21 -0
- package/dist/README.md +294 -0
- package/dist/VERSION +1 -0
- package/dist/apply.py +191 -0
- package/dist/cli.py +270 -0
- package/dist/grok_bridge.py +393 -0
- package/dist/index.js +205 -0
- package/dist/package.json +73 -0
- package/package.json +73 -0
- package/src/bridge/apply.py +191 -0
- package/src/bridge/cli.py +270 -0
- package/src/bridge/grok_bridge.py +393 -0
- package/src/bridge/index.js +205 -0
- package/src/plugin/index.ts +198 -0
- package/src/plugin/openclaw.plugin.json +28 -0
- package/src/plugin/package.json +7 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
grok_bridge.py — General-purpose bridge to xAI Grok 4.20 Multi-Agent Beta via OpenRouter.
|
|
4
|
+
|
|
5
|
+
Supports multiple task modes, custom system prompts, file context ingestion,
|
|
6
|
+
and OpenAI-format tool use passthrough.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 grok_bridge.py --prompt "Refactor this" --mode refactor --files a.js b.js
|
|
10
|
+
python3 grok_bridge.py --prompt "Analyze security" --mode analyze --files src/*.py
|
|
11
|
+
python3 grok_bridge.py --prompt "Build feature" --mode orchestrate --system "You are a Go expert" --files main.go
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
import time
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from openai import OpenAI
|
|
24
|
+
except ImportError:
|
|
25
|
+
print("ERROR: openai package required. Install: pip3 install openai", file=sys.stderr)
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
OPENROUTER_BASE = "https://openrouter.ai/api/v1"
|
|
30
|
+
MODEL_ID = "x-ai/grok-4.20-multi-agent-beta"
|
|
31
|
+
|
|
32
|
+
# Mode-specific system prompts
|
|
33
|
+
MODE_PROMPTS = {
|
|
34
|
+
"refactor": (
|
|
35
|
+
"You are an expert code refactoring engineer. "
|
|
36
|
+
"Improve code quality, maintainability, and performance while preserving behavior. "
|
|
37
|
+
"Output refactored code with clear explanations of what changed and why."
|
|
38
|
+
),
|
|
39
|
+
"analyze": (
|
|
40
|
+
"You are a senior code analyst and security auditor. "
|
|
41
|
+
"Examine the provided code for bugs, security vulnerabilities, performance issues, "
|
|
42
|
+
"and architectural concerns. Be specific with file paths and line references. "
|
|
43
|
+
"Prioritize findings by severity."
|
|
44
|
+
),
|
|
45
|
+
"code": (
|
|
46
|
+
"You are an expert software engineer. "
|
|
47
|
+
"Write clean, production-ready code that follows best practices and idioms for the language. "
|
|
48
|
+
"Include error handling, tests where appropriate, and clear comments."
|
|
49
|
+
),
|
|
50
|
+
"reason": (
|
|
51
|
+
"You are a collaborative multi-agent reasoning system. "
|
|
52
|
+
"Consider multiple perspectives, weigh trade-offs, and provide well-reasoned analysis. "
|
|
53
|
+
"Structure your response clearly with conclusions."
|
|
54
|
+
),
|
|
55
|
+
"orchestrate": None, # Requires --system flag
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_api_key():
|
|
60
|
+
"""Resolve OpenRouter API key from config file, environment, or OpenClaw auth profiles."""
|
|
61
|
+
|
|
62
|
+
# 1. Environment variables
|
|
63
|
+
key = os.environ.get("OPENROUTER_API_KEY") or os.environ.get("XAI_API_KEY")
|
|
64
|
+
if key:
|
|
65
|
+
return key
|
|
66
|
+
|
|
67
|
+
# 2. Grok Swarm config file (for Claude Code without secret management)
|
|
68
|
+
grok_config = Path.home() / ".config" / "grok-swarm" / "config.json"
|
|
69
|
+
if grok_config.exists():
|
|
70
|
+
try:
|
|
71
|
+
with open(grok_config) as f:
|
|
72
|
+
data = json.load(f)
|
|
73
|
+
key = data.get("api_key")
|
|
74
|
+
if key:
|
|
75
|
+
return key
|
|
76
|
+
except (json.JSONDecodeError, KeyError):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# 3. OpenClaw auth profiles (for OpenClaw integration)
|
|
80
|
+
auth_paths = [
|
|
81
|
+
Path.home() / ".openclaw" / "agents" / "coder" / "agent" / "auth-profiles.json",
|
|
82
|
+
Path.home() / ".openclaw" / "agents" / "main" / "agent" / "auth-profiles.json",
|
|
83
|
+
Path.home() / ".openclaw" / "auth-profiles.json",
|
|
84
|
+
Path.home() / ".config" / "openclaw" / "auth-profiles.json",
|
|
85
|
+
]
|
|
86
|
+
for auth_path in auth_paths:
|
|
87
|
+
if auth_path.exists():
|
|
88
|
+
try:
|
|
89
|
+
with open(auth_path) as f:
|
|
90
|
+
data = json.load(f)
|
|
91
|
+
profiles = data.get("profiles", {})
|
|
92
|
+
or_profile = profiles.get("openrouter:default", {})
|
|
93
|
+
key = or_profile.get("key") or or_profile.get("apiKey")
|
|
94
|
+
if key:
|
|
95
|
+
return key
|
|
96
|
+
default = profiles.get("default", {})
|
|
97
|
+
key = default.get("openrouter", {}).get("apiKey") or default.get("openrouter", {}).get("key")
|
|
98
|
+
if key:
|
|
99
|
+
return key
|
|
100
|
+
except (json.JSONDecodeError, KeyError):
|
|
101
|
+
continue
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def read_files(file_paths):
|
|
106
|
+
"""Read and concatenate files with path headers."""
|
|
107
|
+
chunks = []
|
|
108
|
+
total_size = 0
|
|
109
|
+
max_size = 1_500_000
|
|
110
|
+
|
|
111
|
+
for fpath in file_paths:
|
|
112
|
+
p = Path(fpath)
|
|
113
|
+
if not p.exists():
|
|
114
|
+
print(f"WARNING: File not found: {fpath}", file=sys.stderr)
|
|
115
|
+
continue
|
|
116
|
+
if not p.is_file():
|
|
117
|
+
print(f"WARNING: Not a file: {fpath}", file=sys.stderr)
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
content = p.read_text(errors="replace")
|
|
121
|
+
header = f"\n{'='*60}\nFILE: {p.absolute()}\n{'='*60}\n"
|
|
122
|
+
chunk = header + content
|
|
123
|
+
|
|
124
|
+
if total_size + len(chunk) > max_size:
|
|
125
|
+
print(f"WARNING: Size limit reached, skipping remaining files", file=sys.stderr)
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
chunks.append(chunk)
|
|
129
|
+
total_size += len(chunk)
|
|
130
|
+
|
|
131
|
+
return "\n".join(chunks)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def load_tools(tools_path):
|
|
135
|
+
"""Load OpenAI-format tool definitions from a JSON file."""
|
|
136
|
+
if not tools_path:
|
|
137
|
+
return None
|
|
138
|
+
p = Path(tools_path)
|
|
139
|
+
if not p.exists():
|
|
140
|
+
print(f"ERROR: Tools file not found: {tools_path}", file=sys.stderr)
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
with open(p) as f:
|
|
143
|
+
tools = json.load(f)
|
|
144
|
+
if not isinstance(tools, list):
|
|
145
|
+
print(f"ERROR: Tools file must be a JSON array", file=sys.stderr)
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
return tools
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _safe_dest(output_path, file_path):
|
|
151
|
+
"""
|
|
152
|
+
Resolve ``file_path`` relative to ``output_path`` and verify the result
|
|
153
|
+
stays inside ``output_path``. Returns the resolved Path or raises
|
|
154
|
+
ValueError for unsafe paths (absolute, containing ``..``, etc.).
|
|
155
|
+
"""
|
|
156
|
+
raw = Path(file_path)
|
|
157
|
+
if raw.is_absolute():
|
|
158
|
+
raise ValueError(f"Absolute paths are not allowed: {file_path!r}")
|
|
159
|
+
if ".." in raw.parts:
|
|
160
|
+
raise ValueError(f"Path traversal not allowed: {file_path!r}")
|
|
161
|
+
dest = (output_path / raw).resolve()
|
|
162
|
+
resolved_root = output_path.resolve()
|
|
163
|
+
try:
|
|
164
|
+
dest.relative_to(resolved_root)
|
|
165
|
+
except ValueError as exc:
|
|
166
|
+
raise ValueError(f"Path escapes output directory: {file_path!r}") from exc
|
|
167
|
+
return dest
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def parse_and_write_files(response_text, output_dir):
|
|
171
|
+
"""
|
|
172
|
+
Scan response for fenced code blocks with filename annotations and write to disk.
|
|
173
|
+
|
|
174
|
+
Supports patterns:
|
|
175
|
+
```lang:path/to/file ... ```
|
|
176
|
+
```lang
|
|
177
|
+
// FILE: path/to/file
|
|
178
|
+
...
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Returns list of (relative_path, byte_count) tuples written, where
|
|
182
|
+
byte_count is the number of UTF-8 bytes written.
|
|
183
|
+
"""
|
|
184
|
+
written = []
|
|
185
|
+
output_path = Path(output_dir)
|
|
186
|
+
|
|
187
|
+
# Pattern for lang:path at start of block (language tag contains path)
|
|
188
|
+
lang_path_pattern = re.compile(r'^(\w+):([^\s\n]+)\n', re.MULTILINE)
|
|
189
|
+
# Pattern for // FILE: or # FILE: markers
|
|
190
|
+
file_marker_pattern = re.compile(r'^\s*(?://|#)\s*FILE:\s*(.+?)\s*$', re.MULTILINE)
|
|
191
|
+
|
|
192
|
+
def _write_file(file_path, content):
|
|
193
|
+
"""Validate path, write content, and record result. Returns True on success."""
|
|
194
|
+
try:
|
|
195
|
+
dest = _safe_dest(output_path, file_path)
|
|
196
|
+
except ValueError as exc:
|
|
197
|
+
print(f"WARNING: Skipping unsafe path — {exc}", file=sys.stderr)
|
|
198
|
+
return False
|
|
199
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
encoded = content.strip().encode("utf-8", errors="replace")
|
|
201
|
+
dest.write_bytes(encoded)
|
|
202
|
+
written.append((file_path, len(encoded)))
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
# Split into code blocks by ``` fences.
|
|
206
|
+
# Even indices are fence markers or text between fences; skip them.
|
|
207
|
+
# Odd indices are the actual code block contents.
|
|
208
|
+
parts = re.split(r'```', response_text)
|
|
209
|
+
|
|
210
|
+
for i, part in enumerate(parts):
|
|
211
|
+
if i % 2 == 0:
|
|
212
|
+
# Skip even-indexed parts (fences/text between fences)
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
# Check for lang:path at start (language tag contains the path)
|
|
216
|
+
lang_match = lang_path_pattern.match(part)
|
|
217
|
+
if lang_match:
|
|
218
|
+
_write_file(lang_match.group(2), part[lang_match.end():])
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
# Check for // FILE: or # FILE: marker within the block
|
|
222
|
+
marker_match = file_marker_pattern.search(part)
|
|
223
|
+
if marker_match:
|
|
224
|
+
_write_file(marker_match.group(1).strip(), part[marker_match.end():])
|
|
225
|
+
|
|
226
|
+
return written
|
|
227
|
+
|
|
228
|
+
def call_grok(prompt, mode="reason", context="", system_override=None, tools=None, timeout=120):
|
|
229
|
+
"""Make the API call to Grok 4.20 Multi-Agent Beta."""
|
|
230
|
+
api_key = get_api_key()
|
|
231
|
+
if not api_key:
|
|
232
|
+
print("ERROR: No OpenRouter API key found.", file=sys.stderr)
|
|
233
|
+
print("Set OPENROUTER_API_KEY env var or configure in OpenClaw auth-profiles.json", file=sys.stderr)
|
|
234
|
+
sys.exit(1)
|
|
235
|
+
|
|
236
|
+
# Resolve system prompt
|
|
237
|
+
if system_override:
|
|
238
|
+
system_content = system_override
|
|
239
|
+
else:
|
|
240
|
+
system_content = MODE_PROMPTS.get(mode)
|
|
241
|
+
if system_content is None:
|
|
242
|
+
print(f"ERROR: Mode '{mode}' requires --system flag", file=sys.stderr)
|
|
243
|
+
sys.exit(1)
|
|
244
|
+
|
|
245
|
+
# Append context to system prompt
|
|
246
|
+
if context:
|
|
247
|
+
system_content += f"\n\n## Codebase Context\n{context}"
|
|
248
|
+
|
|
249
|
+
client = OpenAI(
|
|
250
|
+
base_url=OPENROUTER_BASE,
|
|
251
|
+
api_key=api_key,
|
|
252
|
+
timeout=timeout,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
messages = [
|
|
256
|
+
{"role": "system", "content": system_content},
|
|
257
|
+
{"role": "user", "content": prompt},
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
kwargs = {
|
|
261
|
+
"model": MODEL_ID,
|
|
262
|
+
"messages": messages,
|
|
263
|
+
"max_tokens": 16384,
|
|
264
|
+
"temperature": 0.3,
|
|
265
|
+
"extra_body": {"agent_count": 4},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if tools:
|
|
269
|
+
kwargs["tools"] = tools
|
|
270
|
+
|
|
271
|
+
start = time.time()
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
response = client.chat.completions.create(**kwargs)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
elapsed = time.time() - start
|
|
277
|
+
print(f"ERROR after {elapsed:.1f}s: {e}", file=sys.stderr)
|
|
278
|
+
sys.exit(1)
|
|
279
|
+
|
|
280
|
+
elapsed = time.time() - start
|
|
281
|
+
|
|
282
|
+
if not response.choices:
|
|
283
|
+
print(f"ERROR: Empty response after {elapsed:.1f}s", file=sys.stderr)
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
|
|
286
|
+
choice = response.choices[0]
|
|
287
|
+
|
|
288
|
+
# Log usage
|
|
289
|
+
if hasattr(response, 'usage') and response.usage:
|
|
290
|
+
u = response.usage
|
|
291
|
+
print(f"USAGE: mode={mode} prompt={u.prompt_tokens} completion={u.completion_tokens} "
|
|
292
|
+
f"total={u.total_tokens} time={elapsed:.1f}s", file=sys.stderr)
|
|
293
|
+
|
|
294
|
+
# Handle content filtering
|
|
295
|
+
if choice.finish_reason == "content_filter":
|
|
296
|
+
print(f"WARNING: Response blocked by content filter after {elapsed:.1f}s", file=sys.stderr)
|
|
297
|
+
if not choice.message.content:
|
|
298
|
+
print("No content returned. Try rephrasing the prompt.", file=sys.stderr)
|
|
299
|
+
sys.exit(2)
|
|
300
|
+
|
|
301
|
+
content = choice.message.content or ""
|
|
302
|
+
|
|
303
|
+
# Handle tool calls (return as JSON if present)
|
|
304
|
+
if choice.message.tool_calls:
|
|
305
|
+
tool_calls = []
|
|
306
|
+
for tc in choice.message.tool_calls:
|
|
307
|
+
tool_calls.append({
|
|
308
|
+
"id": tc.id,
|
|
309
|
+
"type": tc.type,
|
|
310
|
+
"function": {
|
|
311
|
+
"name": tc.function.name,
|
|
312
|
+
"arguments": tc.function.arguments,
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
result = {
|
|
316
|
+
"content": content,
|
|
317
|
+
"tool_calls": tool_calls,
|
|
318
|
+
}
|
|
319
|
+
return json.dumps(result, indent=2)
|
|
320
|
+
|
|
321
|
+
return content.strip()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def main():
|
|
325
|
+
parser = argparse.ArgumentParser(
|
|
326
|
+
description="General-purpose bridge to xAI Grok 4.20 Multi-Agent Beta"
|
|
327
|
+
)
|
|
328
|
+
parser.add_argument("--prompt", required=True, help="Task instruction or question")
|
|
329
|
+
parser.add_argument("--mode", default="reason", choices=list(MODE_PROMPTS.keys()),
|
|
330
|
+
help="Task mode (default: reason)")
|
|
331
|
+
parser.add_argument("--files", nargs="*", default=[], help="Local file paths for context")
|
|
332
|
+
parser.add_argument("--system", help="Override system prompt (for orchestrate mode)")
|
|
333
|
+
parser.add_argument("--tools", help="Path to JSON file with OpenAI-format tool definitions")
|
|
334
|
+
parser.add_argument("--timeout", type=int, default=120, help="Timeout in seconds (default: 120)")
|
|
335
|
+
parser.add_argument("--output", help="Output file path (default: stdout)")
|
|
336
|
+
parser.add_argument("--write-files", action="store_true",
|
|
337
|
+
help="Parse response for annotated code blocks and write to --output-dir")
|
|
338
|
+
parser.add_argument("--output-dir", default="./grok-output/",
|
|
339
|
+
help="Directory for file writes (default: ./grok-output/)")
|
|
340
|
+
|
|
341
|
+
args = parser.parse_args()
|
|
342
|
+
|
|
343
|
+
# Validate orchestrate mode
|
|
344
|
+
if args.mode == "orchestrate" and not args.system:
|
|
345
|
+
print("ERROR: --mode orchestrate requires --system flag", file=sys.stderr)
|
|
346
|
+
sys.exit(1)
|
|
347
|
+
|
|
348
|
+
# Read context files
|
|
349
|
+
context = ""
|
|
350
|
+
if args.files:
|
|
351
|
+
print(f"Reading {len(args.files)} files...", file=sys.stderr)
|
|
352
|
+
context = read_files(args.files)
|
|
353
|
+
print(f"Context size: {len(context):,} chars", file=sys.stderr)
|
|
354
|
+
|
|
355
|
+
# Load tools
|
|
356
|
+
tools = load_tools(args.tools)
|
|
357
|
+
|
|
358
|
+
# Call Grok
|
|
359
|
+
print(f"Calling {MODEL_ID} (mode={args.mode}, 4 agents, timeout={args.timeout}s)...", file=sys.stderr)
|
|
360
|
+
result = call_grok(
|
|
361
|
+
prompt=args.prompt,
|
|
362
|
+
mode=args.mode,
|
|
363
|
+
context=context,
|
|
364
|
+
system_override=args.system,
|
|
365
|
+
tools=tools,
|
|
366
|
+
timeout=args.timeout,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Output
|
|
370
|
+
if args.output:
|
|
371
|
+
Path(args.output).write_text(result)
|
|
372
|
+
print(f"Written to: {args.output}", file=sys.stderr)
|
|
373
|
+
|
|
374
|
+
if args.write_files:
|
|
375
|
+
written = parse_and_write_files(result, args.output_dir)
|
|
376
|
+
if written:
|
|
377
|
+
total_bytes = sum(b for _, b in written)
|
|
378
|
+
print(f"Wrote {len(written)} files to {args.output_dir}")
|
|
379
|
+
for rel_path, byte_count in written:
|
|
380
|
+
print(f" {rel_path} ({byte_count:,} bytes)")
|
|
381
|
+
print(f"Total: {total_bytes:,} bytes")
|
|
382
|
+
else:
|
|
383
|
+
print(
|
|
384
|
+
"No annotated files found in model response to write to disk.\n"
|
|
385
|
+
"Re-run without --write-files to see the full response.",
|
|
386
|
+
file=sys.stderr,
|
|
387
|
+
)
|
|
388
|
+
elif not args.output:
|
|
389
|
+
print(result)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
if __name__ == "__main__":
|
|
393
|
+
main()
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* index.js — Node.js wrapper for grok_bridge.py
|
|
4
|
+
*
|
|
5
|
+
* Enforces timeout at the process level to prevent 502 Bad Gateway errors.
|
|
6
|
+
* Supports multiple task modes, custom system prompts, file context, and tool use.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node index.js --prompt "Analyze security" --mode analyze --files src/*.js
|
|
10
|
+
* node index.js --prompt "Build feature" --mode orchestrate --system "You are a Go expert" --files main.go
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { spawn } = require('child_process');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const BRIDGE_SCRIPT = path.join(__dirname, 'grok_bridge.py');
|
|
17
|
+
const PYTHON = path.join(__dirname, '.venv', 'bin', 'python3');
|
|
18
|
+
|
|
19
|
+
const VALID_MODES = ['refactor', 'analyze', 'code', 'reason', 'orchestrate'];
|
|
20
|
+
|
|
21
|
+
function parseArgs() {
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const parsed = {
|
|
24
|
+
prompt: null,
|
|
25
|
+
mode: 'reason',
|
|
26
|
+
files: [],
|
|
27
|
+
system: null,
|
|
28
|
+
tools: null,
|
|
29
|
+
timeout: 120,
|
|
30
|
+
output: null,
|
|
31
|
+
writeFiles: false,
|
|
32
|
+
outputDir: './grok-output/',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < args.length; i++) {
|
|
36
|
+
switch (args[i]) {
|
|
37
|
+
case '--prompt':
|
|
38
|
+
parsed.prompt = args[++i];
|
|
39
|
+
break;
|
|
40
|
+
case '--mode':
|
|
41
|
+
parsed.mode = args[++i];
|
|
42
|
+
break;
|
|
43
|
+
case '--files':
|
|
44
|
+
while (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
45
|
+
parsed.files.push(args[++i]);
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case '--system':
|
|
49
|
+
parsed.system = args[++i];
|
|
50
|
+
break;
|
|
51
|
+
case '--tools':
|
|
52
|
+
parsed.tools = args[++i];
|
|
53
|
+
break;
|
|
54
|
+
case '--timeout':
|
|
55
|
+
parsed.timeout = parseInt(args[++i], 10) || 120;
|
|
56
|
+
break;
|
|
57
|
+
case '--output':
|
|
58
|
+
parsed.output = args[++i];
|
|
59
|
+
break;
|
|
60
|
+
case '--write-files':
|
|
61
|
+
parsed.writeFiles = true;
|
|
62
|
+
break;
|
|
63
|
+
case '--output-dir':
|
|
64
|
+
parsed.outputDir = args[++i];
|
|
65
|
+
break;
|
|
66
|
+
case '--help':
|
|
67
|
+
console.log(`
|
|
68
|
+
grok_swarm — Bridge to xAI Grok 4.20 Multi-Agent Beta (4-agent swarm)
|
|
69
|
+
|
|
70
|
+
Usage:
|
|
71
|
+
node index.js --prompt "instruction" [options]
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
--prompt <text> Task instruction (required)
|
|
75
|
+
--mode <mode> Task mode: refactor|analyze|code|reason|orchestrate (default: reason)
|
|
76
|
+
--files <path...> Files to include as context
|
|
77
|
+
--system <text> Override system prompt (required for orchestrate mode)
|
|
78
|
+
--tools <path> JSON file with OpenAI-format tool definitions
|
|
79
|
+
--timeout <secs> Timeout in seconds (default: 120)
|
|
80
|
+
--output <path> Output file (default: stdout)
|
|
81
|
+
--write-files Parse response for annotated code blocks and write files
|
|
82
|
+
--output-dir <path> Directory for file writes (default: ./grok-output/)
|
|
83
|
+
--help Show this help
|
|
84
|
+
|
|
85
|
+
Modes:
|
|
86
|
+
refactor Large-scale code refactoring, modernization, migration
|
|
87
|
+
analyze Code review, security audit, architecture assessment
|
|
88
|
+
code Generate new code, implement features, write tests
|
|
89
|
+
reason Complex reasoning, research synthesis, multi-perspective analysis
|
|
90
|
+
orchestrate Full prompt control — requires --system flag
|
|
91
|
+
`);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!parsed.prompt) {
|
|
97
|
+
console.error('ERROR: --prompt is required');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!VALID_MODES.includes(parsed.mode)) {
|
|
102
|
+
console.error(`ERROR: Invalid mode '${parsed.mode}'. Valid: ${VALID_MODES.join(', ')}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (parsed.mode === 'orchestrate' && !parsed.system) {
|
|
107
|
+
console.error('ERROR: --mode orchestrate requires --system flag');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return parsed;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function run() {
|
|
115
|
+
const opts = parseArgs();
|
|
116
|
+
|
|
117
|
+
// Build Python args
|
|
118
|
+
const pyArgs = [
|
|
119
|
+
BRIDGE_SCRIPT,
|
|
120
|
+
'--prompt', opts.prompt,
|
|
121
|
+
'--mode', opts.mode,
|
|
122
|
+
'--timeout', String(opts.timeout),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
if (opts.files.length > 0) {
|
|
126
|
+
pyArgs.push('--files', ...opts.files);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (opts.system) {
|
|
130
|
+
pyArgs.push('--system', opts.system);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (opts.tools) {
|
|
134
|
+
pyArgs.push('--tools', opts.tools);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (opts.output) {
|
|
138
|
+
pyArgs.push('--output', opts.output);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (opts.writeFiles) {
|
|
142
|
+
pyArgs.push('--write-files');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Skip --output-dir if it matches the Python bridge default.
|
|
146
|
+
// This optimization reduces CLI noise; CLI invocations bypass this wrapper
|
|
147
|
+
// via src/plugin/index.ts, which sets defaults at the plugin layer.
|
|
148
|
+
if (opts.outputDir && opts.outputDir !== './grok-output/') {
|
|
149
|
+
pyArgs.push('--output-dir', opts.outputDir);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Spawn Python process
|
|
153
|
+
const child = spawn(PYTHON, pyArgs, {
|
|
154
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
155
|
+
env: { ...process.env },
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
let stdout = '';
|
|
159
|
+
let stderr = '';
|
|
160
|
+
let timedOut = false;
|
|
161
|
+
|
|
162
|
+
// Enforce timeout at process level
|
|
163
|
+
const timer = setTimeout(() => {
|
|
164
|
+
timedOut = true;
|
|
165
|
+
child.kill('SIGTERM');
|
|
166
|
+
setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
167
|
+
}, opts.timeout * 1000);
|
|
168
|
+
|
|
169
|
+
child.stdout.on('data', (data) => {
|
|
170
|
+
stdout += data.toString();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
child.stderr.on('data', (data) => {
|
|
174
|
+
stderr += data.toString();
|
|
175
|
+
process.stderr.write(data);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
child.on('close', (code) => {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
|
|
181
|
+
if (timedOut) {
|
|
182
|
+
console.error(`\nTIMEOUT: Grok call exceeded ${opts.timeout}s limit`);
|
|
183
|
+
process.exit(124);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (code !== 0) {
|
|
187
|
+
console.error(`\nBridge exited with code ${code}`);
|
|
188
|
+
process.exit(code);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (opts.output) {
|
|
192
|
+
console.error(`Output written to: ${opts.output}`);
|
|
193
|
+
} else {
|
|
194
|
+
process.stdout.write(stdout);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
child.on('error', (err) => {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
console.error(`Failed to spawn bridge: ${err.message}`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
run();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khaentertainment/grok-swarm",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Multi-agent intelligence powered by Grok 4.20 via OpenRouter. Give any AI coding agent access to a 4-agent swarm with ~2M token context.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"grok",
|
|
7
|
+
"multi-agent",
|
|
8
|
+
"xai",
|
|
9
|
+
"openrouter",
|
|
10
|
+
"openclaw",
|
|
11
|
+
"claude-code",
|
|
12
|
+
"code-review",
|
|
13
|
+
"refactoring",
|
|
14
|
+
"security",
|
|
15
|
+
"ai"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/KHAEntertainment/grok-multiagent-plugin",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/KHAEntertainment/grok-multiagent-plugin/issues"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "OpenClaw <support@openclaw.dev>",
|
|
23
|
+
"maintainers": [
|
|
24
|
+
{
|
|
25
|
+
"name": "OpenClaw",
|
|
26
|
+
"email": "support@openclaw.dev"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"main": "dist/index.js",
|
|
30
|
+
"bin": {
|
|
31
|
+
"grok-swarm": "dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/",
|
|
35
|
+
"src/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"preinstall": "node scripts/preinstall-check.js",
|
|
41
|
+
"postinstall": "node scripts/postinstall.js",
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"build": "node scripts/build.js",
|
|
44
|
+
"pretest": "npm run build",
|
|
45
|
+
"test": "node scripts/test.js",
|
|
46
|
+
"lint": "eslint dist/",
|
|
47
|
+
"clean": "rm -rf dist/"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"openai": "^4.28.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"eslint": "^8.57.0"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"os": [
|
|
59
|
+
"darwin",
|
|
60
|
+
"linux",
|
|
61
|
+
"win32"
|
|
62
|
+
],
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "https://github.com/KHAEntertainment/grok-multiagent-plugin.git"
|
|
66
|
+
},
|
|
67
|
+
"categories": [
|
|
68
|
+
"ai",
|
|
69
|
+
"coding",
|
|
70
|
+
"developer-tools",
|
|
71
|
+
"plugins"
|
|
72
|
+
]
|
|
73
|
+
}
|