@swarmclawai/swarmclaw 1.0.0 → 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 +39 -0
- package/bin/install-root.test.js +60 -0
- package/bin/server-cmd.js +37 -6
- package/bin/swarmclaw.js +83 -3
- package/bin/update-cmd.js +1 -6
- package/bin/update-cmd.test.js +1 -36
- package/bin/worker-cmd.js +8 -2
- package/package.json +9 -8
- 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/setup/check-provider/helpers.ts +28 -0
- package/src/app/api/setup/check-provider/route.test.ts +1 -1
- package/src/app/api/setup/check-provider/route.ts +5 -32
- 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 +13 -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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
|
|
8
|
+
const { readPackageVersion } = require('./install-root.js')
|
|
9
|
+
const {
|
|
10
|
+
PKG_ROOT,
|
|
11
|
+
SWARMCLAW_HOME,
|
|
12
|
+
findStandaloneServer,
|
|
13
|
+
isGitCheckout,
|
|
14
|
+
} = require('./server-cmd.js')
|
|
15
|
+
|
|
16
|
+
function readPid(pidFile) {
|
|
17
|
+
try {
|
|
18
|
+
const pid = Number.parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10)
|
|
19
|
+
return Number.isFinite(pid) ? pid : null
|
|
20
|
+
} catch {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isProcessRunning(pid) {
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0)
|
|
28
|
+
return true
|
|
29
|
+
} catch {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function buildDoctorReport(opts = {}) {
|
|
35
|
+
const pkgRoot = opts.pkgRoot || PKG_ROOT
|
|
36
|
+
const homeDir = opts.homeDir || SWARMCLAW_HOME
|
|
37
|
+
const pidFile = path.join(homeDir, 'server.pid')
|
|
38
|
+
const dataDir = path.join(homeDir, 'data')
|
|
39
|
+
const workspaceDir = path.join(homeDir, 'workspace')
|
|
40
|
+
const browserProfilesDir = path.join(homeDir, 'browser-profiles')
|
|
41
|
+
const nextCliPath = path.join(pkgRoot, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
42
|
+
const standaloneServer = findStandaloneServer({ pkgRoot })
|
|
43
|
+
const pid = readPid(pidFile)
|
|
44
|
+
const running = pid ? isProcessRunning(pid) : false
|
|
45
|
+
|
|
46
|
+
const serverState = !pid
|
|
47
|
+
? 'not-running'
|
|
48
|
+
: running
|
|
49
|
+
? 'running'
|
|
50
|
+
: 'stale-pid'
|
|
51
|
+
|
|
52
|
+
const recommendations = []
|
|
53
|
+
if (!standaloneServer) {
|
|
54
|
+
if (fs.existsSync(nextCliPath)) {
|
|
55
|
+
recommendations.push('Standalone bundle is missing. Run `swarmclaw run` to build it automatically or `swarmclaw server --build` to prebuild it now.')
|
|
56
|
+
} else {
|
|
57
|
+
recommendations.push('Next.js build dependencies are missing from this install. Reinstall the package before starting SwarmClaw.')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (serverState === 'stale-pid') {
|
|
61
|
+
recommendations.push('A stale PID file was found. Run `swarmclaw stop` to clean it up.')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
packageVersion: readPackageVersion(pkgRoot) || 'unknown',
|
|
66
|
+
packageRoot: pkgRoot,
|
|
67
|
+
installKind: isGitCheckout(pkgRoot) ? 'git' : 'package',
|
|
68
|
+
homeDir,
|
|
69
|
+
dataDir,
|
|
70
|
+
workspaceDir,
|
|
71
|
+
browserProfilesDir,
|
|
72
|
+
server: {
|
|
73
|
+
state: serverState,
|
|
74
|
+
pid,
|
|
75
|
+
pidFile,
|
|
76
|
+
},
|
|
77
|
+
build: {
|
|
78
|
+
standaloneServer,
|
|
79
|
+
nextCliPresent: fs.existsSync(nextCliPath),
|
|
80
|
+
nextCliPath,
|
|
81
|
+
},
|
|
82
|
+
recommendations,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function printHelp() {
|
|
87
|
+
process.stdout.write(`
|
|
88
|
+
Usage: swarmclaw doctor [--json]
|
|
89
|
+
|
|
90
|
+
Show local installation and build diagnostics for SwarmClaw.
|
|
91
|
+
`.trim() + '\n')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function printHumanReport(report) {
|
|
95
|
+
const lines = [
|
|
96
|
+
`Package version: ${report.packageVersion}`,
|
|
97
|
+
`Install kind: ${report.installKind}`,
|
|
98
|
+
`Package root: ${report.packageRoot}`,
|
|
99
|
+
`Home: ${report.homeDir}`,
|
|
100
|
+
`Data: ${report.dataDir}`,
|
|
101
|
+
`Workspace: ${report.workspaceDir}`,
|
|
102
|
+
`Browser profiles: ${report.browserProfilesDir}`,
|
|
103
|
+
`Server: ${report.server.state}${report.server.pid ? ` (PID: ${report.server.pid})` : ''}`,
|
|
104
|
+
`Standalone bundle: ${report.build.standaloneServer ? `yes (${report.build.standaloneServer})` : 'no'}`,
|
|
105
|
+
`Next CLI available: ${report.build.nextCliPresent ? 'yes' : 'no'}`,
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
if (report.recommendations.length > 0) {
|
|
109
|
+
lines.push('', 'Recommendations:')
|
|
110
|
+
for (const recommendation of report.recommendations) {
|
|
111
|
+
lines.push(`- ${recommendation}`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
process.stdout.write(`${lines.join('\n')}\n`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function main(args = process.argv.slice(3)) {
|
|
119
|
+
const json = args.includes('--json')
|
|
120
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
121
|
+
printHelp()
|
|
122
|
+
process.exit(0)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const unknown = args.filter((arg) => arg !== '--json')
|
|
126
|
+
if (unknown.length > 0) {
|
|
127
|
+
process.stderr.write(`[swarmclaw] Unknown argument: ${unknown[0]}\n`)
|
|
128
|
+
printHelp()
|
|
129
|
+
process.exit(1)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const report = buildDoctorReport()
|
|
133
|
+
if (json) {
|
|
134
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
printHumanReport(report)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (require.main === module) {
|
|
141
|
+
main()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
buildDoctorReport,
|
|
146
|
+
isProcessRunning,
|
|
147
|
+
main,
|
|
148
|
+
readPid,
|
|
149
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { buildDoctorReport } = require('./doctor-cmd.js')
|
|
11
|
+
|
|
12
|
+
test('buildDoctorReport recommends a local build when standalone output is missing', () => {
|
|
13
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-doctor-'))
|
|
14
|
+
const pkgRoot = path.join(tempDir, 'pkg')
|
|
15
|
+
const homeDir = path.join(tempDir, '.swarmclaw')
|
|
16
|
+
const nextCli = path.join(pkgRoot, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
17
|
+
|
|
18
|
+
fs.mkdirSync(path.dirname(nextCli), { recursive: true })
|
|
19
|
+
fs.mkdirSync(homeDir, { recursive: true })
|
|
20
|
+
fs.writeFileSync(path.join(pkgRoot, 'package.json'), JSON.stringify({ name: '@swarmclawai/swarmclaw', version: '1.0.1' }), 'utf8')
|
|
21
|
+
fs.writeFileSync(nextCli, '#!/usr/bin/env node\n', 'utf8')
|
|
22
|
+
|
|
23
|
+
const report = buildDoctorReport({ pkgRoot, homeDir })
|
|
24
|
+
|
|
25
|
+
assert.equal(report.installKind, 'package')
|
|
26
|
+
assert.equal(report.build.nextCliPresent, true)
|
|
27
|
+
assert.equal(report.build.standaloneServer, null)
|
|
28
|
+
assert.match(report.recommendations.join('\n'), /swarmclaw run/)
|
|
29
|
+
|
|
30
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('buildDoctorReport flags stale PID files', () => {
|
|
34
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-doctor-stale-'))
|
|
35
|
+
const pkgRoot = path.join(tempDir, 'pkg')
|
|
36
|
+
const homeDir = path.join(tempDir, '.swarmclaw')
|
|
37
|
+
const pidFile = path.join(homeDir, 'server.pid')
|
|
38
|
+
|
|
39
|
+
fs.mkdirSync(homeDir, { recursive: true })
|
|
40
|
+
fs.mkdirSync(pkgRoot, { recursive: true })
|
|
41
|
+
fs.writeFileSync(path.join(pkgRoot, 'package.json'), JSON.stringify({ name: '@swarmclawai/swarmclaw', version: '1.0.1' }), 'utf8')
|
|
42
|
+
fs.writeFileSync(pidFile, '999999\n', 'utf8')
|
|
43
|
+
|
|
44
|
+
const report = buildDoctorReport({ pkgRoot, homeDir })
|
|
45
|
+
|
|
46
|
+
assert.equal(report.server.state, 'stale-pid')
|
|
47
|
+
assert.match(report.recommendations.join('\n'), /swarmclaw stop/)
|
|
48
|
+
|
|
49
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
50
|
+
})
|
package/bin/install-root.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
4
5
|
const fs = require('node:fs')
|
|
5
6
|
const os = require('node:os')
|
|
6
7
|
const path = require('node:path')
|
|
@@ -143,13 +144,51 @@ function detectGlobalInstallManagerForRoot(pkgRoot, execImpl = execFileSync, env
|
|
|
143
144
|
return null
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
function findLocalInstallProjectRoot(pkgRoot) {
|
|
148
|
+
const normalized = normalizeDir(pkgRoot)
|
|
149
|
+
if (!normalized) return null
|
|
150
|
+
|
|
151
|
+
const marker = `${path.sep}node_modules${path.sep}`
|
|
152
|
+
const idx = normalized.indexOf(marker)
|
|
153
|
+
if (idx === -1) return null
|
|
154
|
+
|
|
155
|
+
const projectRoot = normalized.slice(0, idx)
|
|
156
|
+
return projectRoot ? path.resolve(projectRoot) : path.parse(normalized).root
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resolveStateHome(opts = {}) {
|
|
160
|
+
const env = opts.env || process.env
|
|
161
|
+
const explicitHome = normalizeDir(env.SWARMCLAW_HOME)
|
|
162
|
+
if (explicitHome) return explicitHome
|
|
163
|
+
|
|
164
|
+
const pkgRoot = normalizeDir(opts.pkgRoot)
|
|
165
|
+
|| resolvePackageRoot({
|
|
166
|
+
moduleDir: opts.moduleDir,
|
|
167
|
+
argv1: opts.argv1,
|
|
168
|
+
cwd: opts.cwd,
|
|
169
|
+
})
|
|
170
|
+
if (!pkgRoot) return path.join(os.homedir(), '.swarmclaw')
|
|
171
|
+
|
|
172
|
+
const execImpl = opts.execImpl || execFileSync
|
|
173
|
+
if (detectGlobalInstallManagerForRoot(pkgRoot, execImpl, env)) {
|
|
174
|
+
return path.join(os.homedir(), '.swarmclaw')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const projectRoot = findLocalInstallProjectRoot(pkgRoot)
|
|
178
|
+
if (projectRoot) return path.join(projectRoot, '.swarmclaw')
|
|
179
|
+
|
|
180
|
+
return path.join(os.homedir(), '.swarmclaw')
|
|
181
|
+
}
|
|
182
|
+
|
|
146
183
|
module.exports = {
|
|
147
184
|
PACKAGE_NAME,
|
|
148
185
|
candidateDirsFromArgv1,
|
|
149
186
|
detectGlobalInstallManagerForRoot,
|
|
150
187
|
findPackageRoot,
|
|
188
|
+
findLocalInstallProjectRoot,
|
|
151
189
|
readPackageName,
|
|
152
190
|
readPackageVersion,
|
|
153
191
|
resolveGlobalRoot,
|
|
154
192
|
resolvePackageRoot,
|
|
193
|
+
resolveStateHome,
|
|
155
194
|
}
|
package/bin/install-root.test.js
CHANGED
|
@@ -10,7 +10,9 @@ const path = require('node:path')
|
|
|
10
10
|
const {
|
|
11
11
|
candidateDirsFromArgv1,
|
|
12
12
|
detectGlobalInstallManagerForRoot,
|
|
13
|
+
findLocalInstallProjectRoot,
|
|
13
14
|
resolvePackageRoot,
|
|
15
|
+
resolveStateHome,
|
|
14
16
|
} = require('./install-root.js')
|
|
15
17
|
|
|
16
18
|
test('candidateDirsFromArgv1 includes the package directory for node_modules/.bin launchers', () => {
|
|
@@ -59,3 +61,61 @@ test('detectGlobalInstallManagerForRoot matches the owning global root by realpa
|
|
|
59
61
|
|
|
60
62
|
fs.rmSync(rootDir, { recursive: true, force: true })
|
|
61
63
|
})
|
|
64
|
+
|
|
65
|
+
test('findLocalInstallProjectRoot returns the project root for nested pnpm installs', () => {
|
|
66
|
+
const pkgRoot = path.join(
|
|
67
|
+
'/tmp',
|
|
68
|
+
'example',
|
|
69
|
+
'node_modules',
|
|
70
|
+
'.pnpm',
|
|
71
|
+
'@swarmclawai+swarmclaw@1.0.1',
|
|
72
|
+
'node_modules',
|
|
73
|
+
'@swarmclawai',
|
|
74
|
+
'swarmclaw',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert.equal(findLocalInstallProjectRoot(pkgRoot), path.join('/tmp', 'example'))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('resolveStateHome prefers the local project .swarmclaw directory for local installs', () => {
|
|
81
|
+
const projectRoot = path.join('/tmp', 'example')
|
|
82
|
+
const pkgRoot = path.join(projectRoot, 'node_modules', '@swarmclawai', 'swarmclaw')
|
|
83
|
+
const execImpl = () => {
|
|
84
|
+
throw new Error('unexpected global root lookup')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
assert.equal(
|
|
88
|
+
resolveStateHome({
|
|
89
|
+
pkgRoot,
|
|
90
|
+
env: {},
|
|
91
|
+
execImpl,
|
|
92
|
+
}),
|
|
93
|
+
path.join(projectRoot, '.swarmclaw'),
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('resolveStateHome keeps global installs under the user home directory', () => {
|
|
98
|
+
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-state-home-'))
|
|
99
|
+
const npmGlobalRoot = path.join(rootDir, 'npm-global')
|
|
100
|
+
const pkgRoot = path.join(npmGlobalRoot, '@swarmclawai', 'swarmclaw')
|
|
101
|
+
|
|
102
|
+
fs.mkdirSync(path.join(npmGlobalRoot, '@swarmclawai'), { recursive: true })
|
|
103
|
+
fs.mkdirSync(pkgRoot, { recursive: true })
|
|
104
|
+
|
|
105
|
+
const execImpl = (command, args) => {
|
|
106
|
+
if (command === 'npm' && args.join(' ') === 'root -g') return npmGlobalRoot
|
|
107
|
+
if (command === 'pnpm' && args.join(' ') === 'root -g') return path.join(rootDir, 'pnpm-global')
|
|
108
|
+
throw new Error(`unexpected command: ${command} ${args.join(' ')}`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
assert.equal(
|
|
112
|
+
resolveStateHome({
|
|
113
|
+
pkgRoot,
|
|
114
|
+
env: {},
|
|
115
|
+
execImpl,
|
|
116
|
+
}),
|
|
117
|
+
path.join(os.homedir(), '.swarmclaw'),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
fs.rmSync(rootDir, { recursive: true, force: true })
|
|
121
|
+
})
|
package/bin/server-cmd.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
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
9
|
detectPackageManager,
|
|
11
10
|
getInstallCommand,
|
|
@@ -13,21 +12,30 @@ const {
|
|
|
13
12
|
const {
|
|
14
13
|
readPackageVersion,
|
|
15
14
|
resolvePackageRoot,
|
|
15
|
+
resolveStateHome,
|
|
16
16
|
} = require('./install-root.js')
|
|
17
17
|
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
// Paths
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
|
|
22
|
-
const SWARMCLAW_HOME = process.env.SWARMCLAW_HOME || path.join(os.homedir(), '.swarmclaw')
|
|
23
22
|
const PKG_ROOT = resolvePackageRoot({
|
|
24
23
|
moduleDir: __dirname,
|
|
25
24
|
argv1: process.argv[1],
|
|
26
25
|
cwd: process.cwd(),
|
|
27
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
|
+
})
|
|
28
34
|
const PID_FILE = path.join(SWARMCLAW_HOME, 'server.pid')
|
|
29
35
|
const LOG_FILE = path.join(SWARMCLAW_HOME, 'server.log')
|
|
30
36
|
const DATA_DIR = path.join(SWARMCLAW_HOME, 'data')
|
|
37
|
+
const WORKSPACE_DIR = path.join(SWARMCLAW_HOME, 'workspace')
|
|
38
|
+
const BROWSER_PROFILES_DIR = path.join(SWARMCLAW_HOME, 'browser-profiles')
|
|
31
39
|
|
|
32
40
|
// ---------------------------------------------------------------------------
|
|
33
41
|
// Helpers
|
|
@@ -67,6 +75,10 @@ function resolveStandaloneBase(pkgRoot = PKG_ROOT) {
|
|
|
67
75
|
return path.join(pkgRoot, '.next', 'standalone')
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
function isGitCheckout(pkgRoot = PKG_ROOT) {
|
|
79
|
+
return fs.existsSync(path.join(pkgRoot, '.git'))
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
function getVersion() {
|
|
71
83
|
return readPackageVersion(PKG_ROOT) || 'unknown'
|
|
72
84
|
}
|
|
@@ -99,11 +111,12 @@ function runBuild({ pkgRoot = PKG_ROOT } = {}) {
|
|
|
99
111
|
const nextCli = ensurePackageDependencies(pkgRoot)
|
|
100
112
|
|
|
101
113
|
log('Building Next.js application (this may take a minute)...')
|
|
102
|
-
execFileSync(process.execPath, [nextCli, 'build'], {
|
|
114
|
+
execFileSync(process.execPath, [nextCli, 'build', '--webpack'], {
|
|
103
115
|
cwd: pkgRoot,
|
|
104
116
|
stdio: 'inherit',
|
|
105
117
|
env: {
|
|
106
118
|
...process.env,
|
|
119
|
+
SWARMCLAW_HOME,
|
|
107
120
|
DATA_DIR,
|
|
108
121
|
SWARMCLAW_BUILD_MODE: '1',
|
|
109
122
|
},
|
|
@@ -162,7 +175,10 @@ function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
|
|
|
162
175
|
|
|
163
176
|
const env = {
|
|
164
177
|
...process.env,
|
|
178
|
+
SWARMCLAW_HOME,
|
|
165
179
|
DATA_DIR,
|
|
180
|
+
WORKSPACE_DIR,
|
|
181
|
+
BROWSER_PROFILES_DIR,
|
|
166
182
|
HOSTNAME: host,
|
|
167
183
|
PORT: port,
|
|
168
184
|
WS_PORT: wsPort,
|
|
@@ -170,6 +186,7 @@ function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
|
|
|
170
186
|
|
|
171
187
|
log(`Starting server on ${host}:${port} (WebSocket: ${wsPort})...`)
|
|
172
188
|
log(`Package root: ${pkgRoot}`)
|
|
189
|
+
log(`Home: ${SWARMCLAW_HOME}`)
|
|
173
190
|
log(`Data directory: ${DATA_DIR}`)
|
|
174
191
|
|
|
175
192
|
if (opts.detach) {
|
|
@@ -248,6 +265,8 @@ function showStatus() {
|
|
|
248
265
|
log(`Package: ${PKG_ROOT}`)
|
|
249
266
|
log(`Home: ${SWARMCLAW_HOME}`)
|
|
250
267
|
log(`Data: ${DATA_DIR}`)
|
|
268
|
+
log(`Workspace: ${WORKSPACE_DIR}`)
|
|
269
|
+
log(`Browser profiles: ${BROWSER_PROFILES_DIR}`)
|
|
251
270
|
log(`WebSocket port: ${process.env.WS_PORT || '(PORT + 1)'}`)
|
|
252
271
|
|
|
253
272
|
const serverJs = findStandaloneServer()
|
|
@@ -282,8 +301,7 @@ Options:
|
|
|
282
301
|
console.log(help)
|
|
283
302
|
}
|
|
284
303
|
|
|
285
|
-
function main() {
|
|
286
|
-
const args = process.argv.slice(3)
|
|
304
|
+
function main(args = process.argv.slice(3)) {
|
|
287
305
|
let command = 'start'
|
|
288
306
|
let forceBuild = false
|
|
289
307
|
let detach = false
|
|
@@ -330,7 +348,17 @@ function main() {
|
|
|
330
348
|
}
|
|
331
349
|
|
|
332
350
|
if (needsBuild(forceBuild)) {
|
|
333
|
-
|
|
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
|
+
}
|
|
334
362
|
}
|
|
335
363
|
|
|
336
364
|
startServer({ port, wsPort, host, detach })
|
|
@@ -342,10 +370,13 @@ if (require.main === module) {
|
|
|
342
370
|
|
|
343
371
|
module.exports = {
|
|
344
372
|
DATA_DIR,
|
|
373
|
+
BROWSER_PROFILES_DIR,
|
|
345
374
|
PKG_ROOT,
|
|
346
375
|
SWARMCLAW_HOME,
|
|
376
|
+
WORKSPACE_DIR,
|
|
347
377
|
findStandaloneServer,
|
|
348
378
|
getVersion,
|
|
379
|
+
isGitCheckout,
|
|
349
380
|
main,
|
|
350
381
|
needsBuild,
|
|
351
382
|
resolveStandaloneBase,
|
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
|
@@ -75,7 +75,6 @@ function runRegistrySelfUpdate(
|
|
|
75
75
|
packageManager = resolveRegistryPackageManager(),
|
|
76
76
|
execImpl = execFileSync,
|
|
77
77
|
logger = { log, logError },
|
|
78
|
-
rebuildImpl = execFileSync,
|
|
79
78
|
) {
|
|
80
79
|
const update = getGlobalUpdateSpec(packageManager, PACKAGE_NAME)
|
|
81
80
|
logger.log(`No git checkout detected. Updating the global ${PACKAGE_NAME} install via ${packageManager}...`)
|
|
@@ -92,15 +91,11 @@ function runRegistrySelfUpdate(
|
|
|
92
91
|
return 1
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
const rebuildExitCode = rebuildStandaloneServer(rebuildImpl, logger)
|
|
96
|
-
if (rebuildExitCode !== 0) return rebuildExitCode
|
|
97
|
-
|
|
98
94
|
logger.log('Restart the server to apply changes: swarmclaw server stop && swarmclaw server start')
|
|
99
95
|
return 0
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
function main() {
|
|
103
|
-
const args = process.argv.slice(3)
|
|
98
|
+
function main(args = process.argv.slice(3)) {
|
|
104
99
|
if (args.includes('-h') || args.includes('--help')) {
|
|
105
100
|
console.log(`
|
|
106
101
|
Usage: swarmclaw update
|
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
|
-
})
|