@castlekit/castle 0.4.1 → 0.4.3
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.html +1 -1
- package/.next/standalone/.next/server/app/chat.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/chat.segments/chat.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.html +1 -1
- package/.next/standalone/.next/server/app/ui-kit.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/ui-kit/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/ui-kit.segments/ui-kit.segment.rsc +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2824c41d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__361bce14._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_7a67de23._.js +1 -1
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/{static/chunks/2b48886a383c2e37.js → standalone/.next/static/chunks/8c691cfaccdbb9ee.js} +1 -1
- package/.next/standalone/CHANGELOG.md +26 -0
- package/.next/standalone/bin/castle.js +2 -2
- package/.next/standalone/install.ps1 +16 -16
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/app/api/openclaw/agents/[id]/avatar/route.ts +51 -91
- package/.next/standalone/src/app/api/openclaw/agents/route.ts +12 -0
- package/.next/standalone/src/app/page.tsx +3 -3
- package/.next/standalone/src/cli/onboarding.ts +90 -87
- package/.next/{standalone/.next/static/chunks/2b48886a383c2e37.js → static/chunks/8c691cfaccdbb9ee.js} +1 -1
- package/bin/castle.js +2 -2
- package/install.ps1 +16 -16
- package/package.json +1 -1
- package/src/app/api/openclaw/agents/[id]/avatar/route.ts +51 -91
- package/src/app/api/openclaw/agents/route.ts +12 -0
- package/src/app/page.tsx +3 -3
- package/src/cli/onboarding.ts +90 -87
- /package/.next/standalone/.next/static/{IDGX3MGCjqUE6NQVlug5A → PzpTWZTNAsAIh96H48auo}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{IDGX3MGCjqUE6NQVlug5A → PzpTWZTNAsAIh96H48auo}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{IDGX3MGCjqUE6NQVlug5A → PzpTWZTNAsAIh96H48auo}/_ssgManifest.js +0 -0
- /package/.next/static/{IDGX3MGCjqUE6NQVlug5A → PzpTWZTNAsAIh96H48auo}/_buildManifest.js +0 -0
- /package/.next/static/{IDGX3MGCjqUE6NQVlug5A → PzpTWZTNAsAIh96H48auo}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{IDGX3MGCjqUE6NQVlug5A → PzpTWZTNAsAIh96H48auo}/_ssgManifest.js +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import {
|
|
2
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
-
import JSON5 from "json5";
|
|
6
5
|
import sharp from "sharp";
|
|
7
6
|
import { ensureGateway } from "@/lib/gateway-connection";
|
|
8
7
|
import { checkCsrf } from "@/lib/api-security";
|
|
@@ -20,21 +19,6 @@ const ALLOWED_TYPES = new Set([
|
|
|
20
19
|
"image/gif",
|
|
21
20
|
]);
|
|
22
21
|
|
|
23
|
-
interface AgentConfig {
|
|
24
|
-
id: string;
|
|
25
|
-
workspace?: string;
|
|
26
|
-
identity?: Record<string, unknown>;
|
|
27
|
-
[key: string]: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface OpenClawConfig {
|
|
31
|
-
agents?: {
|
|
32
|
-
list?: AgentConfig[];
|
|
33
|
-
[key: string]: unknown;
|
|
34
|
-
};
|
|
35
|
-
[key: string]: unknown;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
22
|
/**
|
|
39
23
|
* Resize and compress an avatar image to 256x256, under 100KB.
|
|
40
24
|
*/
|
|
@@ -59,27 +43,13 @@ async function processAvatar(input: Buffer): Promise<{ data: Buffer; ext: string
|
|
|
59
43
|
return { data, ext: ".jpg" };
|
|
60
44
|
}
|
|
61
45
|
|
|
62
|
-
/**
|
|
63
|
-
* Find the OpenClaw config file path.
|
|
64
|
-
*/
|
|
65
|
-
function getOpenClawConfigPath(): string | null {
|
|
66
|
-
const paths = [
|
|
67
|
-
join(homedir(), ".openclaw", "openclaw.json"),
|
|
68
|
-
join(homedir(), ".openclaw", "openclaw.json5"),
|
|
69
|
-
];
|
|
70
|
-
for (const p of paths) {
|
|
71
|
-
if (existsSync(p)) return p;
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
46
|
/**
|
|
77
47
|
* POST /api/openclaw/agents/[id]/avatar
|
|
78
48
|
*
|
|
79
49
|
* Upload a new avatar image for an agent.
|
|
80
50
|
* - Resizes to 256x256 and compresses under 100KB
|
|
81
|
-
* - Saves to
|
|
82
|
-
* -
|
|
51
|
+
* - Saves to Castle's own avatars directory (~/.castle/avatars/)
|
|
52
|
+
* - Updates OpenClaw config via Gateway's config.patch RPC (never writes to OpenClaw files directly)
|
|
83
53
|
*/
|
|
84
54
|
export async function POST(
|
|
85
55
|
request: NextRequest,
|
|
@@ -128,46 +98,9 @@ export async function POST(
|
|
|
128
98
|
);
|
|
129
99
|
}
|
|
130
100
|
|
|
131
|
-
//
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
return NextResponse.json(
|
|
135
|
-
{ error: "OpenClaw config file not found" },
|
|
136
|
-
{ status: 500 }
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
let config: OpenClawConfig;
|
|
141
|
-
try {
|
|
142
|
-
config = JSON5.parse(readFileSync(configPath, "utf-8"));
|
|
143
|
-
} catch (err) {
|
|
144
|
-
return NextResponse.json(
|
|
145
|
-
{ error: `Failed to read config: ${err instanceof Error ? err.message : "unknown"}` },
|
|
146
|
-
{ status: 500 }
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const agents = config.agents?.list || [];
|
|
151
|
-
const agent = agents.find((a) => a.id === safeId);
|
|
152
|
-
|
|
153
|
-
if (!agent) {
|
|
154
|
-
return NextResponse.json({ error: "Agent not found" }, { status: 404 });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!agent.workspace) {
|
|
158
|
-
return NextResponse.json(
|
|
159
|
-
{ error: "Agent has no workspace configured" },
|
|
160
|
-
{ status: 400 }
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const workspacePath = agent.workspace.startsWith("~")
|
|
165
|
-
? join(homedir(), agent.workspace.slice(1))
|
|
166
|
-
: agent.workspace;
|
|
167
|
-
|
|
168
|
-
// Save processed avatar to workspace
|
|
169
|
-
const avatarsDir = join(workspacePath, "avatars");
|
|
170
|
-
const fileName = `avatar${processed.ext}`;
|
|
101
|
+
// Save processed avatar to Castle's own directory — never write to OpenClaw's filesystem
|
|
102
|
+
const avatarsDir = join(homedir(), ".castle", "avatars");
|
|
103
|
+
const fileName = `${safeId}${processed.ext}`;
|
|
171
104
|
const filePath = join(avatarsDir, fileName);
|
|
172
105
|
|
|
173
106
|
try {
|
|
@@ -180,37 +113,64 @@ export async function POST(
|
|
|
180
113
|
);
|
|
181
114
|
}
|
|
182
115
|
|
|
183
|
-
// Update config
|
|
184
|
-
//
|
|
116
|
+
// Update OpenClaw config via Gateway's config.patch RPC — the proper way to
|
|
117
|
+
// modify OpenClaw config without writing to its files directly.
|
|
118
|
+
const gw = ensureGateway();
|
|
119
|
+
|
|
120
|
+
if (!gw.isConnected) {
|
|
121
|
+
// Avatar is saved locally; config update will happen when Gateway reconnects
|
|
122
|
+
return NextResponse.json({
|
|
123
|
+
success: true,
|
|
124
|
+
avatar: filePath,
|
|
125
|
+
size: processed.data.length,
|
|
126
|
+
message: "Avatar saved locally. Gateway not connected — config will update when it reconnects.",
|
|
127
|
+
configUpdated: false,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
185
131
|
try {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
132
|
+
// Use config.patch to set the agent's avatar path.
|
|
133
|
+
// Gateway validates and applies the patch, then hot-reloads.
|
|
134
|
+
await gw.request("config.patch", {
|
|
135
|
+
agents: {
|
|
136
|
+
list: [
|
|
137
|
+
{
|
|
138
|
+
id: safeId,
|
|
139
|
+
identity: {
|
|
140
|
+
avatar: filePath,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
console.log(`[Avatar API] Config patched for agent ${safeId}`);
|
|
189
147
|
} catch (err) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
148
|
+
console.error(
|
|
149
|
+
`[Avatar API] config.patch failed for agent ${safeId}:`,
|
|
150
|
+
err instanceof Error ? err.message : "unknown"
|
|
193
151
|
);
|
|
152
|
+
// Avatar is still saved locally — not a total failure
|
|
153
|
+
return NextResponse.json({
|
|
154
|
+
success: true,
|
|
155
|
+
avatar: filePath,
|
|
156
|
+
size: processed.data.length,
|
|
157
|
+
message: "Avatar saved but config update failed. Try restarting the Gateway.",
|
|
158
|
+
configUpdated: false,
|
|
159
|
+
});
|
|
194
160
|
}
|
|
195
161
|
|
|
196
|
-
//
|
|
197
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
198
|
-
|
|
199
|
-
// Refresh Castle's agent list from the Gateway
|
|
162
|
+
// Emit a signal so SSE clients re-fetch agents
|
|
200
163
|
try {
|
|
201
|
-
|
|
202
|
-
if (gw.isConnected) {
|
|
203
|
-
// Emit a signal so SSE clients re-fetch agents
|
|
204
|
-
gw.emit("agentAvatarUpdated", { agentId: safeId });
|
|
205
|
-
}
|
|
164
|
+
gw.emit("agentAvatarUpdated", { agentId: safeId });
|
|
206
165
|
} catch {
|
|
207
166
|
// Non-critical
|
|
208
167
|
}
|
|
209
168
|
|
|
210
169
|
return NextResponse.json({
|
|
211
170
|
success: true,
|
|
212
|
-
avatar:
|
|
171
|
+
avatar: filePath,
|
|
213
172
|
size: processed.data.length,
|
|
214
173
|
message: "Avatar updated",
|
|
174
|
+
configUpdated: true,
|
|
215
175
|
});
|
|
216
176
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
2
4
|
import { ensureGateway } from "@/lib/gateway-connection";
|
|
3
5
|
|
|
4
6
|
export const dynamic = "force-dynamic";
|
|
5
7
|
|
|
8
|
+
const CASTLE_AVATARS_DIR = join(homedir(), ".castle", "avatars");
|
|
9
|
+
|
|
6
10
|
interface AgentIdentity {
|
|
7
11
|
name?: string;
|
|
8
12
|
theme?: string;
|
|
@@ -86,6 +90,14 @@ function resolveAvatarUrl(
|
|
|
86
90
|
return `/api/avatars/${key}`;
|
|
87
91
|
}
|
|
88
92
|
|
|
93
|
+
// Absolute path under ~/.castle/avatars/ (from avatar upload via config.patch)
|
|
94
|
+
if (avatar.startsWith("/") || avatar.startsWith("~")) {
|
|
95
|
+
const normalized = resolve(avatar.replace(/^~/, homedir()));
|
|
96
|
+
if (normalized.startsWith(CASTLE_AVATARS_DIR + "/") || normalized === CASTLE_AVATARS_DIR) {
|
|
97
|
+
return `/api/avatars/${agentId}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
return null;
|
|
90
102
|
}
|
|
91
103
|
|
package/src/app/page.tsx
CHANGED
|
@@ -206,7 +206,7 @@ function ConnectionCard({
|
|
|
206
206
|
}) {
|
|
207
207
|
const getSubtitle = () => {
|
|
208
208
|
if (isLoading) return "Connecting to Gateway...";
|
|
209
|
-
if (!isConfigured) return "
|
|
209
|
+
if (!isConfigured) return "OpenClaw not installed — visit openclaw.ai";
|
|
210
210
|
if (isConnected) {
|
|
211
211
|
const parts = ["Connected"];
|
|
212
212
|
if (serverVersion) parts[0] = `Connected to OpenClaw ${serverVersion}`;
|
|
@@ -247,8 +247,8 @@ function ConnectionCard({
|
|
|
247
247
|
{isLoading ? (
|
|
248
248
|
<Badge variant="outline">Connecting...</Badge>
|
|
249
249
|
) : (
|
|
250
|
-
<Badge variant={isConnected ? "success" : "error"}>
|
|
251
|
-
{isConnected ? "Connected" : "Disconnected"}
|
|
250
|
+
<Badge variant={isConnected ? "success" : isConfigured ? "error" : "outline"}>
|
|
251
|
+
{isConnected ? "Connected" : isConfigured ? "Disconnected" : "Not Installed"}
|
|
252
252
|
</Badge>
|
|
253
253
|
)}
|
|
254
254
|
</div>
|
package/src/cli/onboarding.ts
CHANGED
|
@@ -329,6 +329,7 @@ export async function runOnboarding(): Promise<void> {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
// Step 1: Check for OpenClaw
|
|
332
|
+
let openclawSkipped = false;
|
|
332
333
|
const openclawSpinner = p.spinner();
|
|
333
334
|
openclawSpinner.start("Checking for OpenClaw...");
|
|
334
335
|
|
|
@@ -356,34 +357,29 @@ export async function runOnboarding(): Promise<void> {
|
|
|
356
357
|
installSpinner.start("Installing OpenClaw...");
|
|
357
358
|
|
|
358
359
|
const installCmd = process.platform === "win32"
|
|
359
|
-
? 'powershell -NoProfile -ExecutionPolicy Bypass -Command "
|
|
360
|
+
? 'powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://openclaw.ai/install.ps1 -OutFile $env:TEMP\\openclaw-install.ps1; & $env:TEMP\\openclaw-install.ps1 -NoOnboard"'
|
|
360
361
|
: 'curl -fsSL --proto "=https" --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard --no-prompt';
|
|
361
362
|
|
|
362
363
|
try {
|
|
363
|
-
execSync(installCmd, { stdio: "
|
|
364
|
+
execSync(installCmd, { stdio: "inherit", timeout: 300000 });
|
|
364
365
|
installSpinner.stop(BLUE("✔ OpenClaw installed"));
|
|
365
366
|
} catch (error) {
|
|
367
|
+
openclawSkipped = true;
|
|
366
368
|
installSpinner.stop(pc.red("OpenClaw installation failed"));
|
|
367
369
|
const manualCmd = process.platform === "win32"
|
|
368
370
|
? "iwr -useb https://openclaw.ai/install.ps1 | iex"
|
|
369
371
|
: "curl -fsSL https://openclaw.ai/install.sh | bash";
|
|
370
|
-
p.
|
|
371
|
-
`
|
|
372
|
-
BLUE_BOLD("Manual Install")
|
|
372
|
+
p.log.warn(
|
|
373
|
+
`You can install OpenClaw later:\n${BLUE_LIGHT(manualCmd)}`
|
|
373
374
|
);
|
|
374
|
-
p.outro("Come back when OpenClaw is installed!");
|
|
375
|
-
process.exit(1);
|
|
376
375
|
}
|
|
377
376
|
} else {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
);
|
|
385
|
-
p.outro("See you soon!");
|
|
386
|
-
process.exit(0);
|
|
377
|
+
openclawSkipped = true;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (openclawSkipped && !isOpenClawInstalled()) {
|
|
381
|
+
// Skip Gateway config — use defaults and continue to build/start Castle
|
|
382
|
+
p.log.message(pc.dim("Using default settings — you can reconfigure later with castle setup"));
|
|
387
383
|
}
|
|
388
384
|
} else {
|
|
389
385
|
// Auto-detect token and agents in one go
|
|
@@ -416,34 +412,49 @@ export async function runOnboarding(): Promise<void> {
|
|
|
416
412
|
let token = readOpenClawToken();
|
|
417
413
|
let gatewayUrl: string | undefined;
|
|
418
414
|
let isRemote = false;
|
|
415
|
+
let primaryAgent = "assistant";
|
|
416
|
+
let agents: DiscoveredAgent[] = [];
|
|
417
|
+
|
|
418
|
+
if (!openclawSkipped) {
|
|
419
|
+
// If we have auto-detected config, offer a choice
|
|
420
|
+
const hasLocalConfig = !!readOpenClawPort() || isOpenClawInstalled();
|
|
421
|
+
|
|
422
|
+
if (hasLocalConfig && token) {
|
|
423
|
+
// Both auto-detect and manual are available
|
|
424
|
+
const connectionMode = await p.select({
|
|
425
|
+
message: "How would you like to connect?",
|
|
426
|
+
options: [
|
|
427
|
+
{
|
|
428
|
+
value: "auto",
|
|
429
|
+
label: `Auto-detected local Gateway ${pc.dim(`(port ${port})`)}`,
|
|
430
|
+
hint: "Recommended for local setups",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
value: "manual",
|
|
434
|
+
label: "Enter Gateway details manually",
|
|
435
|
+
hint: "For remote, Tailscale, or custom setups",
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
});
|
|
419
439
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
// Both auto-detect and manual are available
|
|
425
|
-
const connectionMode = await p.select({
|
|
426
|
-
message: "How would you like to connect?",
|
|
427
|
-
options: [
|
|
428
|
-
{
|
|
429
|
-
value: "auto",
|
|
430
|
-
label: `Auto-detected local Gateway ${pc.dim(`(port ${port})`)}`,
|
|
431
|
-
hint: "Recommended for local setups",
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
value: "manual",
|
|
435
|
-
label: "Enter Gateway details manually",
|
|
436
|
-
hint: "For remote, Tailscale, or custom setups",
|
|
437
|
-
},
|
|
438
|
-
],
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
if (p.isCancel(connectionMode)) {
|
|
442
|
-
p.cancel("Setup cancelled.");
|
|
443
|
-
process.exit(0);
|
|
444
|
-
}
|
|
440
|
+
if (p.isCancel(connectionMode)) {
|
|
441
|
+
p.cancel("Setup cancelled.");
|
|
442
|
+
process.exit(0);
|
|
443
|
+
}
|
|
445
444
|
|
|
446
|
-
|
|
445
|
+
if (connectionMode === "manual") {
|
|
446
|
+
const manualResult = await promptManualGateway();
|
|
447
|
+
if (!manualResult) {
|
|
448
|
+
p.cancel("Setup cancelled.");
|
|
449
|
+
process.exit(0);
|
|
450
|
+
}
|
|
451
|
+
port = manualResult.port;
|
|
452
|
+
token = manualResult.token;
|
|
453
|
+
gatewayUrl = manualResult.gatewayUrl;
|
|
454
|
+
isRemote = manualResult.isRemote;
|
|
455
|
+
}
|
|
456
|
+
} else if (!token) {
|
|
457
|
+
// No auto-detected token — fall through to manual entry
|
|
447
458
|
const manualResult = await promptManualGateway();
|
|
448
459
|
if (!manualResult) {
|
|
449
460
|
p.cancel("Setup cancelled.");
|
|
@@ -454,57 +465,44 @@ export async function runOnboarding(): Promise<void> {
|
|
|
454
465
|
gatewayUrl = manualResult.gatewayUrl;
|
|
455
466
|
isRemote = manualResult.isRemote;
|
|
456
467
|
}
|
|
457
|
-
} else if (!token) {
|
|
458
|
-
// No auto-detected token — fall through to manual entry
|
|
459
|
-
const manualResult = await promptManualGateway();
|
|
460
|
-
if (!manualResult) {
|
|
461
|
-
p.cancel("Setup cancelled.");
|
|
462
|
-
process.exit(0);
|
|
463
|
-
}
|
|
464
|
-
port = manualResult.port;
|
|
465
|
-
token = manualResult.token;
|
|
466
|
-
gatewayUrl = manualResult.gatewayUrl;
|
|
467
|
-
isRemote = manualResult.isRemote;
|
|
468
|
-
}
|
|
469
468
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
469
|
+
// Step 3: Agent Discovery (use URL if remote, port if local)
|
|
470
|
+
const agentTarget = gatewayUrl || port;
|
|
471
|
+
agents = await discoverAgents(agentTarget, token);
|
|
473
472
|
|
|
474
|
-
|
|
473
|
+
if (agents.length > 0) {
|
|
475
474
|
|
|
476
|
-
|
|
475
|
+
const selectedAgent = await p.select({
|
|
476
|
+
message: "Choose your primary agent",
|
|
477
|
+
options: agents.map((a) => ({
|
|
478
|
+
value: a.id,
|
|
479
|
+
label: `${a.name} ${pc.dim(`<${a.id}>`)}`,
|
|
480
|
+
hint: a.description || undefined,
|
|
481
|
+
})),
|
|
482
|
+
});
|
|
477
483
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
label: `${a.name} ${pc.dim(`<${a.id}>`)}`,
|
|
483
|
-
hint: a.description || undefined,
|
|
484
|
-
})),
|
|
485
|
-
});
|
|
484
|
+
if (p.isCancel(selectedAgent)) {
|
|
485
|
+
p.cancel("Setup cancelled.");
|
|
486
|
+
process.exit(0);
|
|
487
|
+
}
|
|
486
488
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
489
|
+
primaryAgent = selectedAgent as string;
|
|
490
|
+
} else {
|
|
491
|
+
const setPrimary = await p.text({
|
|
492
|
+
message: "Enter the name of your primary agent",
|
|
493
|
+
initialValue: "assistant",
|
|
494
|
+
validate(value: string | undefined) {
|
|
495
|
+
if (!value?.trim()) return "Agent name is required";
|
|
496
|
+
},
|
|
497
|
+
});
|
|
491
498
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
initialValue: "assistant",
|
|
497
|
-
validate(value: string | undefined) {
|
|
498
|
-
if (!value?.trim()) return "Agent name is required";
|
|
499
|
-
},
|
|
500
|
-
});
|
|
499
|
+
if (p.isCancel(setPrimary)) {
|
|
500
|
+
p.cancel("Setup cancelled.");
|
|
501
|
+
process.exit(0);
|
|
502
|
+
}
|
|
501
503
|
|
|
502
|
-
|
|
503
|
-
p.cancel("Setup cancelled.");
|
|
504
|
-
process.exit(0);
|
|
504
|
+
primaryAgent = setPrimary as string;
|
|
505
505
|
}
|
|
506
|
-
|
|
507
|
-
primaryAgent = setPrimary as string;
|
|
508
506
|
}
|
|
509
507
|
|
|
510
508
|
// Step 5: Create Castle config
|
|
@@ -878,6 +876,11 @@ WantedBy=default.target
|
|
|
878
876
|
}
|
|
879
877
|
|
|
880
878
|
p.outro(pc.dim(`Opening ${BLUE(`http://localhost:${castlePort}`)}...`));
|
|
881
|
-
const
|
|
882
|
-
|
|
879
|
+
const url = `http://localhost:${castlePort}`;
|
|
880
|
+
if (process.platform === "win32") {
|
|
881
|
+
execSync(`start "" "${url}"`, { stdio: "ignore" });
|
|
882
|
+
} else {
|
|
883
|
+
const open = (await import("open")).default;
|
|
884
|
+
await open(url);
|
|
885
|
+
}
|
|
883
886
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/.next/static/{IDGX3MGCjqUE6NQVlug5A → PzpTWZTNAsAIh96H48auo}/_clientMiddlewareManifest.json
RENAMED
|
File without changes
|
|
File without changes
|