@chrysb/alphaclaw 0.1.24 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -134,6 +134,8 @@ export const kProviderOrder = [
134
134
  "deepgram",
135
135
  ];
136
136
 
137
+ export const kCoreProviders = new Set(["anthropic", "openai", "google"]);
138
+
137
139
  export const kProviderFeatures = {
138
140
  anthropic: ["Agent Model"],
139
141
  openai: ["Agent Model", "Embeddings", "Audio"],
@@ -264,6 +264,7 @@ const SETUP_API_PREFIXES = [
264
264
  "/api/openclaw",
265
265
  "/api/devices",
266
266
  "/api/sync-cron",
267
+ "/api/telegram",
267
268
  ];
268
269
 
269
270
  module.exports = {
@@ -63,6 +63,8 @@ const normalizeIp = (ip) => String(ip || "").replace(/^::ffff:/, "");
63
63
 
64
64
  const isTruthyEnvFlag = (value) =>
65
65
  ["1", "true", "yes", "on"].includes(String(value || "").trim().toLowerCase());
66
+ const isDebugEnabled = () =>
67
+ isTruthyEnvFlag(process.env.ALPHACLAW_DEBUG) || isTruthyEnvFlag(process.env.DEBUG);
66
68
 
67
69
  const getClientKey = (req) =>
68
70
  normalizeIp(
@@ -172,6 +174,33 @@ const readGoogleCredentials = () => {
172
174
  }
173
175
  };
174
176
 
177
+ const kSecretKeyMatchers = [
178
+ /(?:^|_)TOKEN(?:$|_)/i,
179
+ /(?:^|_)API_KEY(?:$|_)/i,
180
+ /(?:^|_)PASSWORD(?:$|_)/i,
181
+ /(?:^|_)SECRET(?:$|_)/i,
182
+ /(?:^|_)PRIVATE_KEY(?:$|_)/i,
183
+ ];
184
+
185
+ const isSensitiveKey = (key) =>
186
+ kSecretKeyMatchers.some((matcher) => matcher.test(String(key || "")));
187
+
188
+ const buildSecretReplacements = (...sources) => {
189
+ const replacements = [];
190
+ const seen = new Set();
191
+ for (const source of sources) {
192
+ for (const [rawKey, rawValue] of Object.entries(source || {})) {
193
+ const key = String(rawKey || "").trim();
194
+ const value = String(rawValue || "");
195
+ if (!key || !value || !isSensitiveKey(key)) continue;
196
+ if (seen.has(value)) continue;
197
+ seen.add(value);
198
+ replacements.push([value, `\${${key}}`]);
199
+ }
200
+ }
201
+ return replacements.sort((a, b) => b[0].length - a[0].length);
202
+ };
203
+
175
204
  module.exports = {
176
205
  normalizeOpenclawVersion,
177
206
  compareVersionParts,
@@ -180,6 +209,7 @@ module.exports = {
180
209
  getCodexAccountId,
181
210
  normalizeIp,
182
211
  isTruthyEnvFlag,
212
+ isDebugEnabled,
183
213
  getClientKey,
184
214
  resolveGithubRepoUrl,
185
215
  createPkcePair,
@@ -189,4 +219,6 @@ module.exports = {
189
219
  getBaseUrl,
190
220
  getApiEnableUrl,
191
221
  readGoogleCredentials,
222
+ isSensitiveKey,
223
+ buildSecretReplacements,
192
224
  };
@@ -1,5 +1,4 @@
1
- const ensureGithubRepoAccessible = async ({ repoUrl, repoName, remoteUrl, githubToken }) => {
2
- void remoteUrl;
1
+ const ensureGithubRepoAccessible = async ({ repoUrl, repoName, githubToken }) => {
3
2
  const ghHeaders = {
4
3
  Authorization: `token ${githubToken}`,
5
4
  "User-Agent": "openclaw-railway",
@@ -5,7 +5,6 @@ const { ensureGithubRepoAccessible } = require("./github");
5
5
  const { buildOnboardArgs, writeSanitizedOpenclawConfig } = require("./openclaw");
6
6
  const { installControlUiSkill, syncBootstrapPromptFiles } = require("./workspace");
7
7
  const { installHourlyGitSyncScript, installHourlyGitSyncCron } = require("./cron");
8
- const { isTruthyEnvFlag } = require("../helpers");
9
8
 
10
9
  const createOnboardingService = ({
11
10
  fs,
@@ -43,12 +42,11 @@ const createOnboardingService = ({
43
42
  writeEnvFile(varsToSave);
44
43
  reloadEnv();
45
44
 
46
- const remoteUrl = `https://${githubToken}@github.com/${repoUrl}.git`;
45
+ const remoteUrl = `https://github.com/${repoUrl}.git`;
47
46
  const [, repoName] = repoUrl.split("/");
48
47
  const repoCheck = await ensureGithubRepoAccessible({
49
48
  repoUrl,
50
49
  repoName,
51
- remoteUrl,
52
50
  githubToken,
53
51
  });
54
52
  if (!repoCheck.ok) {
@@ -103,11 +101,15 @@ const createOnboardingService = ({
103
101
 
104
102
  installControlUiSkill({ fs, openclawDir: OPENCLAW_DIR, baseUrl: getBaseUrl(req) });
105
103
 
106
- const shouldForcePush = isTruthyEnvFlag(process.env.OPENCLAW_DEBUG_FORCE_PUSH);
107
- const pushArgs = shouldForcePush ? "-u --force" : "-u";
108
104
  await shellCmd(
109
- `cd ${OPENCLAW_DIR} && git add -A && git commit -m "initial setup" && git push ${pushArgs} origin main`,
110
- { timeout: 30000 },
105
+ `alphaclaw git-sync -m "initial setup"`,
106
+ {
107
+ timeout: 30000,
108
+ env: {
109
+ ...process.env,
110
+ GITHUB_TOKEN: githubToken,
111
+ },
112
+ },
111
113
  ).catch((e) => console.error("[onboard] Git push error:", e.message));
112
114
  console.log("[onboard] Initial state committed and pushed");
113
115
 
@@ -1,3 +1,5 @@
1
+ const { buildSecretReplacements } = require("../helpers");
2
+
1
3
  const buildOnboardArgs = ({ varMap, selectedProvider, hasCodexOauth, workspaceDir }) => {
2
4
  const onboardArgs = [
3
5
  "--non-interactive",
@@ -149,18 +151,9 @@ const writeSanitizedOpenclawConfig = ({ fs, openclawDir, varMap }) => {
149
151
  }
150
152
 
151
153
  let content = JSON.stringify(cfg, null, 2);
152
- const replacements = [
153
- [process.env.OPENCLAW_GATEWAY_TOKEN, "${OPENCLAW_GATEWAY_TOKEN}"],
154
- [varMap.ANTHROPIC_API_KEY, "${ANTHROPIC_API_KEY}"],
155
- [varMap.ANTHROPIC_TOKEN, "${ANTHROPIC_TOKEN}"],
156
- [varMap.TELEGRAM_BOT_TOKEN, "${TELEGRAM_BOT_TOKEN}"],
157
- [varMap.DISCORD_BOT_TOKEN, "${DISCORD_BOT_TOKEN}"],
158
- [varMap.OPENAI_API_KEY, "${OPENAI_API_KEY}"],
159
- [varMap.GEMINI_API_KEY, "${GEMINI_API_KEY}"],
160
- [varMap.BRAVE_API_KEY, "${BRAVE_API_KEY}"],
161
- ];
154
+ const replacements = buildSecretReplacements(varMap, process.env);
162
155
  for (const [secret, envRef] of replacements) {
163
- if (secret && secret.length > 8) {
156
+ if (secret) {
164
157
  content = content.split(secret).join(envRef);
165
158
  }
166
159
  }
@@ -1,5 +1,6 @@
1
1
  const path = require("path");
2
- const { kSetupDir } = require("../constants");
2
+ const { kSetupDir, OPENCLAW_DIR } = require("../constants");
3
+ const { renderTopicRegistryMarkdown } = require("../topic-registry");
3
4
 
4
5
  const resolveSetupUiUrl = (baseUrl) => {
5
6
  const normalizedBaseUrl = String(baseUrl || "").trim().replace(/\/+$/, "");
@@ -19,16 +20,38 @@ const resolveSetupUiUrl = (baseUrl) => {
19
20
  return "http://localhost:3000";
20
21
  };
21
22
 
23
+ // Single assembly point for TOOLS.md: template + topic registry.
24
+ // Idempotent — always rebuilds from source so deploys never clobber topic data.
25
+ const isTelegramWorkspaceEnabled = (fs) => {
26
+ try {
27
+ const configPath = `${OPENCLAW_DIR}/openclaw.json`;
28
+ const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
29
+ return Object.keys(cfg.channels?.telegram?.groups || {}).length > 0;
30
+ } catch {
31
+ return false;
32
+ }
33
+ };
34
+
22
35
  const syncBootstrapPromptFiles = ({ fs, workspaceDir, baseUrl }) => {
23
36
  try {
37
+ const setupUiUrl = resolveSetupUiUrl(baseUrl);
24
38
  const bootstrapDir = `${workspaceDir}/hooks/bootstrap`;
25
39
  fs.mkdirSync(bootstrapDir, { recursive: true });
26
40
  fs.copyFileSync(path.join(kSetupDir, "core-prompts", "AGENTS.md"), `${bootstrapDir}/AGENTS.md`);
41
+
27
42
  const toolsTemplate = fs.readFileSync(path.join(kSetupDir, "core-prompts", "TOOLS.md"), "utf8");
28
- const toolsContent = toolsTemplate.replace(
43
+ let toolsContent = toolsTemplate.replace(
29
44
  /\{\{SETUP_UI_URL\}\}/g,
30
- resolveSetupUiUrl(baseUrl),
45
+ setupUiUrl,
31
46
  );
47
+
48
+ const topicSection = renderTopicRegistryMarkdown({
49
+ includeSyncGuidance: isTelegramWorkspaceEnabled(fs),
50
+ });
51
+ if (topicSection) {
52
+ toolsContent += topicSection;
53
+ }
54
+
32
55
  fs.writeFileSync(`${bootstrapDir}/TOOLS.md`, toolsContent);
33
56
  console.log("[onboard] Bootstrap prompt files synced");
34
57
  } catch (e) {
@@ -2,7 +2,8 @@ const crypto = require("crypto");
2
2
  const { kLoginCleanupIntervalMs } = require("../constants");
3
3
 
4
4
  const registerAuthRoutes = ({ app, loginThrottle }) => {
5
- const SETUP_PASSWORD = process.env.SETUP_PASSWORD || "";
5
+ const SETUP_PASSWORD = String(process.env.SETUP_PASSWORD || "").trim();
6
+ const kAuthMisconfigured = !SETUP_PASSWORD;
6
7
  const kSessionTtlMs = 7 * 24 * 60 * 60 * 1000;
7
8
 
8
9
  const signSessionPayload = (payload) =>
@@ -50,7 +51,13 @@ const registerAuthRoutes = ({ app, loginThrottle }) => {
50
51
  };
51
52
 
52
53
  app.post("/api/auth/login", (req, res) => {
53
- if (!SETUP_PASSWORD) return res.json({ ok: true });
54
+ if (kAuthMisconfigured) {
55
+ return res.status(503).json({
56
+ ok: false,
57
+ error:
58
+ "Server misconfigured: SETUP_PASSWORD is missing. Set it in your deployment environment variables and restart.",
59
+ });
60
+ }
54
61
  const now = Date.now();
55
62
  const clientKey = loginThrottle.getClientKey(req);
56
63
  const state = loginThrottle.getOrCreateLoginAttemptState(clientKey, now);
@@ -92,18 +99,29 @@ const registerAuthRoutes = ({ app, loginThrottle }) => {
92
99
  }, kLoginCleanupIntervalMs).unref();
93
100
 
94
101
  const isAuthorizedRequest = (req) => {
95
- if (!SETUP_PASSWORD) return true;
102
+ if (kAuthMisconfigured) return false;
96
103
  const requestPath = req.path || "";
97
104
  if (requestPath.startsWith("/auth/google/callback")) return true;
98
105
  if (requestPath.startsWith("/auth/codex/callback")) return true;
99
106
  const cookies = cookieParser(req);
100
- const query = req.query || {};
101
- const token = cookies.setup_token || query.token;
107
+ const token = cookies.setup_token;
102
108
  return verifySessionToken(token);
103
109
  };
104
110
 
105
111
  const requireAuth = (req, res, next) => {
106
- if (!SETUP_PASSWORD) return next();
112
+ if (kAuthMisconfigured) {
113
+ if (req.path.startsWith("/api/")) {
114
+ return res.status(503).json({
115
+ error:
116
+ "Server misconfigured: SETUP_PASSWORD is missing. Set it in your deployment environment variables and restart.",
117
+ });
118
+ }
119
+ return res
120
+ .status(503)
121
+ .send(
122
+ "Setup auth is not configured. Set SETUP_PASSWORD in your deployment environment and restart.",
123
+ );
124
+ }
107
125
  if (req.path.startsWith("/auth/google/callback")) return next();
108
126
  if (req.path.startsWith("/auth/codex/callback")) return next();
109
127
  if (isAuthorizedRequest(req)) return next();
@@ -3,13 +3,13 @@ const registerProxyRoutes = ({ app, proxy, SETUP_API_PREFIXES, requireAuth }) =>
3
3
  req.url = "/";
4
4
  proxy.web(req, res);
5
5
  });
6
- app.all("/openclaw/*", requireAuth, (req, res) => {
6
+ app.all("/openclaw/*path", requireAuth, (req, res) => {
7
7
  req.url = req.url.replace(/^\/openclaw/, "");
8
8
  proxy.web(req, res);
9
9
  });
10
- app.all("/assets/*", requireAuth, (req, res) => proxy.web(req, res));
10
+ app.all("/assets/*path", requireAuth, (req, res) => proxy.web(req, res));
11
11
 
12
- app.all("/webhook/*", (req, res) => {
12
+ app.all("/webhook/*path", (req, res) => {
13
13
  if (!req.headers.authorization && req.query.token) {
14
14
  req.headers.authorization = `Bearer ${req.query.token}`;
15
15
  delete req.query.token;
@@ -20,7 +20,7 @@ const registerProxyRoutes = ({ app, proxy, SETUP_API_PREFIXES, requireAuth }) =>
20
20
  proxy.web(req, res);
21
21
  });
22
22
 
23
- app.all("/api/*", (req, res) => {
23
+ app.all("/api/*path", (req, res) => {
24
24
  if (SETUP_API_PREFIXES.some((p) => req.path.startsWith(p))) return;
25
25
  proxy.web(req, res);
26
26
  });