@chrysb/alphaclaw 0.1.25 → 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.
- package/bin/alphaclaw.js +391 -54
- package/lib/public/js/app.js +38 -19
- package/lib/public/js/components/channels.js +14 -3
- package/lib/public/js/components/telegram-workspace.js +1377 -0
- package/lib/server/constants.js +1 -0
- package/lib/server/helpers.js +32 -0
- package/lib/server/onboarding/github.js +1 -2
- package/lib/server/onboarding/index.js +9 -7
- package/lib/server/onboarding/openclaw.js +4 -11
- package/lib/server/onboarding/workspace.js +26 -3
- package/lib/server/routes/auth.js +24 -6
- package/lib/server/routes/proxy.js +4 -4
- package/lib/server/routes/telegram.js +407 -0
- package/lib/server/telegram-api.js +65 -0
- package/lib/server/telegram-workspace.js +82 -0
- package/lib/server/topic-registry.js +152 -0
- package/lib/server.js +7 -1
- package/lib/setup/core-prompts/TOOLS.md +1 -1
- package/lib/setup/env.template +3 -0
- package/lib/setup/hourly-git-sync.sh +9 -10
- package/package.json +1 -1
package/bin/alphaclaw.js
CHANGED
|
@@ -5,17 +5,59 @@ const fs = require("fs");
|
|
|
5
5
|
const os = require("os");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const { execSync } = require("child_process");
|
|
8
|
+
const { buildSecretReplacements } = require("../lib/server/helpers");
|
|
8
9
|
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
10
11
|
// Parse CLI flags
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
12
13
|
|
|
13
14
|
const args = process.argv.slice(2);
|
|
14
|
-
const command = args.find((a) => !a.startsWith("-"));
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
const flagValue = (argv, ...flags) => {
|
|
17
|
+
for (const flag of flags) {
|
|
18
|
+
const idx = argv.indexOf(flag);
|
|
19
|
+
if (idx !== -1 && idx + 1 < argv.length) {
|
|
20
|
+
return argv[idx + 1];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const kGlobalValueFlags = new Set(["--root-dir", "--port"]);
|
|
27
|
+
const splitGlobalAndCommandArgs = (argv) => {
|
|
28
|
+
const globalArgs = [];
|
|
29
|
+
let index = 0;
|
|
30
|
+
while (index < argv.length) {
|
|
31
|
+
const token = argv[index];
|
|
32
|
+
if (!token.startsWith("-")) break;
|
|
33
|
+
globalArgs.push(token);
|
|
34
|
+
if (kGlobalValueFlags.has(token) && index + 1 < argv.length) {
|
|
35
|
+
globalArgs.push(argv[index + 1]);
|
|
36
|
+
index += 2;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
index += 1;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
globalArgs,
|
|
43
|
+
commandArgs: argv.slice(index),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const { globalArgs, commandArgs } = splitGlobalAndCommandArgs(args);
|
|
48
|
+
const command = commandArgs[0];
|
|
49
|
+
const commandScope = commandArgs[1];
|
|
50
|
+
const commandAction = commandArgs[2];
|
|
17
51
|
|
|
18
|
-
|
|
52
|
+
const pkg = JSON.parse(
|
|
53
|
+
fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
args.includes("--version") ||
|
|
58
|
+
args.includes("-v") ||
|
|
59
|
+
command === "version"
|
|
60
|
+
) {
|
|
19
61
|
console.log(pkg.version);
|
|
20
62
|
process.exit(0);
|
|
21
63
|
}
|
|
@@ -28,33 +70,55 @@ Usage: alphaclaw <command> [options]
|
|
|
28
70
|
|
|
29
71
|
Commands:
|
|
30
72
|
start Start the AlphaClaw server (Setup UI + gateway manager)
|
|
73
|
+
git-sync Commit and push /data/.openclaw safely using GITHUB_TOKEN
|
|
74
|
+
telegram topic add Add/update Telegram topic mapping by thread ID
|
|
31
75
|
version Print version
|
|
32
76
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
77
|
+
Global options:
|
|
78
|
+
--version, -v Print version
|
|
79
|
+
--help Show this help message
|
|
80
|
+
|
|
81
|
+
start options:
|
|
82
|
+
--root-dir <path> Persistent data directory (default: ~/.alphaclaw)
|
|
83
|
+
--port <number> Server port (default: 3000)
|
|
84
|
+
|
|
85
|
+
git-sync options:
|
|
86
|
+
--message, -m <text> Commit message
|
|
87
|
+
|
|
88
|
+
telegram topic add options:
|
|
89
|
+
--thread <id> Telegram thread ID
|
|
90
|
+
--name <text> Topic name
|
|
91
|
+
--system <text> Optional system instructions
|
|
92
|
+
--group <id> Optional group ID override (auto-resolves when one group exists)
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
alphaclaw git-sync --message "sync workspace"
|
|
96
|
+
alphaclaw telegram topic add --thread 12 --name "Testing"
|
|
97
|
+
alphaclaw telegram topic add --thread 12 --name "Testing" --system "Handle QA requests"
|
|
38
98
|
`);
|
|
39
99
|
process.exit(0);
|
|
40
100
|
}
|
|
41
101
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
102
|
+
const quoteArg = (value) => `'${String(value || "").replace(/'/g, "'\"'\"'")}'`;
|
|
103
|
+
const resolveGithubRepoPath = (value) =>
|
|
104
|
+
String(value || "")
|
|
105
|
+
.trim()
|
|
106
|
+
.replace(/^git@github\.com:/, "")
|
|
107
|
+
.replace(/^https:\/\/github\.com\//, "")
|
|
108
|
+
.replace(/\.git$/, "");
|
|
46
109
|
|
|
47
110
|
// ---------------------------------------------------------------------------
|
|
48
111
|
// 1. Resolve root directory (before requiring any lib/ modules)
|
|
49
112
|
// ---------------------------------------------------------------------------
|
|
50
113
|
|
|
51
|
-
const rootDir =
|
|
52
|
-
||
|
|
53
|
-
|
|
114
|
+
const rootDir =
|
|
115
|
+
flagValue(globalArgs, "--root-dir") ||
|
|
116
|
+
process.env.ALPHACLAW_ROOT_DIR ||
|
|
117
|
+
path.join(os.homedir(), ".alphaclaw");
|
|
54
118
|
|
|
55
119
|
process.env.ALPHACLAW_ROOT_DIR = rootDir;
|
|
56
120
|
|
|
57
|
-
const portFlag = flagValue("--port");
|
|
121
|
+
const portFlag = flagValue(globalArgs, "--port");
|
|
58
122
|
if (portFlag) {
|
|
59
123
|
process.env.PORT = portFlag;
|
|
60
124
|
}
|
|
@@ -73,16 +137,24 @@ console.log(`[alphaclaw] Root directory: ${rootDir}`);
|
|
|
73
137
|
// from the fresh container using the persistent volume marker.
|
|
74
138
|
const pendingUpdateMarker = path.join(rootDir, ".alphaclaw-update-pending");
|
|
75
139
|
if (fs.existsSync(pendingUpdateMarker)) {
|
|
76
|
-
console.log(
|
|
140
|
+
console.log(
|
|
141
|
+
"[alphaclaw] Pending update detected, installing @chrysb/alphaclaw@latest...",
|
|
142
|
+
);
|
|
77
143
|
const alphaPkgRoot = path.resolve(__dirname, "..");
|
|
78
|
-
const nmIndex = alphaPkgRoot.lastIndexOf(
|
|
79
|
-
|
|
144
|
+
const nmIndex = alphaPkgRoot.lastIndexOf(
|
|
145
|
+
`${path.sep}node_modules${path.sep}`,
|
|
146
|
+
);
|
|
147
|
+
const installDir =
|
|
148
|
+
nmIndex >= 0 ? alphaPkgRoot.slice(0, nmIndex) : alphaPkgRoot;
|
|
80
149
|
try {
|
|
81
|
-
execSync(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
150
|
+
execSync(
|
|
151
|
+
"npm install @chrysb/alphaclaw@latest --omit=dev --prefer-online",
|
|
152
|
+
{
|
|
153
|
+
cwd: installDir,
|
|
154
|
+
stdio: "inherit",
|
|
155
|
+
timeout: 180000,
|
|
156
|
+
},
|
|
157
|
+
);
|
|
86
158
|
fs.unlinkSync(pendingUpdateMarker);
|
|
87
159
|
console.log("[alphaclaw] Update applied successfully");
|
|
88
160
|
} catch (e) {
|
|
@@ -154,6 +226,218 @@ if (fs.existsSync(envFilePath)) {
|
|
|
154
226
|
console.log("[alphaclaw] Loaded .env");
|
|
155
227
|
}
|
|
156
228
|
|
|
229
|
+
const runGitSync = () => {
|
|
230
|
+
const githubToken = String(process.env.GITHUB_TOKEN || "").trim();
|
|
231
|
+
const githubRepo = resolveGithubRepoPath(
|
|
232
|
+
process.env.GITHUB_WORKSPACE_REPO || "",
|
|
233
|
+
);
|
|
234
|
+
const commitMessage = String(
|
|
235
|
+
flagValue(commandArgs, "--message", "-m") || "",
|
|
236
|
+
).trim();
|
|
237
|
+
if (!commitMessage) {
|
|
238
|
+
console.error("[alphaclaw] Missing --message for git-sync");
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
if (!githubToken) {
|
|
242
|
+
console.error("[alphaclaw] Missing GITHUB_TOKEN for git-sync");
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
if (!githubRepo) {
|
|
246
|
+
console.error("[alphaclaw] Missing GITHUB_WORKSPACE_REPO for git-sync");
|
|
247
|
+
return 1;
|
|
248
|
+
}
|
|
249
|
+
if (!fs.existsSync(path.join(openclawDir, ".git"))) {
|
|
250
|
+
console.error("[alphaclaw] No git repository at /data/.openclaw");
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const originUrl = `https://github.com/${githubRepo}.git`;
|
|
255
|
+
const branch =
|
|
256
|
+
String(
|
|
257
|
+
execSync("git rev-parse --abbrev-ref HEAD", {
|
|
258
|
+
cwd: openclawDir,
|
|
259
|
+
encoding: "utf8",
|
|
260
|
+
}),
|
|
261
|
+
).trim() || "main";
|
|
262
|
+
const askPassPath = path.join(
|
|
263
|
+
os.tmpdir(),
|
|
264
|
+
`alphaclaw-git-askpass-${process.pid}.sh`,
|
|
265
|
+
);
|
|
266
|
+
const runGit = (gitCommand, { withAuth = false } = {}) => {
|
|
267
|
+
const cmd = withAuth
|
|
268
|
+
? `GIT_TERMINAL_PROMPT=0 GIT_ASKPASS=${quoteArg(askPassPath)} git ${gitCommand}`
|
|
269
|
+
: `git ${gitCommand}`;
|
|
270
|
+
return execSync(cmd, {
|
|
271
|
+
cwd: openclawDir,
|
|
272
|
+
stdio: "pipe",
|
|
273
|
+
encoding: "utf8",
|
|
274
|
+
env: {
|
|
275
|
+
...process.env,
|
|
276
|
+
GITHUB_TOKEN: githubToken,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
fs.writeFileSync(
|
|
283
|
+
askPassPath,
|
|
284
|
+
[
|
|
285
|
+
"#!/usr/bin/env sh",
|
|
286
|
+
'case "$1" in',
|
|
287
|
+
' *Username*) echo "x-access-token" ;;',
|
|
288
|
+
' *Password*) echo "${GITHUB_TOKEN:-}" ;;',
|
|
289
|
+
' *) echo "" ;;',
|
|
290
|
+
"esac",
|
|
291
|
+
"",
|
|
292
|
+
].join("\n"),
|
|
293
|
+
{ mode: 0o700 },
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
runGit(`remote set-url origin ${quoteArg(originUrl)}`);
|
|
297
|
+
try {
|
|
298
|
+
runGit(`ls-remote --exit-code --heads origin ${quoteArg(branch)}`, {
|
|
299
|
+
withAuth: true,
|
|
300
|
+
});
|
|
301
|
+
runGit(`pull --rebase --autostash origin ${quoteArg(branch)}`, {
|
|
302
|
+
withAuth: true,
|
|
303
|
+
});
|
|
304
|
+
} catch {
|
|
305
|
+
console.log(
|
|
306
|
+
`[alphaclaw] Remote branch "${branch}" not found, skipping pull`,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
runGit("add -A");
|
|
310
|
+
try {
|
|
311
|
+
runGit("diff --cached --quiet");
|
|
312
|
+
console.log("[alphaclaw] No changes to commit");
|
|
313
|
+
return 0;
|
|
314
|
+
} catch {}
|
|
315
|
+
runGit(`commit -m ${quoteArg(commitMessage)}`);
|
|
316
|
+
runGit(`push origin ${quoteArg(branch)}`, { withAuth: true });
|
|
317
|
+
const hash = String(runGit("rev-parse --short HEAD")).trim();
|
|
318
|
+
console.log(`[alphaclaw] Git sync complete (${hash})`);
|
|
319
|
+
return 0;
|
|
320
|
+
} catch (e) {
|
|
321
|
+
const details = String(e.stderr || e.stdout || e.message || "").trim();
|
|
322
|
+
console.error(`[alphaclaw] git-sync failed: ${details.slice(0, 400)}`);
|
|
323
|
+
return 1;
|
|
324
|
+
} finally {
|
|
325
|
+
try {
|
|
326
|
+
fs.rmSync(askPassPath, { force: true });
|
|
327
|
+
} catch {}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
if (command === "git-sync") {
|
|
332
|
+
process.exit(runGitSync());
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const runTelegramTopicAdd = () => {
|
|
336
|
+
const topicName = String(flagValue(commandArgs, "--name") || "").trim();
|
|
337
|
+
const threadId = String(flagValue(commandArgs, "--thread") || "").trim();
|
|
338
|
+
const systemInstructions = String(
|
|
339
|
+
flagValue(commandArgs, "--system") || "",
|
|
340
|
+
).trim();
|
|
341
|
+
const requestedGroupId = String(
|
|
342
|
+
flagValue(commandArgs, "--group") || "",
|
|
343
|
+
).trim();
|
|
344
|
+
if (!threadId) {
|
|
345
|
+
console.error("[alphaclaw] Missing --thread for telegram topic add");
|
|
346
|
+
return 1;
|
|
347
|
+
}
|
|
348
|
+
if (!topicName) {
|
|
349
|
+
console.error("[alphaclaw] Missing --name for telegram topic add");
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const configPath = path.join(openclawDir, "openclaw.json");
|
|
354
|
+
if (!fs.existsSync(configPath)) {
|
|
355
|
+
console.error("[alphaclaw] Missing openclaw.json. Run setup first.");
|
|
356
|
+
return 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
361
|
+
const configuredGroups = Object.keys(cfg.channels?.telegram?.groups || {});
|
|
362
|
+
let groupId = requestedGroupId;
|
|
363
|
+
if (!groupId) {
|
|
364
|
+
if (configuredGroups.length === 1) {
|
|
365
|
+
[groupId] = configuredGroups;
|
|
366
|
+
} else if (configuredGroups.length === 0) {
|
|
367
|
+
console.error(
|
|
368
|
+
"[alphaclaw] No Telegram group configured. Configure Telegram workspace first.",
|
|
369
|
+
);
|
|
370
|
+
return 1;
|
|
371
|
+
} else {
|
|
372
|
+
console.error(
|
|
373
|
+
"[alphaclaw] Multiple Telegram groups detected. Provide --group <groupId>.",
|
|
374
|
+
);
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const topicRegistry = require("../lib/server/topic-registry");
|
|
380
|
+
const {
|
|
381
|
+
syncConfigForTelegram,
|
|
382
|
+
} = require("../lib/server/telegram-workspace");
|
|
383
|
+
const {
|
|
384
|
+
syncBootstrapPromptFiles,
|
|
385
|
+
} = require("../lib/server/onboarding/workspace");
|
|
386
|
+
topicRegistry.updateTopic(groupId, threadId, {
|
|
387
|
+
name: topicName,
|
|
388
|
+
...(systemInstructions ? { systemInstructions } : {}),
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const requireMention =
|
|
392
|
+
!!cfg.channels?.telegram?.groups?.[groupId]?.requireMention;
|
|
393
|
+
const syncResult = syncConfigForTelegram({
|
|
394
|
+
fs,
|
|
395
|
+
openclawDir,
|
|
396
|
+
topicRegistry,
|
|
397
|
+
groupId,
|
|
398
|
+
requireMention,
|
|
399
|
+
resolvedUserId: "",
|
|
400
|
+
});
|
|
401
|
+
syncBootstrapPromptFiles({
|
|
402
|
+
fs,
|
|
403
|
+
workspaceDir: path.join(openclawDir, "workspace"),
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
console.log(
|
|
407
|
+
`[alphaclaw] Topic mapped: group=${groupId} thread=${threadId} name=${topicName}`,
|
|
408
|
+
);
|
|
409
|
+
console.log(
|
|
410
|
+
`[alphaclaw] Concurrency updated: agent=${syncResult.maxConcurrent} subagents=${syncResult.subagentMaxConcurrent} topics=${syncResult.totalTopics}`,
|
|
411
|
+
);
|
|
412
|
+
return 0;
|
|
413
|
+
} catch (e) {
|
|
414
|
+
console.error(`[alphaclaw] telegram topic add failed: ${e.message}`);
|
|
415
|
+
return 1;
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
if (
|
|
420
|
+
command === "telegram" &&
|
|
421
|
+
commandScope === "topic" &&
|
|
422
|
+
commandAction === "add"
|
|
423
|
+
) {
|
|
424
|
+
process.exit(runTelegramTopicAdd());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const kSetupPassword = String(process.env.SETUP_PASSWORD || "").trim();
|
|
428
|
+
if (!kSetupPassword) {
|
|
429
|
+
console.error(
|
|
430
|
+
[
|
|
431
|
+
"[alphaclaw] Fatal config error: SETUP_PASSWORD is missing or empty.",
|
|
432
|
+
"[alphaclaw] Set SETUP_PASSWORD in your deployment environment variables and restart.",
|
|
433
|
+
"[alphaclaw] Examples:",
|
|
434
|
+
"[alphaclaw] - Render: Dashboard -> Environment -> Add SETUP_PASSWORD",
|
|
435
|
+
"[alphaclaw] - Railway: Project -> Variables -> Add SETUP_PASSWORD",
|
|
436
|
+
].join("\n"),
|
|
437
|
+
);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
|
|
157
441
|
// ---------------------------------------------------------------------------
|
|
158
442
|
// 7. Set OPENCLAW_HOME globally so all child processes inherit it
|
|
159
443
|
// ---------------------------------------------------------------------------
|
|
@@ -184,7 +468,10 @@ if (!gogInstalled) {
|
|
|
184
468
|
const arch = os.arch() === "arm64" ? "arm64" : "amd64";
|
|
185
469
|
const tarball = `gogcli_${gogVersion}_${platform}_${arch}.tar.gz`;
|
|
186
470
|
const url = `https://github.com/steipete/gogcli/releases/download/v${gogVersion}/${tarball}`;
|
|
187
|
-
execSync(
|
|
471
|
+
execSync(
|
|
472
|
+
`curl -fsSL "${url}" -o /tmp/gog.tar.gz && tar -xzf /tmp/gog.tar.gz -C /tmp/ && mv /tmp/gog /usr/local/bin/gog && chmod +x /usr/local/bin/gog && rm -f /tmp/gog.tar.gz`,
|
|
473
|
+
{ stdio: "inherit" },
|
|
474
|
+
);
|
|
188
475
|
console.log("[alphaclaw] gog CLI installed");
|
|
189
476
|
} catch (e) {
|
|
190
477
|
console.log(`[alphaclaw] gog install skipped: ${e.message}`);
|
|
@@ -195,7 +482,8 @@ if (!gogInstalled) {
|
|
|
195
482
|
// 7. Configure gog keyring (file backend for headless environments)
|
|
196
483
|
// ---------------------------------------------------------------------------
|
|
197
484
|
|
|
198
|
-
process.env.GOG_KEYRING_PASSWORD =
|
|
485
|
+
process.env.GOG_KEYRING_PASSWORD =
|
|
486
|
+
process.env.GOG_KEYRING_PASSWORD || "alphaclaw";
|
|
199
487
|
const gogConfigFile = path.join(openclawDir, "gogcli", "config.json");
|
|
200
488
|
|
|
201
489
|
if (!fs.existsSync(gogConfigFile)) {
|
|
@@ -211,6 +499,31 @@ if (!fs.existsSync(gogConfigFile)) {
|
|
|
211
499
|
// ---------------------------------------------------------------------------
|
|
212
500
|
|
|
213
501
|
const hourlyGitSyncPath = path.join(openclawDir, "hourly-git-sync.sh");
|
|
502
|
+
const packagedHourlyGitSyncPath = path.join(setupDir, "hourly-git-sync.sh");
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
if (fs.existsSync(packagedHourlyGitSyncPath)) {
|
|
506
|
+
const packagedSyncScript = fs.readFileSync(
|
|
507
|
+
packagedHourlyGitSyncPath,
|
|
508
|
+
"utf8",
|
|
509
|
+
);
|
|
510
|
+
const installedSyncScript = fs.existsSync(hourlyGitSyncPath)
|
|
511
|
+
? fs.readFileSync(hourlyGitSyncPath, "utf8")
|
|
512
|
+
: "";
|
|
513
|
+
const shouldInstallSyncScript =
|
|
514
|
+
!installedSyncScript ||
|
|
515
|
+
!installedSyncScript.includes("GIT_ASKPASS") ||
|
|
516
|
+
!installedSyncScript.includes("GITHUB_TOKEN");
|
|
517
|
+
if (shouldInstallSyncScript && packagedSyncScript.trim()) {
|
|
518
|
+
fs.writeFileSync(hourlyGitSyncPath, packagedSyncScript, { mode: 0o755 });
|
|
519
|
+
console.log("[alphaclaw] Refreshed hourly git sync script");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
} catch (e) {
|
|
523
|
+
console.log(
|
|
524
|
+
`[alphaclaw] Hourly git sync script refresh skipped: ${e.message}`,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
214
527
|
|
|
215
528
|
if (fs.existsSync(hourlyGitSyncPath)) {
|
|
216
529
|
try {
|
|
@@ -238,7 +551,9 @@ if (fs.existsSync(hourlyGitSyncPath)) {
|
|
|
238
551
|
fs.writeFileSync(cronFilePath, cronContent, { mode: 0o644 });
|
|
239
552
|
console.log("[alphaclaw] System cron entry installed");
|
|
240
553
|
} else {
|
|
241
|
-
try {
|
|
554
|
+
try {
|
|
555
|
+
fs.unlinkSync(cronFilePath);
|
|
556
|
+
} catch {}
|
|
242
557
|
console.log("[alphaclaw] System cron entry disabled");
|
|
243
558
|
}
|
|
244
559
|
} catch (e) {
|
|
@@ -271,13 +586,18 @@ if (process.env.GOG_CLIENT_CREDENTIALS_JSON && process.env.GOG_REFRESH_TOKEN) {
|
|
|
271
586
|
fs.writeFileSync(tmpCreds, process.env.GOG_CLIENT_CREDENTIALS_JSON);
|
|
272
587
|
execSync(`gog auth credentials set "${tmpCreds}"`, { stdio: "ignore" });
|
|
273
588
|
fs.unlinkSync(tmpCreds);
|
|
274
|
-
fs.writeFileSync(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
589
|
+
fs.writeFileSync(
|
|
590
|
+
tmpToken,
|
|
591
|
+
JSON.stringify({
|
|
592
|
+
email: process.env.GOG_ACCOUNT || "",
|
|
593
|
+
refresh_token: process.env.GOG_REFRESH_TOKEN,
|
|
594
|
+
}),
|
|
595
|
+
);
|
|
278
596
|
execSync(`gog auth tokens import "${tmpToken}"`, { stdio: "ignore" });
|
|
279
597
|
fs.unlinkSync(tmpToken);
|
|
280
|
-
console.log(
|
|
598
|
+
console.log(
|
|
599
|
+
`[alphaclaw] gog CLI configured for ${process.env.GOG_ACCOUNT || "account"}`,
|
|
600
|
+
);
|
|
281
601
|
} catch (e) {
|
|
282
602
|
console.log(`[alphaclaw] gog credentials setup skipped: ${e.message}`);
|
|
283
603
|
}
|
|
@@ -294,17 +614,42 @@ const configPath = path.join(openclawDir, "openclaw.json");
|
|
|
294
614
|
if (fs.existsSync(configPath)) {
|
|
295
615
|
console.log("[alphaclaw] Config exists, reconciling channels...");
|
|
296
616
|
|
|
297
|
-
const githubToken = process.env.GITHUB_TOKEN;
|
|
298
617
|
const githubRepo = process.env.GITHUB_WORKSPACE_REPO;
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
618
|
+
if (fs.existsSync(path.join(openclawDir, ".git"))) {
|
|
619
|
+
if (githubRepo) {
|
|
620
|
+
const repoUrl = githubRepo
|
|
621
|
+
.replace(/^git@github\.com:/, "")
|
|
622
|
+
.replace(/^https:\/\/github\.com\//, "")
|
|
623
|
+
.replace(/\.git$/, "");
|
|
624
|
+
const remoteUrl = `https://github.com/${repoUrl}.git`;
|
|
625
|
+
try {
|
|
626
|
+
execSync(`git remote set-url origin "${remoteUrl}"`, {
|
|
627
|
+
cwd: openclawDir,
|
|
628
|
+
stdio: "ignore",
|
|
629
|
+
});
|
|
630
|
+
console.log("[alphaclaw] Repo ready");
|
|
631
|
+
} catch {}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Migration path: scrub persisted PATs from existing GitHub origin URLs.
|
|
305
635
|
try {
|
|
306
|
-
execSync(
|
|
307
|
-
|
|
636
|
+
const existingOrigin = execSync("git remote get-url origin", {
|
|
637
|
+
cwd: openclawDir,
|
|
638
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
639
|
+
encoding: "utf8",
|
|
640
|
+
}).trim();
|
|
641
|
+
const match = existingOrigin.match(
|
|
642
|
+
/^https:\/\/[^/@]+@github\.com\/(.+)$/i,
|
|
643
|
+
);
|
|
644
|
+
if (match?.[1]) {
|
|
645
|
+
const cleanedPath = String(match[1]).replace(/\.git$/i, "");
|
|
646
|
+
const cleanedOrigin = `https://github.com/${cleanedPath}.git`;
|
|
647
|
+
execSync(`git remote set-url origin "${cleanedOrigin}"`, {
|
|
648
|
+
cwd: openclawDir,
|
|
649
|
+
stdio: "ignore",
|
|
650
|
+
});
|
|
651
|
+
console.log("[alphaclaw] Scrubbed tokenized GitHub remote URL");
|
|
652
|
+
}
|
|
308
653
|
} catch {}
|
|
309
654
|
}
|
|
310
655
|
|
|
@@ -341,19 +686,9 @@ if (fs.existsSync(configPath)) {
|
|
|
341
686
|
|
|
342
687
|
if (changed) {
|
|
343
688
|
let content = JSON.stringify(cfg, null, 2);
|
|
344
|
-
const replacements =
|
|
345
|
-
[process.env.OPENCLAW_GATEWAY_TOKEN, "${OPENCLAW_GATEWAY_TOKEN}"],
|
|
346
|
-
[process.env.ANTHROPIC_API_KEY, "${ANTHROPIC_API_KEY}"],
|
|
347
|
-
[process.env.ANTHROPIC_TOKEN, "${ANTHROPIC_TOKEN}"],
|
|
348
|
-
[process.env.TELEGRAM_BOT_TOKEN, "${TELEGRAM_BOT_TOKEN}"],
|
|
349
|
-
[process.env.DISCORD_BOT_TOKEN, "${DISCORD_BOT_TOKEN}"],
|
|
350
|
-
[process.env.OPENAI_API_KEY, "${OPENAI_API_KEY}"],
|
|
351
|
-
[process.env.GEMINI_API_KEY, "${GEMINI_API_KEY}"],
|
|
352
|
-
[process.env.NOTION_API_KEY, "${NOTION_API_KEY}"],
|
|
353
|
-
[process.env.BRAVE_API_KEY, "${BRAVE_API_KEY}"],
|
|
354
|
-
];
|
|
689
|
+
const replacements = buildSecretReplacements(process.env);
|
|
355
690
|
for (const [secret, envRef] of replacements) {
|
|
356
|
-
if (secret
|
|
691
|
+
if (secret) {
|
|
357
692
|
content = content.split(secret).join(envRef);
|
|
358
693
|
}
|
|
359
694
|
}
|
|
@@ -364,7 +699,9 @@ if (fs.existsSync(configPath)) {
|
|
|
364
699
|
console.error(`[alphaclaw] Channel reconciliation error: ${e.message}`);
|
|
365
700
|
}
|
|
366
701
|
} else {
|
|
367
|
-
console.log(
|
|
702
|
+
console.log(
|
|
703
|
+
"[alphaclaw] No config yet -- onboarding will run from the Setup UI",
|
|
704
|
+
);
|
|
368
705
|
}
|
|
369
706
|
|
|
370
707
|
// ---------------------------------------------------------------------------
|
package/lib/public/js/app.js
CHANGED
|
@@ -28,12 +28,14 @@ import { Providers } from "./components/providers.js";
|
|
|
28
28
|
import { Welcome } from "./components/welcome.js";
|
|
29
29
|
import { Envars } from "./components/envars.js";
|
|
30
30
|
import { ToastContainer, showToast } from "./components/toast.js";
|
|
31
|
+
import { TelegramWorkspace } from "./components/telegram-workspace.js";
|
|
31
32
|
import { ChevronDownIcon } from "./components/icons.js";
|
|
32
33
|
const html = htm.bind(h);
|
|
33
34
|
const kUiTabs = ["general", "providers", "envars"];
|
|
35
|
+
const kSubScreens = ["telegram"];
|
|
34
36
|
const kDefaultUiTab = "general";
|
|
35
37
|
|
|
36
|
-
const GeneralTab = ({ onSwitchTab, isActive }) => {
|
|
38
|
+
const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
37
39
|
const [googleKey, setGoogleKey] = useState(0);
|
|
38
40
|
const [dashboardLoading, setDashboardLoading] = useState(false);
|
|
39
41
|
|
|
@@ -156,7 +158,7 @@ const GeneralTab = ({ onSwitchTab, isActive }) => {
|
|
|
156
158
|
return html`
|
|
157
159
|
<div class="space-y-4">
|
|
158
160
|
<${Gateway} status=${gatewayStatus} openclawVersion=${openclawVersion} />
|
|
159
|
-
<${Channels} channels=${channels} onSwitchTab=${onSwitchTab} />
|
|
161
|
+
<${Channels} channels=${channels} onSwitchTab=${onSwitchTab} onNavigate=${onNavigate} />
|
|
160
162
|
<${Pairings}
|
|
161
163
|
pending=${pending}
|
|
162
164
|
channels=${channels}
|
|
@@ -282,8 +284,13 @@ function App() {
|
|
|
282
284
|
const [onboarded, setOnboarded] = useState(null);
|
|
283
285
|
const [tab, setTab] = useState(() => {
|
|
284
286
|
const hash = window.location.hash.replace("#", "");
|
|
287
|
+
if (kSubScreens.includes(hash)) return kDefaultUiTab;
|
|
285
288
|
return kUiTabs.includes(hash) ? hash : kDefaultUiTab;
|
|
286
289
|
});
|
|
290
|
+
const [subScreen, setSubScreen] = useState(() => {
|
|
291
|
+
const hash = window.location.hash.replace("#", "");
|
|
292
|
+
return kSubScreens.includes(hash) ? hash : null;
|
|
293
|
+
});
|
|
287
294
|
const [acVersion, setAcVersion] = useState(null);
|
|
288
295
|
const [acLatest, setAcLatest] = useState(null);
|
|
289
296
|
const [acHasUpdate, setAcHasUpdate] = useState(false);
|
|
@@ -316,8 +323,8 @@ function App() {
|
|
|
316
323
|
}, []);
|
|
317
324
|
|
|
318
325
|
useEffect(() => {
|
|
319
|
-
history.replaceState(null, "", `#${tab}`);
|
|
320
|
-
}, [tab]);
|
|
326
|
+
history.replaceState(null, "", `#${subScreen || tab}`);
|
|
327
|
+
}, [tab, subScreen]);
|
|
321
328
|
|
|
322
329
|
useEffect(() => {
|
|
323
330
|
if (!onboarded) return;
|
|
@@ -401,6 +408,9 @@ function App() {
|
|
|
401
408
|
`;
|
|
402
409
|
}
|
|
403
410
|
|
|
411
|
+
const navigateToSubScreen = (screen) => setSubScreen(screen);
|
|
412
|
+
const exitSubScreen = () => setSubScreen(null);
|
|
413
|
+
|
|
404
414
|
const kNavItems = [
|
|
405
415
|
{ id: "general", label: "General" },
|
|
406
416
|
{ id: "providers", label: "Providers" },
|
|
@@ -447,8 +457,8 @@ function App() {
|
|
|
447
457
|
${kNavItems.map(
|
|
448
458
|
(item) => html`
|
|
449
459
|
<a
|
|
450
|
-
class=${tab === item.id ? "active" : ""}
|
|
451
|
-
onclick=${() => setTab(item.id)}
|
|
460
|
+
class=${tab === item.id && !subScreen ? "active" : ""}
|
|
461
|
+
onclick=${() => { setSubScreen(null); setTab(item.id); }}
|
|
452
462
|
>
|
|
453
463
|
${item.label}
|
|
454
464
|
</a>
|
|
@@ -471,19 +481,28 @@ function App() {
|
|
|
471
481
|
</div>
|
|
472
482
|
|
|
473
483
|
<div class="app-content">
|
|
474
|
-
<div class="max-w-2xl w-full mx-auto
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
484
|
+
<div class="max-w-2xl w-full mx-auto">
|
|
485
|
+
${subScreen === "telegram"
|
|
486
|
+
? html`
|
|
487
|
+
<div class="pt-4">
|
|
488
|
+
<${TelegramWorkspace} onBack=${exitSubScreen} />
|
|
489
|
+
</div>
|
|
490
|
+
`
|
|
491
|
+
: html`
|
|
492
|
+
<div class="pt-4" style=${{ display: tab === "general" ? "" : "none" }}>
|
|
493
|
+
<${GeneralTab}
|
|
494
|
+
onSwitchTab=${setTab}
|
|
495
|
+
onNavigate=${navigateToSubScreen}
|
|
496
|
+
isActive=${tab === "general" && !subScreen}
|
|
497
|
+
/>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="pt-4" style=${{ display: tab === "providers" ? "" : "none" }}>
|
|
500
|
+
<${Providers} />
|
|
501
|
+
</div>
|
|
502
|
+
<div class="pt-4" style=${{ display: tab === "envars" ? "" : "none" }}>
|
|
503
|
+
<${Envars} />
|
|
504
|
+
</div>
|
|
505
|
+
`}
|
|
487
506
|
</div>
|
|
488
507
|
</div>
|
|
489
508
|
|