@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.
@@ -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
+ }