@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.
@@ -1,7 +1,12 @@
1
1
  export const getModelProvider = (modelKey) => String(modelKey || "").split("/")[0] || "";
2
2
 
3
- export const getAuthProviderFromModelProvider = (provider) =>
4
- provider === "openai-codex" ? "openai" : provider;
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) =>
@@ -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 GATEWAY_PORT = 18789;
15
+ const kDefaultGatewayPort = 18789;
16
16
  const GATEWAY_HOST = "127.0.0.1";
17
- const GATEWAY_URL = `http://${GATEWAY_HOST}:${GATEWAY_PORT}`;
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
- GATEWAY_PORT,
376
+ kDefaultGatewayPort,
358
377
  GATEWAY_HOST,
359
- GATEWAY_URL,
378
+ kDefaultGatewayUrl,
360
379
  OPENCLAW_DIR,
361
380
  GATEWAY_TOKEN,
362
381
  ENV_FILE_PATH,
@@ -6,7 +6,7 @@ const {
6
6
  ALPHACLAW_DIR,
7
7
  OPENCLAW_DIR,
8
8
  GATEWAY_HOST,
9
- GATEWAY_PORT,
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(GATEWAY_PORT, GATEWAY_HOST);
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 configPathToEnvName = (dotPath) => {
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 = cfg.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,