@castlekit/castle 0.4.0 → 0.4.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/.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/api/avatars/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/agents/[id]/avatar/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/agents/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/agents/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/attachments/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/channels/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/chat/storage/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/logs/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/ping/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/session/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/session/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/openclaw/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/avatar/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/settings/route.js.nft.json +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 +1 -1
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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/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/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/standalone/CHANGELOG.md +12 -0
- package/.next/standalone/install.ps1 +437 -0
- package/.next/standalone/install.sh +4 -0
- package/.next/standalone/package.json +2 -1
- package/.next/standalone/src/cli/onboarding.ts +95 -22
- package/install.ps1 +437 -0
- package/install.sh +4 -0
- package/package.json +2 -1
- package/src/cli/onboarding.ts +95 -22
- /package/.next/standalone/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → IDGX3MGCjqUE6NQVlug5A}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → IDGX3MGCjqUE6NQVlug5A}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → IDGX3MGCjqUE6NQVlug5A}/_ssgManifest.js +0 -0
- /package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → IDGX3MGCjqUE6NQVlug5A}/_buildManifest.js +0 -0
- /package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → IDGX3MGCjqUE6NQVlug5A}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → IDGX3MGCjqUE6NQVlug5A}/_ssgManifest.js +0 -0
package/install.ps1
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# Castle Installer for Windows
|
|
2
|
+
# Usage: iwr -useb https://castlekit.com/install.ps1 | iex
|
|
3
|
+
# & ([scriptblock]::Create((iwr -useb https://castlekit.com/install.ps1))) -Version 0.4.0 -NoOnboard -DryRun
|
|
4
|
+
|
|
5
|
+
param(
|
|
6
|
+
[string]$Version = "latest",
|
|
7
|
+
[switch]$NoOnboard,
|
|
8
|
+
[switch]$DryRun,
|
|
9
|
+
[switch]$Verbose,
|
|
10
|
+
[switch]$Help
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
$ErrorActionPreference = "Stop"
|
|
14
|
+
|
|
15
|
+
# ─── Taglines ────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
$Taglines = @(
|
|
18
|
+
"Your kingdom awaits, sire."
|
|
19
|
+
"The throne room is ready."
|
|
20
|
+
"A fortress for your AI agents."
|
|
21
|
+
"All hail the command center."
|
|
22
|
+
"Knights of the round terminal."
|
|
23
|
+
"Raise the drawbridge, lower the latency."
|
|
24
|
+
"By royal decree, your agents are assembled."
|
|
25
|
+
"The court is now in session."
|
|
26
|
+
"From castle walls to API calls."
|
|
27
|
+
"Forged in code, ruled by you."
|
|
28
|
+
"Every king needs a castle."
|
|
29
|
+
"Where agents serve and dragons compile."
|
|
30
|
+
"The siege of busywork ends here."
|
|
31
|
+
"Hear ye, hear ye — your agents await."
|
|
32
|
+
"A castle built on open source bedrock."
|
|
33
|
+
"One does not simply walk in without a CLI."
|
|
34
|
+
"The moat is deep but the docs are deeper."
|
|
35
|
+
"Fear not the dark mode, for it is default."
|
|
36
|
+
"In the land of AI, the castlekeeper wears a hoodie."
|
|
37
|
+
"Excalibur was a sword. This is better."
|
|
38
|
+
"npm install --save-the-kingdom."
|
|
39
|
+
"The Round Table, but make it a dashboard."
|
|
40
|
+
"Dragons? Handled. Bugs? Working on it."
|
|
41
|
+
"A quest to automate the mundane."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
$Tagline = $Taglines | Get-Random
|
|
45
|
+
|
|
46
|
+
# ─── Banner ──────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function Print-Banner {
|
|
49
|
+
$banner = @(
|
|
50
|
+
' |>>>'
|
|
51
|
+
' |'
|
|
52
|
+
' |>>> _ _|_ _ |>>>'
|
|
53
|
+
' | |;| |;| |;| |'
|
|
54
|
+
' _ _|_ _ \. . / _ _|_ _'
|
|
55
|
+
' |;|_|;|_|;| \:. , / |;|_|;|_|;|'
|
|
56
|
+
' \.. / ||; . | \. . /'
|
|
57
|
+
' \. , / ||: . | \: . /'
|
|
58
|
+
' ||: |_ _ ||_ . _ | _ _||: |'
|
|
59
|
+
' ||: .|||_|;|_|;|_|;|_|;|_|;||:. |'
|
|
60
|
+
' ||: ||. . . . ||: .|'
|
|
61
|
+
' ||: . || . . . . , ||: | \,/'
|
|
62
|
+
' ||: ||: , _______ . ||: , | /`\\'
|
|
63
|
+
' ||: || . /+++++++\ . ||: |'
|
|
64
|
+
' ||: ||. |+++++++| . ||: . |'
|
|
65
|
+
' __ ||: . ||: , |+++++++|. . _||_ |'
|
|
66
|
+
" ____--``~ '--~~__|. |+++++__|----~ ~``---, ___"
|
|
67
|
+
"-~--~ ~---__|,--~' ~~----_____-~' ``~----~~"
|
|
68
|
+
)
|
|
69
|
+
# Blue-to-purple gradient using ANSI escape codes
|
|
70
|
+
$gradient = @(27, 27, 33, 33, 63, 63, 99, 99, 135, 135, 141, 141, 177, 177, 177, 176, 176, 176)
|
|
71
|
+
Write-Host ""
|
|
72
|
+
for ($i = 0; $i -lt $banner.Length; $i++) {
|
|
73
|
+
$color = $gradient[$i]
|
|
74
|
+
Write-Host "`e[38;5;${color}m$($banner[$i])`e[0m"
|
|
75
|
+
}
|
|
76
|
+
Write-Host ""
|
|
77
|
+
Write-Host " " -NoNewline
|
|
78
|
+
Write-Host "Castle" -ForegroundColor Blue -NoNewline
|
|
79
|
+
Write-Host " — The multi-agent workspace" -ForegroundColor DarkGray
|
|
80
|
+
Write-Host " $Tagline" -ForegroundColor DarkGray
|
|
81
|
+
Write-Host ""
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# ─── Help ────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
function Print-Usage {
|
|
87
|
+
Write-Host "Castle installer (Windows)"
|
|
88
|
+
Write-Host ""
|
|
89
|
+
Write-Host "Usage:"
|
|
90
|
+
Write-Host " iwr -useb https://castlekit.com/install.ps1 | iex"
|
|
91
|
+
Write-Host " & ([scriptblock]::Create((iwr -useb https://castlekit.com/install.ps1))) [options]"
|
|
92
|
+
Write-Host ""
|
|
93
|
+
Write-Host "Options:"
|
|
94
|
+
Write-Host " -Version <version> npm version to install (default: latest)"
|
|
95
|
+
Write-Host " -NoOnboard Skip setup wizard after install"
|
|
96
|
+
Write-Host " -DryRun Print what would happen (no changes)"
|
|
97
|
+
Write-Host " -Verbose Print debug output"
|
|
98
|
+
Write-Host " -Help Show this help"
|
|
99
|
+
Write-Host ""
|
|
100
|
+
Write-Host "Environment variables:"
|
|
101
|
+
Write-Host " CASTLE_VERSION=latest|<semver>"
|
|
102
|
+
Write-Host " CASTLE_NO_ONBOARD=1"
|
|
103
|
+
Write-Host " CASTLE_DRY_RUN=1"
|
|
104
|
+
Write-Host ""
|
|
105
|
+
Write-Host "Examples:"
|
|
106
|
+
Write-Host " iwr -useb https://castlekit.com/install.ps1 | iex"
|
|
107
|
+
Write-Host ' & ([scriptblock]::Create((iwr -useb https://castlekit.com/install.ps1))) -NoOnboard'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# ─── Environment variable overrides ─────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
if (-not $PSBoundParameters.ContainsKey("Version")) {
|
|
113
|
+
if (-not [string]::IsNullOrWhiteSpace($env:CASTLE_VERSION)) {
|
|
114
|
+
$Version = $env:CASTLE_VERSION
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (-not $PSBoundParameters.ContainsKey("NoOnboard")) {
|
|
118
|
+
if ($env:CASTLE_NO_ONBOARD -eq "1") {
|
|
119
|
+
$NoOnboard = $true
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (-not $PSBoundParameters.ContainsKey("DryRun")) {
|
|
123
|
+
if ($env:CASTLE_DRY_RUN -eq "1") {
|
|
124
|
+
$DryRun = $true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
function Refresh-Path {
|
|
131
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# ─── Node.js ─────────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
function Check-Node {
|
|
137
|
+
try {
|
|
138
|
+
$nodeVersion = (node -v 2>$null)
|
|
139
|
+
if ($nodeVersion) {
|
|
140
|
+
$major = [int]($nodeVersion -replace 'v(\d+)\..*', '$1')
|
|
141
|
+
if ($major -ge 22) {
|
|
142
|
+
Write-Host "[OK] Node.js $nodeVersion found" -ForegroundColor Green
|
|
143
|
+
return $true
|
|
144
|
+
} else {
|
|
145
|
+
Write-Host "[!] Node.js $nodeVersion found, but v22+ required" -ForegroundColor Yellow
|
|
146
|
+
return $false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
Write-Host "[!] Node.js not found" -ForegroundColor Yellow
|
|
151
|
+
return $false
|
|
152
|
+
}
|
|
153
|
+
return $false
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function Install-Node {
|
|
157
|
+
Write-Host "[*] Installing Node.js 22..." -ForegroundColor Yellow
|
|
158
|
+
|
|
159
|
+
# Try winget first (Windows 11 / Windows 10 with App Installer)
|
|
160
|
+
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
|
161
|
+
Write-Host " Using winget..." -ForegroundColor Gray
|
|
162
|
+
winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements
|
|
163
|
+
Refresh-Path
|
|
164
|
+
Write-Host "[OK] Node.js installed via winget" -ForegroundColor Green
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Try Chocolatey
|
|
169
|
+
if (Get-Command choco -ErrorAction SilentlyContinue) {
|
|
170
|
+
Write-Host " Using Chocolatey..." -ForegroundColor Gray
|
|
171
|
+
choco install nodejs-lts -y
|
|
172
|
+
Refresh-Path
|
|
173
|
+
Write-Host "[OK] Node.js installed via Chocolatey" -ForegroundColor Green
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# Try Scoop
|
|
178
|
+
if (Get-Command scoop -ErrorAction SilentlyContinue) {
|
|
179
|
+
Write-Host " Using Scoop..." -ForegroundColor Gray
|
|
180
|
+
scoop install nodejs-lts
|
|
181
|
+
Write-Host "[OK] Node.js installed via Scoop" -ForegroundColor Green
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# No package manager available
|
|
186
|
+
Write-Host ""
|
|
187
|
+
Write-Host "Error: Could not find a package manager (winget, choco, or scoop)" -ForegroundColor Red
|
|
188
|
+
Write-Host ""
|
|
189
|
+
Write-Host "Please install Node.js 22+ manually:" -ForegroundColor Yellow
|
|
190
|
+
Write-Host " https://nodejs.org/en/download/" -ForegroundColor Cyan
|
|
191
|
+
Write-Host ""
|
|
192
|
+
Write-Host "Or install winget (App Installer) from the Microsoft Store." -ForegroundColor Gray
|
|
193
|
+
exit 1
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# ─── Git ─────────────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
function Check-Git {
|
|
199
|
+
if (Get-Command git -ErrorAction SilentlyContinue) {
|
|
200
|
+
Write-Host "[OK] Git found" -ForegroundColor Green
|
|
201
|
+
return $true
|
|
202
|
+
}
|
|
203
|
+
Write-Host "[!] Git not found (optional — needed for some npm packages)" -ForegroundColor Yellow
|
|
204
|
+
return $false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# ─── npm PATH ────────────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
function Ensure-NpmGlobalOnPath {
|
|
210
|
+
$npmPrefix = $null
|
|
211
|
+
try {
|
|
212
|
+
$npmPrefix = (npm config get prefix 2>$null).Trim()
|
|
213
|
+
} catch {
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if ([string]::IsNullOrWhiteSpace($npmPrefix)) { return }
|
|
218
|
+
|
|
219
|
+
# On Windows, npm global bin is directly in the prefix (not prefix/bin)
|
|
220
|
+
$npmBin = $npmPrefix
|
|
221
|
+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
222
|
+
if (-not ($userPath -split ";" | Where-Object { $_ -ieq $npmBin })) {
|
|
223
|
+
[Environment]::SetEnvironmentVariable("Path", "$userPath;$npmBin", "User")
|
|
224
|
+
Refresh-Path
|
|
225
|
+
Write-Host "[!] Added $npmBin to user PATH" -ForegroundColor Yellow
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function Resolve-CastleBin {
|
|
230
|
+
# Check if castle is on PATH
|
|
231
|
+
if (Get-Command castle -ErrorAction SilentlyContinue) {
|
|
232
|
+
return (Get-Command castle).Source
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
Refresh-Path
|
|
236
|
+
|
|
237
|
+
if (Get-Command castle -ErrorAction SilentlyContinue) {
|
|
238
|
+
return (Get-Command castle).Source
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Check common npm global locations
|
|
242
|
+
$npmPrefix = $null
|
|
243
|
+
try { $npmPrefix = (npm config get prefix 2>$null).Trim() } catch {}
|
|
244
|
+
if ($npmPrefix -and (Test-Path (Join-Path $npmPrefix "castle.cmd"))) {
|
|
245
|
+
return (Join-Path $npmPrefix "castle.cmd")
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Check AppData roaming npm
|
|
249
|
+
$roamingNpm = Join-Path $env:APPDATA "npm"
|
|
250
|
+
if (Test-Path (Join-Path $roamingNpm "castle.cmd")) {
|
|
251
|
+
return (Join-Path $roamingNpm "castle.cmd")
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return $null
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# ─── Existing installation ───────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
function Check-ExistingCastle {
|
|
260
|
+
try {
|
|
261
|
+
$null = Get-Command castle -ErrorAction Stop
|
|
262
|
+
Write-Host "[*] Existing Castle installation detected" -ForegroundColor Yellow
|
|
263
|
+
return $true
|
|
264
|
+
} catch {
|
|
265
|
+
return $false
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# ─── Install Castle ──────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
function Install-Castle {
|
|
272
|
+
$installSpec = "@castlekit/castle@$Version"
|
|
273
|
+
|
|
274
|
+
# Check if already installed with matching version
|
|
275
|
+
$resolvedVersion = $null
|
|
276
|
+
try {
|
|
277
|
+
$resolvedVersion = (npm view $installSpec version 2>$null).Trim()
|
|
278
|
+
} catch {}
|
|
279
|
+
|
|
280
|
+
$installedVersion = $null
|
|
281
|
+
try {
|
|
282
|
+
$npmList = npm list -g @castlekit/castle --depth=0 2>$null
|
|
283
|
+
if ($npmList -match '@castlekit/castle@(\S+)') {
|
|
284
|
+
$installedVersion = $Matches[1]
|
|
285
|
+
}
|
|
286
|
+
} catch {}
|
|
287
|
+
|
|
288
|
+
if ($resolvedVersion -and ($installedVersion -eq $resolvedVersion)) {
|
|
289
|
+
Write-Host "[OK] Castle $resolvedVersion already installed" -ForegroundColor Green
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if ($resolvedVersion) {
|
|
294
|
+
Write-Host "[*] Installing Castle $resolvedVersion..." -ForegroundColor Yellow
|
|
295
|
+
} else {
|
|
296
|
+
Write-Host "[*] Installing Castle ($Version)..." -ForegroundColor Yellow
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
# Suppress npm noise
|
|
300
|
+
$prevLogLevel = $env:NPM_CONFIG_LOGLEVEL
|
|
301
|
+
$prevUpdateNotifier = $env:NPM_CONFIG_UPDATE_NOTIFIER
|
|
302
|
+
$prevFund = $env:NPM_CONFIG_FUND
|
|
303
|
+
$prevAudit = $env:NPM_CONFIG_AUDIT
|
|
304
|
+
$env:NPM_CONFIG_LOGLEVEL = "error"
|
|
305
|
+
$env:NPM_CONFIG_UPDATE_NOTIFIER = "false"
|
|
306
|
+
$env:NPM_CONFIG_FUND = "false"
|
|
307
|
+
$env:NPM_CONFIG_AUDIT = "false"
|
|
308
|
+
try {
|
|
309
|
+
$npmOutput = npm install -g $installSpec 2>&1
|
|
310
|
+
if ($LASTEXITCODE -ne 0) {
|
|
311
|
+
Write-Host "[!] npm install failed" -ForegroundColor Red
|
|
312
|
+
$npmOutput | ForEach-Object { Write-Host $_ }
|
|
313
|
+
Write-Host ""
|
|
314
|
+
Write-Host "Try: npm install -g --force $installSpec" -ForegroundColor Cyan
|
|
315
|
+
exit 1
|
|
316
|
+
}
|
|
317
|
+
} finally {
|
|
318
|
+
$env:NPM_CONFIG_LOGLEVEL = $prevLogLevel
|
|
319
|
+
$env:NPM_CONFIG_UPDATE_NOTIFIER = $prevUpdateNotifier
|
|
320
|
+
$env:NPM_CONFIG_FUND = $prevFund
|
|
321
|
+
$env:NPM_CONFIG_AUDIT = $prevAudit
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
Write-Host "[OK] Castle installed" -ForegroundColor Green
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
function Main {
|
|
330
|
+
if ($Help) {
|
|
331
|
+
Print-Usage
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if ($DryRun) {
|
|
336
|
+
Write-Host "[OK] Dry run" -ForegroundColor Green
|
|
337
|
+
Write-Host "[OK] Version: $Version" -ForegroundColor Green
|
|
338
|
+
if ($NoOnboard) {
|
|
339
|
+
Write-Host "[OK] Onboard: skipped" -ForegroundColor Green
|
|
340
|
+
}
|
|
341
|
+
Write-Host "Dry run complete (no changes made)." -ForegroundColor DarkGray
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# Check PowerShell version
|
|
346
|
+
if ($PSVersionTable.PSVersion.Major -lt 5) {
|
|
347
|
+
Write-Host "Error: PowerShell 5+ required" -ForegroundColor Red
|
|
348
|
+
exit 1
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
Write-Host "[OK] Windows detected" -ForegroundColor Green
|
|
352
|
+
|
|
353
|
+
# Check for existing installation
|
|
354
|
+
$isUpgrade = Check-ExistingCastle
|
|
355
|
+
|
|
356
|
+
# Step 1: Node.js
|
|
357
|
+
if (-not (Check-Node)) {
|
|
358
|
+
Install-Node
|
|
359
|
+
|
|
360
|
+
# Verify after install
|
|
361
|
+
if (-not (Check-Node)) {
|
|
362
|
+
Write-Host ""
|
|
363
|
+
Write-Host "Error: Node.js installation may require a terminal restart" -ForegroundColor Red
|
|
364
|
+
Write-Host "Please close this terminal, open a new one, and run this installer again." -ForegroundColor Yellow
|
|
365
|
+
exit 1
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
# Step 2: Git check (non-blocking)
|
|
370
|
+
Check-Git | Out-Null
|
|
371
|
+
|
|
372
|
+
# Step 3: Ensure npm global bin is on PATH
|
|
373
|
+
Ensure-NpmGlobalOnPath
|
|
374
|
+
|
|
375
|
+
# Step 4: Install Castle
|
|
376
|
+
Install-Castle
|
|
377
|
+
|
|
378
|
+
# Resolve castle binary
|
|
379
|
+
Refresh-Path
|
|
380
|
+
$castleBin = Resolve-CastleBin
|
|
381
|
+
|
|
382
|
+
Write-Host ""
|
|
383
|
+
if ($isUpgrade) {
|
|
384
|
+
$updateMessages = @(
|
|
385
|
+
"The castle walls have been reinforced, my liege."
|
|
386
|
+
"New fortifications in place. The kingdom grows stronger."
|
|
387
|
+
"The royal engineers have been busy. Upgrade complete."
|
|
388
|
+
"Fresh stonework, same castle. Miss me?"
|
|
389
|
+
"The drawbridge has been upgraded. Smoother entry guaranteed."
|
|
390
|
+
)
|
|
391
|
+
Write-Host "Castle upgraded successfully!" -ForegroundColor Green
|
|
392
|
+
Write-Host ($updateMessages | Get-Random) -ForegroundColor DarkGray
|
|
393
|
+
} else {
|
|
394
|
+
$completionMessages = @(
|
|
395
|
+
"The castle has been erected. Long may it stand!"
|
|
396
|
+
"Your fortress is ready, sire. What are your orders?"
|
|
397
|
+
"The court is assembled. Your agents await."
|
|
398
|
+
"A fine castle indeed. Time to rule."
|
|
399
|
+
"Stone by stone, the kingdom begins."
|
|
400
|
+
)
|
|
401
|
+
Write-Host "Castle installed successfully!" -ForegroundColor Green
|
|
402
|
+
Write-Host ($completionMessages | Get-Random) -ForegroundColor DarkGray
|
|
403
|
+
}
|
|
404
|
+
Write-Host ""
|
|
405
|
+
|
|
406
|
+
if (-not $castleBin) {
|
|
407
|
+
Write-Host "[!] Castle is not on PATH yet." -ForegroundColor Yellow
|
|
408
|
+
Write-Host "Restart PowerShell, then run: castle setup" -ForegroundColor Cyan
|
|
409
|
+
$npmPrefix = $null
|
|
410
|
+
try { $npmPrefix = (npm config get prefix 2>$null).Trim() } catch {}
|
|
411
|
+
if ($npmPrefix) {
|
|
412
|
+
Write-Host "Expected path: $npmPrefix" -ForegroundColor DarkGray
|
|
413
|
+
}
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# Step 5: Run setup
|
|
418
|
+
if ($NoOnboard) {
|
|
419
|
+
Write-Host "Skipping setup (requested). Run " -NoNewline
|
|
420
|
+
Write-Host "castle setup" -ForegroundColor Cyan -NoNewline
|
|
421
|
+
Write-Host " later."
|
|
422
|
+
} elseif (Test-Path (Join-Path $env:USERPROFILE ".castle\castle.json")) {
|
|
423
|
+
Write-Host "[OK] Castle is already configured" -ForegroundColor Green
|
|
424
|
+
Write-Host "Run " -NoNewline -ForegroundColor DarkGray
|
|
425
|
+
Write-Host "castle setup" -ForegroundColor Cyan -NoNewline
|
|
426
|
+
Write-Host " to reconfigure." -ForegroundColor DarkGray
|
|
427
|
+
} else {
|
|
428
|
+
Write-Host "Starting setup..." -ForegroundColor Cyan
|
|
429
|
+
Write-Host ""
|
|
430
|
+
& $castleBin setup
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# ─── Entry ───────────────────────────────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
Print-Banner
|
|
437
|
+
Main
|
package/install.sh
CHANGED
|
@@ -765,7 +765,11 @@ fi
|
|
|
765
765
|
|
|
766
766
|
if [[ "$OS" == "unknown" ]]; then
|
|
767
767
|
echo -e "${ERROR}Error: Unsupported operating system${NC}"
|
|
768
|
+
echo ""
|
|
768
769
|
echo "This installer supports macOS and Linux (including WSL)."
|
|
770
|
+
echo ""
|
|
771
|
+
echo -e "For ${INFO}Windows${NC} (PowerShell):"
|
|
772
|
+
echo -e " ${ACCENT}iwr -useb https://castlekit.com/install.ps1 | iex${NC}"
|
|
769
773
|
exit 1
|
|
770
774
|
fi
|
|
771
775
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@castlekit/castle",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "The multi-agent workspace",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"public/",
|
|
15
15
|
"index.js",
|
|
16
16
|
"install.sh",
|
|
17
|
+
"install.ps1",
|
|
17
18
|
"next.config.ts",
|
|
18
19
|
"postcss.config.mjs",
|
|
19
20
|
"tsconfig.json",
|
package/src/cli/onboarding.ts
CHANGED
|
@@ -355,28 +355,31 @@ export async function runOnboarding(): Promise<void> {
|
|
|
355
355
|
const installSpinner = p.spinner();
|
|
356
356
|
installSpinner.start("Installing OpenClaw...");
|
|
357
357
|
|
|
358
|
+
const installCmd = process.platform === "win32"
|
|
359
|
+
? 'powershell -NoProfile -ExecutionPolicy Bypass -Command "& { iwr -useb https://openclaw.ai/install.ps1 | iex } -NoOnboard"'
|
|
360
|
+
: 'curl -fsSL --proto "=https" --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard --no-prompt';
|
|
361
|
+
|
|
358
362
|
try {
|
|
359
|
-
execSync(
|
|
360
|
-
'curl -fsSL --proto "=https" --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard --no-prompt',
|
|
361
|
-
{ stdio: "pipe", timeout: 120000 }
|
|
362
|
-
);
|
|
363
|
+
execSync(installCmd, { stdio: "pipe", timeout: 120000 });
|
|
363
364
|
installSpinner.stop(BLUE("✔ OpenClaw installed"));
|
|
364
365
|
} catch (error) {
|
|
365
366
|
installSpinner.stop(pc.red("OpenClaw installation failed"));
|
|
367
|
+
const manualCmd = process.platform === "win32"
|
|
368
|
+
? "iwr -useb https://openclaw.ai/install.ps1 | iex"
|
|
369
|
+
: "curl -fsSL https://openclaw.ai/install.sh | bash";
|
|
366
370
|
p.note(
|
|
367
|
-
`Install OpenClaw manually:\n${BLUE_LIGHT(
|
|
368
|
-
"curl -fsSL https://openclaw.ai/install.sh | bash"
|
|
369
|
-
)}\n\nThen run: ${BLUE_LIGHT("castle setup")}`,
|
|
371
|
+
`Install OpenClaw manually:\n${BLUE_LIGHT(manualCmd)}\n\nThen run: ${BLUE_LIGHT("castle setup")}`,
|
|
370
372
|
BLUE_BOLD("Manual Install")
|
|
371
373
|
);
|
|
372
374
|
p.outro("Come back when OpenClaw is installed!");
|
|
373
375
|
process.exit(1);
|
|
374
376
|
}
|
|
375
377
|
} else {
|
|
378
|
+
const manualCmd = process.platform === "win32"
|
|
379
|
+
? "iwr -useb https://openclaw.ai/install.ps1 | iex"
|
|
380
|
+
: "curl -fsSL https://openclaw.ai/install.sh | bash";
|
|
376
381
|
p.note(
|
|
377
|
-
`Install OpenClaw:\n${BLUE_LIGHT(
|
|
378
|
-
"curl -fsSL https://openclaw.ai/install.sh | bash"
|
|
379
|
-
)}\n\nThen come back and run:\n${BLUE_LIGHT("castle setup")}`,
|
|
382
|
+
`Install OpenClaw:\n${BLUE_LIGHT(manualCmd)}\n\nThen come back and run:\n${BLUE_LIGHT("castle setup")}`,
|
|
380
383
|
BLUE_BOLD("Install OpenClaw First")
|
|
381
384
|
);
|
|
382
385
|
p.outro("See you soon!");
|
|
@@ -619,12 +622,37 @@ export async function runOnboarding(): Promise<void> {
|
|
|
619
622
|
|
|
620
623
|
// Write PID file helper
|
|
621
624
|
const pidFile = join(castleDir, "server.pid");
|
|
625
|
+
const isWin = process.platform === "win32";
|
|
626
|
+
|
|
627
|
+
// Stop existing service FIRST — otherwise the service manager respawns
|
|
628
|
+
// the old server immediately after we kill it, stealing the port.
|
|
629
|
+
if (process.platform === "darwin") {
|
|
630
|
+
const plistPath = join(home(), "Library", "LaunchAgents", "com.castlekit.castle.plist");
|
|
631
|
+
try {
|
|
632
|
+
execSyncChild(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore", timeout: 10000 });
|
|
633
|
+
} catch { /* no existing service */ }
|
|
634
|
+
} else if (process.platform === "linux") {
|
|
635
|
+
try {
|
|
636
|
+
execSyncChild("systemctl --user stop castle.service 2>/dev/null", { stdio: "ignore", timeout: 10000 });
|
|
637
|
+
} catch { /* no existing service */ }
|
|
638
|
+
} else if (isWin) {
|
|
639
|
+
try {
|
|
640
|
+
execSyncChild('schtasks /End /TN "CastleServer" 2>nul', { stdio: "ignore", timeout: 10000 });
|
|
641
|
+
} catch { /* no existing task */ }
|
|
642
|
+
try {
|
|
643
|
+
execSyncChild('schtasks /Delete /TN "CastleServer" /F 2>nul', { stdio: "ignore", timeout: 10000 });
|
|
644
|
+
} catch { /* no existing task */ }
|
|
645
|
+
}
|
|
622
646
|
|
|
623
647
|
// Kill any existing Castle server (by PID file)
|
|
624
648
|
try {
|
|
625
649
|
const existingPid = parseInt(readF(pidFile, "utf-8").trim(), 10);
|
|
626
650
|
if (Number.isInteger(existingPid) && existingPid > 0) {
|
|
627
|
-
|
|
651
|
+
if (isWin) {
|
|
652
|
+
try { execSyncChild(`taskkill /PID ${existingPid} /F 2>nul`, { stdio: "ignore", timeout: 5000 }); } catch { /* ignore */ }
|
|
653
|
+
} else {
|
|
654
|
+
process.kill(existingPid);
|
|
655
|
+
}
|
|
628
656
|
for (let i = 0; i < 30; i++) {
|
|
629
657
|
try {
|
|
630
658
|
process.kill(existingPid, 0);
|
|
@@ -639,14 +667,35 @@ export async function runOnboarding(): Promise<void> {
|
|
|
639
667
|
}
|
|
640
668
|
|
|
641
669
|
// Kill anything else on the target port
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
670
|
+
if (isWin) {
|
|
671
|
+
try {
|
|
672
|
+
const netstatOut = execSyncChild(
|
|
673
|
+
`netstat -ano | findstr ":${castlePort} " | findstr "LISTENING"`,
|
|
674
|
+
{ encoding: "utf-8", timeout: 5000 }
|
|
675
|
+
).toString();
|
|
676
|
+
const pids = new Set<string>();
|
|
677
|
+
for (const line of netstatOut.split("\n")) {
|
|
678
|
+
const parts = line.trim().split(/\s+/);
|
|
679
|
+
const pid = parts[parts.length - 1];
|
|
680
|
+
if (pid && /^\d+$/.test(pid) && pid !== "0") pids.add(pid);
|
|
681
|
+
}
|
|
682
|
+
for (const pid of pids) {
|
|
683
|
+
try { execSyncChild(`taskkill /PID ${pid} /F 2>nul`, { stdio: "ignore", timeout: 5000 }); } catch { /* ignore */ }
|
|
684
|
+
}
|
|
685
|
+
if (pids.size > 0) await new Promise((r) => setTimeout(r, 500));
|
|
686
|
+
} catch {
|
|
687
|
+
// Nothing on port or netstat not available
|
|
688
|
+
}
|
|
689
|
+
} else {
|
|
690
|
+
try {
|
|
691
|
+
execSyncChild(`lsof -ti:${castlePort} | xargs kill -9 2>/dev/null`, {
|
|
692
|
+
stdio: "ignore",
|
|
693
|
+
timeout: 5000,
|
|
694
|
+
});
|
|
695
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
696
|
+
} catch {
|
|
697
|
+
// Nothing on port or lsof not available
|
|
698
|
+
}
|
|
650
699
|
}
|
|
651
700
|
|
|
652
701
|
// Escape XML special characters for plist values
|
|
@@ -693,9 +742,7 @@ ${plistEnvEntries}
|
|
|
693
742
|
</dict>
|
|
694
743
|
</dict>
|
|
695
744
|
</plist>`;
|
|
696
|
-
|
|
697
|
-
execSyncChild(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore", timeout: 10000 });
|
|
698
|
-
} catch { /* ignore */ }
|
|
745
|
+
// Service was already unloaded above — just write new plist and load
|
|
699
746
|
writeFile(plistPath, plist);
|
|
700
747
|
try {
|
|
701
748
|
execSyncChild(`launchctl load "${plistPath}"`, { stdio: "ignore", timeout: 10000 });
|
|
@@ -730,6 +777,32 @@ WantedBy=default.target
|
|
|
730
777
|
} catch {
|
|
731
778
|
// Non-fatal
|
|
732
779
|
}
|
|
780
|
+
} else if (isWin) {
|
|
781
|
+
// Windows: use Task Scheduler to run Castle at logon
|
|
782
|
+
// Build a batch wrapper that sets environment variables and starts the server
|
|
783
|
+
const batPath = join(castleDir, "start-server.bat");
|
|
784
|
+
const envLines = Object.entries(serverEnv)
|
|
785
|
+
.map(([k, v]) => `set "${k}=${v}"`)
|
|
786
|
+
.join("\r\n");
|
|
787
|
+
const batContent = `@echo off\r\n${envLines}\r\ncd /d "${PROJECT_ROOT}"\r\n"${nodePath}" ${serverArgs.map((a) => `"${a}"`).join(" ")}\r\n`;
|
|
788
|
+
writeFile(batPath, batContent);
|
|
789
|
+
|
|
790
|
+
// Task was already deleted above — create fresh
|
|
791
|
+
try {
|
|
792
|
+
execSyncChild(
|
|
793
|
+
`schtasks /Create /TN "CastleServer" /TR "\\"${batPath}\\"" /SC ONLOGON /RL HIGHEST /F 2>nul`,
|
|
794
|
+
{ stdio: "ignore", timeout: 10000 }
|
|
795
|
+
);
|
|
796
|
+
} catch {
|
|
797
|
+
// Non-fatal — fall back to spawning directly
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Also start the task now
|
|
801
|
+
try {
|
|
802
|
+
execSyncChild('schtasks /Run /TN "CastleServer" 2>nul', { stdio: "ignore", timeout: 10000 });
|
|
803
|
+
} catch {
|
|
804
|
+
// Non-fatal
|
|
805
|
+
}
|
|
733
806
|
}
|
|
734
807
|
|
|
735
808
|
// If no service manager started it, spawn directly
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/.next/static/{R01Vc6rRTS_XwAg0Y8ERm → IDGX3MGCjqUE6NQVlug5A}/_clientMiddlewareManifest.json
RENAMED
|
File without changes
|
|
File without changes
|