@chrysb/alphaclaw 0.1.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.
Files changed (53) hide show
  1. package/bin/alphaclaw.js +338 -0
  2. package/lib/public/icons/chevron-down.svg +9 -0
  3. package/lib/public/js/app.js +325 -0
  4. package/lib/public/js/components/badge.js +16 -0
  5. package/lib/public/js/components/channels.js +36 -0
  6. package/lib/public/js/components/credentials-modal.js +336 -0
  7. package/lib/public/js/components/device-pairings.js +72 -0
  8. package/lib/public/js/components/envars.js +354 -0
  9. package/lib/public/js/components/gateway.js +163 -0
  10. package/lib/public/js/components/google.js +223 -0
  11. package/lib/public/js/components/icons.js +23 -0
  12. package/lib/public/js/components/models.js +461 -0
  13. package/lib/public/js/components/pairings.js +74 -0
  14. package/lib/public/js/components/scope-picker.js +106 -0
  15. package/lib/public/js/components/toast.js +31 -0
  16. package/lib/public/js/components/welcome.js +541 -0
  17. package/lib/public/js/hooks/usePolling.js +29 -0
  18. package/lib/public/js/lib/api.js +196 -0
  19. package/lib/public/js/lib/model-config.js +88 -0
  20. package/lib/public/login.html +90 -0
  21. package/lib/public/setup.html +33 -0
  22. package/lib/scripts/systemctl +56 -0
  23. package/lib/server/auth-profiles.js +101 -0
  24. package/lib/server/commands.js +84 -0
  25. package/lib/server/constants.js +282 -0
  26. package/lib/server/env.js +78 -0
  27. package/lib/server/gateway.js +262 -0
  28. package/lib/server/helpers.js +192 -0
  29. package/lib/server/login-throttle.js +86 -0
  30. package/lib/server/onboarding/cron.js +51 -0
  31. package/lib/server/onboarding/github.js +49 -0
  32. package/lib/server/onboarding/index.js +127 -0
  33. package/lib/server/onboarding/openclaw.js +171 -0
  34. package/lib/server/onboarding/validation.js +107 -0
  35. package/lib/server/onboarding/workspace.js +52 -0
  36. package/lib/server/openclaw-version.js +179 -0
  37. package/lib/server/routes/auth.js +80 -0
  38. package/lib/server/routes/codex.js +204 -0
  39. package/lib/server/routes/google.js +390 -0
  40. package/lib/server/routes/models.js +68 -0
  41. package/lib/server/routes/onboarding.js +116 -0
  42. package/lib/server/routes/pages.js +21 -0
  43. package/lib/server/routes/pairings.js +134 -0
  44. package/lib/server/routes/proxy.js +29 -0
  45. package/lib/server/routes/system.js +213 -0
  46. package/lib/server.js +161 -0
  47. package/lib/setup/core-prompts/AGENTS.md +22 -0
  48. package/lib/setup/core-prompts/TOOLS.md +18 -0
  49. package/lib/setup/env.template +19 -0
  50. package/lib/setup/gitignore +12 -0
  51. package/lib/setup/hourly-git-sync.sh +86 -0
  52. package/lib/setup/skills/control-ui/SKILL.md +70 -0
  53. package/package.json +34 -0
