@chrysb/alphaclaw 0.5.1 → 0.5.3
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/lib/public/js/components/models-tab/index.js +203 -48
- package/lib/public/js/components/models-tab/provider-auth-card.js +20 -2
- package/lib/public/js/components/models.js +8 -9
- package/lib/public/js/components/onboarding/welcome-import-step.js +6 -5
- package/lib/public/js/components/onboarding/welcome-secret-review-step.js +12 -6
- package/lib/public/js/components/onboarding/welcome-secret-review-utils.js +19 -0
- package/lib/public/js/components/providers.js +9 -13
- package/lib/public/js/components/usage-tab/overview-section.js +2 -1
- package/lib/public/js/components/welcome/use-welcome.js +3 -0
- package/lib/public/js/lib/model-config.js +149 -2
- package/lib/server/auth-profiles.js +14 -0
- package/lib/server/constants.js +23 -4
- package/lib/server/gateway.js +18 -2
- package/lib/server/onboarding/import/import-applier.js +127 -0
- package/lib/server/onboarding/import/import-scanner.js +8 -1
- package/lib/server/onboarding/import/secret-detector.js +52 -6
- package/lib/server/onboarding/index.js +126 -0
- package/lib/server/onboarding/openclaw.js +88 -5
- package/lib/server/routes/onboarding.js +12 -3
- package/lib/server/routes/proxy.js +7 -4
- package/lib/server/routes/system.js +14 -0
- package/lib/server/webhook-middleware.js +5 -2
- package/lib/server.js +6 -4
- package/package.json +2 -2
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export const getModelProvider = (modelKey) => String(modelKey || "").split("/")[0] || "";
|
|
2
2
|
|
|
3
|
-
export const getAuthProviderFromModelProvider = (provider) =>
|
|
4
|
-
|
|
3
|
+
export const getAuthProviderFromModelProvider = (provider) => {
|
|
4
|
+
const normalized = String(provider || "").trim();
|
|
5
|
+
if (normalized === "openai-codex") return "openai";
|
|
6
|
+
if (normalized === "volcengine-plan") return "volcengine";
|
|
7
|
+
if (normalized === "byteplus-plan") return "byteplus";
|
|
8
|
+
return normalized;
|
|
9
|
+
};
|
|
5
10
|
|
|
6
11
|
export const kFeaturedModelDefs = [
|
|
7
12
|
{
|
|
@@ -71,6 +76,50 @@ export const kProviderAuthFields = {
|
|
|
71
76
|
placeholder: "AI...",
|
|
72
77
|
},
|
|
73
78
|
],
|
|
79
|
+
opencode: [
|
|
80
|
+
{
|
|
81
|
+
key: "OPENCODE_API_KEY",
|
|
82
|
+
label: "OpenCode API Key",
|
|
83
|
+
placeholder: "oc-...",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
openrouter: [
|
|
87
|
+
{
|
|
88
|
+
key: "OPENROUTER_API_KEY",
|
|
89
|
+
label: "OpenRouter API Key",
|
|
90
|
+
url: "https://openrouter.ai",
|
|
91
|
+
linkText: "Get key",
|
|
92
|
+
placeholder: "sk-or-...",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
zai: [
|
|
96
|
+
{
|
|
97
|
+
key: "ZAI_API_KEY",
|
|
98
|
+
label: "Z.AI API Key",
|
|
99
|
+
placeholder: "zai-...",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
"vercel-ai-gateway": [
|
|
103
|
+
{
|
|
104
|
+
key: "AI_GATEWAY_API_KEY",
|
|
105
|
+
label: "AI Gateway API Key",
|
|
106
|
+
placeholder: "aigw_...",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
kilocode: [
|
|
110
|
+
{
|
|
111
|
+
key: "KILOCODE_API_KEY",
|
|
112
|
+
label: "KiloCode API Key",
|
|
113
|
+
placeholder: "kilo_...",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
xai: [
|
|
117
|
+
{
|
|
118
|
+
key: "XAI_API_KEY",
|
|
119
|
+
label: "xAI API Key",
|
|
120
|
+
placeholder: "xai-...",
|
|
121
|
+
},
|
|
122
|
+
],
|
|
74
123
|
mistral: [
|
|
75
124
|
{
|
|
76
125
|
key: "MISTRAL_API_KEY",
|
|
@@ -98,6 +147,55 @@ export const kProviderAuthFields = {
|
|
|
98
147
|
placeholder: "gsk_...",
|
|
99
148
|
},
|
|
100
149
|
],
|
|
150
|
+
cerebras: [
|
|
151
|
+
{
|
|
152
|
+
key: "CEREBRAS_API_KEY",
|
|
153
|
+
label: "Cerebras API Key",
|
|
154
|
+
placeholder: "csk-...",
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
moonshot: [
|
|
158
|
+
{
|
|
159
|
+
key: "MOONSHOT_API_KEY",
|
|
160
|
+
label: "Moonshot API Key",
|
|
161
|
+
placeholder: "sk-...",
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
"kimi-coding": [
|
|
165
|
+
{
|
|
166
|
+
key: "KIMI_API_KEY",
|
|
167
|
+
label: "Kimi API Key",
|
|
168
|
+
placeholder: "sk-...",
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
volcengine: [
|
|
172
|
+
{
|
|
173
|
+
key: "VOLCANO_ENGINE_API_KEY",
|
|
174
|
+
label: "Volcano Engine API Key",
|
|
175
|
+
placeholder: "ve-...",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
byteplus: [
|
|
179
|
+
{
|
|
180
|
+
key: "BYTEPLUS_API_KEY",
|
|
181
|
+
label: "BytePlus API Key",
|
|
182
|
+
placeholder: "bp-...",
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
synthetic: [
|
|
186
|
+
{
|
|
187
|
+
key: "SYNTHETIC_API_KEY",
|
|
188
|
+
label: "Synthetic API Key",
|
|
189
|
+
placeholder: "syn-...",
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
minimax: [
|
|
193
|
+
{
|
|
194
|
+
key: "MINIMAX_API_KEY",
|
|
195
|
+
label: "MiniMax API Key",
|
|
196
|
+
placeholder: "minimax-...",
|
|
197
|
+
},
|
|
198
|
+
],
|
|
101
199
|
deepgram: [
|
|
102
200
|
{
|
|
103
201
|
key: "DEEPGRAM_API_KEY",
|
|
@@ -107,26 +205,61 @@ export const kProviderAuthFields = {
|
|
|
107
205
|
placeholder: "dg-...",
|
|
108
206
|
},
|
|
109
207
|
],
|
|
208
|
+
vllm: [
|
|
209
|
+
{
|
|
210
|
+
key: "VLLM_API_KEY",
|
|
211
|
+
label: "vLLM API Key",
|
|
212
|
+
placeholder: "vllm-local",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
110
215
|
};
|
|
111
216
|
|
|
112
217
|
export const kProviderLabels = {
|
|
113
218
|
anthropic: "Anthropic",
|
|
114
219
|
openai: "OpenAI",
|
|
115
220
|
google: "Gemini",
|
|
221
|
+
opencode: "OpenCode Zen",
|
|
222
|
+
openrouter: "OpenRouter",
|
|
223
|
+
zai: "Z.AI",
|
|
224
|
+
"vercel-ai-gateway": "Vercel AI Gateway",
|
|
225
|
+
kilocode: "Kilo Gateway",
|
|
226
|
+
xai: "xAI",
|
|
116
227
|
mistral: "Mistral",
|
|
228
|
+
cerebras: "Cerebras",
|
|
229
|
+
moonshot: "Moonshot",
|
|
230
|
+
"kimi-coding": "Kimi Coding",
|
|
231
|
+
volcengine: "Volcano Engine",
|
|
232
|
+
byteplus: "BytePlus",
|
|
233
|
+
synthetic: "Synthetic",
|
|
234
|
+
minimax: "MiniMax",
|
|
117
235
|
voyage: "Voyage",
|
|
118
236
|
groq: "Groq",
|
|
119
237
|
deepgram: "Deepgram",
|
|
238
|
+
vllm: "vLLM",
|
|
120
239
|
};
|
|
121
240
|
|
|
122
241
|
export const kProviderOrder = [
|
|
123
242
|
"anthropic",
|
|
124
243
|
"openai",
|
|
125
244
|
"google",
|
|
245
|
+
"zai",
|
|
246
|
+
"xai",
|
|
247
|
+
"openrouter",
|
|
248
|
+
"opencode",
|
|
249
|
+
"kilocode",
|
|
250
|
+
"vercel-ai-gateway",
|
|
251
|
+
"minimax",
|
|
252
|
+
"moonshot",
|
|
253
|
+
"kimi-coding",
|
|
254
|
+
"volcengine",
|
|
255
|
+
"byteplus",
|
|
256
|
+
"synthetic",
|
|
126
257
|
"mistral",
|
|
258
|
+
"cerebras",
|
|
127
259
|
"voyage",
|
|
128
260
|
"groq",
|
|
129
261
|
"deepgram",
|
|
262
|
+
"vllm",
|
|
130
263
|
];
|
|
131
264
|
|
|
132
265
|
export const kCoreProviders = new Set(["anthropic", "openai", "google"]);
|
|
@@ -135,10 +268,24 @@ export const kProviderFeatures = {
|
|
|
135
268
|
anthropic: ["Agent Model"],
|
|
136
269
|
openai: ["Agent Model", "Embeddings", "Audio"],
|
|
137
270
|
google: ["Agent Model", "Embeddings", "Audio"],
|
|
271
|
+
opencode: ["Agent Model"],
|
|
272
|
+
openrouter: ["Agent Model"],
|
|
273
|
+
zai: ["Agent Model"],
|
|
274
|
+
"vercel-ai-gateway": ["Agent Model"],
|
|
275
|
+
kilocode: ["Agent Model"],
|
|
276
|
+
xai: ["Agent Model"],
|
|
138
277
|
mistral: ["Agent Model", "Embeddings", "Audio"],
|
|
278
|
+
cerebras: ["Agent Model"],
|
|
279
|
+
moonshot: ["Agent Model"],
|
|
280
|
+
"kimi-coding": ["Agent Model"],
|
|
281
|
+
volcengine: ["Agent Model"],
|
|
282
|
+
byteplus: ["Agent Model"],
|
|
283
|
+
synthetic: ["Agent Model"],
|
|
284
|
+
minimax: ["Agent Model"],
|
|
139
285
|
voyage: ["Embeddings"],
|
|
140
286
|
groq: ["Agent Model", "Audio"],
|
|
141
287
|
deepgram: ["Audio"],
|
|
288
|
+
vllm: ["Agent Model"],
|
|
142
289
|
};
|
|
143
290
|
|
|
144
291
|
export const kFeatureDefs = [
|
|
@@ -7,10 +7,24 @@ const kApiKeyEnvVarByProvider = {
|
|
|
7
7
|
anthropic: "ANTHROPIC_API_KEY",
|
|
8
8
|
openai: "OPENAI_API_KEY",
|
|
9
9
|
google: "GEMINI_API_KEY",
|
|
10
|
+
opencode: "OPENCODE_API_KEY",
|
|
11
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
12
|
+
zai: "ZAI_API_KEY",
|
|
13
|
+
"vercel-ai-gateway": "AI_GATEWAY_API_KEY",
|
|
14
|
+
kilocode: "KILOCODE_API_KEY",
|
|
15
|
+
xai: "XAI_API_KEY",
|
|
10
16
|
mistral: "MISTRAL_API_KEY",
|
|
17
|
+
cerebras: "CEREBRAS_API_KEY",
|
|
18
|
+
moonshot: "MOONSHOT_API_KEY",
|
|
19
|
+
"kimi-coding": "KIMI_API_KEY",
|
|
20
|
+
volcengine: "VOLCANO_ENGINE_API_KEY",
|
|
21
|
+
byteplus: "BYTEPLUS_API_KEY",
|
|
22
|
+
synthetic: "SYNTHETIC_API_KEY",
|
|
23
|
+
minimax: "MINIMAX_API_KEY",
|
|
11
24
|
voyage: "VOYAGE_API_KEY",
|
|
12
25
|
groq: "GROQ_API_KEY",
|
|
13
26
|
deepgram: "DEEPGRAM_API_KEY",
|
|
27
|
+
vllm: "VLLM_API_KEY",
|
|
14
28
|
};
|
|
15
29
|
|
|
16
30
|
const normalizeSecret = (raw) =>
|
package/lib/server/constants.js
CHANGED
|
@@ -12,9 +12,9 @@ const kNpmPackageRoot = path.resolve(kPackageRoot, "..");
|
|
|
12
12
|
const kSetupDir = path.join(kPackageRoot, "setup");
|
|
13
13
|
|
|
14
14
|
const PORT = parseInt(process.env.PORT || "3000", 10);
|
|
15
|
-
const
|
|
15
|
+
const kDefaultGatewayPort = 18789;
|
|
16
16
|
const GATEWAY_HOST = "127.0.0.1";
|
|
17
|
-
const
|
|
17
|
+
const kDefaultGatewayUrl = `http://${GATEWAY_HOST}:${kDefaultGatewayPort}`;
|
|
18
18
|
const OPENCLAW_DIR = path.join(kRootDir, ".openclaw");
|
|
19
19
|
const GATEWAY_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN || "";
|
|
20
20
|
const ENV_FILE_PATH = path.join(kRootDir, ".env");
|
|
@@ -71,6 +71,25 @@ const kOnboardingModelProviders = new Set([
|
|
|
71
71
|
"openai",
|
|
72
72
|
"openai-codex",
|
|
73
73
|
"google",
|
|
74
|
+
"opencode",
|
|
75
|
+
"openrouter",
|
|
76
|
+
"zai",
|
|
77
|
+
"vercel-ai-gateway",
|
|
78
|
+
"kilocode",
|
|
79
|
+
"xai",
|
|
80
|
+
"mistral",
|
|
81
|
+
"cerebras",
|
|
82
|
+
"moonshot",
|
|
83
|
+
"kimi-coding",
|
|
84
|
+
"volcengine",
|
|
85
|
+
"volcengine-plan",
|
|
86
|
+
"byteplus",
|
|
87
|
+
"byteplus-plan",
|
|
88
|
+
"synthetic",
|
|
89
|
+
"minimax",
|
|
90
|
+
"voyage",
|
|
91
|
+
"groq",
|
|
92
|
+
"vllm",
|
|
74
93
|
]);
|
|
75
94
|
const kFallbackOnboardingModels = [
|
|
76
95
|
{
|
|
@@ -354,9 +373,9 @@ module.exports = {
|
|
|
354
373
|
kNpmPackageRoot,
|
|
355
374
|
kSetupDir,
|
|
356
375
|
PORT,
|
|
357
|
-
|
|
376
|
+
kDefaultGatewayPort,
|
|
358
377
|
GATEWAY_HOST,
|
|
359
|
-
|
|
378
|
+
kDefaultGatewayUrl,
|
|
360
379
|
OPENCLAW_DIR,
|
|
361
380
|
GATEWAY_TOKEN,
|
|
362
381
|
ENV_FILE_PATH,
|
package/lib/server/gateway.js
CHANGED
|
@@ -6,7 +6,7 @@ const {
|
|
|
6
6
|
ALPHACLAW_DIR,
|
|
7
7
|
OPENCLAW_DIR,
|
|
8
8
|
GATEWAY_HOST,
|
|
9
|
-
|
|
9
|
+
kDefaultGatewayPort,
|
|
10
10
|
kControlUiSkillPath,
|
|
11
11
|
kChannelDefs,
|
|
12
12
|
kOnboardingMarkerPath,
|
|
@@ -74,9 +74,23 @@ const isOnboarded = () => {
|
|
|
74
74
|
return false;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
const getGatewayPort = () => {
|
|
78
|
+
try {
|
|
79
|
+
const configPath = `${OPENCLAW_DIR}/openclaw.json`;
|
|
80
|
+
if (!fs.existsSync(configPath)) return kDefaultGatewayPort;
|
|
81
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
82
|
+
const parsedPort = Number.parseInt(String(cfg?.gateway?.port || ""), 10);
|
|
83
|
+
return parsedPort > 0 ? parsedPort : kDefaultGatewayPort;
|
|
84
|
+
} catch {
|
|
85
|
+
return kDefaultGatewayPort;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const getGatewayUrl = () => `http://${GATEWAY_HOST}:${getGatewayPort()}`;
|
|
90
|
+
|
|
77
91
|
const isGatewayRunning = () =>
|
|
78
92
|
new Promise((resolve) => {
|
|
79
|
-
const sock = net.createConnection(
|
|
93
|
+
const sock = net.createConnection(getGatewayPort(), GATEWAY_HOST);
|
|
80
94
|
sock.setTimeout(1000);
|
|
81
95
|
sock.on("connect", () => {
|
|
82
96
|
sock.destroy();
|
|
@@ -341,6 +355,8 @@ const getChannelStatus = () => {
|
|
|
341
355
|
|
|
342
356
|
module.exports = {
|
|
343
357
|
gatewayEnv,
|
|
358
|
+
getGatewayPort,
|
|
359
|
+
getGatewayUrl,
|
|
344
360
|
isOnboarded,
|
|
345
361
|
isGatewayRunning,
|
|
346
362
|
launchGatewayProcess,
|
|
@@ -4,6 +4,11 @@ const {
|
|
|
4
4
|
normalizeHookPath,
|
|
5
5
|
normalizeTransformModulePath,
|
|
6
6
|
} = require("./import-config");
|
|
7
|
+
const {
|
|
8
|
+
getCanonicalEnvVarForConfigPath,
|
|
9
|
+
getEnvRefName,
|
|
10
|
+
isAlreadyEnvRef,
|
|
11
|
+
} = require("./secret-detector");
|
|
7
12
|
|
|
8
13
|
const kEnvVarNamePattern = /^[A-Z_][A-Z0-9_]*$/;
|
|
9
14
|
|
|
@@ -147,6 +152,41 @@ const resolveExtractionTargetPath = (baseDir, file) => {
|
|
|
147
152
|
}
|
|
148
153
|
return resolvedFilePath;
|
|
149
154
|
};
|
|
155
|
+
const isConfigPathIndex = (segment) => /^\d+$/.test(String(segment || ""));
|
|
156
|
+
const setConfigValueAtPath = ({
|
|
157
|
+
root,
|
|
158
|
+
dotPath,
|
|
159
|
+
expectedValue,
|
|
160
|
+
nextValue,
|
|
161
|
+
}) => {
|
|
162
|
+
const pathSegments = String(dotPath || "")
|
|
163
|
+
.split(".")
|
|
164
|
+
.filter(Boolean);
|
|
165
|
+
if (!root || typeof root !== "object" || pathSegments.length === 0) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let current = root;
|
|
170
|
+
for (let index = 0; index < pathSegments.length - 1; index += 1) {
|
|
171
|
+
const segment = pathSegments[index];
|
|
172
|
+
const nextNode = isConfigPathIndex(segment)
|
|
173
|
+
? current?.[Number(segment)]
|
|
174
|
+
: current?.[segment];
|
|
175
|
+
if (!nextNode || typeof nextNode !== "object") {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
current = nextNode;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const lastSegment = pathSegments[pathSegments.length - 1];
|
|
182
|
+
const targetKey = isConfigPathIndex(lastSegment)
|
|
183
|
+
? Number(lastSegment)
|
|
184
|
+
: lastSegment;
|
|
185
|
+
if (typeof current?.[targetKey] !== "string") return false;
|
|
186
|
+
if (current[targetKey] !== expectedValue) return false;
|
|
187
|
+
current[targetKey] = nextValue;
|
|
188
|
+
return true;
|
|
189
|
+
};
|
|
150
190
|
const movePath = (fs, src, dest) => {
|
|
151
191
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
152
192
|
try {
|
|
@@ -280,6 +320,7 @@ const applySecretExtraction = ({ fs, baseDir, approvedSecrets }) => {
|
|
|
280
320
|
rewriteMap.set(fullPath, []);
|
|
281
321
|
}
|
|
282
322
|
rewriteMap.get(fullPath).push({
|
|
323
|
+
configPath: secret.configPath,
|
|
283
324
|
value,
|
|
284
325
|
envRef: `\${${envVar}}`,
|
|
285
326
|
relativeFile: secret.file,
|
|
@@ -290,9 +331,31 @@ const applySecretExtraction = ({ fs, baseDir, approvedSecrets }) => {
|
|
|
290
331
|
for (const [fullPath, replacements] of rewriteMap) {
|
|
291
332
|
try {
|
|
292
333
|
let content = fs.readFileSync(fullPath, "utf8");
|
|
334
|
+
let parsed = null;
|
|
335
|
+
try {
|
|
336
|
+
parsed = JSON.parse(content);
|
|
337
|
+
} catch {}
|
|
293
338
|
const sorted = [...replacements].sort(
|
|
294
339
|
(a, b) => b.value.length - a.value.length,
|
|
295
340
|
);
|
|
341
|
+
let structuredChanged = false;
|
|
342
|
+
if (parsed && typeof parsed === "object") {
|
|
343
|
+
for (const { configPath, value, envRef } of sorted) {
|
|
344
|
+
if (
|
|
345
|
+
setConfigValueAtPath({
|
|
346
|
+
root: parsed,
|
|
347
|
+
dotPath: configPath,
|
|
348
|
+
expectedValue: value,
|
|
349
|
+
nextValue: envRef,
|
|
350
|
+
})
|
|
351
|
+
) {
|
|
352
|
+
structuredChanged = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (structuredChanged) {
|
|
357
|
+
content = JSON.stringify(parsed, null, 2);
|
|
358
|
+
}
|
|
296
359
|
for (const { value, envRef } of sorted) {
|
|
297
360
|
const secretJson = JSON.stringify(value);
|
|
298
361
|
const envRefJson = JSON.stringify(envRef);
|
|
@@ -313,9 +376,73 @@ const applySecretExtraction = ({ fs, baseDir, approvedSecrets }) => {
|
|
|
313
376
|
return { envVars };
|
|
314
377
|
};
|
|
315
378
|
|
|
379
|
+
const remapEnvVars = (envVars, renameMap) => {
|
|
380
|
+
const mapped = [];
|
|
381
|
+
for (const entry of envVars) {
|
|
382
|
+
const key = String(entry?.key || "").trim();
|
|
383
|
+
if (!key) continue;
|
|
384
|
+
const nextKey = renameMap.get(key) || key;
|
|
385
|
+
const existing = mapped.find((item) => item.key === nextKey);
|
|
386
|
+
if (existing) {
|
|
387
|
+
existing.value = entry.value;
|
|
388
|
+
} else {
|
|
389
|
+
mapped.push({ key: nextKey, value: entry.value });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return mapped;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const canonicalizeConfigEnvRefs = ({ fs, baseDir, configFiles = [], envVars = [] }) => {
|
|
396
|
+
const renameMap = new Map();
|
|
397
|
+
let rewrittenRefs = 0;
|
|
398
|
+
|
|
399
|
+
const rewriteNode = (node, parentPath = "") => {
|
|
400
|
+
if (!node || typeof node !== "object") return false;
|
|
401
|
+
let changed = false;
|
|
402
|
+
for (const [key, value] of Object.entries(node)) {
|
|
403
|
+
const dotPath = parentPath ? `${parentPath}.${key}` : key;
|
|
404
|
+
if (typeof value === "string" && isAlreadyEnvRef(value)) {
|
|
405
|
+
const currentEnvRef = getEnvRefName(value);
|
|
406
|
+
const canonicalEnvVar = getCanonicalEnvVarForConfigPath(dotPath);
|
|
407
|
+
if (canonicalEnvVar && currentEnvRef && currentEnvRef !== canonicalEnvVar) {
|
|
408
|
+
node[key] = `\${${canonicalEnvVar}}`;
|
|
409
|
+
renameMap.set(currentEnvRef, canonicalEnvVar);
|
|
410
|
+
rewrittenRefs += 1;
|
|
411
|
+
changed = true;
|
|
412
|
+
}
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (value && typeof value === "object") {
|
|
416
|
+
changed = rewriteNode(value, dotPath) || changed;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return changed;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
for (const configFile of configFiles) {
|
|
423
|
+
const fullPath = resolveExtractionTargetPath(baseDir, configFile);
|
|
424
|
+
if (!fullPath) continue;
|
|
425
|
+
try {
|
|
426
|
+
const parsed = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
|
427
|
+
if (!parsed || typeof parsed !== "object") continue;
|
|
428
|
+
const changed = rewriteNode(parsed);
|
|
429
|
+
if (changed) {
|
|
430
|
+
fs.writeFileSync(fullPath, JSON.stringify(parsed, null, 2));
|
|
431
|
+
}
|
|
432
|
+
} catch {}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
envVars: remapEnvVars(envVars, renameMap),
|
|
437
|
+
rewrittenRefs,
|
|
438
|
+
renamedEnvVars: renameMap.size,
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
|
|
316
442
|
module.exports = {
|
|
317
443
|
promoteCloneToTarget,
|
|
318
444
|
alignHookTransforms,
|
|
319
445
|
applySecretExtraction,
|
|
446
|
+
canonicalizeConfigEnvRefs,
|
|
320
447
|
isValidTempDir,
|
|
321
448
|
};
|
|
@@ -220,6 +220,7 @@ const collectManagedEnvRefs = (value, found) => {
|
|
|
220
220
|
const collectManagedEnvConflicts = (fs, baseDir, configFiles, envFiles) => {
|
|
221
221
|
const managedVars = new Set();
|
|
222
222
|
let gatewayAuthNormalized = false;
|
|
223
|
+
let webhookTokenNormalized = false;
|
|
223
224
|
|
|
224
225
|
for (const configFile of configFiles) {
|
|
225
226
|
try {
|
|
@@ -231,6 +232,11 @@ const collectManagedEnvConflicts = (fs, baseDir, configFiles, envFiles) => {
|
|
|
231
232
|
gatewayAuthNormalized = true;
|
|
232
233
|
managedVars.add("OPENCLAW_GATEWAY_TOKEN");
|
|
233
234
|
}
|
|
235
|
+
const webhookToken = String(cfg?.hooks?.token || "").trim();
|
|
236
|
+
if (webhookToken && webhookToken !== "${WEBHOOK_TOKEN}") {
|
|
237
|
+
webhookTokenNormalized = true;
|
|
238
|
+
managedVars.add("WEBHOOK_TOKEN");
|
|
239
|
+
}
|
|
234
240
|
} catch {}
|
|
235
241
|
}
|
|
236
242
|
|
|
@@ -251,9 +257,10 @@ const collectManagedEnvConflicts = (fs, baseDir, configFiles, envFiles) => {
|
|
|
251
257
|
}
|
|
252
258
|
|
|
253
259
|
return {
|
|
254
|
-
found: managedVars.size > 0 || gatewayAuthNormalized,
|
|
260
|
+
found: managedVars.size > 0 || gatewayAuthNormalized || webhookTokenNormalized,
|
|
255
261
|
vars: [...managedVars].sort(),
|
|
256
262
|
gatewayAuthNormalized,
|
|
263
|
+
webhookTokenNormalized,
|
|
257
264
|
};
|
|
258
265
|
};
|
|
259
266
|
|
|
@@ -59,14 +59,27 @@ const kConfigPathToEnvVar = {
|
|
|
59
59
|
"models.providers.openai.apiKey": "OPENAI_API_KEY",
|
|
60
60
|
"models.providers.anthropic.apiKey": "ANTHROPIC_API_KEY",
|
|
61
61
|
"models.providers.google.apiKey": "GEMINI_API_KEY",
|
|
62
|
+
"models.providers.opencode.apiKey": "OPENCODE_API_KEY",
|
|
62
63
|
"models.providers.openrouter.apiKey": "OPENROUTER_API_KEY",
|
|
64
|
+
"models.providers.zai.apiKey": "ZAI_API_KEY",
|
|
65
|
+
"models.providers.vercel-ai-gateway.apiKey": "AI_GATEWAY_API_KEY",
|
|
66
|
+
"models.providers.kilocode.apiKey": "KILOCODE_API_KEY",
|
|
67
|
+
"models.providers.xai.apiKey": "XAI_API_KEY",
|
|
63
68
|
"models.providers.mistral.apiKey": "MISTRAL_API_KEY",
|
|
64
69
|
"models.providers.groq.apiKey": "GROQ_API_KEY",
|
|
65
70
|
"models.providers.cerebras.apiKey": "CEREBRAS_API_KEY",
|
|
71
|
+
"models.providers.moonshot.apiKey": "MOONSHOT_API_KEY",
|
|
72
|
+
"models.providers.kimi-coding.apiKey": "KIMI_API_KEY",
|
|
73
|
+
"models.providers.volcengine.apiKey": "VOLCANO_ENGINE_API_KEY",
|
|
74
|
+
"models.providers.byteplus.apiKey": "BYTEPLUS_API_KEY",
|
|
75
|
+
"models.providers.synthetic.apiKey": "SYNTHETIC_API_KEY",
|
|
76
|
+
"models.providers.minimax.apiKey": "MINIMAX_API_KEY",
|
|
66
77
|
"models.providers.voyage.apiKey": "VOYAGE_API_KEY",
|
|
78
|
+
"models.providers.vllm.apiKey": "VLLM_API_KEY",
|
|
67
79
|
"tools.web.search.apiKey": "BRAVE_API_KEY",
|
|
68
80
|
"audio.apiKey": "ELEVENLABS_API_KEY",
|
|
69
81
|
"talk.apiKey": "ELEVENLABS_API_KEY",
|
|
82
|
+
"hooks.token": null, // Dropped — normalized to WEBHOOK_TOKEN at deploy/import time
|
|
70
83
|
"gateway.auth.token": null, // Dropped — set at deploy time
|
|
71
84
|
};
|
|
72
85
|
|
|
@@ -99,15 +112,31 @@ const maskValue = (value) => {
|
|
|
99
112
|
return str.slice(0, 4) + "****" + str.slice(-4);
|
|
100
113
|
};
|
|
101
114
|
|
|
102
|
-
const
|
|
115
|
+
const toEnvSegment = (value) =>
|
|
116
|
+
String(value || "")
|
|
117
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
118
|
+
.toUpperCase()
|
|
119
|
+
.replace(/[^A-Z0-9_]/g, "_");
|
|
120
|
+
|
|
121
|
+
const getCanonicalEnvVarForConfigPath = (dotPath) => {
|
|
103
122
|
if (kConfigPathToEnvVar[dotPath] !== undefined) {
|
|
104
123
|
return kConfigPathToEnvVar[dotPath];
|
|
105
124
|
}
|
|
125
|
+
const providerPathMatch = String(dotPath || "").match(
|
|
126
|
+
/^models\.providers\.([^.]+)\.([^.]+)$/,
|
|
127
|
+
);
|
|
128
|
+
if (providerPathMatch) {
|
|
129
|
+
const [, providerKey, fieldKey] = providerPathMatch;
|
|
130
|
+
return `${toEnvSegment(providerKey)}_${toEnvSegment(fieldKey)}`;
|
|
131
|
+
}
|
|
132
|
+
return "";
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const configPathToEnvName = (dotPath) => {
|
|
136
|
+
const canonicalName = getCanonicalEnvVarForConfigPath(dotPath);
|
|
137
|
+
if (canonicalName) return canonicalName;
|
|
106
138
|
const lastKey = dotPath.split(".").pop() || "";
|
|
107
|
-
return lastKey
|
|
108
|
-
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
109
|
-
.toUpperCase()
|
|
110
|
-
.replace(/[^A-Z0-9_]/g, "_");
|
|
139
|
+
return toEnvSegment(lastKey);
|
|
111
140
|
};
|
|
112
141
|
|
|
113
142
|
const walkConfig = (obj, parentPath, results) => {
|
|
@@ -172,6 +201,13 @@ const walkConfig = (obj, parentPath, results) => {
|
|
|
172
201
|
const isAlreadyEnvRef = (value) =>
|
|
173
202
|
/^\$\{[A-Z_][A-Z0-9_]*\}$/.test(String(value || "").trim());
|
|
174
203
|
|
|
204
|
+
const getEnvRefName = (value) => {
|
|
205
|
+
const match = String(value || "")
|
|
206
|
+
.trim()
|
|
207
|
+
.match(/^\$\{([A-Z_][A-Z0-9_]*)\}$/);
|
|
208
|
+
return match?.[1] || "";
|
|
209
|
+
};
|
|
210
|
+
|
|
175
211
|
const parseEnvFileSecrets = (content, fileName) => {
|
|
176
212
|
const results = [];
|
|
177
213
|
const lines = String(content || "").split("\n");
|
|
@@ -248,6 +284,7 @@ const extractPreFillValues = ({ fs, baseDir, configFiles = [] }) => {
|
|
|
248
284
|
try {
|
|
249
285
|
const raw = fs.readFileSync(path.join(baseDir, cfgFile), "utf8");
|
|
250
286
|
const cfg = JSON.parse(raw);
|
|
287
|
+
const configFileName = path.basename(String(cfgFile || "")).toLowerCase();
|
|
251
288
|
|
|
252
289
|
if (cfg.models?.active) preFill.MODEL_KEY = cfg.models.active;
|
|
253
290
|
|
|
@@ -262,7 +299,12 @@ const extractPreFillValues = ({ fs, baseDir, configFiles = [] }) => {
|
|
|
262
299
|
preFill.GEMINI_API_KEY = providers.google.apiKey;
|
|
263
300
|
}
|
|
264
301
|
|
|
265
|
-
const channels =
|
|
302
|
+
const channels =
|
|
303
|
+
cfg.channels && typeof cfg.channels === "object"
|
|
304
|
+
? cfg.channels
|
|
305
|
+
: configFileName.includes("channel")
|
|
306
|
+
? cfg
|
|
307
|
+
: {};
|
|
266
308
|
if (channels.telegram?.botToken && !isAlreadyEnvRef(channels.telegram.botToken)) {
|
|
267
309
|
preFill.TELEGRAM_BOT_TOKEN = channels.telegram.botToken;
|
|
268
310
|
}
|
|
@@ -280,9 +322,13 @@ const extractPreFillValues = ({ fs, baseDir, configFiles = [] }) => {
|
|
|
280
322
|
};
|
|
281
323
|
|
|
282
324
|
module.exports = {
|
|
325
|
+
configPathToEnvName,
|
|
283
326
|
detectSecrets,
|
|
284
327
|
extractPreFillValues,
|
|
328
|
+
getCanonicalEnvVarForConfigPath,
|
|
329
|
+
getEnvRefName,
|
|
285
330
|
isSensitiveKey,
|
|
331
|
+
isAlreadyEnvRef,
|
|
286
332
|
matchesValuePrefix,
|
|
287
333
|
maskValue,
|
|
288
334
|
parseEnvFileSecrets,
|