@chrysb/alphaclaw 0.2.0 → 0.2.1
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 +16 -7
- package/lib/public/js/components/channels.js +1 -1
- package/lib/public/js/components/onboarding/welcome-form-step.js +5 -2
- package/lib/public/js/components/onboarding/welcome-setup-step.js +2 -2
- package/lib/public/js/components/welcome.js +23 -1
- package/lib/public/js/lib/api.js +9 -0
- package/lib/server/onboarding/github.js +96 -30
- package/lib/server/onboarding/index.js +82 -31
- package/lib/server/routes/onboarding.js +36 -0
- package/lib/setup/core-prompts/TOOLS.md +5 -5
- package/lib/setup/gitignore +2 -0
- package/package.json +1 -1
package/bin/alphaclaw.js
CHANGED
|
@@ -252,13 +252,17 @@ const runGitSync = () => {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
const originUrl = `https://github.com/${githubRepo}.git`;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
let branch = "main";
|
|
256
|
+
try {
|
|
257
|
+
branch =
|
|
258
|
+
String(
|
|
259
|
+
execSync("git symbolic-ref --short HEAD", {
|
|
260
|
+
cwd: openclawDir,
|
|
261
|
+
encoding: "utf8",
|
|
262
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
263
|
+
}),
|
|
264
|
+
).trim() || "main";
|
|
265
|
+
} catch {}
|
|
262
266
|
const askPassPath = path.join(
|
|
263
267
|
os.tmpdir(),
|
|
264
268
|
`alphaclaw-git-askpass-${process.pid}.sh`,
|
|
@@ -294,6 +298,8 @@ const runGitSync = () => {
|
|
|
294
298
|
);
|
|
295
299
|
|
|
296
300
|
runGit(`remote set-url origin ${quoteArg(originUrl)}`);
|
|
301
|
+
runGit(`config user.name ${quoteArg("AlphaClaw Agent")}`);
|
|
302
|
+
runGit(`config user.email ${quoteArg("agent@alphaclaw.md")}`);
|
|
297
303
|
try {
|
|
298
304
|
runGit(`ls-remote --exit-code --heads origin ${quoteArg(branch)}`, {
|
|
299
305
|
withAuth: true,
|
|
@@ -316,6 +322,9 @@ const runGitSync = () => {
|
|
|
316
322
|
runGit(`push origin ${quoteArg(branch)}`, { withAuth: true });
|
|
317
323
|
const hash = String(runGit("rev-parse --short HEAD")).trim();
|
|
318
324
|
console.log(`[alphaclaw] Git sync complete (${hash})`);
|
|
325
|
+
console.log(
|
|
326
|
+
`[alphaclaw] Commit URL: https://github.com/${githubRepo}/commit/${hash}`,
|
|
327
|
+
);
|
|
319
328
|
return 0;
|
|
320
329
|
} catch (e) {
|
|
321
330
|
const details = String(e.stderr || e.stdout || e.message || "").trim();
|
|
@@ -17,7 +17,7 @@ export function Channels({ channels, onSwitchTab, onNavigate }) {
|
|
|
17
17
|
${channels ? ALL_CHANNELS.map(ch => {
|
|
18
18
|
const info = channels[ch];
|
|
19
19
|
const channelMeta = kChannelMeta[ch] || { label: ch.charAt(0).toUpperCase() + ch.slice(1), iconSrc: '' };
|
|
20
|
-
const isClickable = ch === 'telegram' && info && onNavigate;
|
|
20
|
+
const isClickable = ch === 'telegram' && info?.status === 'paired' && onNavigate;
|
|
21
21
|
let badge;
|
|
22
22
|
if (!info) {
|
|
23
23
|
badge = html`<a
|
|
@@ -36,6 +36,7 @@ export const WelcomeFormStep = ({
|
|
|
36
36
|
goBack,
|
|
37
37
|
goNext,
|
|
38
38
|
loading,
|
|
39
|
+
githubStepLoading,
|
|
39
40
|
allValid,
|
|
40
41
|
handleSubmit,
|
|
41
42
|
}) => {
|
|
@@ -276,10 +277,12 @@ export const WelcomeFormStep = ({
|
|
|
276
277
|
: html`<div class="w-full"></div>`}
|
|
277
278
|
<button
|
|
278
279
|
onclick=${goNext}
|
|
279
|
-
disabled=${!currentGroupValid}
|
|
280
|
+
disabled=${!currentGroupValid || githubStepLoading}
|
|
280
281
|
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ac-btn-cyan"
|
|
281
282
|
>
|
|
282
|
-
|
|
283
|
+
${activeGroup.id === "github" && githubStepLoading
|
|
284
|
+
? "Checking..."
|
|
285
|
+
: "Next"}
|
|
283
286
|
</button>
|
|
284
287
|
`
|
|
285
288
|
: html`
|
|
@@ -78,7 +78,7 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
|
|
|
78
78
|
const currentTip = kSetupTips[tipIndex];
|
|
79
79
|
|
|
80
80
|
return html`
|
|
81
|
-
<div class="min-h-[320px]
|
|
81
|
+
<div class="relative min-h-[320px] pt-4 pb-20 flex">
|
|
82
82
|
<div
|
|
83
83
|
class="flex-1 flex flex-col items-center justify-center text-center gap-4"
|
|
84
84
|
>
|
|
@@ -107,7 +107,7 @@ export const WelcomeSetupStep = ({ error, loading, onRetry, onBack }) => {
|
|
|
107
107
|
<p class="text-sm text-gray-500">This could take 10-15 seconds</p>
|
|
108
108
|
</div>
|
|
109
109
|
<div
|
|
110
|
-
class="
|
|
110
|
+
class="absolute bottom-3 left-3 right-3 bg-black/20 border border-border rounded-lg px-3 py-2 text-xs text-gray-500"
|
|
111
111
|
>
|
|
112
112
|
<span class="text-gray-400">${currentTip.label}: </span>
|
|
113
113
|
${currentTip.text}
|
|
@@ -3,6 +3,7 @@ import { useState, useEffect, useRef } from "https://esm.sh/preact/hooks";
|
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import {
|
|
5
5
|
runOnboard,
|
|
6
|
+
verifyGithubOnboardingRepo,
|
|
6
7
|
fetchModels,
|
|
7
8
|
fetchCodexStatus,
|
|
8
9
|
disconnectCodex,
|
|
@@ -58,6 +59,7 @@ export const Welcome = ({ onComplete }) => {
|
|
|
58
59
|
const [codexAuthStarted, setCodexAuthStarted] = useState(false);
|
|
59
60
|
const [codexAuthWaiting, setCodexAuthWaiting] = useState(false);
|
|
60
61
|
const [loading, setLoading] = useState(false);
|
|
62
|
+
const [githubStepLoading, setGithubStepLoading] = useState(false);
|
|
61
63
|
const [error, setError] = useState(null);
|
|
62
64
|
const codexPopupPollRef = useRef(null);
|
|
63
65
|
|
|
@@ -360,8 +362,27 @@ export const Welcome = ({ onComplete }) => {
|
|
|
360
362
|
setStep(kWelcomeGroups.length - 1);
|
|
361
363
|
};
|
|
362
364
|
|
|
363
|
-
const goNext = () => {
|
|
365
|
+
const goNext = async () => {
|
|
364
366
|
if (!activeGroup || !currentGroupValid) return;
|
|
367
|
+
if (activeGroup.id === "github") {
|
|
368
|
+
setGithubStepLoading(true);
|
|
369
|
+
setError(null);
|
|
370
|
+
try {
|
|
371
|
+
const result = await verifyGithubOnboardingRepo(
|
|
372
|
+
vals.GITHUB_WORKSPACE_REPO,
|
|
373
|
+
vals.GITHUB_TOKEN,
|
|
374
|
+
);
|
|
375
|
+
if (!result?.ok) {
|
|
376
|
+
setError(result?.error || "GitHub verification failed");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
} catch (err) {
|
|
380
|
+
setError(err?.message || "GitHub verification failed");
|
|
381
|
+
return;
|
|
382
|
+
} finally {
|
|
383
|
+
setGithubStepLoading(false);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
365
386
|
setStep((prev) => Math.min(kWelcomeGroups.length - 1, prev + 1));
|
|
366
387
|
};
|
|
367
388
|
|
|
@@ -438,6 +459,7 @@ export const Welcome = ({ onComplete }) => {
|
|
|
438
459
|
goBack=${goBack}
|
|
439
460
|
goNext=${goNext}
|
|
440
461
|
loading=${loading}
|
|
462
|
+
githubStepLoading=${githubStepLoading}
|
|
441
463
|
allValid=${allValid}
|
|
442
464
|
handleSubmit=${handleSubmit}
|
|
443
465
|
/>
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -167,6 +167,15 @@ export async function runOnboard(vars, modelKey) {
|
|
|
167
167
|
return res.json();
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
export async function verifyGithubOnboardingRepo(repo, token) {
|
|
171
|
+
const res = await authFetch('/api/onboard/github/verify', {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: { 'Content-Type': 'application/json' },
|
|
174
|
+
body: JSON.stringify({ repo, token }),
|
|
175
|
+
});
|
|
176
|
+
return res.json();
|
|
177
|
+
}
|
|
178
|
+
|
|
170
179
|
export const fetchModels = async () => {
|
|
171
180
|
const res = await authFetch('/api/models');
|
|
172
181
|
return res.json();
|
|
@@ -1,48 +1,114 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
1
|
+
const buildGithubHeaders = (githubToken) => ({
|
|
2
|
+
Authorization: `token ${githubToken}`,
|
|
3
|
+
"User-Agent": "openclaw-railway",
|
|
4
|
+
Accept: "application/vnd.github+json",
|
|
5
|
+
});
|
|
7
6
|
|
|
7
|
+
const parseGithubErrorMessage = async (response) => {
|
|
8
8
|
try {
|
|
9
|
-
const
|
|
9
|
+
const payload = await response.json();
|
|
10
|
+
if (typeof payload?.message === "string" && payload.message.trim()) {
|
|
11
|
+
return payload.message.trim();
|
|
12
|
+
}
|
|
13
|
+
} catch {}
|
|
14
|
+
return response.statusText || `HTTP ${response.status}`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const verifyGithubRepoForOnboarding = async ({ repoUrl, githubToken }) => {
|
|
18
|
+
const ghHeaders = buildGithubHeaders(githubToken);
|
|
19
|
+
const [repoOwner] = String(repoUrl || "").split("/", 1);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const userRes = await fetch("https://api.github.com/user", {
|
|
10
23
|
headers: ghHeaders,
|
|
11
24
|
});
|
|
25
|
+
if (!userRes.ok) {
|
|
26
|
+
const details = await parseGithubErrorMessage(userRes);
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
status: 400,
|
|
30
|
+
error: `Cannot verify GitHub token: ${details}`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const authedUser = await userRes.json().catch(() => ({}));
|
|
34
|
+
const authedLogin = String(authedUser?.login || "").trim();
|
|
35
|
+
if (
|
|
36
|
+
repoOwner &&
|
|
37
|
+
authedLogin &&
|
|
38
|
+
repoOwner.toLowerCase() !== authedLogin.toLowerCase()
|
|
39
|
+
) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
status: 400,
|
|
43
|
+
error: `Workspace repo owner must match your token user "${authedLogin}"`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
12
46
|
|
|
47
|
+
const checkRes = await fetch(`https://api.github.com/repos/${repoUrl}`, {
|
|
48
|
+
headers: ghHeaders,
|
|
49
|
+
});
|
|
13
50
|
if (checkRes.status === 404) {
|
|
14
|
-
console.log(`[onboard] Creating repo ${repoUrl}...`);
|
|
15
|
-
const createRes = await fetch("https://api.github.com/user/repos", {
|
|
16
|
-
method: "POST",
|
|
17
|
-
headers: { ...ghHeaders, "Content-Type": "application/json" },
|
|
18
|
-
body: JSON.stringify({
|
|
19
|
-
name: repoName,
|
|
20
|
-
private: true,
|
|
21
|
-
auto_init: false,
|
|
22
|
-
}),
|
|
23
|
-
});
|
|
24
|
-
if (!createRes.ok) {
|
|
25
|
-
const err = await createRes.json().catch(() => ({}));
|
|
26
|
-
return {
|
|
27
|
-
ok: false,
|
|
28
|
-
status: 400,
|
|
29
|
-
error: `Failed to create repo: ${err.message || createRes.statusText}`,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
console.log(`[onboard] Repo ${repoUrl} created`);
|
|
33
51
|
return { ok: true };
|
|
34
52
|
}
|
|
53
|
+
if (checkRes.ok) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
status: 400,
|
|
57
|
+
error: `Repository "${repoUrl}" already exists.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
35
60
|
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
const details = await parseGithubErrorMessage(checkRes);
|
|
38
62
|
return {
|
|
39
63
|
ok: false,
|
|
40
64
|
status: 400,
|
|
41
|
-
error: `Cannot
|
|
65
|
+
error: `Cannot verify repo "${repoUrl}": ${details}`,
|
|
42
66
|
};
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
status: 400,
|
|
71
|
+
error: `GitHub verification error: ${e.message}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const ensureGithubRepoAccessible = async ({
|
|
77
|
+
repoUrl,
|
|
78
|
+
repoName,
|
|
79
|
+
githubToken,
|
|
80
|
+
}) => {
|
|
81
|
+
const ghHeaders = buildGithubHeaders(githubToken);
|
|
82
|
+
const verification = await verifyGithubRepoForOnboarding({
|
|
83
|
+
repoUrl,
|
|
84
|
+
githubToken,
|
|
85
|
+
});
|
|
86
|
+
if (!verification.ok) return verification;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
console.log(`[onboard] Creating repo ${repoUrl}...`);
|
|
90
|
+
const createRes = await fetch("https://api.github.com/user/repos", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { ...ghHeaders, "Content-Type": "application/json" },
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
name: repoName,
|
|
95
|
+
private: true,
|
|
96
|
+
auto_init: false,
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
if (!createRes.ok) {
|
|
100
|
+
const details = await parseGithubErrorMessage(createRes);
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
status: 400,
|
|
104
|
+
error: `Failed to create repo: ${details}`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
console.log(`[onboard] Repo ${repoUrl} created`);
|
|
108
|
+
return { ok: true };
|
|
43
109
|
} catch (e) {
|
|
44
110
|
return { ok: false, status: 400, error: `GitHub error: ${e.message}` };
|
|
45
111
|
}
|
|
46
112
|
};
|
|
47
113
|
|
|
48
|
-
module.exports = { ensureGithubRepoAccessible };
|
|
114
|
+
module.exports = { ensureGithubRepoAccessible, verifyGithubRepoForOnboarding };
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
const { kSetupDir, kRootDir } = require("../constants");
|
|
3
3
|
const { validateOnboardingInput } = require("./validation");
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const {
|
|
5
|
+
ensureGithubRepoAccessible,
|
|
6
|
+
verifyGithubRepoForOnboarding,
|
|
7
|
+
} = require("./github");
|
|
8
|
+
const {
|
|
9
|
+
buildOnboardArgs,
|
|
10
|
+
writeSanitizedOpenclawConfig,
|
|
11
|
+
} = require("./openclaw");
|
|
12
|
+
const {
|
|
13
|
+
installControlUiSkill,
|
|
14
|
+
syncBootstrapPromptFiles,
|
|
15
|
+
} = require("./workspace");
|
|
16
|
+
const {
|
|
17
|
+
installHourlyGitSyncScript,
|
|
18
|
+
installHourlyGitSyncCron,
|
|
19
|
+
} = require("./cron");
|
|
8
20
|
|
|
9
21
|
const createOnboardingService = ({
|
|
10
22
|
fs,
|
|
@@ -22,6 +34,15 @@ const createOnboardingService = ({
|
|
|
22
34
|
}) => {
|
|
23
35
|
const { OPENCLAW_DIR, WORKSPACE_DIR } = constants;
|
|
24
36
|
|
|
37
|
+
const verifyGithubSetup = async ({
|
|
38
|
+
githubRepoInput,
|
|
39
|
+
githubToken,
|
|
40
|
+
resolveGithubRepoUrl,
|
|
41
|
+
}) => {
|
|
42
|
+
const repoUrl = resolveGithubRepoUrl(githubRepoInput);
|
|
43
|
+
return verifyGithubRepoForOnboarding({ repoUrl, githubToken });
|
|
44
|
+
};
|
|
45
|
+
|
|
25
46
|
const completeOnboarding = async ({ req, vars, modelKey }) => {
|
|
26
47
|
const validation = validateOnboardingInput({
|
|
27
48
|
vars,
|
|
@@ -30,14 +51,24 @@ const createOnboardingService = ({
|
|
|
30
51
|
hasCodexOauthProfile,
|
|
31
52
|
});
|
|
32
53
|
if (!validation.ok) {
|
|
33
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
status: validation.status,
|
|
56
|
+
body: { ok: false, error: validation.error },
|
|
57
|
+
};
|
|
34
58
|
}
|
|
35
59
|
|
|
36
|
-
const {
|
|
37
|
-
|
|
60
|
+
const {
|
|
61
|
+
varMap,
|
|
62
|
+
githubToken,
|
|
63
|
+
githubRepoInput,
|
|
64
|
+
selectedProvider,
|
|
65
|
+
hasCodexOauth,
|
|
66
|
+
} = validation.data;
|
|
38
67
|
|
|
39
68
|
const repoUrl = resolveGithubRepoUrl(githubRepoInput);
|
|
40
|
-
const varsToSave = [
|
|
69
|
+
const varsToSave = [
|
|
70
|
+
...vars.filter((v) => v.value && v.key !== "GITHUB_WORKSPACE_REPO"),
|
|
71
|
+
];
|
|
41
72
|
varsToSave.push({ key: "GITHUB_WORKSPACE_REPO", value: repoUrl });
|
|
42
73
|
writeEnvFile(varsToSave);
|
|
43
74
|
reloadEnv();
|
|
@@ -50,22 +81,32 @@ const createOnboardingService = ({
|
|
|
50
81
|
githubToken,
|
|
51
82
|
});
|
|
52
83
|
if (!repoCheck.ok) {
|
|
53
|
-
return {
|
|
84
|
+
return {
|
|
85
|
+
status: repoCheck.status,
|
|
86
|
+
body: { ok: false, error: repoCheck.error },
|
|
87
|
+
};
|
|
54
88
|
}
|
|
55
89
|
|
|
56
90
|
fs.mkdirSync(OPENCLAW_DIR, { recursive: true });
|
|
57
91
|
fs.mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
58
|
-
syncBootstrapPromptFiles({
|
|
92
|
+
syncBootstrapPromptFiles({
|
|
93
|
+
fs,
|
|
94
|
+
workspaceDir: WORKSPACE_DIR,
|
|
95
|
+
baseUrl: getBaseUrl(req),
|
|
96
|
+
});
|
|
59
97
|
|
|
60
98
|
if (!fs.existsSync(`${OPENCLAW_DIR}/.git`)) {
|
|
61
99
|
await shellCmd(
|
|
62
|
-
`cd ${OPENCLAW_DIR} && git init -b main && git remote add origin "${remoteUrl}" && git config user.email "agent@
|
|
100
|
+
`cd ${OPENCLAW_DIR} && git init -b main && git remote add origin "${remoteUrl}" && git config user.email "agent@alphaclaw.md" && git config user.name "AlphaClaw Agent"`,
|
|
63
101
|
);
|
|
64
102
|
console.log("[onboard] Git initialized");
|
|
65
103
|
}
|
|
66
104
|
|
|
67
105
|
if (!fs.existsSync(`${OPENCLAW_DIR}/.gitignore`)) {
|
|
68
|
-
fs.copyFileSync(
|
|
106
|
+
fs.copyFileSync(
|
|
107
|
+
path.join(kSetupDir, "gitignore"),
|
|
108
|
+
`${OPENCLAW_DIR}/.gitignore`,
|
|
109
|
+
);
|
|
69
110
|
}
|
|
70
111
|
|
|
71
112
|
const onboardArgs = buildOnboardArgs({
|
|
@@ -74,14 +115,17 @@ const createOnboardingService = ({
|
|
|
74
115
|
hasCodexOauth,
|
|
75
116
|
workspaceDir: WORKSPACE_DIR,
|
|
76
117
|
});
|
|
77
|
-
await shellCmd(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
118
|
+
await shellCmd(
|
|
119
|
+
`openclaw onboard ${onboardArgs.map((a) => `"${a}"`).join(" ")}`,
|
|
120
|
+
{
|
|
121
|
+
env: {
|
|
122
|
+
...process.env,
|
|
123
|
+
OPENCLAW_HOME: kRootDir,
|
|
124
|
+
OPENCLAW_CONFIG_PATH: `${OPENCLAW_DIR}/openclaw.json`,
|
|
125
|
+
},
|
|
126
|
+
timeout: 120000,
|
|
82
127
|
},
|
|
83
|
-
|
|
84
|
-
});
|
|
128
|
+
);
|
|
85
129
|
console.log("[onboard] Onboard complete");
|
|
86
130
|
|
|
87
131
|
await shellCmd(`openclaw models set "${modelKey}"`, {
|
|
@@ -89,7 +133,9 @@ const createOnboardingService = ({
|
|
|
89
133
|
timeout: 30000,
|
|
90
134
|
}).catch((e) => {
|
|
91
135
|
console.error("[onboard] Failed to set model:", e.message);
|
|
92
|
-
throw new Error(
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Onboarding completed but failed to set model "${modelKey}"`,
|
|
138
|
+
);
|
|
93
139
|
});
|
|
94
140
|
|
|
95
141
|
try {
|
|
@@ -99,28 +145,33 @@ const createOnboardingService = ({
|
|
|
99
145
|
writeSanitizedOpenclawConfig({ fs, openclawDir: OPENCLAW_DIR, varMap });
|
|
100
146
|
ensureGatewayProxyConfig(getBaseUrl(req));
|
|
101
147
|
|
|
102
|
-
installControlUiSkill({
|
|
148
|
+
installControlUiSkill({
|
|
149
|
+
fs,
|
|
150
|
+
openclawDir: OPENCLAW_DIR,
|
|
151
|
+
baseUrl: getBaseUrl(req),
|
|
152
|
+
});
|
|
103
153
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
154
|
+
installHourlyGitSyncScript({ fs, openclawDir: OPENCLAW_DIR });
|
|
155
|
+
await installHourlyGitSyncCron({ fs, openclawDir: OPENCLAW_DIR });
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await shellCmd(`alphaclaw git-sync -m "initial setup"`, {
|
|
107
159
|
timeout: 30000,
|
|
108
160
|
env: {
|
|
109
161
|
...process.env,
|
|
110
162
|
GITHUB_TOKEN: githubToken,
|
|
111
163
|
},
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
await installHourlyGitSyncCron({ fs, openclawDir: OPENCLAW_DIR });
|
|
164
|
+
});
|
|
165
|
+
console.log("[onboard] Initial state committed and pushed");
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.error("[onboard] Git push error:", e.message);
|
|
168
|
+
}
|
|
118
169
|
|
|
119
170
|
startGateway();
|
|
120
171
|
return { status: 200, body: { ok: true } };
|
|
121
172
|
};
|
|
122
173
|
|
|
123
|
-
return { completeOnboarding };
|
|
174
|
+
return { completeOnboarding, verifyGithubSetup };
|
|
124
175
|
};
|
|
125
176
|
|
|
126
177
|
module.exports = { createOnboardingService };
|
|
@@ -111,6 +111,42 @@ const registerOnboardingRoutes = ({
|
|
|
111
111
|
res.status(500).json({ ok: false, error: sanitizeOnboardingError(err) });
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
app.post("/api/onboard/github/verify", async (req, res) => {
|
|
116
|
+
if (isOnboarded()) {
|
|
117
|
+
return res.json({ ok: false, error: "Already onboarded" });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const githubRepoInput = String(req.body?.repo || "").trim();
|
|
122
|
+
const githubToken = String(req.body?.token || "").trim();
|
|
123
|
+
if (!githubRepoInput || !githubToken) {
|
|
124
|
+
return res
|
|
125
|
+
.status(400)
|
|
126
|
+
.json({
|
|
127
|
+
ok: false,
|
|
128
|
+
error: "GitHub token and workspace repo are required",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const result = await onboardingService.verifyGithubSetup({
|
|
133
|
+
githubRepoInput,
|
|
134
|
+
githubToken,
|
|
135
|
+
resolveGithubRepoUrl,
|
|
136
|
+
});
|
|
137
|
+
if (!result.ok) {
|
|
138
|
+
return res
|
|
139
|
+
.status(result.status || 400)
|
|
140
|
+
.json({ ok: false, error: result.error });
|
|
141
|
+
}
|
|
142
|
+
return res.json({ ok: true });
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error("[onboard] GitHub verify error:", err);
|
|
145
|
+
return res
|
|
146
|
+
.status(500)
|
|
147
|
+
.json({ ok: false, error: sanitizeOnboardingError(err) });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
114
150
|
};
|
|
115
151
|
|
|
116
152
|
module.exports = { registerOnboardingRoutes };
|
|
@@ -6,11 +6,11 @@ AlphaClaw UI: `{{SETUP_UI_URL}}`
|
|
|
6
6
|
|
|
7
7
|
### Tabs
|
|
8
8
|
|
|
9
|
-
| Tab
|
|
10
|
-
|
|
|
11
|
-
| General
|
|
12
|
-
| Providers | `{{SETUP_UI_URL}}#providers` | AI provider credentials (Anthropic, OpenAI, Gemini, Mistral, Voyage, Groq, Deepgram), feature capabilities, Codex OAuth
|
|
13
|
-
| Envars
|
|
9
|
+
| Tab | URL | What it manages |
|
|
10
|
+
| --------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
11
|
+
| General | `{{SETUP_UI_URL}}#general` | Gateway status & restart, channel health (Telegram/Discord), pending pairings, feature health (Embeddings/Audio), Google Workspace connection, repo auto-sync schedule, OpenClaw dashboard |
|
|
12
|
+
| Providers | `{{SETUP_UI_URL}}#providers` | AI provider credentials (Anthropic, OpenAI, Gemini, Mistral, Voyage, Groq, Deepgram), feature capabilities, Codex OAuth |
|
|
13
|
+
| Envars | `{{SETUP_UI_URL}}#envars` | View/edit/add environment variables (saved to `/data/.env`), gateway restart to apply changes |
|
|
14
14
|
|
|
15
15
|
### Environment variables
|
|
16
16
|
|
package/lib/setup/gitignore
CHANGED