@@ -0,0 +1,196 @@
1
+ const authFetch = async (url, opts = {}) => {
2
+ const res = await fetch(url, opts);
3
+ if (res.status === 401) {
4
+ window.location.href = '/setup';
5
+ throw new Error('Unauthorized');
6
+ }
7
+ return res;
8
+ };
9
+
10
+ export async function fetchStatus() {
11
+ const res = await authFetch('/api/status');
12
+ return res.json();
13
+ }
14
+
15
+ export async function fetchPairings() {
16
+ const res = await authFetch('/api/pairings');
17
+ return res.json();
18
+ }
19
+
20
+ export async function approvePairing(id, channel) {
21
+ const res = await authFetch(`/api/pairings/${id}/approve`, {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify({ channel }),
25
+ });
26
+ return res.json();
27
+ }
28
+
29
+ export async function rejectPairing(id, channel) {
30
+ const res = await authFetch(`/api/pairings/${id}/reject`, {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({ channel }),
34
+ });
35
+ return res.json();
36
+ }
37
+
38
+ export async function fetchGoogleStatus() {
39
+ const res = await authFetch('/api/google/status');
40
+ return res.json();
41
+ }
42
+
43
+ export async function checkGoogleApis() {
44
+ const res = await authFetch('/api/google/check');
45
+ return res.json();
46
+ }
47
+
48
+ export async function saveGoogleCredentials(clientId, clientSecret, email) {
49
+ const res = await authFetch('/api/google/credentials', {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify({ clientId, clientSecret, email }),
53
+ });
54
+ return res.json();
55
+ }
56
+
57
+ export async function disconnectGoogle() {
58
+ const res = await authFetch('/api/google/disconnect', { method: 'POST' });
59
+ return res.json();
60
+ }
61
+
62
+ export async function restartGateway() {
63
+ const res = await authFetch('/api/gateway/restart', { method: 'POST' });
64
+ return res.json();
65
+ }
66
+
67
+ export async function fetchDashboardUrl() {
68
+ const res = await authFetch('/api/gateway/dashboard');
69
+ return res.json();
70
+ }
71
+
72
+ export async function fetchOpenclawVersion(refresh = false) {
73
+ const query = refresh ? '?refresh=1' : '';
74
+ const res = await authFetch(`/api/openclaw/version${query}`);
75
+ return res.json();
76
+ }
77
+
78
+ export async function updateOpenclaw() {
79
+ const res = await authFetch('/api/openclaw/update', { method: 'POST' });
80
+ return res.json();
81
+ }
82
+
83
+ export async function fetchSyncCron() {
84
+ const res = await authFetch('/api/sync-cron');
85
+ const text = await res.text();
86
+ let data;
87
+ try {
88
+ data = text ? JSON.parse(text) : {};
89
+ } catch {
90
+ throw new Error(text || 'Could not parse sync cron response');
91
+ }
92
+ if (!res.ok) {
93
+ throw new Error(data.error || text || `HTTP ${res.status}`);
94
+ }
95
+ return data;
96
+ }
97
+
98
+ export async function updateSyncCron(payload) {
99
+ const res = await authFetch('/api/sync-cron', {
100
+ method: 'PUT',
101
+ headers: { 'Content-Type': 'application/json' },
102
+ body: JSON.stringify(payload),
103
+ });
104
+ const text = await res.text();
105
+ let data;
106
+ try {
107
+ data = text ? JSON.parse(text) : {};
108
+ } catch {
109
+ throw new Error(text || 'Could not parse sync cron response');
110
+ }
111
+ if (!res.ok) {
112
+ throw new Error(data.error || text || `HTTP ${res.status}`);
113
+ }
114
+ return data;
115
+ }
116
+
117
+ export async function fetchDevicePairings() {
118
+ const res = await authFetch('/api/devices');
119
+ return res.json();
120
+ }
121
+
122
+ export async function approveDevice(id) {
123
+ const res = await authFetch(`/api/devices/${id}/approve`, { method: 'POST' });
124
+ return res.json();
125
+ }
126
+
127
+ export async function rejectDevice(id) {
128
+ const res = await authFetch(`/api/devices/${id}/reject`, { method: 'POST' });
129
+ return res.json();
130
+ }
131
+
132
+ export async function fetchOnboardStatus() {
133
+ const res = await authFetch('/api/onboard/status');
134
+ return res.json();
135
+ }
136
+
137
+ export async function runOnboard(vars, modelKey) {
138
+ const res = await authFetch('/api/onboard', {
139
+ method: 'POST',
140
+ headers: { 'Content-Type': 'application/json' },
141
+ body: JSON.stringify({ vars, modelKey }),
142
+ });
143
+ return res.json();
144
+ }
145
+
146
+ export const fetchModels = async () => {
147
+ const res = await authFetch('/api/models');
148
+ return res.json();
149
+ };
150
+
151
+ export const fetchModelStatus = async () => {
152
+ const res = await authFetch('/api/models/status');
153
+ return res.json();
154
+ };
155
+
156
+ export const setPrimaryModel = async (modelKey) => {
157
+ const res = await authFetch('/api/models/set', {
158
+ method: 'POST',
159
+ headers: { 'Content-Type': 'application/json' },
160
+ body: JSON.stringify({ modelKey }),
161
+ });
162
+ return res.json();
163
+ };
164
+
165
+ export const fetchCodexStatus = async () => {
166
+ const res = await authFetch('/api/codex/status');
167
+ return res.json();
168
+ };
169
+
170
+ export const disconnectCodex = async () => {
171
+ const res = await authFetch('/api/codex/disconnect', { method: 'POST' });
172
+ return res.json();
173
+ };
174
+
175
+ export const exchangeCodexOAuth = async (input) => {
176
+ const res = await authFetch('/api/codex/exchange', {
177
+ method: 'POST',
178
+ headers: { 'Content-Type': 'application/json' },
179
+ body: JSON.stringify({ input }),
180
+ });
181
+ return res.json();
182
+ };
183
+
184
+ export async function fetchEnvVars() {
185
+ const res = await authFetch('/api/env');
186
+ return res.json();
187
+ }
188
+
189
+ export async function saveEnvVars(vars) {
190
+ const res = await authFetch('/api/env', {
191
+ method: 'PUT',
192
+ headers: { 'Content-Type': 'application/json' },
193
+ body: JSON.stringify({ vars }),
194
+ });
195
+ return res.json();
196
+ }
@@ -0,0 +1,88 @@
1
+ export const getModelProvider = (modelKey) => String(modelKey || "").split("/")[0] || "";
2
+
3
+ export const getAuthProviderFromModelProvider = (provider) =>
4
+ provider === "openai-codex" ? "openai" : provider;
5
+
6
+ export const kFeaturedModelDefs = [
7
+ {
8
+ label: "Opus 4.6",
9
+ preferredKeys: ["anthropic/claude-opus-4-6", "anthropic/claude-opus-4-5"],
10
+ },
11
+ {
12
+ label: "Sonnet 4.6",
13
+ preferredKeys: ["anthropic/claude-sonnet-4-6", "anthropic/claude-sonnet-4-5"],
14
+ },
15
+ {
16
+ label: "Codex 5.3",
17
+ preferredKeys: ["openai-codex/gpt-5.3-codex", "openai-codex/gpt-5.2-codex"],
18
+ },
19
+ {
20
+ label: "Gemini 3",
21
+ preferredKeys: ["google/gemini-3-pro-preview", "google/gemini-3-flash-preview"],
22
+ },
23
+ ];
24
+
25
+ export const getFeaturedModels = (allModels) => {
26
+ const picked = [];
27
+ const used = new Set();
28
+ kFeaturedModelDefs.forEach((def) => {
29
+ const found = def.preferredKeys
30
+ .map((key) => allModels.find((model) => model.key === key))
31
+ .find(Boolean);
32
+ if (!found || used.has(found.key)) return;
33
+ picked.push({ ...found, featuredLabel: def.label });
34
+ used.add(found.key);
35
+ });
36
+ return picked;
37
+ };
38
+
39
+ export const kProviderAuthFields = {
40
+ anthropic: [
41
+ {
42
+ key: "ANTHROPIC_API_KEY",
43
+ label: "Anthropic API Key",
44
+ hint: "From console.anthropic.com — recommended",
45
+ placeholder: "sk-ant-...",
46
+ },
47
+ {
48
+ key: "ANTHROPIC_TOKEN",
49
+ label: "Anthropic Setup Token",
50
+ hint: "From claude setup-token (uses your Claude subscription)",
51
+ placeholder: "Token...",
52
+ },
53
+ ],
54
+ openai: [
55
+ {
56
+ key: "OPENAI_API_KEY",
57
+ label: "OpenAI API Key",
58
+ hint: "From platform.openai.com",
59
+ placeholder: "sk-...",
60
+ },
61
+ ],
62
+ google: [
63
+ {
64
+ key: "GEMINI_API_KEY",
65
+ label: "Gemini API Key",
66
+ hint: "From aistudio.google.com",
67
+ placeholder: "AI...",
68
+ },
69
+ ],
70
+ };
71
+
72
+ export const kProviderLabels = {
73
+ anthropic: "Anthropic",
74
+ openai: "OpenAI",
75
+ google: "Gemini",
76
+ };
77
+
78
+ export const kProviderOrder = ["anthropic", "openai", "google"];
79
+
80
+ export const getVisibleAiFieldKeys = (provider) => {
81
+ const authProvider = getAuthProviderFromModelProvider(provider);
82
+ const fields = kProviderAuthFields[authProvider] || [];
83
+ return new Set(fields.map((field) => field.key));
84
+ };
85
+
86
+ export const kAllAiAuthFields = Object.values(kProviderAuthFields)
87
+ .flat()
88
+ .filter((field, idx, arr) => arr.findIndex((item) => item.key === field.key) === idx);
@@ -0,0 +1,90 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenClaw Setup</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ surface: '#141414',
14
+ border: '#222',
15
+ }
16
+ }
17
+ }
18
+ }
19
+ </script>
20
+ </head>
21
+ <body class="bg-[#0a0a0a] text-gray-200 min-h-screen flex items-center justify-center p-4">
22
+ <div class="max-w-sm w-full">
23
+ <div class="flex items-center gap-3 mb-6">
24
+ <div class="text-4xl">🦞</div>
25
+ <div>
26
+ <h1 class="text-2xl font-semibold">OpenClaw Setup</h1>
27
+ <p class="text-gray-500 text-sm">Enter password to continue</p>
28
+ </div>
29
+ </div>
30
+ <form id="login-form" class="bg-surface border border-border rounded-xl p-4 space-y-3">
31
+ <input
32
+ id="password"
33
+ type="password"
34
+ placeholder="Password"
35
+ autofocus
36
+ class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500"
37
+ />
38
+ <div id="error" class="text-red-400 text-xs hidden"></div>
39
+ <button type="submit" class="w-full bg-white text-black text-sm font-medium px-4 py-2 rounded-lg hover:opacity-85">
40
+ Continue
41
+ </button>
42
+ </form>
43
+ </div>
44
+ <script>
45
+ const formEl = document.getElementById('login-form');
46
+ const submitButtonEl = formEl.querySelector('button[type="submit"]');
47
+
48
+ const setPendingState = (isPending) => {
49
+ submitButtonEl.disabled = isPending;
50
+ submitButtonEl.classList.toggle('opacity-70', isPending);
51
+ submitButtonEl.classList.toggle('cursor-not-allowed', isPending);
52
+ };
53
+
54
+ formEl.addEventListener('submit', async (e) => {
55
+ e.preventDefault();
56
+ const password = document.getElementById('password').value;
57
+ const errorEl = document.getElementById('error');
58
+ errorEl.classList.add('hidden');
59
+ setPendingState(true);
60
+ try {
61
+ const res = await fetch('/api/auth/login', {
62
+ method: 'POST',
63
+ headers: { 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({ password }),
65
+ });
66
+ const data = await res.json();
67
+ if (data.ok) {
68
+ window.location.href = '/';
69
+ } else {
70
+ if (res.status === 429) {
71
+ const retryAfterFromHeader = Number.parseInt(res.headers.get('retry-after') || '0', 10);
72
+ const retryAfterSec = Number.isFinite(data.retryAfterSec) && data.retryAfterSec > 0
73
+ ? data.retryAfterSec
74
+ : (Number.isFinite(retryAfterFromHeader) && retryAfterFromHeader > 0 ? retryAfterFromHeader : 30);
75
+ errorEl.textContent = `Too many attempts. Try again in ${retryAfterSec}s.`;
76
+ } else {
77
+ errorEl.textContent = data.error || 'Login failed';
78
+ }
79
+ errorEl.classList.remove('hidden');
80
+ }
81
+ } catch {
82
+ errorEl.textContent = 'Connection error';
83
+ errorEl.classList.remove('hidden');
84
+ } finally {
85
+ setPendingState(false);
86
+ }
87
+ });
88
+ </script>
89
+ </body>
90
+ </html>
@@ -0,0 +1,33 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenClaw Setup</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ surface: '#141414',
14
+ border: '#222',
15
+ }
16
+ }
17
+ }
18
+ }
19
+ </script>
20
+ <style>
21
+ ::placeholder { color: #444 !important; opacity: 1 !important; }
22
+ ::-webkit-input-placeholder { color: #444 !important; }
23
+ ::-moz-placeholder { color: #444 !important; }
24
+ .scope-btn { background: #1a1a1a; color: #555; border: 1px solid #333; transition: all 0.15s; }
25
+ .scope-btn:hover { border-color: #555; }
26
+ .scope-btn.active { background: #065f46; color: #6ee7b7; border-color: #059669; }
27
+ </style>
28
+ </head>
29
+ <body class="bg-[#0a0a0a] text-gray-200 min-h-screen flex justify-center pt-12 pb-8 px-4">
30
+ <div id="app" class="flex justify-center w-full"></div>
31
+ <script type="module" src="./js/app.js"></script>
32
+ </body>
33
+ </html>
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # systemctl shim for Docker (no real systemd available)
3
+ # Translates restart/stop/status into process signals so the Express wrapper handles lifecycle.
4
+
5
+ ACTION=""
6
+ UNIT=""
7
+ for arg in "$@"; do
8
+ case "$arg" in
9
+ --user|--system|--no-pager|--quiet) ;;
10
+ restart|stop|start|status|enable|disable|is-active|is-enabled|daemon-reload|show) ACTION="$arg" ;;
11
+ *) [ -n "$ACTION" ] && [ -z "$UNIT" ] && UNIT="$arg" ;;
12
+ esac
13
+ done
14
+
15
+ case "$ACTION" in
16
+ status)
17
+ if [ -z "$UNIT" ]; then
18
+ # Bare "systemctl --user status" — systemd availability check
19
+ echo "● user-session"
20
+ echo " Active: active (running)"
21
+ exit 0
22
+ fi
23
+ # Status check for a specific unit
24
+ if pgrep -f "openclaw gateway" >/dev/null 2>&1; then
25
+ echo "● ${UNIT}"
26
+ echo " Active: active (running)"
27
+ exit 0
28
+ else
29
+ echo "● ${UNIT}"
30
+ echo " Active: inactive (dead)"
31
+ exit 3
32
+ fi
33
+ ;;
34
+ is-active)
35
+ if pgrep -f "openclaw gateway" >/dev/null 2>&1; then
36
+ echo "active"
37
+ exit 0
38
+ else
39
+ echo "inactive"
40
+ exit 3
41
+ fi
42
+ ;;
43
+ restart|stop)
44
+ pkill -TERM -f "openclaw gateway" 2>/dev/null || true
45
+ sleep 1
46
+ pkill -9 -f "openclaw gateway" 2>/dev/null || true
47
+ rm -f /data/.openclaw/gateway.lock /data/.openclaw/gateway.pid 2>/dev/null
48
+ exit 0
49
+ ;;
50
+ start|enable|is-enabled|disable|daemon-reload|show)
51
+ exit 0
52
+ ;;
53
+ *)
54
+ exit 0
55
+ ;;
56
+ esac
@@ -0,0 +1,101 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { AUTH_PROFILES_PATH, CODEX_PROFILE_ID } = require("./constants");
4
+
5
+ const createAuthProfiles = () => {
6
+ const ensureAuthProfilesStore = () => {
7
+ let store = { version: 1, profiles: {} };
8
+ try {
9
+ if (fs.existsSync(AUTH_PROFILES_PATH)) {
10
+ const parsed = JSON.parse(fs.readFileSync(AUTH_PROFILES_PATH, "utf8"));
11
+ if (
12
+ parsed &&
13
+ typeof parsed === "object" &&
14
+ parsed.profiles &&
15
+ typeof parsed.profiles === "object"
16
+ ) {
17
+ store = {
18
+ version: Number(parsed.version || 1),
19
+ profiles: parsed.profiles,
20
+ order: parsed.order,
21
+ lastGood: parsed.lastGood,
22
+ usageStats: parsed.usageStats,
23
+ };
24
+ }
25
+ }
26
+ } catch {}
27
+ return store;
28
+ };
29
+
30
+ const saveAuthProfilesStore = (store) => {
31
+ fs.mkdirSync(path.dirname(AUTH_PROFILES_PATH), { recursive: true });
32
+ fs.writeFileSync(
33
+ AUTH_PROFILES_PATH,
34
+ JSON.stringify(
35
+ {
36
+ version: Number(store.version || 1),
37
+ profiles: store.profiles || {},
38
+ order: store.order,
39
+ lastGood: store.lastGood,
40
+ usageStats: store.usageStats,
41
+ },
42
+ null,
43
+ 2,
44
+ ),
45
+ );
46
+ };
47
+
48
+ const listCodexProfiles = () => {
49
+ const store = ensureAuthProfilesStore();
50
+ return Object.entries(store.profiles || {})
51
+ .filter(([, cred]) => cred?.provider === "openai-codex")
52
+ .map(([id, cred]) => ({ id, cred }));
53
+ };
54
+
55
+ const getCodexProfile = () => {
56
+ const profiles = listCodexProfiles();
57
+ if (profiles.length === 0) return null;
58
+ const preferred = profiles.find((p) => p.id === CODEX_PROFILE_ID) || profiles[0];
59
+ return { profileId: preferred.id, ...preferred.cred };
60
+ };
61
+
62
+ const hasCodexOauthProfile = () => {
63
+ const profile = getCodexProfile();
64
+ return !!(profile?.access && profile?.refresh);
65
+ };
66
+
67
+ const upsertCodexProfile = ({ access, refresh, expires, accountId }) => {
68
+ const store = ensureAuthProfilesStore();
69
+ store.profiles[CODEX_PROFILE_ID] = {
70
+ type: "oauth",
71
+ provider: "openai-codex",
72
+ access,
73
+ refresh,
74
+ expires,
75
+ ...(accountId ? { accountId } : {}),
76
+ };
77
+ saveAuthProfilesStore(store);
78
+ };
79
+
80
+ const removeCodexProfiles = () => {
81
+ const store = ensureAuthProfilesStore();
82
+ let changed = false;
83
+ for (const [id, cred] of Object.entries(store.profiles || {})) {
84
+ if (cred?.provider === "openai-codex") {
85
+ delete store.profiles[id];
86
+ changed = true;
87
+ }
88
+ }
89
+ if (changed) saveAuthProfilesStore(store);
90
+ return changed;
91
+ };
92
+
93
+ return {
94
+ getCodexProfile,
95
+ hasCodexOauthProfile,
96
+ upsertCodexProfile,
97
+ removeCodexProfiles,
98
+ };
99
+ };
100
+
101
+ module.exports = { createAuthProfiles };
@@ -0,0 +1,84 @@
1
+ const { exec } = require("child_process");
2
+ const { OPENCLAW_DIR, GOG_KEYRING_PASSWORD } = require("./constants");
3
+
4
+ const createCommands = ({ gatewayEnv }) => {
5
+ const shellCmd = (cmd, opts = {}) =>
6
+ new Promise((resolve, reject) => {
7
+ const { logStdout, ...execOpts } = opts;
8
+ const shouldLogStdout =
9
+ typeof logStdout === "boolean" ? logStdout : !cmd.includes("--json");
10
+ console.log(
11
+ `[onboard] Running: ${cmd
12
+ .replace(/ghp_[^\s"]+/g, "***")
13
+ .replace(/sk-[^\s"]+/g, "***")
14
+ .slice(0, 200)}`,
15
+ );
16
+ exec(cmd, { timeout: 60000, ...execOpts }, (err, stdout, stderr) => {
17
+ if (err) {
18
+ console.error(
19
+ `[onboard] Error: ${(stderr || err.message).slice(0, 300)}`,
20
+ );
21
+ return reject(err);
22
+ }
23
+ if (shouldLogStdout && stdout.trim()) {
24
+ console.log(`[onboard] ${stdout.trim().slice(0, 300)}`);
25
+ }
26
+ resolve(stdout.trim());
27
+ });
28
+ });
29
+
30
+ const clawCmd = (cmd, { quiet = false } = {}) =>
31
+ new Promise((resolve) => {
32
+ if (!quiet) console.log(`[wrapper] Running: openclaw ${cmd}`);
33
+ exec(
34
+ `openclaw ${cmd}`,
35
+ {
36
+ env: gatewayEnv(),
37
+ timeout: 15000,
38
+ },
39
+ (err, stdout, stderr) => {
40
+ const result = {
41
+ ok: !err,
42
+ stdout: stdout.trim(),
43
+ stderr: stderr.trim(),
44
+ code: err?.code,
45
+ };
46
+ if (!quiet && !result.ok) {
47
+ console.log(`[wrapper] Error: ${result.stderr.slice(0, 200)}`);
48
+ }
49
+ resolve(result);
50
+ },
51
+ );
52
+ });
53
+
54
+ const gogCmd = (cmd, { quiet = false } = {}) =>
55
+ new Promise((resolve) => {
56
+ if (!quiet) console.log(`[wrapper] Running: gog ${cmd}`);
57
+ exec(
58
+ `gog ${cmd}`,
59
+ {
60
+ timeout: 15000,
61
+ env: {
62
+ ...process.env,
63
+ XDG_CONFIG_HOME: OPENCLAW_DIR,
64
+ GOG_KEYRING_PASSWORD,
65
+ },
66
+ },
67
+ (err, stdout, stderr) => {
68
+ const result = {
69
+ ok: !err,
70
+ stdout: stdout.trim(),
71
+ stderr: stderr.trim(),
72
+ };
73
+ if (!quiet && !result.ok) {
74
+ console.log(`[wrapper] gog error: ${result.stderr.slice(0, 200)}`);
75
+ }
76
+ resolve(result);
77
+ },
78
+ );
79
+ });
80
+
81
+ return { shellCmd, clawCmd, gogCmd };
82
+ };
83
+
84
+ module.exports = { createCommands };