@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/.claude/settings.local.json +80 -0
- package/.editorconfig +15 -0
- package/.env.example +9 -0
- package/CLAUDE.md +111 -0
- package/README.md +141 -0
- package/biome.json +36 -0
- package/example-req-meta.json +24 -0
- package/package.json +46 -0
- package/src/be/db.ts +313 -0
- package/src/claude.ts +80 -0
- package/src/cli.tsx +357 -0
- package/src/commands/hook.ts +6 -0
- package/src/commands/setup.tsx +577 -0
- package/src/hooks/hook.ts +198 -0
- package/src/http.ts +262 -0
- package/src/server.ts +41 -0
- package/src/stdio.ts +21 -0
- package/src/tools/get-swarm.ts +37 -0
- package/src/tools/get-task-details.ts +48 -0
- package/src/tools/get-tasks.ts +78 -0
- package/src/tools/join-swarm.ts +102 -0
- package/src/tools/my-agent-info.ts +61 -0
- package/src/tools/poll-task.ts +109 -0
- package/src/tools/send-task.ts +62 -0
- package/src/tools/store-progress.ts +94 -0
- package/src/tools/utils.ts +115 -0
- package/src/types.ts +42 -0
- package/tsconfig.json +35 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Spinner, TextInput } from "@inkjs/ui";
|
|
3
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
4
|
+
import { useEffect, useState } from "react";
|
|
5
|
+
import pkg from "../../package.json";
|
|
6
|
+
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
const SERVER_NAME = pkg.config?.name ?? "agent-swarm";
|
|
9
|
+
const PKG_NAME = pkg.name;
|
|
10
|
+
const DEFAULT_MCP_BASE_URL = "https://agent-swarm-mcp.desplega.sh";
|
|
11
|
+
|
|
12
|
+
const UUID_REGEX =
|
|
13
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
14
|
+
|
|
15
|
+
const isValidUUID = (value: string): boolean => UUID_REGEX.test(value);
|
|
16
|
+
|
|
17
|
+
type SetupStep =
|
|
18
|
+
| "check_dirs"
|
|
19
|
+
| "restoring"
|
|
20
|
+
| "input_token"
|
|
21
|
+
| "input_agent_id"
|
|
22
|
+
| "updating"
|
|
23
|
+
| "done"
|
|
24
|
+
| "error";
|
|
25
|
+
|
|
26
|
+
interface SetupProps {
|
|
27
|
+
dryRun?: boolean;
|
|
28
|
+
restore?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const BACKUP_FILES = [
|
|
32
|
+
".claude/settings.local.json",
|
|
33
|
+
".mcp.json",
|
|
34
|
+
".gitignore",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
interface SetupState {
|
|
38
|
+
step: SetupStep;
|
|
39
|
+
token: string;
|
|
40
|
+
agentId: string;
|
|
41
|
+
existingToken: string;
|
|
42
|
+
existingAgentId: string;
|
|
43
|
+
error: string | null;
|
|
44
|
+
logs: string[];
|
|
45
|
+
isGitRepo: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const createDefaultSettingsLocal = () => ({
|
|
49
|
+
permissions: {
|
|
50
|
+
allow: [],
|
|
51
|
+
},
|
|
52
|
+
enableAllProjectMcpServers: false,
|
|
53
|
+
enabledMcpjsonServers: [],
|
|
54
|
+
hooks: {},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const createDefaultMcpJson = () => ({
|
|
58
|
+
mcpServers: {},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const createHooksConfig = () => {
|
|
62
|
+
const hookCommand = `bunx ${PKG_NAME}@latest hook`;
|
|
63
|
+
const hookEntry = {
|
|
64
|
+
matcher: "*",
|
|
65
|
+
hooks: [
|
|
66
|
+
{
|
|
67
|
+
type: "command",
|
|
68
|
+
command: hookCommand,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
SessionStart: [hookEntry],
|
|
75
|
+
UserPromptSubmit: [hookEntry],
|
|
76
|
+
PreToolUse: [hookEntry],
|
|
77
|
+
PostToolUse: [hookEntry],
|
|
78
|
+
PreCompact: [hookEntry],
|
|
79
|
+
Stop: [hookEntry],
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function Setup({ dryRun = false, restore = false }: SetupProps) {
|
|
84
|
+
const { exit } = useApp();
|
|
85
|
+
const [state, setState] = useState<SetupState>({
|
|
86
|
+
step: restore ? "restoring" : "check_dirs",
|
|
87
|
+
token: "",
|
|
88
|
+
agentId: "",
|
|
89
|
+
existingToken: "",
|
|
90
|
+
existingAgentId: "",
|
|
91
|
+
error: null,
|
|
92
|
+
logs: [],
|
|
93
|
+
isGitRepo: false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const addLog = (log: string, isDryRunAction = false) => {
|
|
97
|
+
const prefix = isDryRunAction && dryRun ? "[DRY-RUN] Would: " : "";
|
|
98
|
+
setState((s) => ({ ...s, logs: [...s.logs, `${prefix}${log}`] }));
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Helper to create backup
|
|
102
|
+
const createBackup = async (filePath: string): Promise<boolean> => {
|
|
103
|
+
const file = Bun.file(filePath);
|
|
104
|
+
if (await file.exists()) {
|
|
105
|
+
const backupPath = `${filePath}.bak`;
|
|
106
|
+
if (!dryRun) {
|
|
107
|
+
const content = await file.text();
|
|
108
|
+
await Bun.write(backupPath, content);
|
|
109
|
+
}
|
|
110
|
+
addLog(`Backup: ${filePath} -> ${filePath}.bak`, true);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Handle restore mode
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (state.step !== "restoring") return;
|
|
119
|
+
|
|
120
|
+
const restoreFiles = async () => {
|
|
121
|
+
const cwd = process.cwd();
|
|
122
|
+
let restoredCount = 0;
|
|
123
|
+
|
|
124
|
+
for (const relativePath of BACKUP_FILES) {
|
|
125
|
+
const backupPath = `${cwd}/${relativePath}.bak`;
|
|
126
|
+
const originalPath = `${cwd}/${relativePath}`;
|
|
127
|
+
const backupFile = Bun.file(backupPath);
|
|
128
|
+
|
|
129
|
+
if (await backupFile.exists()) {
|
|
130
|
+
if (!dryRun) {
|
|
131
|
+
const content = await backupFile.text();
|
|
132
|
+
await Bun.write(originalPath, content);
|
|
133
|
+
await Bun.$`rm ${backupPath}`;
|
|
134
|
+
}
|
|
135
|
+
addLog(`Restore: ${relativePath}.bak -> ${relativePath}`, true);
|
|
136
|
+
restoredCount++;
|
|
137
|
+
} else {
|
|
138
|
+
addLog(`No backup found: ${relativePath}.bak`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (restoredCount === 0) {
|
|
143
|
+
setState((s) => ({
|
|
144
|
+
...s,
|
|
145
|
+
step: "error",
|
|
146
|
+
error: "No backup files found to restore",
|
|
147
|
+
}));
|
|
148
|
+
} else {
|
|
149
|
+
setState((s) => ({ ...s, step: "done" }));
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
restoreFiles().catch((err) => {
|
|
154
|
+
setState((s) => ({ ...s, step: "error", error: err.message }));
|
|
155
|
+
});
|
|
156
|
+
}, [state.step, dryRun]);
|
|
157
|
+
|
|
158
|
+
// Step 1: Check and create directories/files
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (state.step !== "check_dirs") return;
|
|
161
|
+
|
|
162
|
+
const checkDirs = async () => {
|
|
163
|
+
const cwd = process.cwd();
|
|
164
|
+
|
|
165
|
+
// Check if .claude dir exists
|
|
166
|
+
const claudeDir = Bun.file(`${cwd}/.claude`);
|
|
167
|
+
if (!(await claudeDir.exists())) {
|
|
168
|
+
if (!dryRun) {
|
|
169
|
+
await Bun.$`mkdir -p ${cwd}/.claude`;
|
|
170
|
+
}
|
|
171
|
+
addLog("Create .claude directory", true);
|
|
172
|
+
} else {
|
|
173
|
+
addLog(".claude directory exists");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check if .claude/settings.local.json exists
|
|
177
|
+
const settingsFile = Bun.file(`${cwd}/.claude/settings.local.json`);
|
|
178
|
+
if (!(await settingsFile.exists())) {
|
|
179
|
+
if (!dryRun) {
|
|
180
|
+
await Bun.write(
|
|
181
|
+
settingsFile,
|
|
182
|
+
JSON.stringify(createDefaultSettingsLocal(), null, 2),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
addLog("Create .claude/settings.local.json", true);
|
|
186
|
+
} else {
|
|
187
|
+
addLog(".claude/settings.local.json exists");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check if .mcp.json exists
|
|
191
|
+
const mcpFile = Bun.file(`${cwd}/.mcp.json`);
|
|
192
|
+
if (!(await mcpFile.exists())) {
|
|
193
|
+
if (!dryRun) {
|
|
194
|
+
await Bun.write(mcpFile, JSON.stringify(createDefaultMcpJson(), null, 2));
|
|
195
|
+
}
|
|
196
|
+
addLog("Create .mcp.json", true);
|
|
197
|
+
} else {
|
|
198
|
+
addLog(".mcp.json exists");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if it's a git repo
|
|
202
|
+
let isGitRepo = false;
|
|
203
|
+
try {
|
|
204
|
+
const gitDir = Bun.file(`${cwd}/.git`);
|
|
205
|
+
isGitRepo = await gitDir.exists();
|
|
206
|
+
} catch {
|
|
207
|
+
isGitRepo = false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (isGitRepo) {
|
|
211
|
+
addLog("Git repository detected");
|
|
212
|
+
|
|
213
|
+
// Check .gitignore
|
|
214
|
+
const gitignoreFile = Bun.file(`${cwd}/.gitignore`);
|
|
215
|
+
let gitignoreContent = "";
|
|
216
|
+
|
|
217
|
+
if (await gitignoreFile.exists()) {
|
|
218
|
+
gitignoreContent = await gitignoreFile.text();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const entriesToAdd: string[] = [];
|
|
222
|
+
if (!gitignoreContent.includes(".claude")) {
|
|
223
|
+
entriesToAdd.push(".claude");
|
|
224
|
+
}
|
|
225
|
+
if (!gitignoreContent.includes(".mcp.json")) {
|
|
226
|
+
entriesToAdd.push(".mcp.json");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (entriesToAdd.length > 0) {
|
|
230
|
+
// Backup .gitignore before modifying
|
|
231
|
+
await createBackup(`${cwd}/.gitignore`);
|
|
232
|
+
if (!dryRun) {
|
|
233
|
+
const newEntries = `# Added by ${SERVER_NAME} setup\n${entriesToAdd.join("\n")}\n\n`;
|
|
234
|
+
await Bun.write(gitignoreFile, newEntries + gitignoreContent);
|
|
235
|
+
}
|
|
236
|
+
addLog(`Add to .gitignore: ${entriesToAdd.join(", ")}`, true);
|
|
237
|
+
} else {
|
|
238
|
+
addLog(".gitignore already contains required entries");
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
addLog("Not a git repository (skipping .gitignore update)");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Try to read existing values from .mcp.json
|
|
245
|
+
let existingToken = "";
|
|
246
|
+
let existingAgentId = "";
|
|
247
|
+
try {
|
|
248
|
+
const mcpFile = Bun.file(`${cwd}/.mcp.json`);
|
|
249
|
+
if (await mcpFile.exists()) {
|
|
250
|
+
const mcpConfig = await mcpFile.json();
|
|
251
|
+
const serverConfig = mcpConfig?.mcpServers?.[SERVER_NAME];
|
|
252
|
+
if (serverConfig?.headers) {
|
|
253
|
+
const authHeader = serverConfig.headers.Authorization || "";
|
|
254
|
+
if (authHeader.startsWith("Bearer ")) {
|
|
255
|
+
existingToken = authHeader.slice(7);
|
|
256
|
+
}
|
|
257
|
+
existingAgentId = serverConfig.headers["X-Agent-ID"] || "";
|
|
258
|
+
}
|
|
259
|
+
if (existingToken || existingAgentId) {
|
|
260
|
+
addLog("Found existing configuration values");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
// Ignore errors reading existing config
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
setState((s) => ({
|
|
268
|
+
...s,
|
|
269
|
+
step: "input_token",
|
|
270
|
+
isGitRepo,
|
|
271
|
+
existingToken,
|
|
272
|
+
existingAgentId,
|
|
273
|
+
}));
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
checkDirs().catch((err) => {
|
|
277
|
+
setState((s) => ({ ...s, step: "error", error: err.message }));
|
|
278
|
+
});
|
|
279
|
+
}, [state.step, dryRun]);
|
|
280
|
+
|
|
281
|
+
// Handle final update step
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
if (state.step !== "updating") return;
|
|
284
|
+
|
|
285
|
+
const updateFiles = async () => {
|
|
286
|
+
const cwd = process.cwd();
|
|
287
|
+
const mcpBaseUrl =
|
|
288
|
+
process.env.MCP_BASE_URL || DEFAULT_MCP_BASE_URL;
|
|
289
|
+
|
|
290
|
+
// For dry-run, show what would be written
|
|
291
|
+
const generatedAgentId = state.agentId || crypto.randomUUID();
|
|
292
|
+
|
|
293
|
+
// Create backups before modifying
|
|
294
|
+
await createBackup(`${cwd}/.claude/settings.local.json`);
|
|
295
|
+
await createBackup(`${cwd}/.mcp.json`);
|
|
296
|
+
|
|
297
|
+
// Update .claude/settings.local.json
|
|
298
|
+
const settingsFile = Bun.file(`${cwd}/.claude/settings.local.json`);
|
|
299
|
+
let settings: Record<string, unknown>;
|
|
300
|
+
|
|
301
|
+
if (dryRun && !(await settingsFile.exists())) {
|
|
302
|
+
settings = createDefaultSettingsLocal();
|
|
303
|
+
} else {
|
|
304
|
+
settings = await settingsFile.json();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Ensure permissions.allow exists and add mcp__agent-swarm__*
|
|
308
|
+
if (!settings.permissions) {
|
|
309
|
+
settings.permissions = { allow: [] };
|
|
310
|
+
}
|
|
311
|
+
const permissions = settings.permissions as { allow: string[] };
|
|
312
|
+
if (!permissions.allow) {
|
|
313
|
+
permissions.allow = [];
|
|
314
|
+
}
|
|
315
|
+
const permissionEntry = `mcp__${SERVER_NAME}__*`;
|
|
316
|
+
if (!permissions.allow.includes(permissionEntry)) {
|
|
317
|
+
permissions.allow.push(permissionEntry);
|
|
318
|
+
addLog(`Add "${permissionEntry}" to permissions.allow`, true);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Ensure enabledMcpjsonServers exists and add agent-swarm
|
|
322
|
+
if (!settings.enabledMcpjsonServers) {
|
|
323
|
+
settings.enabledMcpjsonServers = [];
|
|
324
|
+
}
|
|
325
|
+
const enabledServers = settings.enabledMcpjsonServers as string[];
|
|
326
|
+
if (!enabledServers.includes(SERVER_NAME)) {
|
|
327
|
+
enabledServers.push(SERVER_NAME);
|
|
328
|
+
addLog(`Add "${SERVER_NAME}" to enabledMcpjsonServers`, true);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Add hooks
|
|
332
|
+
const newHooks = createHooksConfig();
|
|
333
|
+
settings.hooks = { ...(settings.hooks as object || {}), ...newHooks };
|
|
334
|
+
addLog("Add hooks configuration", true);
|
|
335
|
+
|
|
336
|
+
if (!dryRun) {
|
|
337
|
+
await Bun.write(settingsFile, JSON.stringify(settings, null, 2));
|
|
338
|
+
}
|
|
339
|
+
addLog("Update .claude/settings.local.json", true);
|
|
340
|
+
|
|
341
|
+
// Update .mcp.json
|
|
342
|
+
const mcpFile = Bun.file(`${cwd}/.mcp.json`);
|
|
343
|
+
let mcpConfig: Record<string, unknown>;
|
|
344
|
+
|
|
345
|
+
if (dryRun && !(await mcpFile.exists())) {
|
|
346
|
+
mcpConfig = createDefaultMcpJson();
|
|
347
|
+
} else {
|
|
348
|
+
mcpConfig = await mcpFile.json();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!mcpConfig.mcpServers) {
|
|
352
|
+
mcpConfig.mcpServers = {};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const mcpServers = mcpConfig.mcpServers as Record<string, unknown>;
|
|
356
|
+
mcpServers[SERVER_NAME] = {
|
|
357
|
+
type: "http",
|
|
358
|
+
url: `${mcpBaseUrl}/mcp`,
|
|
359
|
+
headers: {
|
|
360
|
+
Authorization: `Bearer ${state.token}`,
|
|
361
|
+
"X-Agent-ID": generatedAgentId,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
if (!dryRun) {
|
|
366
|
+
await Bun.write(mcpFile, JSON.stringify(mcpConfig, null, 2));
|
|
367
|
+
}
|
|
368
|
+
addLog("Update .mcp.json with server configuration", true);
|
|
369
|
+
|
|
370
|
+
if (dryRun) {
|
|
371
|
+
addLog("");
|
|
372
|
+
addLog(`Agent ID that would be used: ${generatedAgentId}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
setState((s) => ({ ...s, step: "done" }));
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
updateFiles().catch((err) => {
|
|
379
|
+
setState((s) => ({ ...s, step: "error", error: err.message }));
|
|
380
|
+
});
|
|
381
|
+
}, [state.step, state.token, state.agentId, dryRun]);
|
|
382
|
+
|
|
383
|
+
// Exit on done
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
if (state.step === "done" || state.step === "error") {
|
|
386
|
+
const timer = setTimeout(() => exit(), 500);
|
|
387
|
+
return () => clearTimeout(timer);
|
|
388
|
+
}
|
|
389
|
+
}, [state.step, exit]);
|
|
390
|
+
|
|
391
|
+
if (state.step === "error") {
|
|
392
|
+
return (
|
|
393
|
+
<Box flexDirection="column" padding={1}>
|
|
394
|
+
<Text color="red">Setup failed: {state.error}</Text>
|
|
395
|
+
</Box>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (state.step === "restoring") {
|
|
400
|
+
return (
|
|
401
|
+
<Box flexDirection="column" padding={1}>
|
|
402
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
403
|
+
{state.logs.map((log, i) => (
|
|
404
|
+
<Text key={i} dimColor>
|
|
405
|
+
{log}
|
|
406
|
+
</Text>
|
|
407
|
+
))}
|
|
408
|
+
</Box>
|
|
409
|
+
<Spinner label="Restoring from backups..." />
|
|
410
|
+
</Box>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (state.step === "check_dirs") {
|
|
415
|
+
return (
|
|
416
|
+
<Box padding={1}>
|
|
417
|
+
<Spinner label="Checking directories and files..." />
|
|
418
|
+
</Box>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (state.step === "input_token") {
|
|
423
|
+
return (
|
|
424
|
+
<Box flexDirection="column" padding={1}>
|
|
425
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
426
|
+
{state.logs.map((log, i) => (
|
|
427
|
+
<Text key={i} dimColor>
|
|
428
|
+
{log}
|
|
429
|
+
</Text>
|
|
430
|
+
))}
|
|
431
|
+
</Box>
|
|
432
|
+
<Box flexDirection="column">
|
|
433
|
+
<Box>
|
|
434
|
+
<Text bold>Enter your API token</Text>
|
|
435
|
+
{state.existingToken && (
|
|
436
|
+
<Text dimColor> (current: {state.existingToken.slice(0, 8)}...)</Text>
|
|
437
|
+
)}
|
|
438
|
+
<Text bold>: </Text>
|
|
439
|
+
</Box>
|
|
440
|
+
<TextInput
|
|
441
|
+
key="token-input"
|
|
442
|
+
defaultValue={state.existingToken}
|
|
443
|
+
placeholder="your-api-token"
|
|
444
|
+
onSubmit={(value) => {
|
|
445
|
+
if (!value.trim()) {
|
|
446
|
+
setState((s) => ({
|
|
447
|
+
...s,
|
|
448
|
+
step: "error",
|
|
449
|
+
error: "API token is required",
|
|
450
|
+
}));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
setState((s) => ({ ...s, token: value, step: "input_agent_id" }));
|
|
454
|
+
}}
|
|
455
|
+
/>
|
|
456
|
+
</Box>
|
|
457
|
+
</Box>
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (state.step === "input_agent_id") {
|
|
462
|
+
return (
|
|
463
|
+
<Box flexDirection="column" padding={1}>
|
|
464
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
465
|
+
{state.logs.map((log, i) => (
|
|
466
|
+
<Text key={i} dimColor>
|
|
467
|
+
{log}
|
|
468
|
+
</Text>
|
|
469
|
+
))}
|
|
470
|
+
</Box>
|
|
471
|
+
<Box flexDirection="column">
|
|
472
|
+
<Box>
|
|
473
|
+
<Text bold>Enter your Agent ID</Text>
|
|
474
|
+
{state.existingAgentId && (
|
|
475
|
+
<Text dimColor> (current: {state.existingAgentId})</Text>
|
|
476
|
+
)}
|
|
477
|
+
</Box>
|
|
478
|
+
<Text dimColor>(optional, press Enter to generate a new one): </Text>
|
|
479
|
+
<TextInput
|
|
480
|
+
key="agent-id-input"
|
|
481
|
+
defaultValue={state.existingAgentId}
|
|
482
|
+
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
|
483
|
+
onSubmit={(value) => {
|
|
484
|
+
const trimmed = value.trim();
|
|
485
|
+
if (trimmed && !isValidUUID(trimmed)) {
|
|
486
|
+
setState((s) => ({
|
|
487
|
+
...s,
|
|
488
|
+
step: "error",
|
|
489
|
+
error: "Invalid UUID format. Expected: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
490
|
+
}));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
setState((s) => ({ ...s, agentId: trimmed, step: "updating" }));
|
|
494
|
+
}}
|
|
495
|
+
/>
|
|
496
|
+
</Box>
|
|
497
|
+
</Box>
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (state.step === "updating") {
|
|
502
|
+
return (
|
|
503
|
+
<Box flexDirection="column" padding={1}>
|
|
504
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
505
|
+
{state.logs.map((log, i) => (
|
|
506
|
+
<Text key={i} dimColor>
|
|
507
|
+
{log}
|
|
508
|
+
</Text>
|
|
509
|
+
))}
|
|
510
|
+
</Box>
|
|
511
|
+
<Spinner label="Updating configuration files..." />
|
|
512
|
+
</Box>
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (state.step === "done") {
|
|
517
|
+
const mcpBaseUrl = process.env.MCP_BASE_URL || DEFAULT_MCP_BASE_URL;
|
|
518
|
+
|
|
519
|
+
const getDoneMessage = () => {
|
|
520
|
+
if (dryRun && restore) return "Dry-run restore complete!";
|
|
521
|
+
if (dryRun) return "Dry-run complete!";
|
|
522
|
+
if (restore) return "Restore complete!";
|
|
523
|
+
return "Setup complete!";
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
return (
|
|
527
|
+
<Box flexDirection="column" padding={1}>
|
|
528
|
+
{dryRun && (
|
|
529
|
+
<Box marginBottom={1}>
|
|
530
|
+
<Text color="yellow" bold>
|
|
531
|
+
DRY-RUN MODE - No changes were made
|
|
532
|
+
</Text>
|
|
533
|
+
</Box>
|
|
534
|
+
)}
|
|
535
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
536
|
+
{state.logs.map((log, i) => (
|
|
537
|
+
<Text key={i} dimColor>
|
|
538
|
+
{log}
|
|
539
|
+
</Text>
|
|
540
|
+
))}
|
|
541
|
+
</Box>
|
|
542
|
+
<Box marginTop={1}>
|
|
543
|
+
<Text color="green">{getDoneMessage()}</Text>
|
|
544
|
+
</Box>
|
|
545
|
+
{!dryRun && !restore && (
|
|
546
|
+
<Box flexDirection="column" marginTop={1}>
|
|
547
|
+
<Text bold>Next steps:</Text>
|
|
548
|
+
<Text>
|
|
549
|
+
1. Set the <Text color="cyan">MCP_BASE_URL</Text> environment variable
|
|
550
|
+
in your .env file
|
|
551
|
+
</Text>
|
|
552
|
+
<Text dimColor> (Default: {DEFAULT_MCP_BASE_URL})</Text>
|
|
553
|
+
<Text dimColor> (Current: {mcpBaseUrl})</Text>
|
|
554
|
+
<Text>2. Restart Claude Code to apply the changes</Text>
|
|
555
|
+
</Box>
|
|
556
|
+
)}
|
|
557
|
+
{!dryRun && restore && (
|
|
558
|
+
<Box flexDirection="column" marginTop={1}>
|
|
559
|
+
<Text>Files restored from backups. Restart Claude Code to apply.</Text>
|
|
560
|
+
</Box>
|
|
561
|
+
)}
|
|
562
|
+
{dryRun && !restore && (
|
|
563
|
+
<Box flexDirection="column" marginTop={1}>
|
|
564
|
+
<Text>Run without --dry-run to apply these changes.</Text>
|
|
565
|
+
</Box>
|
|
566
|
+
)}
|
|
567
|
+
{dryRun && restore && (
|
|
568
|
+
<Box flexDirection="column" marginTop={1}>
|
|
569
|
+
<Text>Run without --dry-run to restore from backups.</Text>
|
|
570
|
+
</Box>
|
|
571
|
+
)}
|
|
572
|
+
</Box>
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return null;
|
|
577
|
+
}
|