@chrysb/alphaclaw 0.9.0 → 0.9.1-beta.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 +1 -0
- package/lib/public/dist/app.bundle.js +716 -716
- package/lib/public/js/components/welcome/use-welcome.js +6 -0
- package/lib/public/js/lib/model-catalog.js +11 -0
- package/lib/scripts/git +66 -18
- package/lib/server/gateway.js +1 -0
- package/lib/server/model-catalog-cache.js +1 -1
- package/lib/server/onboarding/github.js +83 -2
- package/lib/server/onboarding/index.js +2 -0
- package/lib/setup/core-prompts/AGENTS.md +12 -0
- package/lib/setup/core-prompts/TOOLS.md +12 -0
- package/package.json +2 -2
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getInitialOnboardingModelKey,
|
|
19
19
|
getModelCatalogModels,
|
|
20
20
|
kModelCatalogCacheKey,
|
|
21
|
+
preloadModelCatalog,
|
|
21
22
|
} from "../../lib/model-catalog.js";
|
|
22
23
|
import {
|
|
23
24
|
kWelcomeGroups,
|
|
@@ -129,6 +130,11 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
129
130
|
maxAgeMs: 30000,
|
|
130
131
|
});
|
|
131
132
|
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
// Warm the real catalog immediately so the AI step usually opens ready.
|
|
135
|
+
preloadModelCatalog().catch(() => {});
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
132
138
|
const setValue = (key, value) => {
|
|
133
139
|
if (formError) setFormError(null);
|
|
134
140
|
setStoredValue(key, value);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { fetchModels } from "./api.js";
|
|
2
|
+
import { cachedFetch } from "./api-cache.js";
|
|
1
3
|
import { getFeaturedModels } from "./model-config.js";
|
|
2
4
|
|
|
3
5
|
export const kModelCatalogCacheKey = "/api/models";
|
|
@@ -9,6 +11,15 @@ export const getModelCatalogModels = (payload) =>
|
|
|
9
11
|
export const isModelCatalogRefreshing = (payload) =>
|
|
10
12
|
Boolean(payload?.refreshing);
|
|
11
13
|
|
|
14
|
+
export const preloadModelCatalog = ({
|
|
15
|
+
force = true,
|
|
16
|
+
maxAgeMs = 30000,
|
|
17
|
+
} = {}) =>
|
|
18
|
+
cachedFetch(kModelCatalogCacheKey, fetchModels, {
|
|
19
|
+
force,
|
|
20
|
+
maxAgeMs,
|
|
21
|
+
});
|
|
22
|
+
|
|
12
23
|
export const getInitialOnboardingModelKey = ({
|
|
13
24
|
catalog = [],
|
|
14
25
|
currentModelKey = "",
|
package/lib/scripts/git
CHANGED
|
@@ -6,21 +6,6 @@ REAL_GIT_HINT="@@REAL_GIT@@"
|
|
|
6
6
|
OPENCLAW_REPO_ROOT="@@OPENCLAW_REPO_ROOT@@"
|
|
7
7
|
ASKPASS_PATH="/tmp/alphaclaw-git-askpass.sh"
|
|
8
8
|
|
|
9
|
-
SUBCMD=""
|
|
10
|
-
for arg in "$@"; do
|
|
11
|
-
case "$arg" in
|
|
12
|
-
-*) ;;
|
|
13
|
-
*) SUBCMD="$arg"; break ;;
|
|
14
|
-
esac
|
|
15
|
-
done
|
|
16
|
-
|
|
17
|
-
needs_auth() {
|
|
18
|
-
case "$SUBCMD" in
|
|
19
|
-
push|pull|fetch|clone|ls-remote) return 0 ;;
|
|
20
|
-
*) return 1 ;;
|
|
21
|
-
esac
|
|
22
|
-
}
|
|
23
|
-
|
|
24
9
|
same_path() {
|
|
25
10
|
local left_path right_path
|
|
26
11
|
left_path="$(readlink -f "$1" 2>/dev/null || printf '%s' "$1")"
|
|
@@ -61,12 +46,73 @@ EOF
|
|
|
61
46
|
return 1
|
|
62
47
|
}
|
|
63
48
|
|
|
49
|
+
canonicalize_path() {
|
|
50
|
+
local target_path resolved_path
|
|
51
|
+
target_path="$1"
|
|
52
|
+
[ -n "$target_path" ] || return 1
|
|
53
|
+
|
|
54
|
+
if [ -d "$target_path" ]; then
|
|
55
|
+
resolved_path="$(cd "$target_path" 2>/dev/null && pwd -P)" || resolved_path=""
|
|
56
|
+
if [ -n "$resolved_path" ]; then
|
|
57
|
+
printf '%s\n' "$resolved_path"
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
resolved_path="$(readlink -f "$target_path" 2>/dev/null || true)"
|
|
63
|
+
if [ -n "$resolved_path" ]; then
|
|
64
|
+
printf '%s\n' "$resolved_path"
|
|
65
|
+
return 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
printf '%s\n' "$target_path"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
resolve_effective_pwd() {
|
|
72
|
+
local effective_pwd
|
|
73
|
+
effective_pwd="$(pwd)"
|
|
74
|
+
|
|
75
|
+
while [ "$#" -gt 0 ]; do
|
|
76
|
+
case "$1" in
|
|
77
|
+
-C)
|
|
78
|
+
shift
|
|
79
|
+
[ "$#" -gt 0 ] || break
|
|
80
|
+
case "$1" in
|
|
81
|
+
/*) effective_pwd="$1" ;;
|
|
82
|
+
*) effective_pwd="$effective_pwd/$1" ;;
|
|
83
|
+
esac
|
|
84
|
+
;;
|
|
85
|
+
-c|--exec-path|--git-dir|--work-tree|--namespace|--config-env|--super-prefix|--list-cmds|--attr-source)
|
|
86
|
+
shift
|
|
87
|
+
[ "$#" -gt 0 ] || break
|
|
88
|
+
;;
|
|
89
|
+
--exec-path=*|--git-dir=*|--work-tree=*|--namespace=*|--config-env=*|--super-prefix=*|--list-cmds=*|--attr-source=*)
|
|
90
|
+
;;
|
|
91
|
+
--)
|
|
92
|
+
break
|
|
93
|
+
;;
|
|
94
|
+
-*)
|
|
95
|
+
;;
|
|
96
|
+
*)
|
|
97
|
+
break
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
shift
|
|
101
|
+
done
|
|
102
|
+
|
|
103
|
+
canonicalize_path "$effective_pwd"
|
|
104
|
+
}
|
|
105
|
+
|
|
64
106
|
in_openclaw_root() {
|
|
107
|
+
local candidate_path resolved_repo_root resolved_candidate_path
|
|
108
|
+
candidate_path="$1"
|
|
65
109
|
if [ -z "$OPENCLAW_REPO_ROOT" ]; then
|
|
66
110
|
return 1
|
|
67
111
|
fi
|
|
68
|
-
|
|
69
|
-
|
|
112
|
+
resolved_repo_root="$(canonicalize_path "$OPENCLAW_REPO_ROOT")"
|
|
113
|
+
resolved_candidate_path="$(canonicalize_path "$candidate_path")"
|
|
114
|
+
case "$resolved_candidate_path" in
|
|
115
|
+
"$resolved_repo_root"|"${resolved_repo_root}"/*) return 0 ;;
|
|
70
116
|
*) return 1 ;;
|
|
71
117
|
esac
|
|
72
118
|
}
|
|
@@ -77,7 +123,9 @@ if [ -z "$REAL_GIT" ]; then
|
|
|
77
123
|
exit 127
|
|
78
124
|
fi
|
|
79
125
|
|
|
80
|
-
|
|
126
|
+
EFFECTIVE_PWD="$(resolve_effective_pwd "$@")"
|
|
127
|
+
|
|
128
|
+
if [ "${ALPHACLAW_GIT_NO_AUTH:-}" = "1" ] || [ -z "${GITHUB_TOKEN:-}" ] || ! in_openclaw_root "$EFFECTIVE_PWD"; then
|
|
81
129
|
exec "$REAL_GIT" "$@"
|
|
82
130
|
fi
|
|
83
131
|
|
package/lib/server/gateway.js
CHANGED
|
@@ -123,7 +123,7 @@ const createModelCatalogCache = ({
|
|
|
123
123
|
const loadFreshCatalog = async () => {
|
|
124
124
|
const output = await shellCmd("openclaw models list --all --json", {
|
|
125
125
|
env: gatewayEnv(),
|
|
126
|
-
timeout:
|
|
126
|
+
timeout: 30000,
|
|
127
127
|
});
|
|
128
128
|
const parsed = parseJsonFromNoisyOutput(output);
|
|
129
129
|
const models = normalizeOnboardingModels(parsed?.models || []);
|
|
@@ -63,6 +63,56 @@ const repoContainsOnlyBoilerplate = async (repoUrl, ghHeaders) => {
|
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
const getNextGithubPageUrl = (linkHeader = "") => {
|
|
67
|
+
const nextLink = String(linkHeader || "")
|
|
68
|
+
.split(",")
|
|
69
|
+
.map((entry) => entry.trim())
|
|
70
|
+
.find((entry) => entry.endsWith('rel="next"'));
|
|
71
|
+
const match = nextLink?.match(/<([^>]+)>/);
|
|
72
|
+
return match?.[1] || "";
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const findOwnedRepoByName = async ({
|
|
76
|
+
repoUrl,
|
|
77
|
+
repoOwner,
|
|
78
|
+
repoName,
|
|
79
|
+
viewerLogin,
|
|
80
|
+
ghHeaders,
|
|
81
|
+
}) => {
|
|
82
|
+
if (
|
|
83
|
+
!repoOwner ||
|
|
84
|
+
!repoName ||
|
|
85
|
+
!viewerLogin ||
|
|
86
|
+
repoOwner.toLowerCase() !== viewerLogin.toLowerCase()
|
|
87
|
+
) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let nextUrl =
|
|
92
|
+
"https://api.github.com/user/repos?affiliation=owner&per_page=100&page=1";
|
|
93
|
+
const normalizedRepoUrl = String(repoUrl || "").trim().toLowerCase();
|
|
94
|
+
const normalizedRepoName = String(repoName || "").trim().toLowerCase();
|
|
95
|
+
|
|
96
|
+
while (nextUrl) {
|
|
97
|
+
const res = await fetch(nextUrl, { headers: ghHeaders });
|
|
98
|
+
if (!res.ok) return null;
|
|
99
|
+
|
|
100
|
+
const repos = await res.json();
|
|
101
|
+
if (!Array.isArray(repos)) return null;
|
|
102
|
+
|
|
103
|
+
const existingRepo = repos.find((repo) => {
|
|
104
|
+
const fullName = String(repo?.full_name || "").trim().toLowerCase();
|
|
105
|
+
const name = String(repo?.name || "").trim().toLowerCase();
|
|
106
|
+
return fullName === normalizedRepoUrl || name === normalizedRepoName;
|
|
107
|
+
});
|
|
108
|
+
if (existingRepo) return existingRepo;
|
|
109
|
+
|
|
110
|
+
nextUrl = getNextGithubPageUrl(res.headers?.get?.("link"));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
};
|
|
115
|
+
|
|
66
116
|
const isClassicPat = (token) => String(token || "").startsWith("ghp_");
|
|
67
117
|
const isFineGrainedPat = (token) =>
|
|
68
118
|
String(token || "").startsWith("github_pat_");
|
|
@@ -73,8 +123,9 @@ const verifyGithubRepoForOnboarding = async ({
|
|
|
73
123
|
mode = "new",
|
|
74
124
|
}) => {
|
|
75
125
|
const ghHeaders = buildGithubHeaders(githubToken);
|
|
76
|
-
const [repoOwner] = String(repoUrl || "").split("/"
|
|
126
|
+
const [repoOwner = "", repoName = ""] = String(repoUrl || "").split("/");
|
|
77
127
|
const isExisting = mode === "existing";
|
|
128
|
+
let viewerLogin = "";
|
|
78
129
|
|
|
79
130
|
try {
|
|
80
131
|
const userRes = await fetch("https://api.github.com/user", {
|
|
@@ -106,12 +157,29 @@ const verifyGithubRepoForOnboarding = async ({
|
|
|
106
157
|
};
|
|
107
158
|
}
|
|
108
159
|
}
|
|
109
|
-
await userRes.json().catch(() => ({}));
|
|
160
|
+
const userPayload = await userRes.json().catch(() => ({}));
|
|
161
|
+
viewerLogin = String(userPayload?.login || "").trim();
|
|
110
162
|
|
|
111
163
|
const checkRes = await fetch(`https://api.github.com/repos/${repoUrl}`, {
|
|
112
164
|
headers: ghHeaders,
|
|
113
165
|
});
|
|
114
166
|
if (checkRes.status === 404) {
|
|
167
|
+
const hiddenOwnedRepo = await findOwnedRepoByName({
|
|
168
|
+
repoUrl,
|
|
169
|
+
repoOwner,
|
|
170
|
+
repoName,
|
|
171
|
+
viewerLogin,
|
|
172
|
+
ghHeaders,
|
|
173
|
+
});
|
|
174
|
+
if (hiddenOwnedRepo) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
status: 400,
|
|
178
|
+
error:
|
|
179
|
+
`Repository "${repoUrl}" already exists, but this token cannot inspect it. ` +
|
|
180
|
+
"Choose a different repo name or use a token that can access that repo.",
|
|
181
|
+
};
|
|
182
|
+
}
|
|
115
183
|
if (isExisting) {
|
|
116
184
|
return {
|
|
117
185
|
ok: false,
|
|
@@ -205,6 +273,19 @@ const ensureGithubRepoAccessible = async ({
|
|
|
205
273
|
});
|
|
206
274
|
if (!createRes.ok) {
|
|
207
275
|
const details = await parseGithubErrorMessage(createRes);
|
|
276
|
+
if (
|
|
277
|
+
String(details || "")
|
|
278
|
+
.toLowerCase()
|
|
279
|
+
.includes("name already exists on this account")
|
|
280
|
+
) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
status: 400,
|
|
284
|
+
error:
|
|
285
|
+
`Repository "${repoUrl}" already exists. ` +
|
|
286
|
+
"Choose a different repo name or use a token that can access that repo.",
|
|
287
|
+
};
|
|
288
|
+
}
|
|
208
289
|
const hint =
|
|
209
290
|
createRes.status === 404 || createRes.status === 403
|
|
210
291
|
? ' Ensure your token is a classic PAT with the "repo" scope.'
|
|
@@ -25,6 +25,18 @@ If any of these apply, outline your approach first — what you intend to do, in
|
|
|
25
25
|
|
|
26
26
|
Your `.openclaw` directory is version-controlled and this is how work survives container restarts.
|
|
27
27
|
|
|
28
|
+
### Persistent Storage Rules
|
|
29
|
+
|
|
30
|
+
This deployment runs in an ephemeral container. `/tmp`, other temp directories, and files outside `/data` can disappear on restart or redeploy.
|
|
31
|
+
|
|
32
|
+
Anything that must survive redeploys must live under `/data/.openclaw`.
|
|
33
|
+
|
|
34
|
+
For plugins and other durable artifacts:
|
|
35
|
+
|
|
36
|
+
- Prefer normal `openclaw plugins install <spec>` flows for persistent installs.
|
|
37
|
+
- If you must stage or unpack a local plugin first, stage it under `/data/.openclaw/...`, not `/tmp/...`.
|
|
38
|
+
- Never persist `plugins.load.paths` entries that point at temp directories.
|
|
39
|
+
|
|
28
40
|
Anytime you add, edit, or remove workspace files, openclaw.json, cron.json, skills, or external resources (third-party pages, databases, integrations), **commit and push your changes to git**. Never force push; always pull first if there might be remote changes.
|
|
29
41
|
|
|
30
42
|
Whenever you do this, end your message with a **Changes committed** summary. Use workspace-relative paths for local files.
|
|
@@ -21,6 +21,18 @@ Do not deflect actionable requests to the Setup UI. If a command or tool is avai
|
|
|
21
21
|
|
|
22
22
|
Changes to env vars are made through the **Envars** tab (`{{SETUP_UI_URL}}#envars`). After saving, a gateway restart may be required to pick up the changes — the UI prompts for this automatically. Do not edit `/data/.env` directly; use the Setup UI so changes are validated and the gateway restart is handled.
|
|
23
23
|
|
|
24
|
+
### Persistent storage
|
|
25
|
+
|
|
26
|
+
This deployment runs in an ephemeral container. `/tmp` and other temp locations do not survive redeploys.
|
|
27
|
+
|
|
28
|
+
Anything persistent must live under `/data/.openclaw`.
|
|
29
|
+
|
|
30
|
+
For plugins and local tooling:
|
|
31
|
+
|
|
32
|
+
- Prefer normal `openclaw plugins install <spec>` flows for durable installs.
|
|
33
|
+
- If you need to stage a local plugin or helper files first, put them under `/data/.openclaw/...`, not `/tmp/...`.
|
|
34
|
+
- Do not leave durable `plugins.load.paths` entries pointing at temp directories.
|
|
35
|
+
|
|
24
36
|
### Google Workspace
|
|
25
37
|
|
|
26
38
|
Google Workspace is connected via the **General** tab (`{{SETUP_UI_URL}}#general`). The user provides OAuth client credentials from Google Cloud Console, then authorizes access to the services they need (Gmail, Calendar, Drive, Sheets, Docs, Tasks, Contacts, Meet). Connected accounts and `gog` CLI usage are covered by the gog-cli skill.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrysb/alphaclaw",
|
|
3
|
-
"version": "0.9.0",
|
|
3
|
+
"version": "0.9.1-beta.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"express": "^4.21.0",
|
|
38
38
|
"http-proxy": "^1.18.1",
|
|
39
|
-
"openclaw": "2026.4.
|
|
39
|
+
"openclaw": "2026.4.10",
|
|
40
40
|
"patch-package": "^8.0.1",
|
|
41
41
|
"ws": "^8.19.0"
|
|
42
42
|
},
|