@swarmclawai/swarmclaw 1.7.1 → 1.7.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/README.md +10 -0
- package/electron-dist/main.js +218 -0
- package/package.json +2 -2
- package/scripts/run-next-build.mjs +1 -1
- package/src/app/api/setup/check-provider/route.ts +5 -62
- package/src/app/api/setup/doctor/route.ts +19 -9
- package/src/components/auth/setup-wizard/step-providers.tsx +81 -42
- package/src/components/layout/update-banner.tsx +43 -9
- package/src/lib/provider-sets.test.ts +19 -0
- package/src/lib/provider-sets.ts +8 -3
- package/src/lib/providers/cli-provider-metadata.test.ts +38 -0
- package/src/lib/providers/cli-provider-metadata.ts +208 -0
- package/src/lib/providers/cli-utils.ts +4 -43
- package/src/lib/providers/generic-cli.ts +2 -31
- package/src/lib/providers/index.ts +14 -44
- package/src/lib/server/cli-provider-readiness.test.ts +45 -0
- package/src/lib/server/cli-provider-readiness.ts +84 -0
- package/src/lib/server/provider-health.test.ts +6 -0
- package/src/lib/server/provider-health.ts +2 -2
- package/src/lib/setup-defaults.test.ts +8 -0
- package/src/lib/setup-defaults.ts +38 -178
- package/tsconfig.json +1 -0
package/README.md
CHANGED
|
@@ -399,6 +399,16 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
399
399
|
|
|
400
400
|
## Releases
|
|
401
401
|
|
|
402
|
+
### v1.7.2 Highlights
|
|
403
|
+
|
|
404
|
+
CLI provider usability follow-up for v1.7.0/v1.7.1. The expanded coding-agent roster is now easier to find, configure, and validate from onboarding and setup diagnostics.
|
|
405
|
+
|
|
406
|
+
- **Shared CLI provider registry.** Bespoke and generic CLI providers now share one metadata source for display names, binary names, capabilities, setup defaults, and provider-set behavior, reducing drift across onboarding, runtime routing, setup doctor, and capability prompts.
|
|
407
|
+
- **Onboarding exposes the full CLI roster.** The setup wizard groups providers by CLI agents, gateways/local runtimes, API providers, and custom endpoints, with search so the 31 extended CLI providers added in v1.7 are usable without digging through settings.
|
|
408
|
+
- **Connection checks for every CLI provider.** Bespoke CLIs keep auth-aware checks, while generic CLIs verify that the expected binary is on PATH and return actionable install guidance when missing.
|
|
409
|
+
- **Update banner polish.** Source installs now show the target stable tag/version, remember dismissal per release target, and make the required restart after update explicit.
|
|
410
|
+
- **macOS desktop note.** macOS builds remain ad-hoc signed and not notarized in this release, so the existing Gatekeeper/quarantine workaround still applies until Developer ID signing is available.
|
|
411
|
+
|
|
402
412
|
### v1.7.1 Highlights
|
|
403
413
|
|
|
404
414
|
Republish of v1.7.0 from the correct commit. The v1.7.0 tarball on npm was inadvertently published from a pre-rebase tree that did not include the v1.6.1 codex continuity fixes (PR #62) or the v1.6.2 plan doc. v1.7.1 ships the same coding-agent-roster expansion on top of the correct v1.6.1+ history.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const electron_1 = require("electron");
|
|
40
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
41
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
42
|
+
const paths_1 = require("./paths");
|
|
43
|
+
const server_lifecycle_1 = require("./server-lifecycle");
|
|
44
|
+
const menu_1 = require("./menu");
|
|
45
|
+
const DEV_URL_DEFAULT = 'http://127.0.0.1:3456';
|
|
46
|
+
const LOG_TAIL_BYTES = 1500;
|
|
47
|
+
let mainWindow = null;
|
|
48
|
+
let serverHandle = null;
|
|
49
|
+
let serverLogFile = null;
|
|
50
|
+
let isQuitting = false;
|
|
51
|
+
const gotLock = electron_1.app.requestSingleInstanceLock();
|
|
52
|
+
if (!gotLock) {
|
|
53
|
+
electron_1.app.quit();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
electron_1.app.on('second-instance', () => {
|
|
57
|
+
if (mainWindow) {
|
|
58
|
+
if (mainWindow.isMinimized())
|
|
59
|
+
mainWindow.restore();
|
|
60
|
+
mainWindow.focus();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
electron_1.app.on('ready', () => void onReady());
|
|
64
|
+
electron_1.app.on('window-all-closed', () => {
|
|
65
|
+
if (process.platform !== 'darwin')
|
|
66
|
+
electron_1.app.quit();
|
|
67
|
+
});
|
|
68
|
+
electron_1.app.on('activate', () => {
|
|
69
|
+
if (mainWindow !== null)
|
|
70
|
+
return;
|
|
71
|
+
if (serverHandle) {
|
|
72
|
+
createMainWindow(serverHandle.url);
|
|
73
|
+
}
|
|
74
|
+
else if (!electron_1.app.isPackaged) {
|
|
75
|
+
createMainWindow(process.env.SWARMCLAW_DEV_URL || DEV_URL_DEFAULT);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
electron_1.app.on('before-quit', () => {
|
|
79
|
+
isQuitting = true;
|
|
80
|
+
});
|
|
81
|
+
electron_1.app.on('will-quit', async (event) => {
|
|
82
|
+
if (!serverHandle)
|
|
83
|
+
return;
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
try {
|
|
86
|
+
await serverHandle.stop();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
serverHandle = null;
|
|
90
|
+
electron_1.app.exit(0);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function onReady() {
|
|
95
|
+
const paths = (0, paths_1.resolveRuntimePaths)();
|
|
96
|
+
(0, menu_1.buildAppMenu)(paths, () => mainWindow);
|
|
97
|
+
const iconPath = resolveIconPath();
|
|
98
|
+
if (process.platform === 'darwin' && iconPath && electron_1.app.dock) {
|
|
99
|
+
const img = electron_1.nativeImage.createFromPath(iconPath);
|
|
100
|
+
if (!img.isEmpty())
|
|
101
|
+
electron_1.app.dock.setIcon(img);
|
|
102
|
+
}
|
|
103
|
+
if (!electron_1.app.isPackaged) {
|
|
104
|
+
const devUrl = process.env.SWARMCLAW_DEV_URL || DEV_URL_DEFAULT;
|
|
105
|
+
console.log(`[swarmclaw] dev mode, loading ${devUrl}`);
|
|
106
|
+
createMainWindow(devUrl);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
serverLogFile = node_path_1.default.join(electron_1.app.getPath('userData'), 'logs', 'server.log');
|
|
110
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(serverLogFile), { recursive: true });
|
|
111
|
+
try {
|
|
112
|
+
serverHandle = await (0, server_lifecycle_1.startEmbeddedServer)({
|
|
113
|
+
paths,
|
|
114
|
+
logFile: serverLogFile,
|
|
115
|
+
onStdout: (c) => process.stdout.write(`[swarmclaw] ${c}`),
|
|
116
|
+
onStderr: (c) => process.stderr.write(`[swarmclaw] ${c}`),
|
|
117
|
+
onExit: (code, signal) => {
|
|
118
|
+
if (!isQuitting) {
|
|
119
|
+
console.error(`[swarmclaw] server exited unexpectedly (code=${code}, signal=${signal ?? 'none'})`);
|
|
120
|
+
void showServerCrashDialog(code, signal);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
await showStartupFailureDialog(err, paths);
|
|
127
|
+
electron_1.app.exit(1);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
createMainWindow(serverHandle.url);
|
|
131
|
+
void Promise.resolve().then(() => __importStar(require('./updater'))).then((m) => m.initAutoUpdater());
|
|
132
|
+
}
|
|
133
|
+
function resolveIconPath() {
|
|
134
|
+
const candidate = electron_1.app.isPackaged
|
|
135
|
+
? node_path_1.default.join(process.resourcesPath, 'icon.png')
|
|
136
|
+
: node_path_1.default.join(__dirname, '..', 'resources', 'icon.png');
|
|
137
|
+
return node_fs_1.default.existsSync(candidate) ? candidate : undefined;
|
|
138
|
+
}
|
|
139
|
+
function createMainWindow(startUrl) {
|
|
140
|
+
const iconPath = resolveIconPath();
|
|
141
|
+
mainWindow = new electron_1.BrowserWindow({
|
|
142
|
+
width: 1440,
|
|
143
|
+
height: 900,
|
|
144
|
+
minWidth: 1024,
|
|
145
|
+
minHeight: 640,
|
|
146
|
+
backgroundColor: '#0b0b0f',
|
|
147
|
+
show: true,
|
|
148
|
+
...(iconPath ? { icon: iconPath } : {}),
|
|
149
|
+
webPreferences: {
|
|
150
|
+
contextIsolation: true,
|
|
151
|
+
nodeIntegration: false,
|
|
152
|
+
sandbox: false,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
const wc = mainWindow.webContents;
|
|
156
|
+
if (!electron_1.app.isPackaged)
|
|
157
|
+
wc.openDevTools({ mode: 'detach' });
|
|
158
|
+
wc.on('did-start-loading', () => console.log('[swarmclaw] did-start-loading'));
|
|
159
|
+
wc.on('did-finish-load', () => console.log('[swarmclaw] did-finish-load'));
|
|
160
|
+
wc.on('did-fail-load', (_e, code, desc, url) => console.error(`[swarmclaw] did-fail-load code=${code} desc=${desc} url=${url}`));
|
|
161
|
+
wc.on('render-process-gone', (_e, details) => console.error(`[swarmclaw] render-process-gone reason=${details.reason}`));
|
|
162
|
+
wc.on('unresponsive', () => console.error('[swarmclaw] webContents unresponsive'));
|
|
163
|
+
mainWindow.on('closed', () => {
|
|
164
|
+
mainWindow = null;
|
|
165
|
+
});
|
|
166
|
+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
167
|
+
if (url.startsWith(startUrl))
|
|
168
|
+
return { action: 'allow' };
|
|
169
|
+
void electron_1.shell.openExternal(url);
|
|
170
|
+
return { action: 'deny' };
|
|
171
|
+
});
|
|
172
|
+
void mainWindow.loadURL(startUrl).catch((err) => {
|
|
173
|
+
console.error('[swarmclaw] loadURL rejected:', err);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async function showServerCrashDialog(code, signal) {
|
|
177
|
+
const buttons = serverLogFile ? ['Open Logs Folder', 'Quit'] : ['Quit'];
|
|
178
|
+
const quitButtonId = buttons.length - 1;
|
|
179
|
+
const detail = buildLogDetail(`code=${code ?? 'null'} signal=${signal ?? 'none'}`);
|
|
180
|
+
const res = await electron_1.dialog.showMessageBox({
|
|
181
|
+
type: 'error',
|
|
182
|
+
buttons,
|
|
183
|
+
defaultId: quitButtonId,
|
|
184
|
+
cancelId: quitButtonId,
|
|
185
|
+
title: 'SwarmClaw stopped',
|
|
186
|
+
message: 'The SwarmClaw server exited unexpectedly.',
|
|
187
|
+
detail,
|
|
188
|
+
});
|
|
189
|
+
if (serverLogFile && res.response === 0)
|
|
190
|
+
electron_1.shell.showItemInFolder(serverLogFile);
|
|
191
|
+
electron_1.app.exit(1);
|
|
192
|
+
}
|
|
193
|
+
async function showStartupFailureDialog(err, paths) {
|
|
194
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
195
|
+
const base = `${message}\n\nStandalone entry: ${paths.standaloneEntry}\nData dir: ${paths.dataDir}`;
|
|
196
|
+
const detail = buildLogDetail(base);
|
|
197
|
+
const buttons = serverLogFile ? ['Open Logs Folder', 'Quit'] : ['Quit'];
|
|
198
|
+
const quitButtonId = buttons.length - 1;
|
|
199
|
+
const res = await electron_1.dialog.showMessageBox({
|
|
200
|
+
type: 'error',
|
|
201
|
+
buttons,
|
|
202
|
+
defaultId: quitButtonId,
|
|
203
|
+
cancelId: quitButtonId,
|
|
204
|
+
title: 'SwarmClaw failed to start',
|
|
205
|
+
message: 'The embedded server did not start.',
|
|
206
|
+
detail,
|
|
207
|
+
});
|
|
208
|
+
if (serverLogFile && res.response === 0)
|
|
209
|
+
electron_1.shell.showItemInFolder(serverLogFile);
|
|
210
|
+
}
|
|
211
|
+
function buildLogDetail(base) {
|
|
212
|
+
if (!serverLogFile)
|
|
213
|
+
return base;
|
|
214
|
+
const tail = (0, server_lifecycle_1.tailLogFile)(serverLogFile, LOG_TAIL_BYTES).trim();
|
|
215
|
+
if (!tail)
|
|
216
|
+
return `${base}\n\nLog file: ${serverLogFile}\n(no output captured yet)`;
|
|
217
|
+
return `${base}\n\nLog tail (${serverLogFile}):\n${tail}`;
|
|
218
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
|
|
5
5
|
"main": "electron-dist/main.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
|
|
88
88
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts src/lib/server/storage-auth.test.ts src/lib/server/storage-auth-docker.test.ts",
|
|
89
89
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/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/connectors/swarmdock.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/session-tools/swarmdock.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openai.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
90
|
-
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
90
|
+
"test:runtime": "tsx --test src/lib/a2a/agent-card.test.ts src/lib/strip-internal-metadata.test.ts src/lib/provider-sets.test.ts src/lib/providers/opencode-cli.test.ts src/lib/providers/cli-provider-metadata.test.ts src/lib/providers/cli-utils.test.ts src/lib/providers/generic-cli.test.ts src/lib/server/cli-provider-readiness.test.ts src/lib/server/provider-health.test.ts src/lib/server/mcp-gateway-runtime.test.ts src/lib/server/mcp-connection-pool.test.ts src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/chat-execution/chat-turn-preparation.test.ts src/lib/server/chat-execution/iteration-timers.test.ts src/lib/server/chat-execution/post-stream-finalization.test.ts src/lib/server/chats/clear-undo-snapshots.test.ts src/lib/server/connectors/email.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts src/lib/server/observability/otel-config.test.ts src/lib/server/safe-parse-body.test.ts src/lib/server/missions/mission-templates.test.ts src/lib/app/view-constants.test.ts src/lib/quality/quality-summary.test.ts src/app/api/approvals/route.test.ts src/app/api/agents/agents-route.test.ts src/app/api/tasks/tasks-route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/chats/clear-route.test.ts src/app/api/chats/compact-route.test.ts src/app/api/chats/context-status-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/healthz/route.test.ts src/app/api/logs/route.test.ts src/app/api/providers/[id]/route.test.ts src/app/api/tts/route.test.ts",
|
|
91
91
|
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
92
92
|
"test:e2e": "node --import tsx scripts/browser-e2e-smoke.ts",
|
|
93
93
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
@@ -11,7 +11,7 @@ import { ensureBuildBootstrapPaths } from './build-bootstrap-env.mjs'
|
|
|
11
11
|
|
|
12
12
|
const require = createRequire(import.meta.url)
|
|
13
13
|
|
|
14
|
-
export const DEFAULT_MAX_OLD_SPACE_SIZE_MB = '
|
|
14
|
+
export const DEFAULT_MAX_OLD_SPACE_SIZE_MB = '12288'
|
|
15
15
|
export const MIN_MAX_OLD_SPACE_SIZE_MB = 1024
|
|
16
16
|
export const FALLBACK_MIN_MAX_OLD_SPACE_SIZE_MB = 512
|
|
17
17
|
export const RESERVED_BUILD_MEMORY_MB = 768
|
|
@@ -2,39 +2,12 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadCredentials, decryptKey, loadProviderConfigs } from '@/lib/server/storage'
|
|
3
3
|
import { listCredentialIdsByProvider } from '@/lib/server/credentials/credential-service'
|
|
4
4
|
import { getDeviceId, wsConnect, rpcOnConnectedGateway } from '@/lib/providers/openclaw'
|
|
5
|
-
import {
|
|
5
|
+
import { isCliProviderId } from '@/lib/providers/cli-provider-metadata'
|
|
6
|
+
import { checkCliProviderReady } from '@/lib/server/cli-provider-readiness'
|
|
6
7
|
import { OPENAI_COMPATIBLE_DEFAULTS } from '@/lib/server/provider-health'
|
|
7
8
|
import { resolveOllamaRuntimeConfig } from '@/lib/server/ollama-runtime'
|
|
8
9
|
import { normalizeOllamaSetupEndpoint, normalizeOpenClawUrl, parseErrorMessage } from './helpers'
|
|
9
10
|
|
|
10
|
-
type SetupProvider =
|
|
11
|
-
| 'claude-cli'
|
|
12
|
-
| 'codex-cli'
|
|
13
|
-
| 'opencode-cli'
|
|
14
|
-
| 'gemini-cli'
|
|
15
|
-
| 'copilot-cli'
|
|
16
|
-
| 'droid-cli'
|
|
17
|
-
| 'cursor-cli'
|
|
18
|
-
| 'qwen-code-cli'
|
|
19
|
-
| 'goose'
|
|
20
|
-
| 'openai'
|
|
21
|
-
| 'openrouter'
|
|
22
|
-
| 'anthropic'
|
|
23
|
-
| 'google'
|
|
24
|
-
| 'deepseek'
|
|
25
|
-
| 'groq'
|
|
26
|
-
| 'together'
|
|
27
|
-
| 'mistral'
|
|
28
|
-
| 'xai'
|
|
29
|
-
| 'fireworks'
|
|
30
|
-
| 'nebius'
|
|
31
|
-
| 'deepinfra'
|
|
32
|
-
| 'ollama'
|
|
33
|
-
| 'openclaw'
|
|
34
|
-
| 'hermes'
|
|
35
|
-
|
|
36
|
-
type CliSetupProvider = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'gemini-cli' | 'copilot-cli' | 'droid-cli' | 'cursor-cli' | 'qwen-code-cli' | 'goose'
|
|
37
|
-
|
|
38
11
|
interface SetupCheckBody {
|
|
39
12
|
provider?: string
|
|
40
13
|
apiKey?: string
|
|
@@ -280,43 +253,13 @@ async function checkOpenClaw(apiKey: string, endpointRaw: string): Promise<{ ok:
|
|
|
280
253
|
return { ok: true, message: 'Connected to OpenClaw gateway.', normalizedEndpoint, deviceId, recommendedModel }
|
|
281
254
|
}
|
|
282
255
|
|
|
283
|
-
function checkCliProvider(provider: CliSetupProvider): { ok: boolean; message: string } {
|
|
284
|
-
const env = buildCliEnv()
|
|
285
|
-
const config = {
|
|
286
|
-
'claude-cli': { binary: 'claude', backend: 'claude' as const, label: 'Claude Code CLI' },
|
|
287
|
-
'codex-cli': { binary: 'codex', backend: 'codex' as const, label: 'OpenAI Codex CLI' },
|
|
288
|
-
'opencode-cli': { binary: 'opencode', backend: 'opencode' as const, label: 'OpenCode CLI' },
|
|
289
|
-
'gemini-cli': { binary: 'gemini', backend: 'gemini' as const, label: 'Gemini CLI' },
|
|
290
|
-
'copilot-cli': { binary: 'copilot', backend: 'copilot' as const, label: 'GitHub Copilot CLI' },
|
|
291
|
-
'droid-cli': { binary: 'droid', backend: 'droid' as const, label: 'Factory Droid CLI' },
|
|
292
|
-
'cursor-cli': { binary: 'cursor-agent', backend: 'cursor' as const, label: 'Cursor Agent CLI' },
|
|
293
|
-
'qwen-code-cli': { binary: 'qwen', backend: 'qwen' as const, label: 'Qwen Code CLI' },
|
|
294
|
-
goose: { binary: 'goose', backend: 'goose' as const, label: 'Goose CLI' },
|
|
295
|
-
}[provider]
|
|
296
|
-
|
|
297
|
-
if (!config) return { ok: false, message: 'Unknown CLI provider.' }
|
|
298
|
-
const binary = resolveCliBinary(config.binary)
|
|
299
|
-
if (!binary) {
|
|
300
|
-
return {
|
|
301
|
-
ok: false,
|
|
302
|
-
message: `${config.label} is not installed. Install \`${config.binary}\` and ensure it is on your PATH.`,
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
const auth = probeCliAuth(binary, config.backend, env, process.cwd())
|
|
306
|
-
if (!auth.authenticated) {
|
|
307
|
-
return { ok: false, message: auth.errorMessage || `${config.label} is not configured.` }
|
|
308
|
-
}
|
|
309
|
-
return { ok: true, message: `${config.label} is installed and ready.` }
|
|
310
|
-
}
|
|
311
|
-
|
|
312
256
|
export async function POST(req: Request) {
|
|
313
257
|
const body = parseBody(await req.json().catch(() => ({})))
|
|
314
|
-
const provider = clean(body.provider)
|
|
258
|
+
const provider = clean(body.provider)
|
|
315
259
|
let apiKey = clean(body.apiKey)
|
|
316
260
|
const credentialId = clean(body.credentialId)
|
|
317
261
|
let endpoint = clean(body.endpoint)
|
|
318
262
|
const model = clean(body.model)
|
|
319
|
-
const CLI_PROVIDERS = new Set<CliSetupProvider>(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'cursor-cli', 'qwen-code-cli', 'goose'])
|
|
320
263
|
|
|
321
264
|
// Resolve credentialId to an API key if no raw key was provided
|
|
322
265
|
if (!apiKey && credentialId) {
|
|
@@ -355,8 +298,8 @@ export async function POST(req: Request) {
|
|
|
355
298
|
} catch { /* best effort */ }
|
|
356
299
|
}
|
|
357
300
|
|
|
358
|
-
if (
|
|
359
|
-
const result =
|
|
301
|
+
if (isCliProviderId(provider)) {
|
|
302
|
+
const result = checkCliProviderReady(provider)
|
|
360
303
|
return NextResponse.json(result)
|
|
361
304
|
}
|
|
362
305
|
|
|
@@ -6,6 +6,7 @@ import { DATA_DIR } from '@/lib/server/data-dir'
|
|
|
6
6
|
import { loadAgents, loadCredentials, loadSettings, loadCollection } from '@/lib/server/storage'
|
|
7
7
|
import { dedup, errorMessage } from '@/lib/shared-utils'
|
|
8
8
|
import { detectDocker } from '@/lib/server/sandbox/docker-detect'
|
|
9
|
+
import { BESPOKE_CLI_PROVIDER_METADATA, GENERIC_CLI_PROVIDER_METADATA } from '@/lib/providers/cli-provider-metadata'
|
|
9
10
|
|
|
10
11
|
type CheckStatus = 'pass' | 'warn' | 'fail'
|
|
11
12
|
|
|
@@ -198,15 +199,11 @@ export async function GET(req: Request) {
|
|
|
198
199
|
} catch { /* best-effort */ }
|
|
199
200
|
|
|
200
201
|
const optionalBinaries: Array<{ id: string; label: string; command: string }> = [
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
{ id: 'droid-cli', label: 'Factory Droid CLI', command: 'droid' },
|
|
207
|
-
{ id: 'cursor-cli', label: 'Cursor Agent CLI', command: 'cursor-agent' },
|
|
208
|
-
{ id: 'qwen-code-cli', label: 'Qwen Code CLI', command: 'qwen' },
|
|
209
|
-
{ id: 'goose', label: 'Goose CLI', command: 'goose' },
|
|
202
|
+
...BESPOKE_CLI_PROVIDER_METADATA.map((provider) => ({
|
|
203
|
+
id: provider.id,
|
|
204
|
+
label: provider.displayName,
|
|
205
|
+
command: provider.binaryName,
|
|
206
|
+
})),
|
|
210
207
|
{ id: 'google-workspace-cli', label: 'Google Workspace CLI', command: 'gws' },
|
|
211
208
|
]
|
|
212
209
|
|
|
@@ -223,6 +220,19 @@ export async function GET(req: Request) {
|
|
|
223
220
|
)
|
|
224
221
|
}
|
|
225
222
|
|
|
223
|
+
const genericCliInstalled = GENERIC_CLI_PROVIDER_METADATA
|
|
224
|
+
.filter((provider) => commandExists(provider.binaryName))
|
|
225
|
+
.map((provider) => provider.displayName)
|
|
226
|
+
pushCheck(
|
|
227
|
+
checks,
|
|
228
|
+
'extended-cli-providers',
|
|
229
|
+
'Extended CLI provider roster',
|
|
230
|
+
genericCliInstalled.length > 0 ? 'pass' : 'warn',
|
|
231
|
+
genericCliInstalled.length > 0
|
|
232
|
+
? `${genericCliInstalled.length} extended CLI provider(s) detected: ${genericCliInstalled.slice(0, 8).join(', ')}${genericCliInstalled.length > 8 ? ', ...' : ''}.`
|
|
233
|
+
: `${GENERIC_CLI_PROVIDER_METADATA.length} optional extended CLI provider(s) are available in SwarmClaw; install any matching CLI when you want to use it.`,
|
|
234
|
+
)
|
|
235
|
+
|
|
226
236
|
const extensionSettings = (settings?.extensionSettings && typeof settings.extensionSettings === 'object')
|
|
227
237
|
? settings.extensionSettings as Record<string, Record<string, unknown>>
|
|
228
238
|
: {}
|
|
@@ -18,6 +18,7 @@ export function StepProviders({
|
|
|
18
18
|
onContinue,
|
|
19
19
|
onSkip,
|
|
20
20
|
}: StepProvidersProps) {
|
|
21
|
+
const [providerSearch, setProviderSearch] = useState('')
|
|
21
22
|
const [doctorState, setDoctorState] = useState<'idle' | 'checking' | 'done' | 'error'>('idle')
|
|
22
23
|
const [doctorError, setDoctorError] = useState('')
|
|
23
24
|
const [doctorReport, setDoctorReport] = useState<SetupDoctorResponse | null>(null)
|
|
@@ -36,6 +37,23 @@ export function StepProviders({
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
const normalizedSearch = providerSearch.trim().toLowerCase()
|
|
41
|
+
const visibleProviders = SETUP_PROVIDERS.filter((candidate) => {
|
|
42
|
+
if (!normalizedSearch) return true
|
|
43
|
+
return [
|
|
44
|
+
candidate.name,
|
|
45
|
+
candidate.description,
|
|
46
|
+
candidate.badge || '',
|
|
47
|
+
candidate.id,
|
|
48
|
+
].some((part) => part.toLowerCase().includes(normalizedSearch))
|
|
49
|
+
})
|
|
50
|
+
const providerGroups = [
|
|
51
|
+
{ id: 'cli', label: 'CLI Agents', items: visibleProviders.filter((candidate) => candidate.category === 'cli') },
|
|
52
|
+
{ id: 'gateway', label: 'Gateways and Local Runtimes', items: visibleProviders.filter((candidate) => candidate.category === 'gateway' || candidate.category === 'local') },
|
|
53
|
+
{ id: 'api', label: 'API Providers', items: visibleProviders.filter((candidate) => !candidate.category || candidate.category === 'api') },
|
|
54
|
+
{ id: 'custom', label: 'Custom', items: visibleProviders.filter((candidate) => candidate.category === 'custom') },
|
|
55
|
+
].filter((group) => group.items.length > 0)
|
|
56
|
+
|
|
39
57
|
return (
|
|
40
58
|
<StepShell>
|
|
41
59
|
<h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
|
|
@@ -50,51 +68,72 @@ export function StepProviders({
|
|
|
50
68
|
|
|
51
69
|
<ConfiguredProviderChips providers={configuredProviders} onRemove={onRemoveProvider} />
|
|
52
70
|
|
|
71
|
+
<input
|
|
72
|
+
type="search"
|
|
73
|
+
value={providerSearch}
|
|
74
|
+
onChange={(e) => setProviderSearch(e.target.value)}
|
|
75
|
+
placeholder="Search providers, CLIs, or runtimes..."
|
|
76
|
+
className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface text-text text-[13px]
|
|
77
|
+
outline-none transition-all duration-200 placeholder:text-text-3/50 focus:border-accent-bright/30 mb-4"
|
|
78
|
+
/>
|
|
79
|
+
|
|
53
80
|
<div className="flex flex-col gap-3 max-h-[42vh] overflow-y-auto pr-1">
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
{isConfigured ? (
|
|
78
|
-
<span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-300 text-[10px] uppercase tracking-[0.08em] font-600">
|
|
79
|
-
Connected · Edit
|
|
80
|
-
</span>
|
|
81
|
-
) : candidate.badge ? (
|
|
82
|
-
<span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-accent-bright/15 text-accent-bright text-[10px] uppercase tracking-[0.08em] font-600">
|
|
83
|
-
{candidate.badge}
|
|
81
|
+
{providerGroups.map((group) => (
|
|
82
|
+
<div key={group.id} className="space-y-2">
|
|
83
|
+
<div className="px-1 text-[10px] font-700 uppercase tracking-[0.1em] text-text-3/70">
|
|
84
|
+
{group.label}
|
|
85
|
+
</div>
|
|
86
|
+
{group.items.map((candidate) => {
|
|
87
|
+
const isConfigured = configuredProviderIds.has(candidate.id)
|
|
88
|
+
return (
|
|
89
|
+
<button
|
|
90
|
+
key={candidate.id}
|
|
91
|
+
onClick={() => onSelectProvider(candidate.id)}
|
|
92
|
+
className={`w-full px-5 py-4 rounded-[14px] border bg-surface text-left
|
|
93
|
+
transition-all duration-200 flex items-start gap-4 cursor-pointer
|
|
94
|
+
${isConfigured
|
|
95
|
+
? 'border-emerald-500/25 hover:border-emerald-500/40 hover:bg-surface-hover'
|
|
96
|
+
: 'border-white/[0.08] hover:border-accent-bright/30 hover:bg-surface-hover'
|
|
97
|
+
}`}
|
|
98
|
+
>
|
|
99
|
+
<div className={`w-10 h-10 rounded-[10px] border flex items-center justify-center shrink-0 mt-0.5 ${
|
|
100
|
+
isConfigured ? 'bg-emerald-500/10 border-emerald-500/20' : 'bg-white/[0.04] border-white/[0.06]'
|
|
101
|
+
}`}>
|
|
102
|
+
<span className={`text-[16px] font-display font-700 ${isConfigured ? 'text-emerald-400' : 'text-accent-bright'}`}>
|
|
103
|
+
{candidate.icon}
|
|
84
104
|
</span>
|
|
85
|
-
) : null}
|
|
86
|
-
</div>
|
|
87
|
-
<div className="text-[13px] text-text-3 leading-relaxed">{candidate.description}</div>
|
|
88
|
-
{!candidate.requiresKey && !isConfigured && (
|
|
89
|
-
<div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
|
|
90
|
-
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
|
91
|
-
No API key required
|
|
92
105
|
</div>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
<div className="flex-1">
|
|
107
|
+
<div className="text-[15px] font-display font-600 text-text mb-1">
|
|
108
|
+
{candidate.name}
|
|
109
|
+
{isConfigured ? (
|
|
110
|
+
<span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-300 text-[10px] uppercase tracking-[0.08em] font-600">
|
|
111
|
+
Connected · Edit
|
|
112
|
+
</span>
|
|
113
|
+
) : candidate.badge ? (
|
|
114
|
+
<span className="ml-2 inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-accent-bright/15 text-accent-bright text-[10px] uppercase tracking-[0.08em] font-600">
|
|
115
|
+
{candidate.badge}
|
|
116
|
+
</span>
|
|
117
|
+
) : null}
|
|
118
|
+
</div>
|
|
119
|
+
<div className="text-[13px] text-text-3 leading-relaxed">{candidate.description}</div>
|
|
120
|
+
{!candidate.requiresKey && !isConfigured && (
|
|
121
|
+
<div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
|
|
122
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
|
123
|
+
No API key required
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</button>
|
|
128
|
+
)
|
|
129
|
+
})}
|
|
130
|
+
</div>
|
|
131
|
+
))}
|
|
132
|
+
{providerGroups.length === 0 && (
|
|
133
|
+
<div className="px-5 py-6 rounded-[14px] border border-white/[0.08] bg-surface text-center text-[13px] text-text-3">
|
|
134
|
+
No providers match that search.
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
98
137
|
</div>
|
|
99
138
|
|
|
100
139
|
<div className="mt-4 text-left">
|