@swarmclawai/swarmclaw 0.7.4 → 0.7.6

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/README.md CHANGED
@@ -18,6 +18,7 @@ Inspired by [OpenClaw](https://github.com/openclaw).
18
18
 
19
19
  - [Getting Started](https://swarmclaw.ai/docs/getting-started) - install and first-run setup
20
20
  - [Providers](https://swarmclaw.ai/docs/providers) - provider setup and failover options
21
+ - [OpenClaw Setup](https://swarmclaw.ai/docs/openclaw-setup) - local, VPS, and hosted OpenClaw deployment paths
21
22
  - [Agents](https://swarmclaw.ai/docs/agents) - agent configuration, tools, and platform capabilities
22
23
  - [Tools](https://swarmclaw.ai/docs/tools) - built-in tool reference and guardrails
23
24
  - [Orchestration](https://swarmclaw.ai/docs/orchestration) - multi-agent flows, checkpoints, and restore
@@ -39,6 +40,20 @@ SwarmClaw includes the `openclaw` CLI as a bundled dependency, so there is no se
39
40
 
40
41
  The Providers screen now supports named OpenClaw gateway profiles with discovery, health checks, default-gateway selection, and an External Agent Runtimes view for remote workers that register/heartbeat into SwarmClaw.
41
42
 
43
+ SwarmClaw now also includes **Smart Deploy** for OpenClaw in three places:
44
+
45
+ - **Onboarding** - non-technical users can launch a local OpenClaw runtime or generate a remote bundle before they finish first-run setup
46
+ - **Providers -> OpenClaw Gateways** - operators can deploy or prepare more gateways later without leaving the main app
47
+ - **Gateway editor** - every gateway profile includes the same deploy panel for local restarts, VPS bundles, and hosted repo-backed deployments
48
+
49
+ The deployment flow stays **in-house and official-only**:
50
+
51
+ - local deploys run the bundled official `openclaw` CLI directly from SwarmClaw
52
+ - VPS deploys use the official OpenClaw Docker image with prefilled `.env`, `docker-compose.yml`, `bootstrap.sh`, and `cloud-init.yaml`
53
+ - hosted templates target the official OpenClaw repo for Render, Fly.io, and Railway
54
+
55
+ Supported VPS presets currently include Hetzner, DigitalOcean, Vultr, Linode, Lightsail, Google Cloud, Azure, OCI, and a generic Ubuntu host path. Smart defaults prefill the gateway token, endpoint, storage paths, and copy-paste commands so the resulting gateway can be saved into SwarmClaw with minimal manual editing.
56
+
42
57
  The OpenClaw Control Plane in SwarmClaw adds:
43
58
  - Reload mode switching (`hot`, `hybrid`, `full`)
44
59
  - Config issue detection and guided repair
@@ -59,6 +74,15 @@ Each agent can point to a **different** OpenClaw gateway profile or direct endpo
59
74
 
60
75
  URLs without a protocol are auto-prefixed with `http://`. For remote gateways with TLS, use `https://` explicitly.
61
76
 
77
+ CLI operators can use the same deploy surface without opening the UI:
78
+
79
+ ```bash
80
+ swarmclaw openclaw deploy-status
81
+ swarmclaw openclaw deploy-local-start --data '{"port":18789}'
82
+ swarmclaw openclaw deploy-bundle --data '{"template":"docker","provider":"hetzner","target":"openclaw.example.com"}'
83
+ swarmclaw openclaw deploy-bundle --data '{"template":"render","target":"https://openclaw.example.com"}'
84
+ ```
85
+
62
86
  ## SwarmClaw ClawHub Skill
63
87
 
64
88
  Use the `swarmclaw` ClawHub skill when you want an OpenClaw agent to operate your SwarmClaw control plane directly from chat: list agents, dispatch tasks, check chats, run diagnostics, and coordinate multi-agent work.
@@ -78,7 +102,6 @@ Skill source and runbook: [`swarmclaw/SKILL.md`](swarmclaw/SKILL.md).
78
102
 
79
103
  ## Requirements
80
104
 
81
- - **Node.js** 22.6+
82
105
  - **Node.js** 22.6+
83
106
  - One of: **npm** 10+, **pnpm**, **Yarn**, or **Bun**
84
107
  - **Claude Code CLI** (optional, for `claude-cli` provider) — [Install](https://docs.anthropic.com/en/docs/claude-code/overview)
@@ -117,7 +140,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
117
140
  ```
118
141
 
119
142
  The installer resolves the latest stable release tag and installs that version by default.
120
- To pin a version: `SWARMCLAW_VERSION=v0.7.4 curl ... | bash`
143
+ To pin a version: `SWARMCLAW_VERSION=v0.7.6 curl ... | bash`
121
144
 
122
145
  Or run locally from the repo (friendly for non-technical users):
123
146
 
@@ -670,7 +693,7 @@ npm run update:easy # safe update helper for local installs
670
693
  SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
671
694
 
672
695
  ```bash
673
- # example patch release (v0.7.4 style)
696
+ # example patch release (v0.7.6 style)
674
697
  npm version patch
675
698
  git push origin main --follow-tags
676
699
  ```
@@ -680,15 +703,15 @@ On `v*` tags, GitHub Actions will:
680
703
  2. Create a GitHub Release
681
704
  3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
682
705
 
683
- #### v0.7.4 Release Readiness Notes
706
+ #### v0.7.6 Release Readiness Notes
684
707
 
685
- Before shipping `v0.7.4`, confirm the following user-facing changes are reflected in docs:
708
+ Before shipping `v0.7.6`, confirm the following user-facing changes are reflected in docs:
686
709
 
687
710
  1. Sandbox docs are updated everywhere to reflect the current Deno-only `sandbox_exec` behavior and the guidance to prefer `http_request` for simple API calls.
688
- 2. OpenClaw docs cover the current gateway/runtime behavior, including per-agent gateway routing, control-plane actions, and inspector-side advanced controls.
689
- 3. Site and README install/version strings are updated to `v0.7.4`, including install snippets, release notes index text, and sidebar/footer labels.
690
- 4. Release notes summarize the user-visible setup/auth/runtime changes from the current worktree, especially gateway/external-agent/setup flow improvements.
691
- 5. CLI and tool docs do not reference removed or non-functional surfaces such as the old `openclaw_sandbox` bridge.
711
+ 2. OpenClaw docs cover the current gateway/runtime behavior, including Smart Deploy, official-only Docker/repo paths, local one-click startup, and the main-provider-screen deploy entry points.
712
+ 3. Site and README install/version strings are updated to `v0.7.6`, including install snippets, release notes index text, and sidebar/footer labels.
713
+ 4. Release notes summarize the user-visible setup/auth/runtime changes from the current worktree, especially Smart Deploy, VPS presets, and onboarding/provider-screen improvements.
714
+ 5. CLI and tool docs include the new `openclaw deploy-*` surfaces and do not reference removed or non-functional bridges.
692
715
 
693
716
  ## CLI
694
717
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "Self-hosted AI agent orchestration dashboard — manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -57,7 +57,7 @@
57
57
  "lint:baseline:update": "node ./scripts/lint-baseline.mjs update",
58
58
  "cli": "node ./bin/swarmclaw.js",
59
59
  "test:cli": "node --test src/cli/*.test.js bin/*.test.js",
60
- "test:openclaw": "tsx --test src/lib/openclaw-agent-id.test.ts src/lib/openclaw-endpoint.test.ts src/lib/server/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw-agent-resolver.test.ts src/lib/server/openclaw-skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/task-quality-gate.test.ts src/lib/server/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts",
60
+ "test:openclaw": "tsx --test src/lib/openclaw-agent-id.test.ts src/lib/openclaw-endpoint.test.ts src/lib/server/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw-agent-resolver.test.ts src/lib/server/openclaw-deploy.test.ts src/lib/server/openclaw-skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/task-quality-gate.test.ts src/lib/server/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts",
61
61
  "test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
62
62
  "postinstall": "node ./scripts/postinstall.mjs"
63
63
  },
@@ -1,98 +1,13 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { genId } from '@/lib/id'
3
- import { loadAgents, saveAgents, loadSessions, saveSessions } from '@/lib/server/storage'
4
- import { WORKSPACE_DIR } from '@/lib/server/data-dir'
5
- import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agent-runtime-config'
2
+ import { ensureAgentThreadSession } from '@/lib/server/agent-thread-session'
6
3
 
7
4
  export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
8
5
  const { id: agentId } = await params
9
- const agents = loadAgents()
10
- const agent = agents[agentId]
11
- if (!agent) {
12
- return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
13
- }
14
-
15
6
  const body = await req.json().catch(() => ({}))
16
7
  const user = body.user || 'default'
17
- const sessions = loadSessions()
18
-
19
- // If the agent already has a shortcut chat session, return it.
20
- if (agent.threadSessionId && sessions[agent.threadSessionId]) {
21
- const existing = sessions[agent.threadSessionId] as Record<string, unknown>
22
- let changed = false
23
- if (existing.shortcutForAgentId !== agentId) {
24
- existing.shortcutForAgentId = agentId
25
- changed = true
26
- }
27
- if (existing.name !== agent.name) {
28
- existing.name = agent.name
29
- changed = true
30
- }
31
- if (changed) saveSessions(sessions)
32
- return NextResponse.json(existing)
33
- }
34
-
35
- // Legacy fallback for older shortcut sessions that were named using the
36
- // old agent-thread convention before the explicit link was persisted.
37
- const existing = Object.values(sessions).find(
38
- (s: Record<string, unknown>) =>
39
- (
40
- s.shortcutForAgentId === agentId
41
- || s.name === `agent-thread:${agentId}`
42
- )
43
- && s.user === user
44
- )
45
- if (existing) {
46
- agent.threadSessionId = (existing as Record<string, unknown>).id as string
47
- agent.updatedAt = Date.now()
48
- saveAgents(agents)
49
- let changed = false
50
- const existingRecord = existing as Record<string, unknown>
51
- if (existingRecord.shortcutForAgentId !== agentId) {
52
- existingRecord.shortcutForAgentId = agentId
53
- changed = true
54
- }
55
- if (existingRecord.name !== agent.name) {
56
- existingRecord.name = agent.name
57
- changed = true
58
- }
59
- if (changed) saveSessions(sessions)
60
- return NextResponse.json(existing)
61
- }
62
-
63
- // Create a new shortcut chat session for this agent.
64
- const sessionId = `agent-chat-${agentId}-${genId()}`
65
- const now = Date.now()
66
- const baseSession = {
67
- id: sessionId,
68
- name: agent.name,
69
- shortcutForAgentId: agentId,
70
- cwd: WORKSPACE_DIR,
71
- user: user,
72
- provider: agent.provider,
73
- model: agent.model,
74
- credentialId: agent.credentialId || null,
75
- fallbackCredentialIds: agent.fallbackCredentialIds || [],
76
- apiEndpoint: agent.apiEndpoint || null,
77
- claudeSessionId: null,
78
- messages: [],
79
- createdAt: now,
80
- lastActiveAt: now,
81
- active: false,
82
- sessionType: 'human' as const,
83
- agentId,
84
- plugins: agent.plugins || agent.tools || [],
85
- heartbeatEnabled: agent.heartbeatEnabled || false,
86
- heartbeatIntervalSec: agent.heartbeatIntervalSec || null,
8
+ const session = ensureAgentThreadSession(agentId, user)
9
+ if (!session) {
10
+ return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
87
11
  }
88
- const session = applyResolvedRoute(baseSession, resolvePrimaryAgentRoute(agent))
89
-
90
- sessions[sessionId] = session as Record<string, unknown>
91
- saveSessions(sessions)
92
-
93
- agent.threadSessionId = sessionId
94
- agent.updatedAt = Date.now()
95
- saveAgents(agents)
96
-
97
12
  return NextResponse.json(session)
98
13
  }
@@ -0,0 +1,101 @@
1
+ import { NextResponse } from 'next/server'
2
+ import {
3
+ buildOpenClawDeployBundle,
4
+ getOpenClawLocalDeployStatus,
5
+ startOpenClawLocalDeploy,
6
+ stopOpenClawLocalDeploy,
7
+ type OpenClawRemoteDeployProvider,
8
+ type OpenClawRemoteDeployTemplate,
9
+ } from '@/lib/server/openclaw-deploy'
10
+
11
+ export const dynamic = 'force-dynamic'
12
+
13
+ function parsePort(value: unknown): number | undefined {
14
+ const parsed = typeof value === 'number'
15
+ ? value
16
+ : typeof value === 'string'
17
+ ? Number.parseInt(value, 10)
18
+ : Number.NaN
19
+ return Number.isFinite(parsed) ? parsed : undefined
20
+ }
21
+
22
+ function parseTemplate(value: unknown): OpenClawRemoteDeployTemplate | undefined {
23
+ if (value === 'docker' || value === 'render' || value === 'fly' || value === 'railway') {
24
+ return value
25
+ }
26
+ return undefined
27
+ }
28
+
29
+ function parseProvider(value: unknown): OpenClawRemoteDeployProvider | undefined {
30
+ if (
31
+ value === 'hetzner'
32
+ || value === 'digitalocean'
33
+ || value === 'vultr'
34
+ || value === 'linode'
35
+ || value === 'lightsail'
36
+ || value === 'gcp'
37
+ || value === 'azure'
38
+ || value === 'oci'
39
+ || value === 'generic'
40
+ ) {
41
+ return value
42
+ }
43
+ return undefined
44
+ }
45
+
46
+ export async function GET() {
47
+ return NextResponse.json({
48
+ local: getOpenClawLocalDeployStatus(),
49
+ })
50
+ }
51
+
52
+ export async function POST(req: Request) {
53
+ const body = await req.json().catch(() => ({}))
54
+ const action = typeof body?.action === 'string' ? body.action : ''
55
+
56
+ try {
57
+ if (action === 'start-local') {
58
+ const result = await startOpenClawLocalDeploy({
59
+ port: parsePort(body.port),
60
+ token: typeof body.token === 'string' ? body.token : null,
61
+ })
62
+ return NextResponse.json({
63
+ ok: true,
64
+ local: result.local,
65
+ token: result.token,
66
+ })
67
+ }
68
+
69
+ if (action === 'stop-local') {
70
+ return NextResponse.json({
71
+ ok: true,
72
+ local: stopOpenClawLocalDeploy(),
73
+ })
74
+ }
75
+
76
+ if (action === 'bundle') {
77
+ const bundle = buildOpenClawDeployBundle({
78
+ template: parseTemplate(body.template),
79
+ target: typeof body.target === 'string' ? body.target : null,
80
+ token: typeof body.token === 'string' ? body.token : null,
81
+ scheme: body.scheme === 'http' ? 'http' : 'https',
82
+ port: parsePort(body.port),
83
+ provider: parseProvider(body.provider),
84
+ })
85
+ return NextResponse.json({
86
+ ok: true,
87
+ bundle,
88
+ })
89
+ }
90
+
91
+ return NextResponse.json({ ok: false, error: 'Unknown deploy action.' }, { status: 400 })
92
+ } catch (err: unknown) {
93
+ return NextResponse.json(
94
+ {
95
+ ok: false,
96
+ error: err instanceof Error ? err.message : 'OpenClaw deploy action failed.',
97
+ },
98
+ { status: 500 },
99
+ )
100
+ }
101
+ }
package/src/cli/index.js CHANGED
@@ -310,6 +310,19 @@ const COMMAND_GROUPS = [
310
310
  description: 'OpenClaw discovery, gateway control, and runtime APIs',
311
311
  commands: [
312
312
  cmd('discover', 'GET', '/openclaw/discover', 'Discover OpenClaw gateways'),
313
+ cmd('deploy-status', 'GET', '/openclaw/deploy', 'Get managed OpenClaw deploy status'),
314
+ cmd('deploy-local-start', 'POST', '/openclaw/deploy', 'Start a managed local OpenClaw deployment (use --data JSON for port/token overrides)', {
315
+ expectsJsonBody: true,
316
+ defaultBody: { action: 'start-local' },
317
+ }),
318
+ cmd('deploy-local-stop', 'POST', '/openclaw/deploy', 'Stop the managed local OpenClaw deployment', {
319
+ expectsJsonBody: true,
320
+ defaultBody: { action: 'stop-local' },
321
+ }),
322
+ cmd('deploy-bundle', 'POST', '/openclaw/deploy', 'Generate an OpenClaw remote deployment bundle (use --data JSON for template/target/token)', {
323
+ expectsJsonBody: true,
324
+ defaultBody: { action: 'bundle' },
325
+ }),
313
326
  cmd('directory', 'GET', '/openclaw/directory', 'List directory entries from running OpenClaw connectors'),
314
327
  cmd('gateway-status', 'GET', '/openclaw/gateway', 'Check OpenClaw gateway connection status'),
315
328
  cmd('gateway', 'POST', '/openclaw/gateway', 'Call OpenClaw gateway RPC/control action', { expectsJsonBody: true }),
@@ -163,6 +163,40 @@ test('runCli sends authenticated request and emits compact JSON when --json is s
163
163
  assert.equal(stderr.toString(), '')
164
164
  })
165
165
 
166
+ test('openclaw deploy bundle command merges action with provided JSON body', async () => {
167
+ const stdout = makeWritable()
168
+ const stderr = makeWritable()
169
+ const calls = []
170
+
171
+ const fetchImpl = async (url, init) => {
172
+ calls.push({ url: String(url), init })
173
+ return jsonResponse({ ok: true, bundle: { template: 'docker' } })
174
+ }
175
+
176
+ const exitCode = await runCli(
177
+ ['openclaw', 'deploy-bundle', '--data', '{"template":"docker","target":"openclaw.example.com"}', '--json'],
178
+ {
179
+ fetchImpl,
180
+ stdout,
181
+ stderr,
182
+ env: {},
183
+ cwd: process.cwd(),
184
+ }
185
+ )
186
+
187
+ assert.equal(exitCode, 0)
188
+ assert.equal(calls.length, 1)
189
+ assert.match(calls[0].url, /\/api\/openclaw\/deploy$/)
190
+ assert.equal(calls[0].init.method, 'POST')
191
+ assert.deepEqual(JSON.parse(String(calls[0].init.body)), {
192
+ action: 'bundle',
193
+ template: 'docker',
194
+ target: 'openclaw.example.com',
195
+ })
196
+ assert.equal(stdout.toString().trim(), '{"ok":true,"bundle":{"template":"docker"}}')
197
+ assert.equal(stderr.toString(), '')
198
+ })
199
+
166
200
  test('runCli falls back to platform-api-key.txt when env key is missing', async () => {
167
201
  const stdout = makeWritable()
168
202
  const stderr = makeWritable()
package/src/cli/spec.js CHANGED
@@ -211,6 +211,25 @@ const COMMAND_GROUPS = {
211
211
  description: 'OpenClaw discovery, gateway control, and runtime APIs',
212
212
  commands: {
213
213
  discover: { description: 'Discover OpenClaw gateways', method: 'GET', path: '/openclaw/discover' },
214
+ 'deploy-status': { description: 'Get managed OpenClaw deploy status', method: 'GET', path: '/openclaw/deploy' },
215
+ 'deploy-local-start': {
216
+ description: 'Start a managed local OpenClaw deployment (use --data JSON for port/token overrides)',
217
+ method: 'POST',
218
+ path: '/openclaw/deploy',
219
+ staticBody: { action: 'start-local' },
220
+ },
221
+ 'deploy-local-stop': {
222
+ description: 'Stop the managed local OpenClaw deployment',
223
+ method: 'POST',
224
+ path: '/openclaw/deploy',
225
+ staticBody: { action: 'stop-local' },
226
+ },
227
+ 'deploy-bundle': {
228
+ description: 'Generate an OpenClaw remote deployment bundle (use --data JSON for template/target/token)',
229
+ method: 'POST',
230
+ path: '/openclaw/deploy',
231
+ staticBody: { action: 'bundle' },
232
+ },
214
233
  directory: { description: 'List directory entries from running OpenClaw connectors', method: 'GET', path: '/openclaw/directory' },
215
234
  'gateway-status': { description: 'Check OpenClaw gateway connection status', method: 'GET', path: '/openclaw/gateway' },
216
235
  gateway: { description: 'Call OpenClaw gateway RPC/control action', method: 'POST', path: '/openclaw/gateway' },
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useMemo, useState } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { OpenClawDeployPanel } from '@/components/openclaw/openclaw-deploy-panel'
5
6
  import { useAppStore } from '@/stores/use-app-store'
6
7
  import type { ProviderType, Credential, GatewayProfile } from '@/types'
7
8
  import {
@@ -142,12 +143,6 @@ function isLocalOpenClawEndpoint(value: string | null | undefined): boolean {
142
143
  return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '0.0.0.0'
143
144
  }
144
145
 
145
- function resolveOpenClawPort(value: string | null | undefined): number {
146
- const parsed = parseProviderUrl(value)
147
- const port = parsed ? Number(parsed.port) : NaN
148
- return Number.isFinite(port) && port > 0 ? port : 18789
149
- }
150
-
151
146
  function resolveOpenClawDashboardUrl(value: string | null | undefined): string {
152
147
  const parsed = parseProviderUrl(value)
153
148
  if (!parsed) return 'http://localhost:18789'
@@ -342,7 +337,6 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
342
337
  const [checkErrorCode, setCheckErrorCode] = useState<string | null>(null)
343
338
  const [openclawDeviceId, setOpenclawDeviceId] = useState<string | null>(null)
344
339
  const [providerSuggestedModel, setProviderSuggestedModel] = useState('')
345
- const [commandCopyState, setCommandCopyState] = useState<'idle' | 'copied' | 'failed'>('idle')
346
340
 
347
341
  const [doctorState, setDoctorState] = useState<'idle' | 'checking' | 'done' | 'error'>('idle')
348
342
  const [doctorError, setDoctorError] = useState('')
@@ -381,9 +375,6 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
381
375
  ? resolveOpenClawDashboardUrl(openClawEndpointValue)
382
376
  : null
383
377
  const openClawLocal = provider === 'openclaw' ? isLocalOpenClawEndpoint(openClawEndpointValue) : false
384
- const openClawPort = provider === 'openclaw' ? resolveOpenClawPort(openClawEndpointValue) : 18789
385
- const openClawLocalCommand = `npx openclaw gateway run --bind loopback --port ${openClawPort} --verbose`
386
- const openClawLocalCommandPnpm = `pnpm openclaw gateway run --bind loopback --port ${openClawPort} --verbose`
387
378
 
388
379
  const resetProviderForm = () => {
389
380
  setProvider(null)
@@ -396,7 +387,6 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
396
387
  setCheckErrorCode(null)
397
388
  setOpenclawDeviceId(null)
398
389
  setProviderSuggestedModel('')
399
- setCommandCopyState('idle')
400
390
  setError('')
401
391
  }
402
392
 
@@ -447,11 +437,31 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
447
437
  setCheckErrorCode(null)
448
438
  setOpenclawDeviceId(null)
449
439
  setProviderSuggestedModel(getDefaultModelForProvider(nextProvider))
450
- setCommandCopyState('idle')
451
440
  setError('')
452
441
  setStep('connect')
453
442
  }
454
443
 
444
+ const applyOpenClawDeployPatch = (patch: {
445
+ endpoint?: string
446
+ token?: string
447
+ name?: string
448
+ }) => {
449
+ if (patch.endpoint) {
450
+ setEndpoint(patch.endpoint)
451
+ }
452
+ if (patch.token) {
453
+ setApiKey(patch.token)
454
+ setCredentialId(null)
455
+ }
456
+ if (patch.name && (!providerLabel.trim() || providerLabel.trim() === (selectedProvider?.name || ''))) {
457
+ setProviderLabel(patch.name)
458
+ }
459
+ setCheckState('idle')
460
+ setCheckMessage('')
461
+ setCheckErrorCode(null)
462
+ setError('')
463
+ }
464
+
455
465
  const runConnectionCheck = async (): Promise<boolean> => {
456
466
  if (!provider || !selectedProvider) return false
457
467
  if (requiresKey && !apiKey.trim()) {
@@ -603,17 +613,6 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
603
613
  }))
604
614
  }
605
615
 
606
- const copyOpenClawLocalCommand = async () => {
607
- try {
608
- await navigator.clipboard.writeText(openClawLocalCommand)
609
- setCommandCopyState('copied')
610
- window.setTimeout(() => setCommandCopyState('idle'), 1200)
611
- } catch {
612
- setCommandCopyState('failed')
613
- window.setTimeout(() => setCommandCopyState('idle'), 1800)
614
- }
615
- }
616
-
617
616
  const createAgentsAndFinish = async () => {
618
617
  const enabledDrafts = draftAgents.filter((draft) => draft.enabled)
619
618
  if (enabledDrafts.some((draft) => !draft.provider)) {
@@ -1063,6 +1062,16 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
1063
1062
 
1064
1063
  {provider === 'openclaw' && (
1065
1064
  <div className="rounded-[14px] border border-white/[0.08] bg-surface p-4 space-y-4">
1065
+ <OpenClawDeployPanel
1066
+ compact
1067
+ endpoint={openClawEndpointValue}
1068
+ token={apiKey}
1069
+ suggestedName={providerLabel || selectedProvider.name}
1070
+ title="Smart Deploy OpenClaw"
1071
+ description="Launch the bundled official OpenClaw gateway locally, or generate an official-image VPS bundle for major providers without relying on third-party deployment services."
1072
+ onApply={applyOpenClawDeployPatch}
1073
+ />
1074
+
1066
1075
  <div className="grid gap-3 md:grid-cols-2">
1067
1076
  <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-3">
1068
1077
  <div className="text-[12px] uppercase tracking-[0.08em] text-text-3 mb-2">Remote gateway</div>
@@ -1077,37 +1086,12 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
1077
1086
  </p>
1078
1087
  </div>
1079
1088
  <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-3">
1080
- <div className="text-[12px] uppercase tracking-[0.08em] text-text-3 mb-2">Run locally</div>
1089
+ <div className="text-[12px] uppercase tracking-[0.08em] text-text-3 mb-2">Safe defaults</div>
1081
1090
  <p className="text-[13px] text-text-2 leading-relaxed">
1082
- Use this when SwarmClaw and OpenClaw are on the same host. <code className="text-text-2">localhost</code> always refers to the SwarmClaw host.
1091
+ Smart Deploy generates a gateway token for you, defaults to the standard OpenClaw ports, and prefills this setup form automatically.
1083
1092
  </p>
1084
- <div className="mt-3 rounded-[10px] border border-white/[0.06] bg-surface px-3 py-2">
1085
- <code className="block overflow-x-auto whitespace-nowrap text-[12px] text-text-2">
1086
- {openClawLocalCommand}
1087
- </code>
1088
- </div>
1089
- <div className="mt-2 flex items-center gap-2">
1090
- <button
1091
- type="button"
1092
- onClick={copyOpenClawLocalCommand}
1093
- className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-white/[0.03] text-[12px] text-text cursor-pointer hover:bg-white/[0.06] transition-all duration-200"
1094
- >
1095
- {commandCopyState === 'copied'
1096
- ? 'Copied'
1097
- : commandCopyState === 'failed'
1098
- ? 'Copy failed'
1099
- : 'Copy command'}
1100
- </button>
1101
- <button
1102
- type="button"
1103
- onClick={() => { setEndpoint(selectedProvider.defaultEndpoint || 'http://localhost:18789/v1'); setCheckState('idle'); setCheckMessage(''); setCheckErrorCode(null) }}
1104
- className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-white/[0.03] text-[12px] text-text cursor-pointer hover:bg-white/[0.06] transition-all duration-200"
1105
- >
1106
- Use local default
1107
- </button>
1108
- </div>
1109
- <p className="mt-2 text-[11px] text-text-3">
1110
- In a source checkout, use <code className="text-text-2">{openClawLocalCommandPnpm}</code>.
1093
+ <p className="mt-2 text-[12px] text-text-3 leading-relaxed">
1094
+ Local quickstart uses the bundled official OpenClaw CLI. Remote quickstart uses the official OpenClaw Docker image or the official repo for managed hosts.
1111
1095
  </p>
1112
1096
  </div>
1113
1097
  </div>