@agentsquared/cli 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.
@@ -0,0 +1,89 @@
1
+ import { spawn } from 'node:child_process'
2
+
3
+ function clean(value) {
4
+ return `${value ?? ''}`.trim()
5
+ }
6
+
7
+ export function parseOpenClawJson(text) {
8
+ const trimmed = clean(text)
9
+ if (!trimmed) {
10
+ return null
11
+ }
12
+ try {
13
+ return JSON.parse(trimmed)
14
+ } catch {
15
+ const lines = trimmed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
16
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
17
+ const candidate = lines.slice(index).join('\n')
18
+ try {
19
+ return JSON.parse(candidate)
20
+ } catch {
21
+ // keep scanning upward for the first trailing valid JSON payload
22
+ }
23
+ }
24
+ return null
25
+ }
26
+ }
27
+
28
+ export function openClawCliEnv(baseEnv = process.env) {
29
+ return {
30
+ ...baseEnv,
31
+ OPENCLAW_LOG_LEVEL: 'error'
32
+ }
33
+ }
34
+
35
+ export function runOpenClawCli(command, args, {
36
+ cwd = '',
37
+ timeoutMs = 10000,
38
+ env = process.env
39
+ } = {}) {
40
+ const normalizedCommand = clean(command) || 'openclaw'
41
+ return new Promise((resolve, reject) => {
42
+ const child = spawn(normalizedCommand, args, {
43
+ cwd: clean(cwd) || undefined,
44
+ env: openClawCliEnv(env),
45
+ stdio: ['ignore', 'pipe', 'pipe']
46
+ })
47
+ let stdout = ''
48
+ let stderr = ''
49
+ let settled = false
50
+ const timer = setTimeout(() => {
51
+ if (settled) {
52
+ return
53
+ }
54
+ settled = true
55
+ child.kill('SIGTERM')
56
+ reject(new Error(`${normalizedCommand} ${args.join(' ')} timed out after ${timeoutMs}ms`))
57
+ }, Math.max(500, timeoutMs))
58
+
59
+ child.stdout.on('data', (chunk) => {
60
+ stdout += chunk.toString()
61
+ })
62
+ child.stderr.on('data', (chunk) => {
63
+ stderr += chunk.toString()
64
+ })
65
+ child.on('error', (error) => {
66
+ if (settled) {
67
+ return
68
+ }
69
+ settled = true
70
+ clearTimeout(timer)
71
+ reject(error)
72
+ })
73
+ child.on('close', (code) => {
74
+ if (settled) {
75
+ return
76
+ }
77
+ settled = true
78
+ clearTimeout(timer)
79
+ if (code !== 0) {
80
+ reject(new Error(clean(stderr) || `${normalizedCommand} ${args.join(' ')} exited with status ${code}`))
81
+ return
82
+ }
83
+ resolve({
84
+ stdout: stdout.trim(),
85
+ stderr: stderr.trim()
86
+ })
87
+ })
88
+ })
89
+ }
@@ -0,0 +1,259 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+
5
+ import { resolveOpenClawGatewayBootstrap, withOpenClawGatewayClient } from './ws_client.mjs'
6
+
7
+ function clean(value) {
8
+ return `${value ?? ''}`.trim()
9
+ }
10
+
11
+ function readJson(filePath = '') {
12
+ try {
13
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
14
+ } catch {
15
+ return null
16
+ }
17
+ }
18
+
19
+ function asArray(value) {
20
+ return Array.isArray(value) ? value : []
21
+ }
22
+
23
+ function normalizeAgentEntries(value) {
24
+ return asArray(value)
25
+ .filter((item) => item && typeof item === 'object')
26
+ .map((item) => ({
27
+ ...item,
28
+ resolvedId: clean(item?.id || item?.agentId || item?.name),
29
+ resolvedWorkspaceDir: clean(item?.workspaceDir || item?.workspace || item?.agentDir),
30
+ isDefault: Boolean(item?.isDefault || item?.default)
31
+ }))
32
+ .filter((item) => item.resolvedId || item.resolvedWorkspaceDir)
33
+ }
34
+
35
+ function defaultOpenClawConfigPath() {
36
+ return path.join(os.homedir(), '.openclaw', 'openclaw.json')
37
+ }
38
+
39
+ function summarizeConfig(configPath = '') {
40
+ const resolvedPath = clean(configPath) || defaultOpenClawConfigPath()
41
+ const config = readJson(resolvedPath)
42
+ if (!config || typeof config !== 'object') {
43
+ return {
44
+ exists: false,
45
+ path: resolvedPath,
46
+ defaultAgentId: '',
47
+ workspaceDir: '',
48
+ agents: []
49
+ }
50
+ }
51
+ const agents = normalizeAgentEntries(config?.agents?.list)
52
+ const defaultAgent = agents.find((entry) => entry.isDefault) ?? agents[0] ?? null
53
+ return {
54
+ exists: true,
55
+ path: resolvedPath,
56
+ defaultAgentId: clean(defaultAgent?.resolvedId) || 'main',
57
+ workspaceDir: clean(defaultAgent?.resolvedWorkspaceDir || config?.agents?.defaults?.workspace),
58
+ agents
59
+ }
60
+ }
61
+
62
+ function extractOpenClawAgentInfo(payload = null) {
63
+ const root = payload && typeof payload === 'object' ? payload : {}
64
+ const container = root?.agents && typeof root.agents === 'object' ? root.agents : root
65
+ const nestedAgents = normalizeAgentEntries(container?.agents)
66
+ const directAgents = normalizeAgentEntries(container)
67
+ const agents = nestedAgents.length > 0 ? nestedAgents : directAgents
68
+ const defaultId = clean(
69
+ container?.defaultId
70
+ || container?.defaultAgentId
71
+ || root?.defaultId
72
+ || root?.defaultAgentId
73
+ )
74
+ const defaultAgent = agents.find((entry) => entry.resolvedId === defaultId)
75
+ ?? agents.find((entry) => entry.isDefault)
76
+ ?? agents[0]
77
+ ?? null
78
+ return {
79
+ defaultAgentId: defaultId || clean(defaultAgent?.resolvedId),
80
+ workspaceDir: clean(defaultAgent?.resolvedWorkspaceDir),
81
+ agents
82
+ }
83
+ }
84
+
85
+ export function resolveOpenClawAgentSelection(detectedHostRuntime = null) {
86
+ const agentsList = extractOpenClawAgentInfo(detectedHostRuntime?.agentsList)
87
+ const overview = extractOpenClawAgentInfo(detectedHostRuntime?.overviewStatus)
88
+ const gatewayHealth = extractOpenClawAgentInfo(detectedHostRuntime?.gatewayHealth)
89
+ const configSummary = detectedHostRuntime?.configSummary && typeof detectedHostRuntime.configSummary === 'object'
90
+ ? detectedHostRuntime.configSummary
91
+ : summarizeConfig(clean(detectedHostRuntime?.configPath))
92
+ const defaultAgentId = clean(
93
+ agentsList.defaultAgentId
94
+ || overview.defaultAgentId
95
+ || gatewayHealth.defaultAgentId
96
+ || configSummary.defaultAgentId
97
+ )
98
+ const workspaceDir = clean(
99
+ agentsList.workspaceDir
100
+ || overview.workspaceDir
101
+ || gatewayHealth.workspaceDir
102
+ || configSummary.workspaceDir
103
+ )
104
+ return {
105
+ defaultAgentId,
106
+ workspaceDir,
107
+ configSummary
108
+ }
109
+ }
110
+
111
+ async function requestProbe(client, method, params = {}, timeoutMs = 10000) {
112
+ try {
113
+ const payload = await client.request(method, params, timeoutMs)
114
+ return {
115
+ ok: true,
116
+ reason: 'ok',
117
+ payload
118
+ }
119
+ } catch (error) {
120
+ const message = clean(error?.message)
121
+ return {
122
+ ok: false,
123
+ reason: message.includes('timed out after') ? 'timeout' : (message || 'request-error'),
124
+ error: message
125
+ }
126
+ }
127
+ }
128
+
129
+ async function probeOpenClawGatewayWs(options = {}) {
130
+ try {
131
+ return await withOpenClawGatewayClient({
132
+ ...options,
133
+ pairingStrategy: 'none',
134
+ connectTimeoutMs: 10000,
135
+ requestTimeoutMs: 10000
136
+ }, async (client, bootstrap) => {
137
+ const [health, agentsList, status] = await Promise.all([
138
+ requestProbe(client, 'health'),
139
+ requestProbe(client, 'agents.list'),
140
+ requestProbe(client, 'status')
141
+ ])
142
+ return {
143
+ ok: health.ok || agentsList.ok || status.ok,
144
+ bootstrap,
145
+ health,
146
+ agentsList,
147
+ status
148
+ }
149
+ })
150
+ } catch (error) {
151
+ return {
152
+ ok: false,
153
+ error: clean(error?.message) || 'gateway-connect-error'
154
+ }
155
+ }
156
+ }
157
+
158
+ export async function detectOpenClawHostEnvironment({
159
+ configPath = '',
160
+ gatewayUrl = '',
161
+ gatewayToken = '',
162
+ gatewayPassword = ''
163
+ } = {}) {
164
+ const bootstrap = await resolveOpenClawGatewayBootstrap({
165
+ configPath,
166
+ gatewayUrl,
167
+ gatewayToken,
168
+ gatewayPassword
169
+ })
170
+ const resolvedConfigPath = clean(bootstrap.configPath) || defaultOpenClawConfigPath()
171
+ const configSummary = summarizeConfig(resolvedConfigPath)
172
+ const wsProbe = await probeOpenClawGatewayWs({
173
+ gatewayUrl: clean(bootstrap.gatewayUrl),
174
+ gatewayToken: clean(bootstrap.gatewayToken),
175
+ gatewayPassword: clean(bootstrap.gatewayPassword)
176
+ })
177
+ const agentsListPayload = wsProbe?.agentsList?.ok ? wsProbe.agentsList.payload : null
178
+ const statusPayload = wsProbe?.status?.ok ? wsProbe.status.payload : null
179
+ const healthPayload = wsProbe?.health?.ok ? wsProbe.health.payload : null
180
+ const selection = resolveOpenClawAgentSelection({
181
+ agentsList: agentsListPayload,
182
+ overviewStatus: statusPayload,
183
+ gatewayHealth: healthPayload,
184
+ configSummary,
185
+ configPath: resolvedConfigPath
186
+ })
187
+ const workspaceDir = clean(selection.workspaceDir)
188
+ if (wsProbe?.agentsList?.ok && agentsListPayload) {
189
+ return {
190
+ id: 'openclaw',
191
+ detected: true,
192
+ confidence: 'high',
193
+ reason: 'openclaw-ws-agents-list',
194
+ agentsList: agentsListPayload,
195
+ overviewStatus: statusPayload,
196
+ gatewayHealth: healthPayload,
197
+ configSummary,
198
+ configPath: resolvedConfigPath,
199
+ gatewayBootstrap: wsProbe.bootstrap || bootstrap,
200
+ workspaceDir,
201
+ rpcHealthy: true
202
+ }
203
+ }
204
+
205
+ if (wsProbe?.status?.ok && statusPayload) {
206
+ return {
207
+ id: 'openclaw',
208
+ detected: true,
209
+ confidence: 'medium',
210
+ reason: 'openclaw-ws-status',
211
+ overviewStatus: statusPayload,
212
+ gatewayHealth: healthPayload,
213
+ configSummary,
214
+ configPath: resolvedConfigPath,
215
+ gatewayBootstrap: wsProbe.bootstrap || bootstrap,
216
+ workspaceDir
217
+ }
218
+ }
219
+
220
+ if (wsProbe?.health?.ok && healthPayload) {
221
+ return {
222
+ id: 'openclaw',
223
+ detected: true,
224
+ confidence: 'low',
225
+ reason: 'openclaw-ws-health',
226
+ gatewayHealth: healthPayload,
227
+ overviewStatus: statusPayload,
228
+ configSummary,
229
+ configPath: resolvedConfigPath,
230
+ gatewayBootstrap: wsProbe.bootstrap || bootstrap,
231
+ workspaceDir
232
+ }
233
+ }
234
+
235
+ if (configSummary.exists) {
236
+ return {
237
+ id: 'openclaw',
238
+ detected: true,
239
+ confidence: 'low',
240
+ reason: 'openclaw-config-present',
241
+ overviewStatus: statusPayload,
242
+ gatewayHealth: healthPayload,
243
+ agentsList: agentsListPayload,
244
+ configSummary,
245
+ configPath: resolvedConfigPath,
246
+ gatewayBootstrap: bootstrap,
247
+ gatewayProbeError: clean(wsProbe?.error || wsProbe?.agentsList?.error || wsProbe?.status?.error || wsProbe?.health?.error),
248
+ workspaceDir
249
+ }
250
+ }
251
+
252
+ return {
253
+ id: 'none',
254
+ detected: false,
255
+ confidence: 'low',
256
+ reason: 'no-supported-host-runtime-detected',
257
+ suggested: 'openclaw'
258
+ }
259
+ }