@desplega.ai/agent-swarm 1.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/src/cli.tsx ADDED
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env bun
2
+ import { Spinner } from "@inkjs/ui";
3
+ import { Box, render, Text, useApp } from "ink";
4
+ import { useEffect, useState } from "react";
5
+ import pkg from "../package.json";
6
+ import { runClaude } from "./claude.ts";
7
+ import { runHook } from "./commands/hook.ts";
8
+ import { Setup } from "./commands/setup.tsx";
9
+
10
+ // Get CLI name from bin field (assumes single key)
11
+ const binName = Object.keys(pkg.bin)[0];
12
+
13
+ // Restore cursor on exit
14
+ const restoreCursor = () => process.stdout.write("\x1B[?25h");
15
+ process.on("exit", restoreCursor);
16
+ process.on("SIGINT", () => {
17
+ restoreCursor();
18
+ process.exit(0);
19
+ });
20
+
21
+ interface ParsedArgs {
22
+ command: string | undefined;
23
+ port: string;
24
+ key: string;
25
+ msg: string;
26
+ headless: boolean;
27
+ dryRun: boolean;
28
+ restore: boolean;
29
+ additionalArgs: string[];
30
+ }
31
+
32
+ function parseArgs(args: string[]): ParsedArgs {
33
+ const command = args[0] && !args[0].startsWith("-") ? args[0] : undefined;
34
+ let port = process.env.PORT || "3013";
35
+ let key = process.env.API_KEY || "";
36
+ let msg = "";
37
+ let headless = false;
38
+ let dryRun = false;
39
+ let restore = false;
40
+ let additionalArgs: string[] = [];
41
+
42
+ // Find if there's a "--" separator for additional args
43
+ const separatorIndex = args.indexOf("--");
44
+ const mainArgs = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
45
+ additionalArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
46
+
47
+ for (let i = 0; i < mainArgs.length; i++) {
48
+ const arg = mainArgs[i];
49
+ if (arg === "-p" || arg === "--port") {
50
+ port = mainArgs[i + 1] || port;
51
+ i++;
52
+ } else if (arg === "-k" || arg === "--key") {
53
+ key = mainArgs[i + 1] || key;
54
+ i++;
55
+ } else if (arg === "-m" || arg === "--msg") {
56
+ msg = mainArgs[i + 1] || msg;
57
+ i++;
58
+ } else if (arg === "--headless") {
59
+ headless = true;
60
+ } else if (arg === "--dry-run") {
61
+ dryRun = true;
62
+ } else if (arg === "--restore") {
63
+ restore = true;
64
+ }
65
+ }
66
+
67
+ return { command, port, key, msg, headless, dryRun, restore, additionalArgs };
68
+ }
69
+
70
+ function Help() {
71
+ const { exit } = useApp();
72
+ useEffect(() => {
73
+ exit();
74
+ }, [exit]);
75
+
76
+ return (
77
+ <Box flexDirection="column" padding={1}>
78
+ <Box>
79
+ <Text bold color="cyan">
80
+ {binName}
81
+ </Text>
82
+ <Text dimColor> v{pkg.version}</Text>
83
+ </Box>
84
+ <Text dimColor>{pkg.description}</Text>
85
+
86
+ <Box marginTop={1} flexDirection="column">
87
+ <Text bold>Usage:</Text>
88
+ <Text>
89
+ {" "}
90
+ {binName} {"<command>"} [options]
91
+ </Text>
92
+ <Text dimColor>
93
+ {" "}
94
+ or: bunx {binName} {"<command>"} [options]
95
+ </Text>
96
+ </Box>
97
+
98
+ <Box marginTop={1} flexDirection="column">
99
+ <Text bold>Commands:</Text>
100
+ <Box>
101
+ <Box width={12}>
102
+ <Text color="green">setup</Text>
103
+ </Box>
104
+ <Text>Set up agent-swarm in your project</Text>
105
+ </Box>
106
+ <Box>
107
+ <Box width={12}>
108
+ <Text color="green">hook</Text>
109
+ </Box>
110
+ <Text>Handle Claude Code hook events (stdin)</Text>
111
+ </Box>
112
+ <Box>
113
+ <Box width={12}>
114
+ <Text color="green">mcp</Text>
115
+ </Box>
116
+ <Text>Start the MCP HTTP server</Text>
117
+ </Box>
118
+ <Box>
119
+ <Box width={12}>
120
+ <Text color="green">claude</Text>
121
+ </Box>
122
+ <Text>Run Claude CLI</Text>
123
+ </Box>
124
+ <Box>
125
+ <Box width={12}>
126
+ <Text color="green">version</Text>
127
+ </Box>
128
+ <Text>Show version number</Text>
129
+ </Box>
130
+ <Box>
131
+ <Box width={12}>
132
+ <Text color="green">help</Text>
133
+ </Box>
134
+ <Text>Show this help message</Text>
135
+ </Box>
136
+ </Box>
137
+
138
+ <Box marginTop={1} flexDirection="column">
139
+ <Text bold>Options for 'setup':</Text>
140
+ <Box>
141
+ <Box width={24}>
142
+ <Text color="yellow">--dry-run</Text>
143
+ </Box>
144
+ <Text>Show what would be changed without writing</Text>
145
+ </Box>
146
+ <Box>
147
+ <Box width={24}>
148
+ <Text color="yellow">--restore</Text>
149
+ </Box>
150
+ <Text>Restore files from .bak backups</Text>
151
+ </Box>
152
+ </Box>
153
+
154
+ <Box marginTop={1} flexDirection="column">
155
+ <Text bold>Options for 'mcp':</Text>
156
+ <Box>
157
+ <Box width={24}>
158
+ <Text color="yellow">-p, --port {"<port>"}</Text>
159
+ </Box>
160
+ <Text>Port to listen on (default: 3013)</Text>
161
+ </Box>
162
+ <Box>
163
+ <Box width={24}>
164
+ <Text color="yellow">-k, --key {"<key>"}</Text>
165
+ </Box>
166
+ <Text>API key for authentication</Text>
167
+ </Box>
168
+ </Box>
169
+
170
+ <Box marginTop={1} flexDirection="column">
171
+ <Text bold>Options for 'claude':</Text>
172
+ <Box>
173
+ <Box width={24}>
174
+ <Text color="yellow">-m, --msg {"<message>"}</Text>
175
+ </Box>
176
+ <Text>Message to send to Claude</Text>
177
+ </Box>
178
+ <Box>
179
+ <Box width={24}>
180
+ <Text color="yellow">--headless</Text>
181
+ </Box>
182
+ <Text>Run in headless mode (stream JSON output)</Text>
183
+ </Box>
184
+ <Box>
185
+ <Box width={24}>
186
+ <Text color="yellow">-- {"<args...>"}</Text>
187
+ </Box>
188
+ <Text>Additional arguments to pass to Claude CLI</Text>
189
+ </Box>
190
+ </Box>
191
+
192
+ <Box marginTop={1} flexDirection="column">
193
+ <Text bold>Examples:</Text>
194
+ <Text dimColor> {binName} setup</Text>
195
+ <Text dimColor> {binName} setup --dry-run</Text>
196
+ <Text dimColor> {binName} mcp</Text>
197
+ <Text dimColor> {binName} mcp --port 8080</Text>
198
+ <Text dimColor> {binName} mcp -p 8080 -k my-secret-key</Text>
199
+ <Text dimColor> {binName} claude</Text>
200
+ <Text dimColor> {binName} claude --headless -m "Hello"</Text>
201
+ <Text dimColor> {binName} claude -- --resume</Text>
202
+ </Box>
203
+
204
+ <Box marginTop={1} flexDirection="column">
205
+ <Text bold>Environment variables:</Text>
206
+ <Box>
207
+ <Box width={16}>
208
+ <Text color="magenta">PORT</Text>
209
+ </Box>
210
+ <Text>Default port for the MCP server</Text>
211
+ </Box>
212
+ <Box>
213
+ <Box width={16}>
214
+ <Text color="magenta">API_KEY</Text>
215
+ </Box>
216
+ <Text>API key for authentication (Bearer token)</Text>
217
+ </Box>
218
+ <Box>
219
+ <Box width={16}>
220
+ <Text color="magenta">MCP_BASE_URL</Text>
221
+ </Box>
222
+ <Text>Base URL for the MCP server (used by setup)</Text>
223
+ </Box>
224
+ </Box>
225
+ </Box>
226
+ );
227
+ }
228
+
229
+ function McpServer({ port, apiKey }: { port: string; apiKey: string }) {
230
+ const [status, setStatus] = useState<"starting" | "running" | "error">("starting");
231
+ const [error, setError] = useState<string | null>(null);
232
+
233
+ useEffect(() => {
234
+ process.env.PORT = port;
235
+ process.env.API_KEY = apiKey;
236
+
237
+ import("./http.ts")
238
+ .then(() => {
239
+ setStatus("running");
240
+ })
241
+ .catch((err) => {
242
+ setStatus("error");
243
+ setError(err.message);
244
+ });
245
+ }, [port, apiKey]);
246
+
247
+ if (status === "error") {
248
+ return (
249
+ <Box flexDirection="column" padding={1}>
250
+ <Text color="red">✗ Failed to start MCP server</Text>
251
+ {error && <Text dimColor>{error}</Text>}
252
+ </Box>
253
+ );
254
+ }
255
+
256
+ if (status === "starting") {
257
+ return (
258
+ <Box padding={1}>
259
+ <Spinner label="Starting MCP server..." />
260
+ </Box>
261
+ );
262
+ }
263
+
264
+ return (
265
+ <Box flexDirection="column" padding={1}>
266
+ <Box>
267
+ <Text color="green">✓ </Text>
268
+ <Text>MCP HTTP server running on </Text>
269
+ <Text color="cyan" bold>
270
+ http://localhost:{port}/mcp
271
+ </Text>
272
+ </Box>
273
+ {apiKey && <Text dimColor>API key authentication enabled</Text>}
274
+ <Text dimColor>Press Ctrl+C to stop</Text>
275
+ </Box>
276
+ );
277
+ }
278
+
279
+ interface ClaudeRunnerProps {
280
+ msg: string;
281
+ headless: boolean;
282
+ additionalArgs: string[];
283
+ }
284
+
285
+ function ClaudeRunner({ msg, headless, additionalArgs }: ClaudeRunnerProps) {
286
+ const { exit } = useApp();
287
+
288
+ useEffect(() => {
289
+ runClaude({
290
+ msg,
291
+ headless,
292
+ additionalArgs,
293
+ })
294
+ .then(() => exit())
295
+ .catch((err) => exit(err));
296
+ }, [msg, headless, additionalArgs, exit]);
297
+
298
+ return null;
299
+ }
300
+
301
+ function UnknownCommand({ command }: { command: string }) {
302
+ const { exit } = useApp();
303
+ useEffect(() => {
304
+ exit(new Error(`Unknown command: ${command}`));
305
+ }, [exit, command]);
306
+
307
+ return (
308
+ <Box flexDirection="column" padding={1}>
309
+ <Text color="red">Unknown command: {command}</Text>
310
+ <Text dimColor>Run '{binName} help' for usage information</Text>
311
+ </Box>
312
+ );
313
+ }
314
+
315
+ function Version() {
316
+ const { exit } = useApp();
317
+ useEffect(() => {
318
+ exit();
319
+ }, [exit]);
320
+
321
+ return (
322
+ <Box padding={1}>
323
+ <Text>
324
+ {binName} v{pkg.version}
325
+ </Text>
326
+ </Box>
327
+ );
328
+ }
329
+
330
+ function App({ args }: { args: ParsedArgs }) {
331
+ const { command, port, key, msg, headless, dryRun, restore, additionalArgs } = args;
332
+
333
+ switch (command) {
334
+ case "setup":
335
+ return <Setup dryRun={dryRun} restore={restore} />;
336
+ case "mcp":
337
+ return <McpServer port={port} apiKey={key} />;
338
+ case "claude":
339
+ return <ClaudeRunner msg={msg} headless={headless} additionalArgs={additionalArgs} />;
340
+ case "version":
341
+ return <Version />;
342
+ case "help":
343
+ case undefined:
344
+ return <Help />;
345
+ default:
346
+ return <UnknownCommand command={command} />;
347
+ }
348
+ }
349
+
350
+ const args = parseArgs(process.argv.slice(2));
351
+
352
+ // Handle hook command separately (no UI needed)
353
+ if (args.command === "hook") {
354
+ runHook();
355
+ } else {
356
+ render(<App args={args} />);
357
+ }
@@ -0,0 +1,6 @@
1
+ import { handleHook } from "../hooks/hook";
2
+
3
+ export async function runHook(): Promise<void> {
4
+ await handleHook();
5
+ process.exit(0);
6
+ }