@chrysb/alphaclaw 0.9.14 → 0.9.16

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.
@@ -28,6 +28,7 @@ const registerSystemRoutes = ({
28
28
  doctorService,
29
29
  }) => {
30
30
  let envRestartPending = false;
31
+ let openclawSecretRuntimePromise = null;
31
32
  const kManagedChannelTokenPattern =
32
33
  /^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN)(?:_[A-Z0-9_]+)?$/;
33
34
  const kEnvVarsReservedForUserInput = new Set([
@@ -92,6 +93,97 @@ const registerSystemRoutes = ({
92
93
  }
93
94
  return null;
94
95
  };
96
+ const getEnvFileValue = (key) =>
97
+ (typeof readEnvFile === "function" ? readEnvFile() : []).find(
98
+ (entry) => entry?.key === key,
99
+ )?.value;
100
+ const normalizeSecretValue = (value) => {
101
+ if (typeof value !== "string") return "";
102
+ const trimmed = String(value || "").trim();
103
+ if (trimmed.length >= 2) {
104
+ const first = trimmed[0];
105
+ const last = trimmed[trimmed.length - 1];
106
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
107
+ return trimmed.slice(1, -1).trim();
108
+ }
109
+ }
110
+ return trimmed;
111
+ };
112
+ const getEnvObject = () => {
113
+ const env = { ...process.env };
114
+ for (const entry of typeof readEnvFile === "function" ? readEnvFile() : []) {
115
+ const key = String(entry?.key || "").trim();
116
+ if (!key) continue;
117
+ if (!normalizeSecretValue(env[key])) {
118
+ env[key] = normalizeSecretValue(entry?.value);
119
+ }
120
+ }
121
+ return env;
122
+ };
123
+ const loadOpenclawSecretRuntime = async () => {
124
+ openclawSecretRuntimePromise ||= Promise.all([
125
+ import("openclaw/plugin-sdk/secret-input"),
126
+ import("openclaw/plugin-sdk/runtime-secret-resolution"),
127
+ ]).then(([secretInput, runtimeSecretResolution]) => ({
128
+ coerceSecretRef: secretInput.coerceSecretRef,
129
+ resolveSecretRefValues: runtimeSecretResolution.resolveSecretRefValues,
130
+ }));
131
+ return openclawSecretRuntimePromise;
132
+ };
133
+ const resolveSecretRefToken = async ({ config, value, env }) => {
134
+ try {
135
+ const { coerceSecretRef, resolveSecretRefValues } =
136
+ await loadOpenclawSecretRuntime();
137
+ const ref = coerceSecretRef(value, config?.secrets?.defaults);
138
+ if (!ref) return "";
139
+ const resolved = await resolveSecretRefValues([ref], { config, env });
140
+ const refKey = `${ref.source}:${ref.provider}:${ref.id}`;
141
+ return normalizeSecretValue(resolved.get(refKey));
142
+ } catch {
143
+ return "";
144
+ }
145
+ };
146
+ const resolveEnvReference = (value) => {
147
+ const match = String(value || "").trim().match(/^\$\{([A-Z_][A-Z0-9_]*)\}$/);
148
+ if (!match) return "";
149
+ const envKey = match[1];
150
+ const envValue = process.env[envKey] || getEnvFileValue(envKey);
151
+ return normalizeSecretValue(envValue);
152
+ };
153
+ const getDashboardTokenFromConfig = async () => {
154
+ const config = readOpenclawConfig({
155
+ fsModule: fs,
156
+ openclawDir: OPENCLAW_DIR,
157
+ fallback: {},
158
+ });
159
+ const env = getEnvObject();
160
+ const configuredToken = config?.gateway?.auth?.token;
161
+ const resolvedSecretRefToken = await resolveSecretRefToken({
162
+ config,
163
+ value: configuredToken,
164
+ env,
165
+ });
166
+ if (resolvedSecretRefToken) return resolvedSecretRefToken;
167
+ if (typeof configuredToken === "string" && configuredToken.trim()) {
168
+ const trimmedToken = normalizeSecretValue(configuredToken);
169
+ if (/^\$\{[A-Z_][A-Z0-9_]*\}$/.test(trimmedToken)) {
170
+ return resolveEnvReference(trimmedToken);
171
+ }
172
+ return trimmedToken;
173
+ }
174
+ return normalizeSecretValue(env.OPENCLAW_GATEWAY_TOKEN);
175
+ };
176
+ const buildDashboardUrl = (token) =>
177
+ token ? `/openclaw/#token=${encodeURIComponent(token)}` : "/openclaw";
178
+ const extractDashboardTokenFromOutput = (stdout) => {
179
+ const tokenMatch = String(stdout || "").match(/[#?&]token=([^\s&#]+)/);
180
+ if (!tokenMatch) return "";
181
+ try {
182
+ return decodeURIComponent(tokenMatch[1]);
183
+ } catch {
184
+ return tokenMatch[1];
185
+ }
186
+ };
95
187
  const getRawSessionKey = (sessionRow = {}) =>
96
188
  String(sessionRow?.key || sessionRow?.sessionKey || sessionRow?.id || "").trim();
97
189
  const getRawSessionsFromPayload = (payload) => {
@@ -692,14 +784,22 @@ const registerSystemRoutes = ({
692
784
 
693
785
  app.get("/api/gateway/dashboard", async (req, res) => {
694
786
  if (!isOnboarded()) return res.json({ ok: false, url: "/openclaw" });
787
+ const token = await getDashboardTokenFromConfig();
788
+ if (token) {
789
+ return res.json({
790
+ ok: true,
791
+ url: buildDashboardUrl(token),
792
+ source: "config",
793
+ });
794
+ }
695
795
  const result = await clawCmd("dashboard --no-open");
696
796
  if (result.ok && result.stdout) {
697
- const tokenMatch = result.stdout.match(/#token=([a-zA-Z0-9]+)/);
698
- if (tokenMatch) {
699
- return res.json({ ok: true, url: `/openclaw/#token=${tokenMatch[1]}` });
797
+ const cliToken = extractDashboardTokenFromOutput(result.stdout);
798
+ if (cliToken) {
799
+ return res.json({ ok: true, url: buildDashboardUrl(cliToken) });
700
800
  }
701
801
  }
702
- res.json({ ok: true, url: "/openclaw" });
802
+ res.json({ ok: true, url: "/openclaw", needsAuth: true });
703
803
  });
704
804
 
705
805
  app.get("/api/restart-status", async (req, res) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.9.14",
3
+ "version": "0.9.16",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "express": "^4.21.0",
35
35
  "http-proxy": "^1.18.1",
36
- "openclaw": "2026.5.2",
36
+ "openclaw": "2026.5.6",
37
37
  "ws": "^8.19.0"
38
38
  },
39
39
  "devDependencies": {