@swarmclawai/swarmclaw 0.9.9 → 1.0.2
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/bin/doctor-cmd.js +149 -0
- package/bin/doctor-cmd.test.js +50 -0
- package/bin/install-root.js +194 -0
- package/bin/install-root.test.js +121 -0
- package/bin/server-cmd.js +90 -111
- package/bin/swarmclaw.js +83 -3
- package/bin/update-cmd.js +33 -20
- package/bin/update-cmd.test.js +1 -36
- package/bin/worker-cmd.js +23 -17
- package/next.config.ts +2 -0
- package/package.json +11 -10
- package/src/app/api/gateways/[id]/health/route.ts +2 -32
- package/src/app/api/gateways/health-route.test.ts +1 -1
- package/src/app/api/openclaw/dashboard-url/route.test.ts +166 -0
- package/src/app/api/openclaw/dashboard-url/route.ts +68 -0
- package/src/app/api/setup/check-provider/helpers.ts +28 -0
- package/src/app/api/setup/check-provider/route.test.ts +17 -1
- package/src/app/api/setup/check-provider/route.ts +29 -36
- package/src/app/api/tasks/import/github/helpers.ts +100 -0
- package/src/app/api/tasks/import/github/route.test.ts +1 -1
- package/src/app/api/tasks/import/github/route.ts +2 -92
- package/src/app/api/webhooks/[id]/helpers.ts +253 -0
- package/src/app/api/webhooks/[id]/route.ts +2 -243
- package/src/app/api/webhooks/route.test.ts +4 -2
- package/src/cli/binary.test.js +57 -0
- package/src/cli/index.js +14 -1
- package/src/cli/server-cmd.test.js +21 -20
- package/src/components/auth/setup-wizard/index.tsx +16 -0
- package/src/components/auth/setup-wizard/step-agents.tsx +34 -23
- package/src/components/auth/setup-wizard/step-connect.tsx +8 -0
- package/src/components/auth/setup-wizard/types.ts +2 -0
- package/src/components/auth/setup-wizard/utils.test.ts +79 -0
- package/src/components/chat/chat-header.tsx +45 -2
- package/src/lib/providers/openclaw-exports.test.ts +23 -0
- package/src/lib/providers/openclaw.ts +1 -1
- package/src/lib/server/data-dir.test.ts +35 -0
- package/src/lib/server/data-dir.ts +11 -0
- package/src/lib/server/openclaw/health.ts +30 -1
- package/src/lib/server/session-tools/file-send.test.ts +18 -2
- package/src/lib/server/session-tools/file.ts +11 -7
- package/src/lib/server/skills/skill-discovery.test.ts +34 -1
- package/src/lib/server/skills/skill-discovery.ts +9 -4
- package/src/lib/setup-defaults.test.ts +42 -0
- package/src/lib/setup-defaults.ts +1 -1
package/bin/server-cmd.js
CHANGED
|
@@ -5,35 +5,37 @@
|
|
|
5
5
|
const fs = require('node:fs')
|
|
6
6
|
const path = require('node:path')
|
|
7
7
|
const { spawn, execFileSync } = require('node:child_process')
|
|
8
|
-
const os = require('node:os')
|
|
9
8
|
const {
|
|
10
|
-
LOCKFILE_NAMES,
|
|
11
9
|
detectPackageManager,
|
|
12
10
|
getInstallCommand,
|
|
13
11
|
} = require('./package-manager.js')
|
|
12
|
+
const {
|
|
13
|
+
readPackageVersion,
|
|
14
|
+
resolvePackageRoot,
|
|
15
|
+
resolveStateHome,
|
|
16
|
+
} = require('./install-root.js')
|
|
14
17
|
|
|
15
18
|
// ---------------------------------------------------------------------------
|
|
16
19
|
// Paths
|
|
17
20
|
// ---------------------------------------------------------------------------
|
|
18
21
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
const PKG_ROOT = resolvePackageRoot({
|
|
23
|
+
moduleDir: __dirname,
|
|
24
|
+
argv1: process.argv[1],
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
})
|
|
27
|
+
const SWARMCLAW_HOME = resolveStateHome({
|
|
28
|
+
pkgRoot: PKG_ROOT,
|
|
29
|
+
moduleDir: __dirname,
|
|
30
|
+
argv1: process.argv[1],
|
|
31
|
+
cwd: process.cwd(),
|
|
32
|
+
env: process.env,
|
|
33
|
+
})
|
|
22
34
|
const PID_FILE = path.join(SWARMCLAW_HOME, 'server.pid')
|
|
23
35
|
const LOG_FILE = path.join(SWARMCLAW_HOME, 'server.log')
|
|
24
36
|
const DATA_DIR = path.join(SWARMCLAW_HOME, 'data')
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const BUILD_COPY_ENTRIES = [
|
|
28
|
-
'src',
|
|
29
|
-
'public',
|
|
30
|
-
'scripts',
|
|
31
|
-
'next.config.ts',
|
|
32
|
-
'tsconfig.json',
|
|
33
|
-
'postcss.config.mjs',
|
|
34
|
-
'package.json',
|
|
35
|
-
...LOCKFILE_NAMES,
|
|
36
|
-
]
|
|
37
|
+
const WORKSPACE_DIR = path.join(SWARMCLAW_HOME, 'workspace')
|
|
38
|
+
const BROWSER_PROFILES_DIR = path.join(SWARMCLAW_HOME, 'browser-profiles')
|
|
37
39
|
|
|
38
40
|
// ---------------------------------------------------------------------------
|
|
39
41
|
// Helpers
|
|
@@ -69,116 +71,74 @@ function isProcessRunning(pid) {
|
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
fs.cpSync(src, dest, { recursive: true, dereference })
|
|
74
|
+
function resolveStandaloneBase(pkgRoot = PKG_ROOT) {
|
|
75
|
+
return path.join(pkgRoot, '.next', 'standalone')
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
function
|
|
78
|
-
fs.
|
|
79
|
-
fs.symlinkSync(src, dest)
|
|
78
|
+
function isGitCheckout(pkgRoot = PKG_ROOT) {
|
|
79
|
+
return fs.existsSync(path.join(pkgRoot, '.git'))
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
function getVersion() {
|
|
83
|
+
return readPackageVersion(PKG_ROOT) || 'unknown'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ensurePackageDependencies(pkgRoot = PKG_ROOT) {
|
|
87
|
+
const nextCli = path.join(pkgRoot, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
88
|
+
if (fs.existsSync(nextCli)) return nextCli
|
|
89
|
+
|
|
90
|
+
const packageManager = detectPackageManager(pkgRoot, process.env)
|
|
91
|
+
const install = getInstallCommand(packageManager)
|
|
92
|
+
log(`Installing dependencies with ${packageManager}...`)
|
|
93
|
+
execFileSync(install.command, install.args, { cwd: pkgRoot, stdio: 'inherit' })
|
|
94
|
+
return nextCli
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
// ---------------------------------------------------------------------------
|
|
93
98
|
// Build
|
|
94
99
|
// ---------------------------------------------------------------------------
|
|
95
100
|
|
|
96
|
-
function needsBuild(forceBuild) {
|
|
101
|
+
function needsBuild(forceBuild, { pkgRoot = PKG_ROOT } = {}) {
|
|
97
102
|
if (forceBuild) return true
|
|
98
|
-
|
|
99
|
-
if (!info) return true
|
|
100
|
-
if (info.version !== getVersion()) return true
|
|
101
|
-
if (!findStandaloneServer()) return true
|
|
102
|
-
return false
|
|
103
|
+
return !findStandaloneServer({ pkgRoot })
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
function runBuild() {
|
|
106
|
+
function runBuild({ pkgRoot = PKG_ROOT } = {}) {
|
|
106
107
|
log('Preparing build environment...')
|
|
107
108
|
ensureDir(SWARMCLAW_HOME)
|
|
108
109
|
ensureDir(DATA_DIR)
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
// app source symlinks that point outside the workspace root.
|
|
112
|
-
for (const entry of BUILD_COPY_ENTRIES) {
|
|
113
|
-
const src = path.join(PKG_ROOT, entry)
|
|
114
|
-
const dest = path.join(SWARMCLAW_HOME, entry)
|
|
115
|
-
|
|
116
|
-
if (!fs.existsSync(src)) {
|
|
117
|
-
log(`Warning: ${entry} not found in package, skipping`)
|
|
118
|
-
continue
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
copyPath(src, dest)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Reuse package dependencies via symlink to avoid multi-GB duplication in
|
|
125
|
-
// SWARMCLAW_HOME. Build runs with webpack mode for symlink compatibility.
|
|
126
|
-
const nmSrc = path.join(PKG_ROOT, 'node_modules')
|
|
127
|
-
const nmDest = path.join(SWARMCLAW_HOME, 'node_modules')
|
|
128
|
-
if (fs.existsSync(nmSrc)) {
|
|
129
|
-
symlinkPath(nmSrc, nmDest)
|
|
130
|
-
} else {
|
|
131
|
-
// If node_modules doesn't exist at PKG_ROOT, install
|
|
132
|
-
const packageManager = detectPackageManager(SWARMCLAW_HOME, process.env)
|
|
133
|
-
const install = getInstallCommand(packageManager)
|
|
134
|
-
log(`Installing dependencies with ${packageManager}...`)
|
|
135
|
-
execFileSync(install.command, install.args, { cwd: SWARMCLAW_HOME, stdio: 'inherit' })
|
|
136
|
-
}
|
|
111
|
+
const nextCli = ensurePackageDependencies(pkgRoot)
|
|
137
112
|
|
|
138
|
-
// Run Next.js build
|
|
139
113
|
log('Building Next.js application (this may take a minute)...')
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
cwd: SWARMCLAW_HOME,
|
|
114
|
+
execFileSync(process.execPath, [nextCli, 'build', '--webpack'], {
|
|
115
|
+
cwd: pkgRoot,
|
|
143
116
|
stdio: 'inherit',
|
|
144
117
|
env: {
|
|
145
118
|
...process.env,
|
|
119
|
+
SWARMCLAW_HOME,
|
|
120
|
+
DATA_DIR,
|
|
146
121
|
SWARMCLAW_BUILD_MODE: '1',
|
|
147
122
|
},
|
|
148
123
|
})
|
|
149
124
|
|
|
150
|
-
// Write built marker
|
|
151
|
-
fs.writeFileSync(BUILT_MARKER, JSON.stringify({ builtAt: new Date().toISOString(), version: getVersion() }))
|
|
152
125
|
log('Build complete.')
|
|
153
126
|
}
|
|
154
127
|
|
|
155
|
-
function getVersion() {
|
|
156
|
-
try {
|
|
157
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'))
|
|
158
|
-
return pkg.version || 'unknown'
|
|
159
|
-
} catch {
|
|
160
|
-
return 'unknown'
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
128
|
// ---------------------------------------------------------------------------
|
|
165
129
|
// Find standalone server.js
|
|
166
130
|
// ---------------------------------------------------------------------------
|
|
167
131
|
|
|
168
|
-
function findStandaloneServer() {
|
|
169
|
-
|
|
170
|
-
// The path mirrors the build machine's directory structure
|
|
171
|
-
const standaloneBase = path.join(SWARMCLAW_HOME, '.next', 'standalone')
|
|
132
|
+
function findStandaloneServer({ pkgRoot = PKG_ROOT } = {}) {
|
|
133
|
+
const standaloneBase = resolveStandaloneBase(pkgRoot)
|
|
172
134
|
|
|
173
135
|
if (!fs.existsSync(standaloneBase)) {
|
|
174
136
|
return null
|
|
175
137
|
}
|
|
176
138
|
|
|
177
|
-
// Try direct server.js first
|
|
178
139
|
const direct = path.join(standaloneBase, 'server.js')
|
|
179
140
|
if (fs.existsSync(direct)) return direct
|
|
180
141
|
|
|
181
|
-
// Recursively search for server.js (handles nested paths from build machine)
|
|
182
142
|
function search(dir) {
|
|
183
143
|
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
184
144
|
for (const entry of entries) {
|
|
@@ -199,34 +159,42 @@ function findStandaloneServer() {
|
|
|
199
159
|
// Start server
|
|
200
160
|
// ---------------------------------------------------------------------------
|
|
201
161
|
|
|
202
|
-
function startServer(opts) {
|
|
203
|
-
const serverJs = findStandaloneServer()
|
|
162
|
+
function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
|
|
163
|
+
const serverJs = findStandaloneServer({ pkgRoot })
|
|
204
164
|
if (!serverJs) {
|
|
205
|
-
logError('Standalone server.js not found. Try running: swarmclaw server --build')
|
|
165
|
+
logError('Standalone server.js not found in the installed package. Try running: swarmclaw server --build')
|
|
206
166
|
process.exit(1)
|
|
207
167
|
}
|
|
208
168
|
|
|
169
|
+
ensureDir(SWARMCLAW_HOME)
|
|
170
|
+
ensureDir(DATA_DIR)
|
|
171
|
+
|
|
209
172
|
const port = opts.port || '3456'
|
|
210
173
|
const wsPort = opts.wsPort || String(Number(port) + 1)
|
|
211
174
|
const host = opts.host || '0.0.0.0'
|
|
212
175
|
|
|
213
176
|
const env = {
|
|
214
177
|
...process.env,
|
|
178
|
+
SWARMCLAW_HOME,
|
|
179
|
+
DATA_DIR,
|
|
180
|
+
WORKSPACE_DIR,
|
|
181
|
+
BROWSER_PROFILES_DIR,
|
|
182
|
+
HOSTNAME: host,
|
|
215
183
|
PORT: port,
|
|
216
184
|
WS_PORT: wsPort,
|
|
217
|
-
HOSTNAME: host,
|
|
218
|
-
DATA_DIR,
|
|
219
185
|
}
|
|
220
186
|
|
|
221
187
|
log(`Starting server on ${host}:${port} (WebSocket: ${wsPort})...`)
|
|
188
|
+
log(`Package root: ${pkgRoot}`)
|
|
189
|
+
log(`Home: ${SWARMCLAW_HOME}`)
|
|
222
190
|
log(`Data directory: ${DATA_DIR}`)
|
|
223
191
|
|
|
224
192
|
if (opts.detach) {
|
|
225
|
-
// Detached mode — run in background
|
|
226
193
|
const logStream = fs.openSync(LOG_FILE, 'a')
|
|
227
194
|
const child = spawn(process.execPath, [serverJs], {
|
|
228
|
-
|
|
195
|
+
cwd: pkgRoot,
|
|
229
196
|
detached: true,
|
|
197
|
+
env,
|
|
230
198
|
stdio: ['ignore', logStream, logStream],
|
|
231
199
|
})
|
|
232
200
|
|
|
@@ -236,8 +204,8 @@ function startServer(opts) {
|
|
|
236
204
|
log(`Logs: ${LOG_FILE}`)
|
|
237
205
|
process.exit(0)
|
|
238
206
|
} else {
|
|
239
|
-
// Foreground mode
|
|
240
207
|
const child = spawn(process.execPath, [serverJs], {
|
|
208
|
+
cwd: pkgRoot,
|
|
241
209
|
env,
|
|
242
210
|
stdio: 'inherit',
|
|
243
211
|
})
|
|
@@ -246,7 +214,6 @@ function startServer(opts) {
|
|
|
246
214
|
process.exit(code || 0)
|
|
247
215
|
})
|
|
248
216
|
|
|
249
|
-
// Forward signals
|
|
250
217
|
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
251
218
|
process.on(sig, () => child.kill(sig))
|
|
252
219
|
}
|
|
@@ -288,27 +255,23 @@ function showStatus() {
|
|
|
288
255
|
const pid = readPid()
|
|
289
256
|
if (!pid) {
|
|
290
257
|
log('Server: not running (no PID file)')
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (isProcessRunning(pid)) {
|
|
258
|
+
} else if (isProcessRunning(pid)) {
|
|
295
259
|
log(`Server: running (PID: ${pid})`)
|
|
296
260
|
} else {
|
|
297
261
|
log(`Server: not running (stale PID: ${pid})`)
|
|
298
262
|
try { fs.unlinkSync(PID_FILE) } catch {}
|
|
299
263
|
}
|
|
300
264
|
|
|
265
|
+
log(`Package: ${PKG_ROOT}`)
|
|
301
266
|
log(`Home: ${SWARMCLAW_HOME}`)
|
|
302
267
|
log(`Data: ${DATA_DIR}`)
|
|
268
|
+
log(`Workspace: ${WORKSPACE_DIR}`)
|
|
269
|
+
log(`Browser profiles: ${BROWSER_PROFILES_DIR}`)
|
|
303
270
|
log(`WebSocket port: ${process.env.WS_PORT || '(PORT + 1)'}`)
|
|
304
271
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
log(`Built: ${info.builtAt || 'unknown'} (v${info.version || '?'})`)
|
|
309
|
-
} catch {
|
|
310
|
-
log('Built: yes')
|
|
311
|
-
}
|
|
272
|
+
const serverJs = findStandaloneServer()
|
|
273
|
+
if (serverJs) {
|
|
274
|
+
log(`Built: yes (${serverJs})`)
|
|
312
275
|
} else {
|
|
313
276
|
log('Built: no')
|
|
314
277
|
}
|
|
@@ -338,8 +301,7 @@ Options:
|
|
|
338
301
|
console.log(help)
|
|
339
302
|
}
|
|
340
303
|
|
|
341
|
-
function main() {
|
|
342
|
-
const args = process.argv.slice(3) // skip node, bin, 'server'
|
|
304
|
+
function main(args = process.argv.slice(3)) {
|
|
343
305
|
let command = 'start'
|
|
344
306
|
let forceBuild = false
|
|
345
307
|
let detach = false
|
|
@@ -385,9 +347,18 @@ function main() {
|
|
|
385
347
|
return
|
|
386
348
|
}
|
|
387
349
|
|
|
388
|
-
// command === 'start'
|
|
389
350
|
if (needsBuild(forceBuild)) {
|
|
390
|
-
|
|
351
|
+
if (!forceBuild) {
|
|
352
|
+
const installKind = isGitCheckout() ? 'checkout' : 'installed package'
|
|
353
|
+
log(`Standalone server bundle not found in this ${installKind}. Building locally...`)
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
runBuild()
|
|
357
|
+
} catch (err) {
|
|
358
|
+
logError(`Build failed: ${err.message}`)
|
|
359
|
+
logError('Retry manually with: swarmclaw server --build')
|
|
360
|
+
process.exit(1)
|
|
361
|
+
}
|
|
391
362
|
}
|
|
392
363
|
|
|
393
364
|
startServer({ port, wsPort, host, detach })
|
|
@@ -398,8 +369,16 @@ if (require.main === module) {
|
|
|
398
369
|
}
|
|
399
370
|
|
|
400
371
|
module.exports = {
|
|
372
|
+
DATA_DIR,
|
|
373
|
+
BROWSER_PROFILES_DIR,
|
|
374
|
+
PKG_ROOT,
|
|
375
|
+
SWARMCLAW_HOME,
|
|
376
|
+
WORKSPACE_DIR,
|
|
377
|
+
findStandaloneServer,
|
|
401
378
|
getVersion,
|
|
379
|
+
isGitCheckout,
|
|
402
380
|
main,
|
|
403
381
|
needsBuild,
|
|
404
|
-
|
|
382
|
+
resolveStandaloneBase,
|
|
383
|
+
runBuild,
|
|
405
384
|
}
|
package/bin/swarmclaw.js
CHANGED
|
@@ -96,6 +96,19 @@ function normalizeLegacyCliEnv(env) {
|
|
|
96
96
|
return nextEnv
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function printPackageVersion() {
|
|
100
|
+
const pkg = require('../package.json')
|
|
101
|
+
process.stdout.write(`${pkg.name || 'swarmclaw'} ${pkg.version || '0.0.0'}\n`)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function printVersionHelp() {
|
|
105
|
+
process.stdout.write(`
|
|
106
|
+
Usage: swarmclaw version
|
|
107
|
+
|
|
108
|
+
Show the installed SwarmClaw package version.
|
|
109
|
+
`.trim() + '\n')
|
|
110
|
+
}
|
|
111
|
+
|
|
99
112
|
async function runMappedCli(argv) {
|
|
100
113
|
const cliPath = path.join(__dirname, '..', 'src', 'cli', 'index.js')
|
|
101
114
|
const cliModule = await import(cliPath)
|
|
@@ -106,25 +119,91 @@ async function runMappedCli(argv) {
|
|
|
106
119
|
return runCli(argv)
|
|
107
120
|
}
|
|
108
121
|
|
|
122
|
+
async function runHelp(argv) {
|
|
123
|
+
const [target, ...rest] = argv
|
|
124
|
+
if (!target) {
|
|
125
|
+
const code = await runMappedCli(['--help'])
|
|
126
|
+
process.exitCode = typeof code === 'number' ? code : 1
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (target === 'run' || target === 'start' || target === 'stop' || target === 'status' || target === 'server') {
|
|
131
|
+
require('./server-cmd.js').main(['--help'])
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
if (target === 'worker') {
|
|
135
|
+
require('./worker-cmd.js').main(['--help'])
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
if (target === 'doctor') {
|
|
139
|
+
require('./doctor-cmd.js').main(['--help'])
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
if (target === 'update') {
|
|
143
|
+
require('./update-cmd.js').main(['--help'])
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
if (target === 'version') {
|
|
147
|
+
printVersionHelp()
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const forwarded = rest.includes('--help') || rest.includes('-h')
|
|
152
|
+
? [target, ...rest]
|
|
153
|
+
: [target, ...rest, '--help']
|
|
154
|
+
const code = shouldUseLegacyTsCli(forwarded)
|
|
155
|
+
? runLegacyTsCli(forwarded)
|
|
156
|
+
: await runMappedCli(forwarded)
|
|
157
|
+
|
|
158
|
+
process.exitCode = typeof code === 'number' ? code : 1
|
|
159
|
+
}
|
|
160
|
+
|
|
109
161
|
async function main() {
|
|
110
162
|
const argv = process.argv.slice(2)
|
|
111
163
|
const top = argv[0]
|
|
112
164
|
|
|
113
165
|
// Default to 'server' when invoked with no arguments.
|
|
114
166
|
if (!top) {
|
|
115
|
-
require('./server-cmd.js').main()
|
|
167
|
+
require('./server-cmd.js').main([])
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (top === '-v') {
|
|
172
|
+
printPackageVersion()
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (top === 'version' && argv.length === 1) {
|
|
177
|
+
printPackageVersion()
|
|
116
178
|
return
|
|
117
179
|
}
|
|
118
180
|
|
|
119
|
-
|
|
181
|
+
if (top === 'help') {
|
|
182
|
+
await runHelp(argv.slice(1))
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Route local lifecycle/maintenance commands to CJS scripts (no TS dependency).
|
|
120
187
|
if (top === 'server') {
|
|
121
|
-
require('./server-cmd.js').main()
|
|
188
|
+
require('./server-cmd.js').main(argv.slice(1))
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
if (top === 'run' || top === 'start') {
|
|
192
|
+
require('./server-cmd.js').main(argv.slice(1))
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
if (top === 'status' || top === 'stop') {
|
|
196
|
+
require('./server-cmd.js').main([top, ...argv.slice(1)])
|
|
122
197
|
return
|
|
123
198
|
}
|
|
124
199
|
if (top === 'worker') {
|
|
125
200
|
require('./worker-cmd.js').main()
|
|
126
201
|
return
|
|
127
202
|
}
|
|
203
|
+
if (top === 'doctor') {
|
|
204
|
+
require('./doctor-cmd.js').main(argv.slice(1))
|
|
205
|
+
return
|
|
206
|
+
}
|
|
128
207
|
if (top === 'update') {
|
|
129
208
|
require('./update-cmd.js').main()
|
|
130
209
|
return
|
|
@@ -146,6 +225,7 @@ module.exports = {
|
|
|
146
225
|
hasTsxRuntime,
|
|
147
226
|
TS_CLI_ACTIONS,
|
|
148
227
|
normalizeLegacyCliEnv,
|
|
228
|
+
printPackageVersion,
|
|
149
229
|
supportsStripTypes,
|
|
150
230
|
shouldUseLegacyTsCli,
|
|
151
231
|
}
|
package/bin/update-cmd.js
CHANGED
|
@@ -10,11 +10,19 @@ const {
|
|
|
10
10
|
getGlobalUpdateSpec,
|
|
11
11
|
getInstallCommand,
|
|
12
12
|
} = require('./package-manager.js')
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const {
|
|
14
|
+
PACKAGE_NAME,
|
|
15
|
+
detectGlobalInstallManagerForRoot,
|
|
16
|
+
resolvePackageRoot,
|
|
17
|
+
} = require('./install-root.js')
|
|
18
|
+
|
|
19
|
+
const PKG_ROOT = resolvePackageRoot({
|
|
20
|
+
moduleDir: __dirname,
|
|
21
|
+
argv1: process.argv[1],
|
|
22
|
+
cwd: process.cwd(),
|
|
23
|
+
})
|
|
16
24
|
const RELEASE_TAG_RE = /^v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/
|
|
17
|
-
const
|
|
25
|
+
const FALLBACK_PACKAGE_MANAGER = detectPackageManager(PKG_ROOT)
|
|
18
26
|
|
|
19
27
|
function run(cmd) {
|
|
20
28
|
return execSync(cmd, { encoding: 'utf-8', cwd: PKG_ROOT, timeout: 60_000 }).trim()
|
|
@@ -36,6 +44,12 @@ function getLatestStableTag() {
|
|
|
36
44
|
return tags.find((t) => RELEASE_TAG_RE.test(t)) || null
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
function resolveRegistryPackageManager(execImpl = execFileSync) {
|
|
48
|
+
return detectGlobalInstallManagerForRoot(PKG_ROOT, execImpl, process.env)
|
|
49
|
+
|| detectPackageManager(PKG_ROOT, process.env)
|
|
50
|
+
|| FALLBACK_PACKAGE_MANAGER
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
function rebuildStandaloneServer(
|
|
40
54
|
execImpl = execFileSync,
|
|
41
55
|
logger = { log, logError },
|
|
@@ -58,10 +72,9 @@ function rebuildStandaloneServer(
|
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
function runRegistrySelfUpdate(
|
|
61
|
-
packageManager =
|
|
75
|
+
packageManager = resolveRegistryPackageManager(),
|
|
62
76
|
execImpl = execFileSync,
|
|
63
77
|
logger = { log, logError },
|
|
64
|
-
rebuildImpl = execFileSync,
|
|
65
78
|
) {
|
|
66
79
|
const update = getGlobalUpdateSpec(packageManager, PACKAGE_NAME)
|
|
67
80
|
logger.log(`No git checkout detected. Updating the global ${PACKAGE_NAME} install via ${packageManager}...`)
|
|
@@ -78,30 +91,25 @@ function runRegistrySelfUpdate(
|
|
|
78
91
|
return 1
|
|
79
92
|
}
|
|
80
93
|
|
|
81
|
-
const rebuildExitCode = rebuildStandaloneServer(rebuildImpl, logger)
|
|
82
|
-
if (rebuildExitCode !== 0) return rebuildExitCode
|
|
83
|
-
|
|
84
94
|
logger.log('Restart the server to apply changes: swarmclaw server stop && swarmclaw server start')
|
|
85
95
|
return 0
|
|
86
96
|
}
|
|
87
97
|
|
|
88
|
-
function main() {
|
|
89
|
-
const args = process.argv.slice(3)
|
|
98
|
+
function main(args = process.argv.slice(3)) {
|
|
90
99
|
if (args.includes('-h') || args.includes('--help')) {
|
|
91
100
|
console.log(`
|
|
92
101
|
Usage: swarmclaw update
|
|
93
102
|
|
|
94
103
|
If running from a git checkout, pull the latest SwarmClaw release tag.
|
|
95
|
-
If running from a registry install, update the global package with
|
|
104
|
+
If running from a registry install, update the global package with its owning package manager.
|
|
96
105
|
`.trim())
|
|
97
106
|
process.exit(0)
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
// Verify we're in a git repo
|
|
101
109
|
try {
|
|
102
110
|
run('git rev-parse --git-dir')
|
|
103
111
|
} catch {
|
|
104
|
-
process.exit(runRegistrySelfUpdate(
|
|
112
|
+
process.exit(runRegistrySelfUpdate())
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
const beforeRef = run('git rev-parse HEAD')
|
|
@@ -127,7 +135,6 @@ If running from a registry install, update the global package with ${PACKAGE_MAN
|
|
|
127
135
|
process.exit(0)
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
// Check for uncommitted changes
|
|
131
138
|
const dirty = run('git status --porcelain')
|
|
132
139
|
if (dirty) {
|
|
133
140
|
logError('Local changes detected. Commit or stash them first, then retry.')
|
|
@@ -138,7 +145,6 @@ If running from a registry install, update the global package with ${PACKAGE_MAN
|
|
|
138
145
|
run(`git checkout -B stable ${latestTag}^{commit}`)
|
|
139
146
|
pullOutput = `Updated to stable release ${latestTag}.`
|
|
140
147
|
} else {
|
|
141
|
-
// Fallback: pull from origin/main
|
|
142
148
|
const behindCount = parseInt(run('git rev-list HEAD..origin/main --count'), 10) || 0
|
|
143
149
|
if (behindCount === 0) {
|
|
144
150
|
log(`Already up to date (${beforeSha}).`)
|
|
@@ -158,16 +164,21 @@ If running from a registry install, update the global package with ${PACKAGE_MAN
|
|
|
158
164
|
const newSha = run('git rev-parse --short HEAD')
|
|
159
165
|
log(pullOutput)
|
|
160
166
|
|
|
161
|
-
// Install deps if package files changed
|
|
162
167
|
try {
|
|
163
168
|
const diff = run(`git diff --name-only ${beforeSha}..HEAD`)
|
|
164
169
|
if (dependenciesChanged(diff)) {
|
|
165
|
-
const
|
|
166
|
-
|
|
170
|
+
const packageManager = detectPackageManager(PKG_ROOT, process.env)
|
|
171
|
+
const install = getInstallCommand(packageManager, true)
|
|
172
|
+
log(`Package files changed — running ${packageManager} install...`)
|
|
167
173
|
execFileSync(install.command, install.args, { cwd: PKG_ROOT, stdio: 'inherit', timeout: 120_000 })
|
|
168
174
|
}
|
|
169
175
|
} catch {
|
|
170
|
-
// If diff fails, skip install check
|
|
176
|
+
// If diff fails, skip install check.
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const rebuildExitCode = rebuildStandaloneServer()
|
|
180
|
+
if (rebuildExitCode !== 0) {
|
|
181
|
+
process.exit(rebuildExitCode)
|
|
171
182
|
}
|
|
172
183
|
|
|
173
184
|
log(`Done (${beforeSha} → ${newSha}, channel: ${channel}).`)
|
|
@@ -180,5 +191,7 @@ if (require.main === module) {
|
|
|
180
191
|
|
|
181
192
|
module.exports = {
|
|
182
193
|
main,
|
|
194
|
+
rebuildStandaloneServer,
|
|
195
|
+
resolveRegistryPackageManager,
|
|
183
196
|
runRegistrySelfUpdate,
|
|
184
197
|
}
|
package/bin/update-cmd.test.js
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
const test = require('node:test')
|
|
5
5
|
const assert = require('node:assert/strict')
|
|
6
|
-
const path = require('node:path')
|
|
7
6
|
|
|
8
7
|
const { runRegistrySelfUpdate } = require('./update-cmd.js')
|
|
9
8
|
|
|
10
|
-
test('runRegistrySelfUpdate executes the manager-specific global update command
|
|
9
|
+
test('runRegistrySelfUpdate executes the manager-specific global update command', () => {
|
|
11
10
|
const messages = []
|
|
12
11
|
const captured = []
|
|
13
12
|
|
|
@@ -20,9 +19,6 @@ test('runRegistrySelfUpdate executes the manager-specific global update command
|
|
|
20
19
|
log: (message) => messages.push(`log:${message}`),
|
|
21
20
|
logError: (message) => messages.push(`err:${message}`),
|
|
22
21
|
},
|
|
23
|
-
(command, args, options) => {
|
|
24
|
-
captured.push({ command, args, options })
|
|
25
|
-
},
|
|
26
22
|
)
|
|
27
23
|
|
|
28
24
|
assert.equal(exitCode, 0)
|
|
@@ -36,20 +32,9 @@ test('runRegistrySelfUpdate executes the manager-specific global update command
|
|
|
36
32
|
timeout: 120_000,
|
|
37
33
|
},
|
|
38
34
|
},
|
|
39
|
-
{
|
|
40
|
-
command: process.execPath,
|
|
41
|
-
args: [path.join(process.cwd(), 'bin', 'server-cmd.js'), '--build'],
|
|
42
|
-
options: {
|
|
43
|
-
cwd: process.cwd(),
|
|
44
|
-
stdio: 'inherit',
|
|
45
|
-
timeout: 600_000,
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
35
|
])
|
|
49
36
|
assert.match(messages.join('\n'), /updating the global @swarmclawai\/swarmclaw install via pnpm/i)
|
|
50
37
|
assert.match(messages.join('\n'), /global update complete via pnpm/i)
|
|
51
|
-
assert.match(messages.join('\n'), /rebuilding the standalone server bundle/i)
|
|
52
|
-
assert.match(messages.join('\n'), /standalone server bundle rebuilt/i)
|
|
53
38
|
})
|
|
54
39
|
|
|
55
40
|
test('runRegistrySelfUpdate reports a manual retry command when the registry update fails', () => {
|
|
@@ -70,23 +55,3 @@ test('runRegistrySelfUpdate reports a manual retry command when the registry upd
|
|
|
70
55
|
assert.match(messages.join('\n'), /registry update failed: spawn bun ENOENT/i)
|
|
71
56
|
assert.match(messages.join('\n'), /retry manually with: bun add -g @swarmclawai\/swarmclaw@latest/i)
|
|
72
57
|
})
|
|
73
|
-
|
|
74
|
-
test('runRegistrySelfUpdate reports a manual rebuild command when the rebuild step fails', () => {
|
|
75
|
-
const messages = []
|
|
76
|
-
|
|
77
|
-
const exitCode = runRegistrySelfUpdate(
|
|
78
|
-
'npm',
|
|
79
|
-
() => {},
|
|
80
|
-
{
|
|
81
|
-
log: (message) => messages.push(`log:${message}`),
|
|
82
|
-
logError: (message) => messages.push(`err:${message}`),
|
|
83
|
-
},
|
|
84
|
-
() => {
|
|
85
|
-
throw new Error('build failed')
|
|
86
|
-
},
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
assert.equal(exitCode, 1)
|
|
90
|
-
assert.match(messages.join('\n'), /standalone rebuild failed: build failed/i)
|
|
91
|
-
assert.match(messages.join('\n'), /retry manually with: swarmclaw server --build/i)
|
|
92
|
-
})
|