@swarmclawai/swarmclaw 0.9.9 → 1.0.0
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/install-root.js +155 -0
- package/bin/install-root.test.js +61 -0
- package/bin/server-cmd.js +54 -106
- package/bin/update-cmd.js +32 -14
- package/bin/worker-cmd.js +15 -15
- package/next.config.ts +2 -0
- package/package.json +3 -3
- 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/route.test.ts +16 -0
- package/src/app/api/setup/check-provider/route.ts +27 -7
- package/src/cli/index.js +1 -0
- 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/setup-defaults.test.ts +42 -0
- package/src/lib/setup-defaults.ts +1 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const os = require('node:os')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
const { execFileSync } = require('node:child_process')
|
|
8
|
+
|
|
9
|
+
const PACKAGE_NAME = '@swarmclawai/swarmclaw'
|
|
10
|
+
const CORE_PACKAGE_NAMES = new Set([PACKAGE_NAME])
|
|
11
|
+
|
|
12
|
+
function normalizeDir(value) {
|
|
13
|
+
if (!value) return null
|
|
14
|
+
const trimmed = String(value).trim()
|
|
15
|
+
if (!trimmed) return null
|
|
16
|
+
return path.resolve(trimmed)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readPackageJson(rootDir) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'))
|
|
22
|
+
} catch {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readPackageName(rootDir) {
|
|
28
|
+
return readPackageJson(rootDir)?.name?.trim() || null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readPackageVersion(rootDir) {
|
|
32
|
+
const version = readPackageJson(rootDir)?.version
|
|
33
|
+
return typeof version === 'string' && version.trim() ? version.trim() : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function* iterAncestorDirs(startDir, maxDepth = 12) {
|
|
37
|
+
let current = path.resolve(startDir)
|
|
38
|
+
for (let i = 0; i < maxDepth; i += 1) {
|
|
39
|
+
yield current
|
|
40
|
+
const parent = path.dirname(current)
|
|
41
|
+
if (parent === current) break
|
|
42
|
+
current = parent
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findPackageRoot(startDir, maxDepth = 12) {
|
|
47
|
+
for (const current of iterAncestorDirs(startDir, maxDepth)) {
|
|
48
|
+
const name = readPackageName(current)
|
|
49
|
+
if (name && CORE_PACKAGE_NAMES.has(name)) return current
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function candidateDirsFromArgv1(argv1) {
|
|
55
|
+
const normalized = normalizeDir(argv1)
|
|
56
|
+
if (!normalized) return []
|
|
57
|
+
|
|
58
|
+
const candidates = [path.dirname(normalized)]
|
|
59
|
+
try {
|
|
60
|
+
const resolved = fs.realpathSync(normalized)
|
|
61
|
+
if (resolved !== normalized) candidates.push(path.dirname(resolved))
|
|
62
|
+
} catch {}
|
|
63
|
+
|
|
64
|
+
const parts = normalized.split(path.sep)
|
|
65
|
+
const binIndex = parts.lastIndexOf('.bin')
|
|
66
|
+
if (binIndex > 0 && parts[binIndex - 1] === 'node_modules') {
|
|
67
|
+
const binName = path.basename(normalized)
|
|
68
|
+
const nodeModulesDir = parts.slice(0, binIndex).join(path.sep)
|
|
69
|
+
candidates.push(path.join(nodeModulesDir, binName))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return candidates
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolvePackageRoot(opts = {}) {
|
|
76
|
+
const candidates = []
|
|
77
|
+
const moduleDir = normalizeDir(opts.moduleDir)
|
|
78
|
+
if (moduleDir) candidates.push(moduleDir)
|
|
79
|
+
const argv1 = opts.argv1 === undefined ? process.argv[1] : opts.argv1
|
|
80
|
+
candidates.push(...candidateDirsFromArgv1(argv1))
|
|
81
|
+
const cwd = opts.cwd === undefined ? process.cwd() : opts.cwd
|
|
82
|
+
if (normalizeDir(cwd)) candidates.push(path.resolve(cwd))
|
|
83
|
+
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
const found = findPackageRoot(candidate)
|
|
86
|
+
if (found) return found
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return moduleDir ? path.resolve(moduleDir, '..') : null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function tryRealpath(targetPath) {
|
|
93
|
+
try {
|
|
94
|
+
return fs.realpathSync(targetPath)
|
|
95
|
+
} catch {
|
|
96
|
+
return path.resolve(targetPath)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function runRootCommand(command, args, execImpl = execFileSync) {
|
|
101
|
+
try {
|
|
102
|
+
return String(execImpl(command, args, {
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
105
|
+
})).trim()
|
|
106
|
+
} catch {
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveGlobalRoot(manager, execImpl = execFileSync, env = process.env) {
|
|
112
|
+
if (manager === 'bun') {
|
|
113
|
+
const bunInstall = String(env.BUN_INSTALL || '').trim() || path.join(os.homedir(), '.bun')
|
|
114
|
+
return path.join(bunInstall, 'install', 'global', 'node_modules')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (manager === 'pnpm') {
|
|
118
|
+
return runRootCommand('pnpm', ['root', '-g'], execImpl)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return runRootCommand('npm', ['root', '-g'], execImpl)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function detectGlobalInstallManagerForRoot(pkgRoot, execImpl = execFileSync, env = process.env) {
|
|
125
|
+
const pkgReal = tryRealpath(pkgRoot)
|
|
126
|
+
|
|
127
|
+
for (const manager of ['npm', 'pnpm']) {
|
|
128
|
+
const globalRoot = resolveGlobalRoot(manager, execImpl, env)
|
|
129
|
+
if (!globalRoot) continue
|
|
130
|
+
|
|
131
|
+
for (const name of CORE_PACKAGE_NAMES) {
|
|
132
|
+
const expectedReal = tryRealpath(path.join(globalRoot, name))
|
|
133
|
+
if (path.resolve(expectedReal) === path.resolve(pkgReal)) return manager
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const bunRoot = resolveGlobalRoot('bun', execImpl, env)
|
|
138
|
+
for (const name of CORE_PACKAGE_NAMES) {
|
|
139
|
+
const expectedReal = tryRealpath(path.join(bunRoot, name))
|
|
140
|
+
if (path.resolve(expectedReal) === path.resolve(pkgReal)) return 'bun'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
PACKAGE_NAME,
|
|
148
|
+
candidateDirsFromArgv1,
|
|
149
|
+
detectGlobalInstallManagerForRoot,
|
|
150
|
+
findPackageRoot,
|
|
151
|
+
readPackageName,
|
|
152
|
+
readPackageVersion,
|
|
153
|
+
resolveGlobalRoot,
|
|
154
|
+
resolvePackageRoot,
|
|
155
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
const assert = require('node:assert/strict')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const os = require('node:os')
|
|
8
|
+
const path = require('node:path')
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
candidateDirsFromArgv1,
|
|
12
|
+
detectGlobalInstallManagerForRoot,
|
|
13
|
+
resolvePackageRoot,
|
|
14
|
+
} = require('./install-root.js')
|
|
15
|
+
|
|
16
|
+
test('candidateDirsFromArgv1 includes the package directory for node_modules/.bin launchers', () => {
|
|
17
|
+
const launcher = path.join('/tmp', 'example', 'node_modules', '.bin', 'swarmclaw')
|
|
18
|
+
const candidates = candidateDirsFromArgv1(launcher)
|
|
19
|
+
assert.deepEqual(candidates, [
|
|
20
|
+
path.join('/tmp', 'example', 'node_modules', '.bin'),
|
|
21
|
+
path.join('/tmp', 'example', 'node_modules', 'swarmclaw'),
|
|
22
|
+
])
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('resolvePackageRoot finds the package root from argv1 candidates', () => {
|
|
26
|
+
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-install-root-'))
|
|
27
|
+
const pkgRoot = path.join(rootDir, 'node_modules', '@swarmclawai', 'swarmclaw')
|
|
28
|
+
const binPath = path.join(rootDir, 'node_modules', '.bin', 'swarmclaw')
|
|
29
|
+
const actualBin = path.join(pkgRoot, 'bin', 'swarmclaw.js')
|
|
30
|
+
|
|
31
|
+
fs.mkdirSync(path.join(pkgRoot, 'bin'), { recursive: true })
|
|
32
|
+
fs.mkdirSync(path.dirname(binPath), { recursive: true })
|
|
33
|
+
fs.writeFileSync(path.join(pkgRoot, 'package.json'), JSON.stringify({ name: '@swarmclawai/swarmclaw' }), 'utf8')
|
|
34
|
+
fs.writeFileSync(actualBin, '#!/usr/bin/env node\n', 'utf8')
|
|
35
|
+
fs.symlinkSync(actualBin, binPath)
|
|
36
|
+
|
|
37
|
+
assert.equal(resolvePackageRoot({ argv1: binPath, cwd: rootDir }), fs.realpathSync(pkgRoot))
|
|
38
|
+
|
|
39
|
+
fs.rmSync(rootDir, { recursive: true, force: true })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('detectGlobalInstallManagerForRoot matches the owning global root by realpath', () => {
|
|
43
|
+
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-global-root-'))
|
|
44
|
+
const npmGlobalRoot = path.join(rootDir, 'npm-global')
|
|
45
|
+
const pnpmGlobalRoot = path.join(rootDir, 'pnpm-global')
|
|
46
|
+
const pkgRoot = path.join(pnpmGlobalRoot, '@swarmclawai', 'swarmclaw')
|
|
47
|
+
|
|
48
|
+
fs.mkdirSync(path.join(npmGlobalRoot, '@swarmclawai'), { recursive: true })
|
|
49
|
+
fs.mkdirSync(path.join(pnpmGlobalRoot, '@swarmclawai'), { recursive: true })
|
|
50
|
+
fs.mkdirSync(pkgRoot, { recursive: true })
|
|
51
|
+
|
|
52
|
+
const execImpl = (command, args) => {
|
|
53
|
+
if (command === 'npm' && args.join(' ') === 'root -g') return npmGlobalRoot
|
|
54
|
+
if (command === 'pnpm' && args.join(' ') === 'root -g') return pnpmGlobalRoot
|
|
55
|
+
throw new Error(`unexpected command: ${command} ${args.join(' ')}`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
assert.equal(detectGlobalInstallManagerForRoot(pkgRoot, execImpl), 'pnpm')
|
|
59
|
+
|
|
60
|
+
fs.rmSync(rootDir, { recursive: true, force: true })
|
|
61
|
+
})
|
package/bin/server-cmd.js
CHANGED
|
@@ -7,34 +7,28 @@ const path = require('node:path')
|
|
|
7
7
|
const { spawn, execFileSync } = require('node:child_process')
|
|
8
8
|
const os = require('node:os')
|
|
9
9
|
const {
|
|
10
|
-
LOCKFILE_NAMES,
|
|
11
10
|
detectPackageManager,
|
|
12
11
|
getInstallCommand,
|
|
13
12
|
} = require('./package-manager.js')
|
|
13
|
+
const {
|
|
14
|
+
readPackageVersion,
|
|
15
|
+
resolvePackageRoot,
|
|
16
|
+
} = require('./install-root.js')
|
|
14
17
|
|
|
15
18
|
// ---------------------------------------------------------------------------
|
|
16
19
|
// Paths
|
|
17
20
|
// ---------------------------------------------------------------------------
|
|
18
21
|
|
|
19
22
|
const SWARMCLAW_HOME = process.env.SWARMCLAW_HOME || path.join(os.homedir(), '.swarmclaw')
|
|
20
|
-
const PKG_ROOT =
|
|
21
|
-
|
|
23
|
+
const PKG_ROOT = resolvePackageRoot({
|
|
24
|
+
moduleDir: __dirname,
|
|
25
|
+
argv1: process.argv[1],
|
|
26
|
+
cwd: process.cwd(),
|
|
27
|
+
})
|
|
22
28
|
const PID_FILE = path.join(SWARMCLAW_HOME, 'server.pid')
|
|
23
29
|
const LOG_FILE = path.join(SWARMCLAW_HOME, 'server.log')
|
|
24
30
|
const DATA_DIR = path.join(SWARMCLAW_HOME, 'data')
|
|
25
31
|
|
|
26
|
-
// Files/directories to copy from the npm package into SWARMCLAW_HOME
|
|
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
|
-
|
|
38
32
|
// ---------------------------------------------------------------------------
|
|
39
33
|
// Helpers
|
|
40
34
|
// ---------------------------------------------------------------------------
|
|
@@ -69,116 +63,69 @@ function isProcessRunning(pid) {
|
|
|
69
63
|
}
|
|
70
64
|
}
|
|
71
65
|
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
fs.cpSync(src, dest, { recursive: true, dereference })
|
|
66
|
+
function resolveStandaloneBase(pkgRoot = PKG_ROOT) {
|
|
67
|
+
return path.join(pkgRoot, '.next', 'standalone')
|
|
75
68
|
}
|
|
76
69
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
fs.symlinkSync(src, dest)
|
|
70
|
+
function getVersion() {
|
|
71
|
+
return readPackageVersion(PKG_ROOT) || 'unknown'
|
|
80
72
|
}
|
|
81
73
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
74
|
+
function ensurePackageDependencies(pkgRoot = PKG_ROOT) {
|
|
75
|
+
const nextCli = path.join(pkgRoot, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
76
|
+
if (fs.existsSync(nextCli)) return nextCli
|
|
77
|
+
|
|
78
|
+
const packageManager = detectPackageManager(pkgRoot, process.env)
|
|
79
|
+
const install = getInstallCommand(packageManager)
|
|
80
|
+
log(`Installing dependencies with ${packageManager}...`)
|
|
81
|
+
execFileSync(install.command, install.args, { cwd: pkgRoot, stdio: 'inherit' })
|
|
82
|
+
return nextCli
|
|
90
83
|
}
|
|
91
84
|
|
|
92
85
|
// ---------------------------------------------------------------------------
|
|
93
86
|
// Build
|
|
94
87
|
// ---------------------------------------------------------------------------
|
|
95
88
|
|
|
96
|
-
function needsBuild(forceBuild) {
|
|
89
|
+
function needsBuild(forceBuild, { pkgRoot = PKG_ROOT } = {}) {
|
|
97
90
|
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
|
|
91
|
+
return !findStandaloneServer({ pkgRoot })
|
|
103
92
|
}
|
|
104
93
|
|
|
105
|
-
function runBuild() {
|
|
94
|
+
function runBuild({ pkgRoot = PKG_ROOT } = {}) {
|
|
106
95
|
log('Preparing build environment...')
|
|
107
96
|
ensureDir(SWARMCLAW_HOME)
|
|
108
97
|
ensureDir(DATA_DIR)
|
|
109
98
|
|
|
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
|
-
}
|
|
99
|
+
const nextCli = ensurePackageDependencies(pkgRoot)
|
|
123
100
|
|
|
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
|
-
}
|
|
137
|
-
|
|
138
|
-
// Run Next.js build
|
|
139
101
|
log('Building Next.js application (this may take a minute)...')
|
|
140
|
-
const nextCli = path.join(SWARMCLAW_HOME, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
141
102
|
execFileSync(process.execPath, [nextCli, 'build'], {
|
|
142
|
-
cwd:
|
|
103
|
+
cwd: pkgRoot,
|
|
143
104
|
stdio: 'inherit',
|
|
144
105
|
env: {
|
|
145
106
|
...process.env,
|
|
107
|
+
DATA_DIR,
|
|
146
108
|
SWARMCLAW_BUILD_MODE: '1',
|
|
147
109
|
},
|
|
148
110
|
})
|
|
149
111
|
|
|
150
|
-
// Write built marker
|
|
151
|
-
fs.writeFileSync(BUILT_MARKER, JSON.stringify({ builtAt: new Date().toISOString(), version: getVersion() }))
|
|
152
112
|
log('Build complete.')
|
|
153
113
|
}
|
|
154
114
|
|
|
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
115
|
// ---------------------------------------------------------------------------
|
|
165
116
|
// Find standalone server.js
|
|
166
117
|
// ---------------------------------------------------------------------------
|
|
167
118
|
|
|
168
|
-
function findStandaloneServer() {
|
|
169
|
-
|
|
170
|
-
// The path mirrors the build machine's directory structure
|
|
171
|
-
const standaloneBase = path.join(SWARMCLAW_HOME, '.next', 'standalone')
|
|
119
|
+
function findStandaloneServer({ pkgRoot = PKG_ROOT } = {}) {
|
|
120
|
+
const standaloneBase = resolveStandaloneBase(pkgRoot)
|
|
172
121
|
|
|
173
122
|
if (!fs.existsSync(standaloneBase)) {
|
|
174
123
|
return null
|
|
175
124
|
}
|
|
176
125
|
|
|
177
|
-
// Try direct server.js first
|
|
178
126
|
const direct = path.join(standaloneBase, 'server.js')
|
|
179
127
|
if (fs.existsSync(direct)) return direct
|
|
180
128
|
|
|
181
|
-
// Recursively search for server.js (handles nested paths from build machine)
|
|
182
129
|
function search(dir) {
|
|
183
130
|
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
184
131
|
for (const entry of entries) {
|
|
@@ -199,34 +146,38 @@ function findStandaloneServer() {
|
|
|
199
146
|
// Start server
|
|
200
147
|
// ---------------------------------------------------------------------------
|
|
201
148
|
|
|
202
|
-
function startServer(opts) {
|
|
203
|
-
const serverJs = findStandaloneServer()
|
|
149
|
+
function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
|
|
150
|
+
const serverJs = findStandaloneServer({ pkgRoot })
|
|
204
151
|
if (!serverJs) {
|
|
205
|
-
logError('Standalone server.js not found. Try running: swarmclaw server --build')
|
|
152
|
+
logError('Standalone server.js not found in the installed package. Try running: swarmclaw server --build')
|
|
206
153
|
process.exit(1)
|
|
207
154
|
}
|
|
208
155
|
|
|
156
|
+
ensureDir(SWARMCLAW_HOME)
|
|
157
|
+
ensureDir(DATA_DIR)
|
|
158
|
+
|
|
209
159
|
const port = opts.port || '3456'
|
|
210
160
|
const wsPort = opts.wsPort || String(Number(port) + 1)
|
|
211
161
|
const host = opts.host || '0.0.0.0'
|
|
212
162
|
|
|
213
163
|
const env = {
|
|
214
164
|
...process.env,
|
|
165
|
+
DATA_DIR,
|
|
166
|
+
HOSTNAME: host,
|
|
215
167
|
PORT: port,
|
|
216
168
|
WS_PORT: wsPort,
|
|
217
|
-
HOSTNAME: host,
|
|
218
|
-
DATA_DIR,
|
|
219
169
|
}
|
|
220
170
|
|
|
221
171
|
log(`Starting server on ${host}:${port} (WebSocket: ${wsPort})...`)
|
|
172
|
+
log(`Package root: ${pkgRoot}`)
|
|
222
173
|
log(`Data directory: ${DATA_DIR}`)
|
|
223
174
|
|
|
224
175
|
if (opts.detach) {
|
|
225
|
-
// Detached mode — run in background
|
|
226
176
|
const logStream = fs.openSync(LOG_FILE, 'a')
|
|
227
177
|
const child = spawn(process.execPath, [serverJs], {
|
|
228
|
-
|
|
178
|
+
cwd: pkgRoot,
|
|
229
179
|
detached: true,
|
|
180
|
+
env,
|
|
230
181
|
stdio: ['ignore', logStream, logStream],
|
|
231
182
|
})
|
|
232
183
|
|
|
@@ -236,8 +187,8 @@ function startServer(opts) {
|
|
|
236
187
|
log(`Logs: ${LOG_FILE}`)
|
|
237
188
|
process.exit(0)
|
|
238
189
|
} else {
|
|
239
|
-
// Foreground mode
|
|
240
190
|
const child = spawn(process.execPath, [serverJs], {
|
|
191
|
+
cwd: pkgRoot,
|
|
241
192
|
env,
|
|
242
193
|
stdio: 'inherit',
|
|
243
194
|
})
|
|
@@ -246,7 +197,6 @@ function startServer(opts) {
|
|
|
246
197
|
process.exit(code || 0)
|
|
247
198
|
})
|
|
248
199
|
|
|
249
|
-
// Forward signals
|
|
250
200
|
for (const sig of ['SIGINT', 'SIGTERM']) {
|
|
251
201
|
process.on(sig, () => child.kill(sig))
|
|
252
202
|
}
|
|
@@ -288,27 +238,21 @@ function showStatus() {
|
|
|
288
238
|
const pid = readPid()
|
|
289
239
|
if (!pid) {
|
|
290
240
|
log('Server: not running (no PID file)')
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (isProcessRunning(pid)) {
|
|
241
|
+
} else if (isProcessRunning(pid)) {
|
|
295
242
|
log(`Server: running (PID: ${pid})`)
|
|
296
243
|
} else {
|
|
297
244
|
log(`Server: not running (stale PID: ${pid})`)
|
|
298
245
|
try { fs.unlinkSync(PID_FILE) } catch {}
|
|
299
246
|
}
|
|
300
247
|
|
|
248
|
+
log(`Package: ${PKG_ROOT}`)
|
|
301
249
|
log(`Home: ${SWARMCLAW_HOME}`)
|
|
302
250
|
log(`Data: ${DATA_DIR}`)
|
|
303
251
|
log(`WebSocket port: ${process.env.WS_PORT || '(PORT + 1)'}`)
|
|
304
252
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
log(`Built: ${info.builtAt || 'unknown'} (v${info.version || '?'})`)
|
|
309
|
-
} catch {
|
|
310
|
-
log('Built: yes')
|
|
311
|
-
}
|
|
253
|
+
const serverJs = findStandaloneServer()
|
|
254
|
+
if (serverJs) {
|
|
255
|
+
log(`Built: yes (${serverJs})`)
|
|
312
256
|
} else {
|
|
313
257
|
log('Built: no')
|
|
314
258
|
}
|
|
@@ -339,7 +283,7 @@ Options:
|
|
|
339
283
|
}
|
|
340
284
|
|
|
341
285
|
function main() {
|
|
342
|
-
const args = process.argv.slice(3)
|
|
286
|
+
const args = process.argv.slice(3)
|
|
343
287
|
let command = 'start'
|
|
344
288
|
let forceBuild = false
|
|
345
289
|
let detach = false
|
|
@@ -385,7 +329,6 @@ function main() {
|
|
|
385
329
|
return
|
|
386
330
|
}
|
|
387
331
|
|
|
388
|
-
// command === 'start'
|
|
389
332
|
if (needsBuild(forceBuild)) {
|
|
390
333
|
runBuild()
|
|
391
334
|
}
|
|
@@ -398,8 +341,13 @@ if (require.main === module) {
|
|
|
398
341
|
}
|
|
399
342
|
|
|
400
343
|
module.exports = {
|
|
344
|
+
DATA_DIR,
|
|
345
|
+
PKG_ROOT,
|
|
346
|
+
SWARMCLAW_HOME,
|
|
347
|
+
findStandaloneServer,
|
|
401
348
|
getVersion,
|
|
402
349
|
main,
|
|
403
350
|
needsBuild,
|
|
404
|
-
|
|
351
|
+
resolveStandaloneBase,
|
|
352
|
+
runBuild,
|
|
405
353
|
}
|
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,7 +72,7 @@ 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
78
|
rebuildImpl = execFileSync,
|
|
@@ -92,16 +106,15 @@ function main() {
|
|
|
92
106
|
Usage: swarmclaw update
|
|
93
107
|
|
|
94
108
|
If running from a git checkout, pull the latest SwarmClaw release tag.
|
|
95
|
-
If running from a registry install, update the global package with
|
|
109
|
+
If running from a registry install, update the global package with its owning package manager.
|
|
96
110
|
`.trim())
|
|
97
111
|
process.exit(0)
|
|
98
112
|
}
|
|
99
113
|
|
|
100
|
-
// Verify we're in a git repo
|
|
101
114
|
try {
|
|
102
115
|
run('git rev-parse --git-dir')
|
|
103
116
|
} catch {
|
|
104
|
-
process.exit(runRegistrySelfUpdate(
|
|
117
|
+
process.exit(runRegistrySelfUpdate())
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
const beforeRef = run('git rev-parse HEAD')
|
|
@@ -127,7 +140,6 @@ If running from a registry install, update the global package with ${PACKAGE_MAN
|
|
|
127
140
|
process.exit(0)
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
// Check for uncommitted changes
|
|
131
143
|
const dirty = run('git status --porcelain')
|
|
132
144
|
if (dirty) {
|
|
133
145
|
logError('Local changes detected. Commit or stash them first, then retry.')
|
|
@@ -138,7 +150,6 @@ If running from a registry install, update the global package with ${PACKAGE_MAN
|
|
|
138
150
|
run(`git checkout -B stable ${latestTag}^{commit}`)
|
|
139
151
|
pullOutput = `Updated to stable release ${latestTag}.`
|
|
140
152
|
} else {
|
|
141
|
-
// Fallback: pull from origin/main
|
|
142
153
|
const behindCount = parseInt(run('git rev-list HEAD..origin/main --count'), 10) || 0
|
|
143
154
|
if (behindCount === 0) {
|
|
144
155
|
log(`Already up to date (${beforeSha}).`)
|
|
@@ -158,16 +169,21 @@ If running from a registry install, update the global package with ${PACKAGE_MAN
|
|
|
158
169
|
const newSha = run('git rev-parse --short HEAD')
|
|
159
170
|
log(pullOutput)
|
|
160
171
|
|
|
161
|
-
// Install deps if package files changed
|
|
162
172
|
try {
|
|
163
173
|
const diff = run(`git diff --name-only ${beforeSha}..HEAD`)
|
|
164
174
|
if (dependenciesChanged(diff)) {
|
|
165
|
-
const
|
|
166
|
-
|
|
175
|
+
const packageManager = detectPackageManager(PKG_ROOT, process.env)
|
|
176
|
+
const install = getInstallCommand(packageManager, true)
|
|
177
|
+
log(`Package files changed — running ${packageManager} install...`)
|
|
167
178
|
execFileSync(install.command, install.args, { cwd: PKG_ROOT, stdio: 'inherit', timeout: 120_000 })
|
|
168
179
|
}
|
|
169
180
|
} catch {
|
|
170
|
-
// If diff fails, skip install check
|
|
181
|
+
// If diff fails, skip install check.
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const rebuildExitCode = rebuildStandaloneServer()
|
|
185
|
+
if (rebuildExitCode !== 0) {
|
|
186
|
+
process.exit(rebuildExitCode)
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
log(`Done (${beforeSha} → ${newSha}, channel: ${channel}).`)
|
|
@@ -180,5 +196,7 @@ if (require.main === module) {
|
|
|
180
196
|
|
|
181
197
|
module.exports = {
|
|
182
198
|
main,
|
|
199
|
+
rebuildStandaloneServer,
|
|
200
|
+
resolveRegistryPackageManager,
|
|
183
201
|
runRegistrySelfUpdate,
|
|
184
202
|
}
|
package/bin/worker-cmd.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
const fs = require('node:fs')
|
|
5
|
-
const path = require('node:path')
|
|
6
|
-
const os = require('node:os')
|
|
7
4
|
const { spawn } = require('node:child_process')
|
|
8
5
|
|
|
6
|
+
const {
|
|
7
|
+
DATA_DIR,
|
|
8
|
+
PKG_ROOT,
|
|
9
|
+
SWARMCLAW_HOME,
|
|
10
|
+
findStandaloneServer,
|
|
11
|
+
} = require('./server-cmd.js')
|
|
12
|
+
|
|
9
13
|
function printHelp() {
|
|
10
14
|
const help = `
|
|
11
15
|
Usage: swarmclaw worker [options]
|
|
@@ -32,27 +36,23 @@ function main() {
|
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
const SWARMCLAW_HOME = process.env.SWARMCLAW_HOME || path.join(os.homedir(), '.swarmclaw')
|
|
36
|
-
const DATA_DIR = path.join(SWARMCLAW_HOME, 'data')
|
|
37
|
-
|
|
38
39
|
process.env.DATA_DIR = DATA_DIR
|
|
39
40
|
process.env.SWARMCLAW_DAEMON_BACKGROUND_SERVICES = '1'
|
|
40
|
-
// Flag that tells Next.js NOT to start the HTTP/Websocket listener, just boot the daemon.
|
|
41
41
|
process.env.SWARMCLAW_WORKER_ONLY = '1'
|
|
42
42
|
|
|
43
|
-
console.log(
|
|
43
|
+
console.log('[swarmclaw] Starting dedicated background worker...')
|
|
44
|
+
console.log(`[swarmclaw] Package root: ${PKG_ROOT}`)
|
|
45
|
+
console.log(`[swarmclaw] Home: ${SWARMCLAW_HOME}`)
|
|
44
46
|
console.log(`[swarmclaw] Data directory: ${DATA_DIR}`)
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!fs.existsSync(serverJs)) {
|
|
51
|
-
console.error('Standalone server.js not found. Try running: swarmclaw server --build')
|
|
52
|
-
process.exit(1)
|
|
48
|
+
const serverJs = findStandaloneServer()
|
|
49
|
+
if (!serverJs) {
|
|
50
|
+
console.error('[swarmclaw] Standalone server.js not found in the installed package. Try running: swarmclaw server --build')
|
|
51
|
+
process.exit(1)
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
const child = spawn(process.execPath, [serverJs], {
|
|
55
|
+
cwd: PKG_ROOT,
|
|
56
56
|
env: process.env,
|
|
57
57
|
stdio: 'inherit',
|
|
58
58
|
})
|