mlclaw 0.1.0 → 0.2.2
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/.agents/skills/mlclaw/SKILL.md +70 -18
- package/Dockerfile +21 -4
- package/README.md +77 -24
- package/assets/hf-logo.svg +8 -0
- package/assets/hf-tooling/manifest.json +50 -0
- package/assets/hf-tooling/skills/hf-cli/SKILL.md +218 -0
- package/assets/hf-tooling/skills/hf-mem/SKILL.md +79 -0
- package/assets/hf-tooling/skills/huggingface-best/SKILL.md +134 -0
- package/assets/hf-tooling/skills/huggingface-datasets/SKILL.md +107 -0
- package/assets/hf-tooling/skills/huggingface-gradio/SKILL.md +298 -0
- package/assets/hf-tooling/skills/huggingface-gradio/examples.md +613 -0
- package/assets/hf-tooling/skills/huggingface-local-models/SKILL.md +113 -0
- package/assets/hf-tooling/skills/huggingface-local-models/references/hardware.md +38 -0
- package/assets/hf-tooling/skills/huggingface-local-models/references/hub-discovery.md +178 -0
- package/assets/hf-tooling/skills/huggingface-local-models/references/quantization.md +256 -0
- package/assets/hf-tooling/skills/huggingface-papers/SKILL.md +239 -0
- package/assets/hf-tooling/skills/huggingface-spaces/SKILL.md +239 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/buckets.md +89 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/debugging.md +236 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/gradio.md +200 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/grants.md +56 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/inference-providers.md +85 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/known-errors.md +232 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/requirements.md +169 -0
- package/assets/hf-tooling/skills/huggingface-spaces/references/zerogpu.md +349 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/SKILL.md +120 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/references/baseline_hf_api.py +57 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/references/baseline_hf_api.sh +40 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/references/baseline_hf_api.tsx +57 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/references/find_models_by_paper.sh +230 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/references/hf_enrich_models.sh +96 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/references/hf_model_card_frontmatter.sh +188 -0
- package/assets/hf-tooling/skills/huggingface-tool-builder/references/hf_model_papers_auth.sh +171 -0
- package/assets/hf-tooling/skills/huggingface-zerogpu/SKILL.md +289 -0
- package/assets/hf-tooling/skills/huggingface-zerogpu/references/concurrency.md +79 -0
- package/assets/hf-tooling/skills/huggingface-zerogpu/references/cuda-and-deps.md +66 -0
- package/assets/hf-tooling/skills/huggingface-zerogpu/references/how-quota-works.md +74 -0
- package/assets/hf-tooling/skills/huggingface-zerogpu/references/how-zerogpu-works.md +50 -0
- package/assets/hf-tooling/templates/.agents/mcp/huggingface.json +11 -0
- package/assets/hf-tooling/templates/.env.example +5 -0
- package/assets/hf-tooling/templates/examples/huggingface/README.md +16 -0
- package/assets/hf-tooling/templates/examples/huggingface/bucket-sync.md +19 -0
- package/assets/hf-tooling/templates/examples/huggingface/dataset-upload.py +22 -0
- package/assets/hf-tooling/templates/examples/huggingface/hf-discover.md +13 -0
- package/assets/hf-tooling/templates/examples/huggingface/runtime-inspection.py +9 -0
- package/assets/mlclaw-control-ui/assets/index-D2TFes32.js +9 -0
- package/assets/mlclaw-control-ui/assets/index-DP72PFuv.css +1 -0
- package/assets/mlclaw-control-ui/index.html +13 -0
- package/assets/mlclaw.svg +298 -124
- package/dist/hf-tooling-seed.js +261 -0
- package/dist/mlclaw-space-runtime.js +6746 -3321
- package/dist/mlclaw.mjs +428 -178
- package/entrypoint.sh +4 -0
- package/mlclaw.ps1 +4 -2
- package/mlclaw.sh +5 -3
- package/openclaw.default.json +1 -0
- package/package.json +16 -2
- package/space/README.md +32 -2
- package/tsconfig.json +3 -2
package/dist/mlclaw.mjs
CHANGED
|
@@ -9756,10 +9756,10 @@ function parseGatewayLocation(value) {
|
|
|
9756
9756
|
|
|
9757
9757
|
// src/mlclaw/git.ts
|
|
9758
9758
|
import { execFile as execFile2 } from "node:child_process";
|
|
9759
|
-
import
|
|
9759
|
+
import fs12 from "node:fs/promises";
|
|
9760
9760
|
import os4 from "node:os";
|
|
9761
|
-
import
|
|
9762
|
-
import { fileURLToPath as
|
|
9761
|
+
import path13 from "node:path";
|
|
9762
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9763
9763
|
import { promisify as promisify2 } from "node:util";
|
|
9764
9764
|
|
|
9765
9765
|
// src/vendor/hfjs-xet/error.ts
|
|
@@ -14549,6 +14549,17 @@ var HubApi = class {
|
|
|
14549
14549
|
throw err;
|
|
14550
14550
|
}
|
|
14551
14551
|
}
|
|
14552
|
+
async bucketExists(bucketId) {
|
|
14553
|
+
try {
|
|
14554
|
+
await this.bucket(bucketId).assertBucketAccessible();
|
|
14555
|
+
return true;
|
|
14556
|
+
} catch (err) {
|
|
14557
|
+
if (err instanceof BucketHttpError && err.status === 404) {
|
|
14558
|
+
return false;
|
|
14559
|
+
}
|
|
14560
|
+
throw err;
|
|
14561
|
+
}
|
|
14562
|
+
}
|
|
14552
14563
|
async createDockerSpace(repoId, options) {
|
|
14553
14564
|
const [owner, name] = splitRepoId(repoId);
|
|
14554
14565
|
const me2 = await this.whoami();
|
|
@@ -14578,6 +14589,17 @@ var HubApi = class {
|
|
|
14578
14589
|
throw err;
|
|
14579
14590
|
}
|
|
14580
14591
|
}
|
|
14592
|
+
async spaceExists(repoId) {
|
|
14593
|
+
try {
|
|
14594
|
+
await this.requestJson(`/api/spaces/${repoId}`);
|
|
14595
|
+
return true;
|
|
14596
|
+
} catch (err) {
|
|
14597
|
+
if (err instanceof HubApiError2 && err.status === 404) {
|
|
14598
|
+
return false;
|
|
14599
|
+
}
|
|
14600
|
+
throw err;
|
|
14601
|
+
}
|
|
14602
|
+
}
|
|
14581
14603
|
async addSpaceVariable(repoId, key, value) {
|
|
14582
14604
|
await this.requestJson(`/api/spaces/${repoId}/variables`, {
|
|
14583
14605
|
method: "POST",
|
|
@@ -14778,15 +14800,73 @@ function sseDataToText(raw) {
|
|
|
14778
14800
|
return lines.join("");
|
|
14779
14801
|
}
|
|
14780
14802
|
|
|
14803
|
+
// src/mlclaw/runtime-image.ts
|
|
14804
|
+
import fs11 from "node:fs";
|
|
14805
|
+
import path12 from "node:path";
|
|
14806
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14807
|
+
var DEFAULT_OPENCLAW_VERSION = "2026.7.1-beta.2";
|
|
14808
|
+
var DEFAULT_RUNTIME_IMAGE_REPOSITORY = "ghcr.io/osolmaz/mlclaw";
|
|
14809
|
+
var PACKAGE_METADATA = readPackageMetadata();
|
|
14810
|
+
var PACKAGE_VERSION = packageString("version", "unknown");
|
|
14811
|
+
var OPENCLAW_VERSION = packageConfigString("openclawVersion", DEFAULT_OPENCLAW_VERSION);
|
|
14812
|
+
var OPENCLAW_BASE_IMAGE = `ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION}`;
|
|
14813
|
+
var RUNTIME_IMAGE_REPOSITORY = packageConfigString("runtimeImageRepository", DEFAULT_RUNTIME_IMAGE_REPOSITORY);
|
|
14814
|
+
var DEFAULT_RUNTIME_IMAGE_TAG = `${PACKAGE_VERSION}-openclaw-${OPENCLAW_VERSION}`;
|
|
14815
|
+
var DEFAULT_RUNTIME_IMAGE = `${RUNTIME_IMAGE_REPOSITORY}:${DEFAULT_RUNTIME_IMAGE_TAG}`;
|
|
14816
|
+
function resolveRuntimeImage(value, env = process.env) {
|
|
14817
|
+
return value?.trim() || env.MLCLAW_RUNTIME_IMAGE?.trim() || DEFAULT_RUNTIME_IMAGE;
|
|
14818
|
+
}
|
|
14819
|
+
function resolveSpaceRuntimeImage(opts, env = process.env) {
|
|
14820
|
+
if (opts.bundledRuntime) {
|
|
14821
|
+
if (opts.runtimeImage?.trim() || env.MLCLAW_RUNTIME_IMAGE?.trim()) {
|
|
14822
|
+
throw new Error("--bundled-runtime cannot be combined with --runtime-image or MLCLAW_RUNTIME_IMAGE");
|
|
14823
|
+
}
|
|
14824
|
+
return void 0;
|
|
14825
|
+
}
|
|
14826
|
+
return resolveRuntimeImage(opts.runtimeImage, env);
|
|
14827
|
+
}
|
|
14828
|
+
function bundledSpaceRuntimeRef(templateRev) {
|
|
14829
|
+
return `bundled:${templateRev}`;
|
|
14830
|
+
}
|
|
14831
|
+
function packageString(key, fallback) {
|
|
14832
|
+
const value = PACKAGE_METADATA[key];
|
|
14833
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
14834
|
+
}
|
|
14835
|
+
function packageConfigString(key, fallback) {
|
|
14836
|
+
const value = PACKAGE_METADATA.config?.[key];
|
|
14837
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
14838
|
+
}
|
|
14839
|
+
function readPackageMetadata() {
|
|
14840
|
+
let dir = path12.dirname(fileURLToPath2(import.meta.url));
|
|
14841
|
+
while (true) {
|
|
14842
|
+
const candidate = path12.join(dir, "package.json");
|
|
14843
|
+
try {
|
|
14844
|
+
return JSON.parse(fs11.readFileSync(candidate, "utf8"));
|
|
14845
|
+
} catch (err) {
|
|
14846
|
+
if (!isMissingFileError(err)) {
|
|
14847
|
+
throw err;
|
|
14848
|
+
}
|
|
14849
|
+
}
|
|
14850
|
+
const parent = path12.dirname(dir);
|
|
14851
|
+
if (parent === dir) {
|
|
14852
|
+
throw new Error("could not find package.json while resolving default runtime image");
|
|
14853
|
+
}
|
|
14854
|
+
dir = parent;
|
|
14855
|
+
}
|
|
14856
|
+
}
|
|
14857
|
+
function isMissingFileError(err) {
|
|
14858
|
+
return err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
14859
|
+
}
|
|
14860
|
+
|
|
14781
14861
|
// src/mlclaw/git.ts
|
|
14782
14862
|
var execFileAsync2 = promisify2(execFile2);
|
|
14783
14863
|
async function pushTemplateToSpace(params) {
|
|
14784
|
-
const tempRoot = await
|
|
14864
|
+
const tempRoot = await fs12.mkdtemp(path13.join(os4.tmpdir(), "mlclaw-space-"));
|
|
14785
14865
|
try {
|
|
14786
14866
|
const sourceDir = params.sourceDir ?? process.env.MLCLAW_SOURCE_DIR ?? await findPackagedSourceRoot();
|
|
14787
14867
|
const templateRev = await currentTemplateRev(sourceDir);
|
|
14788
|
-
const outDir =
|
|
14789
|
-
await
|
|
14868
|
+
const outDir = path13.join(tempRoot, "space");
|
|
14869
|
+
await fs12.mkdir(outDir, { recursive: true });
|
|
14790
14870
|
await generateSpaceRepo(sourceDir, outDir, {
|
|
14791
14871
|
...params.runtimeImage ? { runtimeImage: params.runtimeImage } : {}
|
|
14792
14872
|
});
|
|
@@ -14804,7 +14884,7 @@ async function pushTemplateToSpace(params) {
|
|
|
14804
14884
|
});
|
|
14805
14885
|
return { templateRev };
|
|
14806
14886
|
} finally {
|
|
14807
|
-
await
|
|
14887
|
+
await fs12.rm(tempRoot, { recursive: true, force: true });
|
|
14808
14888
|
}
|
|
14809
14889
|
}
|
|
14810
14890
|
async function currentTemplateRev(sourceDir) {
|
|
@@ -14817,18 +14897,22 @@ async function currentTemplateRev(sourceDir) {
|
|
|
14817
14897
|
}
|
|
14818
14898
|
} catch {
|
|
14819
14899
|
}
|
|
14820
|
-
const pkg = JSON.parse(await
|
|
14900
|
+
const pkg = JSON.parse(await fs12.readFile(path13.join(sourceDir, "package.json"), "utf8"));
|
|
14821
14901
|
return `npm:${pkg.name ?? "mlclaw"}@${pkg.version ?? "unknown"}`;
|
|
14822
14902
|
}
|
|
14823
14903
|
async function generateSpaceRepo(sourceDir, outDir, options = {}) {
|
|
14824
14904
|
const copies = [
|
|
14825
14905
|
[".gitattributes", ".gitattributes"],
|
|
14906
|
+
["assets/hf-logo.svg", "assets/hf-logo.svg"],
|
|
14826
14907
|
["assets/mlclaw.svg", "assets/mlclaw.svg"],
|
|
14908
|
+
["assets/mlclaw-control-ui", "assets/mlclaw-control-ui"],
|
|
14909
|
+
["assets/hf-tooling", "assets/hf-tooling"],
|
|
14827
14910
|
["space/README.md", "README.md"]
|
|
14828
14911
|
];
|
|
14829
14912
|
if (!options.runtimeImage) {
|
|
14830
14913
|
copies.push(
|
|
14831
14914
|
["dist/hf-state-sync.js", "runtime/hf-state-sync.js"],
|
|
14915
|
+
["dist/hf-tooling-seed.js", "runtime/hf-tooling-seed.js"],
|
|
14832
14916
|
["dist/mlclaw-space-runtime.js", "runtime/mlclaw-space-runtime.js"],
|
|
14833
14917
|
["entrypoint.sh", "runtime/entrypoint.sh"],
|
|
14834
14918
|
["openclaw.default.json", "runtime/openclaw.default.json"],
|
|
@@ -14838,10 +14922,10 @@ async function generateSpaceRepo(sourceDir, outDir, options = {}) {
|
|
|
14838
14922
|
);
|
|
14839
14923
|
}
|
|
14840
14924
|
for (const [from, to] of copies) {
|
|
14841
|
-
await copyExisting(
|
|
14925
|
+
await copyExisting(path13.join(sourceDir, from), path13.join(outDir, to));
|
|
14842
14926
|
}
|
|
14843
|
-
await
|
|
14844
|
-
|
|
14927
|
+
await fs12.writeFile(
|
|
14928
|
+
path13.join(outDir, "Dockerfile"),
|
|
14845
14929
|
options.runtimeImage ? imageDockerfile(options.runtimeImage) : bundledDockerfile(),
|
|
14846
14930
|
"utf8"
|
|
14847
14931
|
);
|
|
@@ -14851,17 +14935,30 @@ function imageDockerfile(runtimeImage) {
|
|
|
14851
14935
|
`;
|
|
14852
14936
|
}
|
|
14853
14937
|
function bundledDockerfile() {
|
|
14854
|
-
return `FROM
|
|
14938
|
+
return `FROM ${OPENCLAW_BASE_IMAGE}
|
|
14855
14939
|
|
|
14856
14940
|
LABEL org.opencontainers.image.source="https://github.com/osolmaz/mlclaw"
|
|
14857
14941
|
LABEL org.opencontainers.image.description="ML Claw runtime for OpenClaw on Hugging Face"
|
|
14858
14942
|
|
|
14859
14943
|
USER root
|
|
14860
14944
|
RUN apt-get update \\
|
|
14861
|
-
&& apt-get install -y --no-install-recommends gosu zstd \\
|
|
14945
|
+
&& apt-get install -y --no-install-recommends gosu python3 python3-pip python3-venv zstd \\
|
|
14862
14946
|
&& rm -rf /var/lib/apt/lists/*
|
|
14947
|
+
RUN python3 -m pip install --break-system-packages --no-cache-dir \\
|
|
14948
|
+
"huggingface_hub==1.19.0" \\
|
|
14949
|
+
"datasets==5.0.0" \\
|
|
14950
|
+
"safetensors==0.8.0" \\
|
|
14951
|
+
"fastapi==0.137.1" \\
|
|
14952
|
+
"pydantic==2.13.4" \\
|
|
14953
|
+
"rich==15.0.0" \\
|
|
14954
|
+
"starlette==1.3.1" \\
|
|
14955
|
+
"typer==0.25.1" \\
|
|
14956
|
+
"uvicorn==0.49.0" \\
|
|
14957
|
+
"uv==0.11.28" \\
|
|
14958
|
+
"hf-discover==1.3.7"
|
|
14863
14959
|
|
|
14864
14960
|
COPY --chown=node:node runtime/hf-state-sync.js /app/hf-state-sync.js
|
|
14961
|
+
COPY --chown=node:node runtime/hf-tooling-seed.js /app/hf-tooling-seed.js
|
|
14865
14962
|
COPY --chown=node:node runtime/mlclaw-space-runtime.js /app/mlclaw-space-runtime.js
|
|
14866
14963
|
COPY --chown=node:node runtime/openclaw.default.json /app/openclaw.default.json
|
|
14867
14964
|
COPY --chown=node:node runtime/entrypoint.sh /app/entrypoint.sh
|
|
@@ -14886,13 +14983,13 @@ CMD ["/app/entrypoint.sh"]
|
|
|
14886
14983
|
`;
|
|
14887
14984
|
}
|
|
14888
14985
|
async function findPackagedSourceRoot() {
|
|
14889
|
-
const start =
|
|
14986
|
+
const start = path13.dirname(fileURLToPath3(import.meta.url));
|
|
14890
14987
|
let dir = start;
|
|
14891
14988
|
while (true) {
|
|
14892
14989
|
if (await hasPackagedSourceFiles(dir)) {
|
|
14893
14990
|
return dir;
|
|
14894
14991
|
}
|
|
14895
|
-
const parent =
|
|
14992
|
+
const parent = path13.dirname(dir);
|
|
14896
14993
|
if (parent === dir) {
|
|
14897
14994
|
throw new Error("Could not find packaged ML Claw source files. Reinstall the mlclaw npm package.");
|
|
14898
14995
|
}
|
|
@@ -14909,20 +15006,20 @@ async function hasPackagedSourceFiles(dir) {
|
|
|
14909
15006
|
"src/hf-bucket-client/client.ts"
|
|
14910
15007
|
];
|
|
14911
15008
|
try {
|
|
14912
|
-
await Promise.all(required.map((file) =>
|
|
15009
|
+
await Promise.all(required.map((file) => fs12.access(path13.join(dir, file))));
|
|
14913
15010
|
return true;
|
|
14914
15011
|
} catch {
|
|
14915
15012
|
return false;
|
|
14916
15013
|
}
|
|
14917
15014
|
}
|
|
14918
15015
|
async function copyExisting(from, to) {
|
|
14919
|
-
const stat = await
|
|
14920
|
-
await
|
|
15016
|
+
const stat = await fs12.stat(from);
|
|
15017
|
+
await fs12.mkdir(path13.dirname(to), { recursive: true });
|
|
14921
15018
|
if (stat.isDirectory()) {
|
|
14922
|
-
await
|
|
15019
|
+
await fs12.cp(from, to, { recursive: true });
|
|
14923
15020
|
} else {
|
|
14924
|
-
await
|
|
14925
|
-
await
|
|
15021
|
+
await fs12.copyFile(from, to);
|
|
15022
|
+
await fs12.chmod(to, stat.mode);
|
|
14926
15023
|
}
|
|
14927
15024
|
}
|
|
14928
15025
|
async function readFilesForCommit(root) {
|
|
@@ -14930,24 +15027,24 @@ async function readFilesForCommit(root) {
|
|
|
14930
15027
|
for (const relativePath of await listFiles(root)) {
|
|
14931
15028
|
files.push({
|
|
14932
15029
|
path: relativePath,
|
|
14933
|
-
content: await
|
|
15030
|
+
content: await fs12.readFile(path13.join(root, relativePath))
|
|
14934
15031
|
});
|
|
14935
15032
|
}
|
|
14936
15033
|
return files;
|
|
14937
15034
|
}
|
|
14938
15035
|
async function listFiles(root, dir = "") {
|
|
14939
|
-
const absoluteDir =
|
|
14940
|
-
const entries = await
|
|
15036
|
+
const absoluteDir = path13.join(root, dir);
|
|
15037
|
+
const entries = await fs12.readdir(absoluteDir, { withFileTypes: true });
|
|
14941
15038
|
const files = [];
|
|
14942
15039
|
for (const entry of entries) {
|
|
14943
|
-
const relativePath =
|
|
14944
|
-
const absolutePath =
|
|
15040
|
+
const relativePath = path13.posix.join(dir.split(path13.sep).join(path13.posix.sep), entry.name);
|
|
15041
|
+
const absolutePath = path13.join(root, relativePath);
|
|
14945
15042
|
if (entry.isDirectory()) {
|
|
14946
15043
|
files.push(...await listFiles(root, relativePath));
|
|
14947
15044
|
} else if (entry.isFile()) {
|
|
14948
15045
|
files.push(relativePath);
|
|
14949
15046
|
} else {
|
|
14950
|
-
const stat = await
|
|
15047
|
+
const stat = await fs12.stat(absolutePath);
|
|
14951
15048
|
if (stat.isFile()) {
|
|
14952
15049
|
files.push(relativePath);
|
|
14953
15050
|
}
|
|
@@ -15014,9 +15111,9 @@ async function assertNoLiveForeignLease(params) {
|
|
|
15014
15111
|
}
|
|
15015
15112
|
|
|
15016
15113
|
// src/mlclaw/local-config.ts
|
|
15017
|
-
import
|
|
15114
|
+
import fs13 from "node:fs/promises";
|
|
15018
15115
|
import os5 from "node:os";
|
|
15019
|
-
import
|
|
15116
|
+
import path14 from "node:path";
|
|
15020
15117
|
function defaultConfigRoot(env = process.env) {
|
|
15021
15118
|
const explicit = env.MLCLAW_CONFIG_HOME?.trim();
|
|
15022
15119
|
if (explicit) {
|
|
@@ -15024,32 +15121,32 @@ function defaultConfigRoot(env = process.env) {
|
|
|
15024
15121
|
}
|
|
15025
15122
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
15026
15123
|
if (xdg) {
|
|
15027
|
-
return
|
|
15124
|
+
return path14.join(xdg, "mlclaw");
|
|
15028
15125
|
}
|
|
15029
|
-
return
|
|
15126
|
+
return path14.join(os5.homedir(), ".config", "mlclaw");
|
|
15030
15127
|
}
|
|
15031
15128
|
function localConfigPaths(root) {
|
|
15032
15129
|
return {
|
|
15033
15130
|
root,
|
|
15034
|
-
deploymentsDir:
|
|
15035
|
-
secretsDir:
|
|
15131
|
+
deploymentsDir: path14.join(root, "deployments"),
|
|
15132
|
+
secretsDir: path14.join(root, "secrets")
|
|
15036
15133
|
};
|
|
15037
15134
|
}
|
|
15038
15135
|
function manifestPath(root, agent) {
|
|
15039
|
-
return
|
|
15136
|
+
return path14.join(localConfigPaths(root).deploymentsDir, `${agent}.json`);
|
|
15040
15137
|
}
|
|
15041
15138
|
function secretEnvPath(root, agent) {
|
|
15042
|
-
return
|
|
15139
|
+
return path14.join(localConfigPaths(root).secretsDir, `${agent}.env`);
|
|
15043
15140
|
}
|
|
15044
15141
|
async function writeManifest(root, manifest) {
|
|
15045
15142
|
const file = manifestPath(root, manifest.agent);
|
|
15046
|
-
await
|
|
15047
|
-
await
|
|
15143
|
+
await fs13.mkdir(path14.dirname(file), { recursive: true });
|
|
15144
|
+
await fs13.writeFile(file, `${JSON.stringify(manifest, null, 2)}
|
|
15048
15145
|
`, "utf8");
|
|
15049
15146
|
}
|
|
15050
15147
|
async function readManifest(root, agent) {
|
|
15051
15148
|
const file = manifestPath(root, agent);
|
|
15052
|
-
const parsed = JSON.parse(await
|
|
15149
|
+
const parsed = JSON.parse(await fs13.readFile(file, "utf8"));
|
|
15053
15150
|
if (parsed.version !== 1) {
|
|
15054
15151
|
throw new Error(`unsupported deployment manifest version in ${file}`);
|
|
15055
15152
|
}
|
|
@@ -15057,7 +15154,7 @@ async function readManifest(root, agent) {
|
|
|
15057
15154
|
}
|
|
15058
15155
|
async function manifestExists(root, agent) {
|
|
15059
15156
|
try {
|
|
15060
|
-
await
|
|
15157
|
+
await fs13.access(manifestPath(root, agent));
|
|
15061
15158
|
return true;
|
|
15062
15159
|
} catch {
|
|
15063
15160
|
return false;
|
|
@@ -15069,12 +15166,12 @@ function renderSecretEnv(values) {
|
|
|
15069
15166
|
}
|
|
15070
15167
|
async function writeSecretEnv(root, agent, values) {
|
|
15071
15168
|
const file = secretEnvPath(root, agent);
|
|
15072
|
-
await
|
|
15073
|
-
await
|
|
15074
|
-
await
|
|
15169
|
+
await fs13.mkdir(path14.dirname(file), { recursive: true, mode: 448 });
|
|
15170
|
+
await fs13.writeFile(file, renderSecretEnv(values), { encoding: "utf8", mode: 384 });
|
|
15171
|
+
await fs13.chmod(file, 384);
|
|
15075
15172
|
}
|
|
15076
15173
|
async function readSecretEnv(root, agent) {
|
|
15077
|
-
return parseSecretEnv(await
|
|
15174
|
+
return parseSecretEnv(await fs13.readFile(secretEnvPath(root, agent), "utf8"));
|
|
15078
15175
|
}
|
|
15079
15176
|
function parseSecretEnv(raw) {
|
|
15080
15177
|
const out = {};
|
|
@@ -15116,46 +15213,6 @@ function namesFor(owner, agentName) {
|
|
|
15116
15213
|
};
|
|
15117
15214
|
}
|
|
15118
15215
|
|
|
15119
|
-
// src/mlclaw/runtime-image.ts
|
|
15120
|
-
import fs13 from "node:fs";
|
|
15121
|
-
import path14 from "node:path";
|
|
15122
|
-
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
15123
|
-
var RUNTIME_IMAGE_REPOSITORY = "ghcr.io/osolmaz/mlclaw-runtime";
|
|
15124
|
-
var DEFAULT_RUNTIME_IMAGE = `${RUNTIME_IMAGE_REPOSITORY}:${readPackageVersion()}`;
|
|
15125
|
-
function resolveRuntimeImage(value, env = process.env) {
|
|
15126
|
-
return value?.trim() || env.MLCLAW_RUNTIME_IMAGE?.trim() || DEFAULT_RUNTIME_IMAGE;
|
|
15127
|
-
}
|
|
15128
|
-
function resolveRuntimeImageOverride(value, env = process.env) {
|
|
15129
|
-
return value?.trim() || env.MLCLAW_RUNTIME_IMAGE?.trim() || void 0;
|
|
15130
|
-
}
|
|
15131
|
-
function bundledSpaceRuntimeRef(templateRev) {
|
|
15132
|
-
return `bundled:${templateRev}`;
|
|
15133
|
-
}
|
|
15134
|
-
function readPackageVersion() {
|
|
15135
|
-
let dir = path14.dirname(fileURLToPath3(import.meta.url));
|
|
15136
|
-
while (true) {
|
|
15137
|
-
const candidate = path14.join(dir, "package.json");
|
|
15138
|
-
try {
|
|
15139
|
-
const parsed = JSON.parse(fs13.readFileSync(candidate, "utf8"));
|
|
15140
|
-
if (typeof parsed.version === "string" && parsed.version.trim()) {
|
|
15141
|
-
return parsed.version.trim();
|
|
15142
|
-
}
|
|
15143
|
-
} catch (err) {
|
|
15144
|
-
if (!isMissingFileError(err)) {
|
|
15145
|
-
throw err;
|
|
15146
|
-
}
|
|
15147
|
-
}
|
|
15148
|
-
const parent = path14.dirname(dir);
|
|
15149
|
-
if (parent === dir) {
|
|
15150
|
-
throw new Error("could not find package.json while resolving default runtime image");
|
|
15151
|
-
}
|
|
15152
|
-
dir = parent;
|
|
15153
|
-
}
|
|
15154
|
-
}
|
|
15155
|
-
function isMissingFileError(err) {
|
|
15156
|
-
return err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
15157
|
-
}
|
|
15158
|
-
|
|
15159
15216
|
// src/mlclaw/telegram.ts
|
|
15160
15217
|
var TELEGRAM_GET_ME_TIMEOUT_MS = 3e4;
|
|
15161
15218
|
var TELEGRAM_GET_ME_ATTEMPTS = 4;
|
|
@@ -15192,7 +15249,7 @@ function delay(ms) {
|
|
|
15192
15249
|
}
|
|
15193
15250
|
|
|
15194
15251
|
// src/mlclaw/cli.ts
|
|
15195
|
-
var DEFAULT_MODEL = "huggingface/google/gemma-4-26B-A4B-it";
|
|
15252
|
+
var DEFAULT_MODEL = "huggingface/google/gemma-4-26B-A4B-it:deepinfra";
|
|
15196
15253
|
var DEFAULT_HARDWARE = "cpu-basic";
|
|
15197
15254
|
var TELEGRAM_HARDWARE = "cpu-upgrade";
|
|
15198
15255
|
var TELEGRAM_SLEEP_TIME = -1;
|
|
@@ -15205,6 +15262,7 @@ var SPACE_HANDOFF_TIMEOUT_MS = 12e4;
|
|
|
15205
15262
|
var SPACE_HANDOFF_POLL_MS = 5e3;
|
|
15206
15263
|
var STALE_PATH_VARS = ["OPENCLAW_STATE_DIR", "OPENCLAW_WORKSPACE_DIR", "OPENCLAW_CONFIG_PATH"];
|
|
15207
15264
|
var SNAPSHOT_MANIFEST_REMOTE_NAME = "manifest.json";
|
|
15265
|
+
var DEFAULT_CANONICAL_TEMPLATE_SPACE = "osolmaz/mlclaw";
|
|
15208
15266
|
var PAID_HARDWARE_COST_NOTE = "Paid Hugging Face Space hardware costs money while allocated. The cheapest option is cpu-upgrade at $0.03/hour, about $22/month if kept always on.";
|
|
15209
15267
|
var defaultPrompt = {
|
|
15210
15268
|
isInteractive: () => Boolean(process4.stdin.isTTY && process4.stdout.isTTY),
|
|
@@ -15237,10 +15295,10 @@ function createProgram(runtimeOverrides = {}) {
|
|
|
15237
15295
|
program2.name("mlclaw").description("Deploy OpenClaw to a Hugging Face Space and private bucket").showHelpAfterError().exitOverride((err) => {
|
|
15238
15296
|
throw err;
|
|
15239
15297
|
});
|
|
15240
|
-
program2.command("bootstrap", { isDefault: true }).description("Create or update a Hugging Face OpenClaw deployment").option("--owner <owner>", "Hugging Face user or organization").option("--name <name>", "Agent and runtime resource base name").option("--bucket <owner/bucket>", "State bucket to create or adopt").option("--gateway <local|space>", "Where the live gateway runs").option("--telegram-token <token>", "Optional Telegram bot token").option("--telegram-token-file <path>", "File containing TELEGRAM_BOT_TOKEN=... or a raw token").option("--telegram-user-id <id>", "Allowed Telegram user ID").option("--telegram-api-root <url>", "Telegram API root override").option("--telegram-proxy <url>", "Telegram proxy URL override").option("--hardware <flavor>", "Hugging Face Space hardware flavor").option("--sleep-time <seconds>", "Space sleep timeout in seconds; -1 means never sleep", parseInteger).option("--model <model>", "OpenClaw model identifier", DEFAULT_MODEL).option("--runtime-image <image>", "ML Claw runtime image").option("--public-space", "Create the Hugging Face Space as public instead of private", false).addOption(new Option("--gateway-token <token>").hideHelp()).option("--docker-context <name>", "Docker context for local gateway mode").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if a stale runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (opts) => {
|
|
15298
|
+
program2.command("bootstrap", { isDefault: true }).description("Create or update a Hugging Face OpenClaw deployment").option("--owner <owner>", "Hugging Face user or organization").option("--name <name>", "Agent and runtime resource base name").option("--bucket <owner/bucket>", "State bucket to create or adopt").option("--gateway <local|space>", "Where the live gateway runs").option("--telegram-token <token>", "Optional Telegram bot token").option("--telegram-token-file <path>", "File containing TELEGRAM_BOT_TOKEN=... or a raw token").option("--telegram-user-id <id>", "Allowed Telegram user ID").option("--telegram-api-root <url>", "Telegram API root override").option("--telegram-proxy <url>", "Telegram proxy URL override").option("--hardware <flavor>", "Hugging Face Space hardware flavor").option("--sleep-time <seconds>", "Space sleep timeout in seconds; -1 means never sleep", parseInteger).option("--model <model>", "OpenClaw model identifier", DEFAULT_MODEL).option("--runtime-image <image>", "ML Claw runtime image").option("--bundled-runtime", "Generate a bundled Space runtime instead of using the prebuilt ML Claw image", false).option("--public-space", "Create the Hugging Face Space as public instead of private", false).addOption(new Option("--gateway-token <token>").hideHelp()).option("--docker-context <name>", "Docker context for local gateway mode").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if a stale runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (opts) => {
|
|
15241
15299
|
await bootstrap(opts, runtime);
|
|
15242
15300
|
});
|
|
15243
|
-
program2.command("update").description("Regenerate and upload current ML Claw Space files").argument("<owner/space>", "Hugging Face Space repo ID").option("--runtime-image <image>", "Runtime image to write into the generated Space Dockerfile").option("--force", "Update even if the Space does not look like ML Claw", false).action(async (repoId, opts) => {
|
|
15301
|
+
program2.command("update").description("Regenerate and upload current ML Claw Space files").argument("<owner/space>", "Hugging Face Space repo ID").option("--runtime-image <image>", "Runtime image to write into the generated Space Dockerfile").option("--bundled-runtime", "Generate a bundled Space runtime instead of using the prebuilt ML Claw image", false).option("--force", "Update even if the Space does not look like ML Claw", false).action(async (repoId, opts) => {
|
|
15244
15302
|
const token = await runtime.readToken(runtime.env);
|
|
15245
15303
|
const hub = runtime.hubFactory(token);
|
|
15246
15304
|
await update(repoId, opts, hub, token, runtime);
|
|
@@ -15272,7 +15330,7 @@ function createProgram(runtimeOverrides = {}) {
|
|
|
15272
15330
|
gateway.command("logs").argument("<agent>", "Agent name").option("--tail <lines>", "Number of log lines", parseInteger, 200).action(async (agent, opts) => {
|
|
15273
15331
|
await gatewayLogs(agent, opts, runtime);
|
|
15274
15332
|
});
|
|
15275
|
-
gateway.command("migrate").argument("<agent>", "Agent name").requiredOption("--to <local|space>", "Target gateway location").option("--hardware <flavor>", "Hugging Face Space hardware flavor").option("--sleep-time <seconds>", "Space sleep timeout in seconds; -1 means never sleep", parseInteger).option("--runtime-image <image>", "ML Claw runtime image").option("--public-space", "Create the Hugging Face Space as public instead of private", false).option("--docker-context <name>", "Docker context for local gateway startup when migrating to local").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if another live runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (agent, opts) => {
|
|
15333
|
+
gateway.command("migrate").argument("<agent>", "Agent name").requiredOption("--to <local|space>", "Target gateway location").option("--hardware <flavor>", "Hugging Face Space hardware flavor").option("--sleep-time <seconds>", "Space sleep timeout in seconds; -1 means never sleep", parseInteger).option("--runtime-image <image>", "ML Claw runtime image").option("--bundled-runtime", "Generate a bundled Space runtime instead of using the prebuilt ML Claw image", false).option("--public-space", "Create the Hugging Face Space as public instead of private", false).option("--docker-context <name>", "Docker context for local gateway startup when migrating to local").option("--no-pull", "Do not docker pull before starting a local gateway").option("--takeover", "Start even if another live runtime lease is present", false).option("--yes", "Confirm paid hardware prompts for automation", false).action(async (agent, opts) => {
|
|
15276
15334
|
await gatewayMigrate(agent, opts, runtime);
|
|
15277
15335
|
});
|
|
15278
15336
|
gateway.command("rebind").argument("<agent>", "Agent name").requiredOption("--docker-context <name>", "Target Docker context").option("--no-pull", "Do not docker pull before starting the rebound local gateway").option("--takeover", "Rebind even if the old Docker context is unavailable", false).action(async (agent, opts) => {
|
|
@@ -15307,75 +15365,43 @@ async function bootstrap(opts, runtime) {
|
|
|
15307
15365
|
const owner = opts.owner ?? me2.name;
|
|
15308
15366
|
const telegramToken = await readOptionalTelegramToken(opts, runtime);
|
|
15309
15367
|
const bot = telegramToken ? await runtime.getTelegramBot(telegramToken, opts.telegramApiRoot) : void 0;
|
|
15310
|
-
|
|
15368
|
+
let agentName = slugifyAgentName(opts.name ?? bot?.username ?? await promptAgentName(runtime));
|
|
15311
15369
|
const telegramUserId = telegramToken ? opts.telegramUserId ?? runtime.env.TELEGRAM_ALLOWED_USERS ?? await promptRequired("Telegram allowed user ID", runtime) : void 0;
|
|
15312
|
-
const names = namesFor(owner, agentName);
|
|
15313
15370
|
const model = opts.model ?? DEFAULT_MODEL;
|
|
15314
15371
|
const runtimeImage = resolveRuntimeImage(opts.runtimeImage, runtime.env);
|
|
15315
|
-
const templateRuntimeImage =
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15372
|
+
const templateRuntimeImage = resolveSpaceRuntimeImage(opts, runtime.env);
|
|
15373
|
+
let plan;
|
|
15374
|
+
for (; ; ) {
|
|
15375
|
+
plan = await resolveBootstrapPlan({
|
|
15376
|
+
opts,
|
|
15377
|
+
owner,
|
|
15378
|
+
agentName,
|
|
15379
|
+
hfToken,
|
|
15380
|
+
model,
|
|
15381
|
+
runtimeImage,
|
|
15382
|
+
hub,
|
|
15383
|
+
runtime,
|
|
15384
|
+
...requestedGatewayLocation ? { requestedGatewayLocation } : {},
|
|
15385
|
+
...telegramToken ? { telegramToken } : {},
|
|
15386
|
+
...telegramUserId ? { telegramUserId } : {}
|
|
15387
|
+
});
|
|
15388
|
+
const alternative = await promptAlternativeBootstrapName({
|
|
15389
|
+
plan,
|
|
15390
|
+
explicitBucket: opts.bucket,
|
|
15391
|
+
yes: Boolean(opts.yes),
|
|
15392
|
+
runtime
|
|
15393
|
+
});
|
|
15394
|
+
if (!alternative) {
|
|
15395
|
+
break;
|
|
15396
|
+
}
|
|
15397
|
+
agentName = alternative;
|
|
15323
15398
|
}
|
|
15324
|
-
const
|
|
15325
|
-
const localRuntimeId = existingManifest?.localRuntimeId ?? newLocalRuntimeId(agentName);
|
|
15326
|
-
const localGateway = gatewayLocation === "local" ? await resolveLocalGatewayBinding({
|
|
15327
|
-
manifest: existingManifest,
|
|
15328
|
-
requestedContext: opts.dockerContext,
|
|
15329
|
-
runtime,
|
|
15330
|
-
persist: false
|
|
15331
|
-
}) : existingManifest?.localGateway;
|
|
15332
|
-
const bucket = await resolveBootstrapBucket({
|
|
15333
|
-
explicitBucket: opts.bucket,
|
|
15334
|
-
defaultBucket: names.bucket,
|
|
15335
|
-
existingManifest,
|
|
15336
|
-
bucketPrefix,
|
|
15337
|
-
hub,
|
|
15338
|
-
yes: Boolean(opts.yes),
|
|
15339
|
-
runtime
|
|
15340
|
-
});
|
|
15341
|
-
const manifest = {
|
|
15342
|
-
version: 1,
|
|
15343
|
-
agent: agentName,
|
|
15344
|
-
owner,
|
|
15345
|
-
bucket,
|
|
15346
|
-
space: names.space,
|
|
15347
|
-
localRuntimeId,
|
|
15348
|
-
gatewayLocation,
|
|
15349
|
-
model,
|
|
15350
|
-
runtimeImage,
|
|
15351
|
-
...localGateway ? { localGateway } : {},
|
|
15352
|
-
createdAt: existingManifest?.createdAt ?? now,
|
|
15353
|
-
updatedAt: now
|
|
15354
|
-
};
|
|
15355
|
-
const secrets = deploymentSecrets({
|
|
15356
|
-
hfToken,
|
|
15357
|
-
...telegramToken ? { telegramToken } : {},
|
|
15358
|
-
...telegramUserId ? { telegramUserId } : {},
|
|
15359
|
-
sessionSecret,
|
|
15360
|
-
bucket,
|
|
15361
|
-
model,
|
|
15362
|
-
agentName,
|
|
15363
|
-
runtimeImage,
|
|
15364
|
-
gatewayLocation,
|
|
15365
|
-
runtimeId: gatewayLocation === "local" ? manifest.localRuntimeId : spaceRuntimeId(agentName),
|
|
15366
|
-
...bucketPrefix ? { bucketPrefix } : {},
|
|
15367
|
-
...opts.telegramProxy ? { telegramProxy: opts.telegramProxy } : {},
|
|
15368
|
-
...opts.telegramApiRoot ? { telegramApiRoot: opts.telegramApiRoot } : {}
|
|
15369
|
-
});
|
|
15399
|
+
const { names, gatewayLocation, bucketPlan, bucket, spacePlan, manifest, secrets } = plan;
|
|
15370
15400
|
let deployedSpaceRuntime;
|
|
15371
15401
|
if (gatewayLocation === "space") {
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
bucketPrefix,
|
|
15376
|
-
runtimeId: spaceRuntimeId(agentName),
|
|
15377
|
-
takeover: Boolean(opts.takeover)
|
|
15378
|
-
});
|
|
15402
|
+
if (!spacePlan) {
|
|
15403
|
+
throw new Error("internal error: Space plan was not resolved");
|
|
15404
|
+
}
|
|
15379
15405
|
const paidHardware = await resolveHardware({
|
|
15380
15406
|
requestedHardware: opts.hardware ?? (telegramToken ? TELEGRAM_HARDWARE : DEFAULT_HARDWARE),
|
|
15381
15407
|
...typeof opts.sleepTime === "number" ? { requestedSleepTime: opts.sleepTime } : telegramToken ? { requestedSleepTime: TELEGRAM_SLEEP_TIME } : {},
|
|
@@ -15383,6 +15409,24 @@ async function bootstrap(opts, runtime) {
|
|
|
15383
15409
|
yes: Boolean(opts.yes),
|
|
15384
15410
|
runtime
|
|
15385
15411
|
});
|
|
15412
|
+
await confirmBootstrapPlan({
|
|
15413
|
+
manifest,
|
|
15414
|
+
bucketPlan,
|
|
15415
|
+
spacePlan,
|
|
15416
|
+
hasExistingManifest: plan.hasExistingManifest,
|
|
15417
|
+
hardware: paidHardware.hardware,
|
|
15418
|
+
...typeof paidHardware.sleepTime === "number" ? { sleepTime: paidHardware.sleepTime } : {},
|
|
15419
|
+
yes: Boolean(opts.yes),
|
|
15420
|
+
runtime
|
|
15421
|
+
});
|
|
15422
|
+
await createOrAdoptBucket({ hub, bucketPlan, runtime });
|
|
15423
|
+
await assertNoLiveForeignLease({
|
|
15424
|
+
hub,
|
|
15425
|
+
bucket,
|
|
15426
|
+
bucketPrefix: plan.bucketPrefix,
|
|
15427
|
+
runtimeId: spaceRuntimeId(agentName),
|
|
15428
|
+
takeover: Boolean(opts.takeover)
|
|
15429
|
+
});
|
|
15386
15430
|
const deployed = await deploySpaceGateway({
|
|
15387
15431
|
hub,
|
|
15388
15432
|
runtime,
|
|
@@ -15391,6 +15435,7 @@ async function bootstrap(opts, runtime) {
|
|
|
15391
15435
|
secrets,
|
|
15392
15436
|
allowedUsers: me2.name,
|
|
15393
15437
|
hardware: paidHardware.hardware,
|
|
15438
|
+
spaceExists: spacePlan.exists,
|
|
15394
15439
|
publicSpace: Boolean(opts.publicSpace),
|
|
15395
15440
|
...typeof paidHardware.sleepTime === "number" ? { sleepTime: paidHardware.sleepTime } : {},
|
|
15396
15441
|
...templateRuntimeImage ? { templateRuntimeImage } : {}
|
|
@@ -15398,10 +15443,19 @@ async function bootstrap(opts, runtime) {
|
|
|
15398
15443
|
deployedSpaceRuntime = deployed.runtimeImage;
|
|
15399
15444
|
await writeLocalDeployment(runtime.configRoot, manifest, secrets);
|
|
15400
15445
|
} else {
|
|
15446
|
+
await confirmBootstrapPlan({
|
|
15447
|
+
manifest,
|
|
15448
|
+
bucketPlan,
|
|
15449
|
+
hasExistingManifest: plan.hasExistingManifest,
|
|
15450
|
+
hardware: "local Docker",
|
|
15451
|
+
yes: Boolean(opts.yes),
|
|
15452
|
+
runtime
|
|
15453
|
+
});
|
|
15454
|
+
await createOrAdoptBucket({ hub, bucketPlan, runtime });
|
|
15401
15455
|
await assertNoLiveForeignLease({
|
|
15402
15456
|
hub,
|
|
15403
15457
|
bucket,
|
|
15404
|
-
bucketPrefix,
|
|
15458
|
+
bucketPrefix: plan.bucketPrefix,
|
|
15405
15459
|
runtimeId: manifest.localRuntimeId,
|
|
15406
15460
|
takeover: Boolean(opts.takeover)
|
|
15407
15461
|
});
|
|
@@ -15417,6 +15471,7 @@ async function bootstrap(opts, runtime) {
|
|
|
15417
15471
|
runtime.stdout.log(`Bucket: https://huggingface.co/buckets/${bucket}`);
|
|
15418
15472
|
if (gatewayLocation === "space") {
|
|
15419
15473
|
runtime.stdout.log(`Space: https://huggingface.co/spaces/${names.space}`);
|
|
15474
|
+
runtime.stdout.log(`Agent URL: ${spacePublicUrl(names.space)}`);
|
|
15420
15475
|
} else {
|
|
15421
15476
|
runtime.stdout.log(`Local: ${containerNameFor(agentName)}`);
|
|
15422
15477
|
}
|
|
@@ -15429,29 +15484,163 @@ async function bootstrap(opts, runtime) {
|
|
|
15429
15484
|
if (deployedSpaceRuntime) {
|
|
15430
15485
|
runtime.stdout.log(`Space runtime: ${deployedSpaceRuntime}`);
|
|
15431
15486
|
}
|
|
15432
|
-
runtime.prompt.outro(gatewayLocation === "space" ?
|
|
15487
|
+
runtime.prompt.outro(gatewayLocation === "space" ? `Restart requested. Your agent will soon be available at ${spacePublicUrl(names.space)}.` : "Local gateway start requested.");
|
|
15488
|
+
}
|
|
15489
|
+
function spacePublicUrl(repoId) {
|
|
15490
|
+
return `https://${repoId.replace("/", "-")}.hf.space`;
|
|
15491
|
+
}
|
|
15492
|
+
async function resolveBootstrapPlan(params) {
|
|
15493
|
+
const { opts, owner, agentName, requestedGatewayLocation, hfToken, telegramToken, telegramUserId, model, runtimeImage, hub, runtime } = params;
|
|
15494
|
+
const names = namesFor(owner, agentName);
|
|
15495
|
+
const sessionSecret = randomBytes(48).toString("base64url");
|
|
15496
|
+
const now = runtime.now().toISOString();
|
|
15497
|
+
const existingManifest = await readManifest(runtime.configRoot, agentName).catch(() => null);
|
|
15498
|
+
const existingSecrets = await readSecretEnv(runtime.configRoot, agentName).catch(() => ({}));
|
|
15499
|
+
const gatewayLocation = requestedGatewayLocation ?? existingManifest?.gatewayLocation ?? DEFAULT_GATEWAY_LOCATION;
|
|
15500
|
+
if (opts.dockerContext && gatewayLocation !== "local") {
|
|
15501
|
+
throw new Error("--docker-context only applies to local gateway mode");
|
|
15502
|
+
}
|
|
15503
|
+
const bucketPrefix = bootstrapBucketPrefix(existingManifest, existingSecrets, runtime);
|
|
15504
|
+
const localRuntimeId = existingManifest?.localRuntimeId ?? newLocalRuntimeId(agentName);
|
|
15505
|
+
const localGateway = gatewayLocation === "local" ? await resolveLocalGatewayBinding({
|
|
15506
|
+
manifest: existingManifest,
|
|
15507
|
+
requestedContext: opts.dockerContext,
|
|
15508
|
+
runtime,
|
|
15509
|
+
persist: false
|
|
15510
|
+
}) : existingManifest?.localGateway;
|
|
15511
|
+
const bucketPlan = await resolveBootstrapBucket({
|
|
15512
|
+
explicitBucket: opts.bucket,
|
|
15513
|
+
defaultBucket: names.bucket,
|
|
15514
|
+
existingManifest,
|
|
15515
|
+
bucketPrefix,
|
|
15516
|
+
hub
|
|
15517
|
+
});
|
|
15518
|
+
const bucket = bucketPlan.bucket;
|
|
15519
|
+
const spacePlan = gatewayLocation === "space" ? {
|
|
15520
|
+
space: names.space,
|
|
15521
|
+
exists: await hub.spaceExists(names.space),
|
|
15522
|
+
visibility: opts.publicSpace ? "public" : "private"
|
|
15523
|
+
} : void 0;
|
|
15524
|
+
const manifest = {
|
|
15525
|
+
version: 1,
|
|
15526
|
+
agent: agentName,
|
|
15527
|
+
owner,
|
|
15528
|
+
bucket,
|
|
15529
|
+
space: names.space,
|
|
15530
|
+
localRuntimeId,
|
|
15531
|
+
gatewayLocation,
|
|
15532
|
+
model,
|
|
15533
|
+
runtimeImage,
|
|
15534
|
+
...localGateway ? { localGateway } : {},
|
|
15535
|
+
createdAt: existingManifest?.createdAt ?? now,
|
|
15536
|
+
updatedAt: now
|
|
15537
|
+
};
|
|
15538
|
+
const secrets = deploymentSecrets({
|
|
15539
|
+
hfToken,
|
|
15540
|
+
...telegramToken ? { telegramToken } : {},
|
|
15541
|
+
...telegramUserId ? { telegramUserId } : {},
|
|
15542
|
+
sessionSecret,
|
|
15543
|
+
bucket,
|
|
15544
|
+
model,
|
|
15545
|
+
agentName,
|
|
15546
|
+
runtimeImage,
|
|
15547
|
+
gatewayLocation,
|
|
15548
|
+
runtimeId: gatewayLocation === "local" ? manifest.localRuntimeId : spaceRuntimeId(agentName),
|
|
15549
|
+
...bucketPrefix ? { bucketPrefix } : {},
|
|
15550
|
+
...opts.telegramProxy ? { telegramProxy: opts.telegramProxy } : {},
|
|
15551
|
+
...opts.telegramApiRoot ? { telegramApiRoot: opts.telegramApiRoot } : {}
|
|
15552
|
+
});
|
|
15553
|
+
return {
|
|
15554
|
+
agentName,
|
|
15555
|
+
names,
|
|
15556
|
+
hasExistingManifest: Boolean(existingManifest),
|
|
15557
|
+
gatewayLocation,
|
|
15558
|
+
...bucketPrefix ? { bucketPrefix } : {},
|
|
15559
|
+
bucketPlan,
|
|
15560
|
+
bucket,
|
|
15561
|
+
...spacePlan ? { spacePlan } : {},
|
|
15562
|
+
manifest,
|
|
15563
|
+
secrets
|
|
15564
|
+
};
|
|
15433
15565
|
}
|
|
15434
15566
|
async function resolveBootstrapBucket(params) {
|
|
15435
15567
|
const explicitBucket = params.explicitBucket ? parseBucketId(params.explicitBucket) : void 0;
|
|
15436
15568
|
const bucket = explicitBucket ?? params.existingManifest?.bucket ?? params.defaultBucket;
|
|
15437
|
-
params.
|
|
15438
|
-
await params.hub
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15569
|
+
const exists = await params.hub.bucketExists(bucket);
|
|
15570
|
+
const inspection = exists ? await inspectStateBucket(params.hub, bucket, params.bucketPrefix) : { objectCount: 0 };
|
|
15571
|
+
return {
|
|
15572
|
+
bucket,
|
|
15573
|
+
exists,
|
|
15574
|
+
objectCount: inspection.objectCount
|
|
15575
|
+
};
|
|
15576
|
+
}
|
|
15577
|
+
async function promptAlternativeBootstrapName(params) {
|
|
15578
|
+
const existingDefaultBucket = !params.explicitBucket && params.plan.bucketPlan.exists;
|
|
15579
|
+
const existingSpace = params.plan.spacePlan?.exists === true;
|
|
15580
|
+
if (!existingDefaultBucket && !existingSpace || params.yes || !params.runtime.prompt.isInteractive()) {
|
|
15581
|
+
return void 0;
|
|
15582
|
+
}
|
|
15583
|
+
const current = params.plan.agentName;
|
|
15584
|
+
const suggestion = `${current}-2`;
|
|
15585
|
+
params.runtime.prompt.note(
|
|
15586
|
+
`The name ${current} maps to existing ML Claw resources. Enter another name for a fresh deployment, or leave this blank to update the existing one.`,
|
|
15587
|
+
"Existing resources"
|
|
15588
|
+
);
|
|
15589
|
+
const value = await params.runtime.prompt.text({
|
|
15590
|
+
message: "Alternative agent name",
|
|
15591
|
+
placeholder: suggestion
|
|
15592
|
+
});
|
|
15593
|
+
if (q(value)) {
|
|
15594
|
+
params.runtime.prompt.cancel("Cancelled");
|
|
15595
|
+
throw new Error("cancelled");
|
|
15596
|
+
}
|
|
15597
|
+
const raw = value.trim();
|
|
15598
|
+
if (!raw) {
|
|
15599
|
+
return void 0;
|
|
15600
|
+
}
|
|
15601
|
+
const alternative = slugifyAgentName(raw);
|
|
15602
|
+
return alternative === current ? void 0 : alternative;
|
|
15603
|
+
}
|
|
15604
|
+
async function confirmBootstrapPlan(params) {
|
|
15605
|
+
const lines = [
|
|
15606
|
+
`Agent: ${params.manifest.agent}`,
|
|
15607
|
+
`Gateway: ${params.manifest.gatewayLocation}`,
|
|
15608
|
+
`Bucket: ${params.bucketPlan.bucket} (${params.bucketPlan.exists ? `exists; keeping ${params.bucketPlan.objectCount} object(s)` : "will be created as private"})`
|
|
15609
|
+
];
|
|
15610
|
+
if (params.spacePlan) {
|
|
15611
|
+
lines.push(`Space: ${params.spacePlan.space} (${params.spacePlan.exists ? "exists; files, variables, secrets, and runtime will be updated" : `will be created as ${params.spacePlan.visibility}`})`);
|
|
15612
|
+
lines.push(`Hardware: ${params.hardware}`);
|
|
15613
|
+
if (typeof params.sleepTime === "number") {
|
|
15614
|
+
lines.push(`Sleep time: ${params.sleepTime}`);
|
|
15615
|
+
}
|
|
15616
|
+
} else {
|
|
15617
|
+
lines.push(`Local runtime: ${containerNameFor(params.manifest.agent)} (${params.hardware})`);
|
|
15618
|
+
}
|
|
15619
|
+
if (params.bucketPlan.exists || params.spacePlan?.exists) {
|
|
15620
|
+
lines.push(`Fresh deployment: use a different name, for example --name ${params.manifest.agent}-2`);
|
|
15621
|
+
}
|
|
15622
|
+
lines.push(`Model: ${params.manifest.model}`);
|
|
15623
|
+
lines.push(`Runtime image: ${params.manifest.runtimeImage}`);
|
|
15624
|
+
params.runtime.prompt.note(lines.join("\n"), "Bootstrap plan");
|
|
15625
|
+
if (params.yes) {
|
|
15626
|
+
return;
|
|
15627
|
+
}
|
|
15628
|
+
if (!params.runtime.prompt.isInteractive()) {
|
|
15629
|
+
throw new Error("bootstrap confirmation required. Pass --yes to continue non-interactively.");
|
|
15630
|
+
}
|
|
15631
|
+
const ok = await promptConfirm("Continue with this bootstrap plan?", true, params.runtime);
|
|
15632
|
+
if (!ok) {
|
|
15633
|
+
throw new Error("bootstrap was not confirmed");
|
|
15453
15634
|
}
|
|
15454
|
-
|
|
15635
|
+
}
|
|
15636
|
+
async function createOrAdoptBucket(params) {
|
|
15637
|
+
if (params.bucketPlan.exists) {
|
|
15638
|
+
params.runtime.stdout.log(`Using existing private bucket ${params.bucketPlan.bucket}`);
|
|
15639
|
+
return;
|
|
15640
|
+
} else {
|
|
15641
|
+
params.runtime.stdout.log(`Creating private bucket ${params.bucketPlan.bucket}`);
|
|
15642
|
+
}
|
|
15643
|
+
await params.hub.createBucket(params.bucketPlan.bucket, true);
|
|
15455
15644
|
}
|
|
15456
15645
|
async function stateAdopt(agent, opts, runtime) {
|
|
15457
15646
|
const bucket = parseBucketId(requiredOption(opts.bucket, "--bucket"));
|
|
@@ -15622,14 +15811,14 @@ async function writeLocalDeployment(configRoot2, manifest, secrets) {
|
|
|
15622
15811
|
}
|
|
15623
15812
|
async function deploySpaceGateway(params) {
|
|
15624
15813
|
const { hub, runtime, hfToken, manifest, secrets } = params;
|
|
15625
|
-
runtime.stdout.log(`Creating ${params.publicSpace ? "public" : "private"} Space ${manifest.space}`);
|
|
15814
|
+
runtime.stdout.log(params.spaceExists ? `Updating existing Space ${manifest.space}` : `Creating ${params.publicSpace ? "public" : "private"} Space ${manifest.space}`);
|
|
15626
15815
|
await hub.createDockerSpace(manifest.space, {
|
|
15627
15816
|
private: !params.publicSpace,
|
|
15628
15817
|
hardware: params.hardware,
|
|
15629
15818
|
...typeof params.sleepTime === "number" ? { sleepTimeSeconds: params.sleepTime } : {}
|
|
15630
15819
|
});
|
|
15631
15820
|
await hub.requestSpaceHardware(manifest.space, params.hardware, params.sleepTime);
|
|
15632
|
-
runtime.stdout.log(params.templateRuntimeImage ? "Generating Space files from
|
|
15821
|
+
runtime.stdout.log(params.templateRuntimeImage ? "Generating Space files from prebuilt runtime image" : "Generating bundled Space runtime files");
|
|
15633
15822
|
const { templateRev } = await runtime.pushTemplateToSpace({
|
|
15634
15823
|
targetRepo: manifest.space,
|
|
15635
15824
|
token: hfToken,
|
|
@@ -15830,7 +16019,7 @@ async function gatewayMigrate(agent, opts, runtime) {
|
|
|
15830
16019
|
});
|
|
15831
16020
|
await handoffAndStopLocalGateway({ manifest: current, hub, runtime, bucketPrefix });
|
|
15832
16021
|
const me2 = await hub.whoami();
|
|
15833
|
-
const templateRuntimeImage =
|
|
16022
|
+
const templateRuntimeImage = resolveSpaceRuntimeImage(opts, runtime.env);
|
|
15834
16023
|
await deploySpaceGateway({
|
|
15835
16024
|
hub,
|
|
15836
16025
|
runtime,
|
|
@@ -16179,10 +16368,11 @@ function shouldPull(opts) {
|
|
|
16179
16368
|
}
|
|
16180
16369
|
async function update(repoId, opts, hub, hfToken, runtime) {
|
|
16181
16370
|
const variables = await hub.getSpaceVariables(repoId);
|
|
16182
|
-
|
|
16371
|
+
const canonicalTemplate = isCanonicalTemplateSpace(repoId, runtime.env);
|
|
16372
|
+
if (!canonicalTemplate && !variables.has("MLCLAW_TEMPLATE_REV") && !variables.has("OPENCLAW_HF_TEMPLATE_REV") && !opts.force) {
|
|
16183
16373
|
throw new Error(`${repoId} does not look like a ML Claw deployment; pass --force to update anyway`);
|
|
16184
16374
|
}
|
|
16185
|
-
const runtimeImage =
|
|
16375
|
+
const runtimeImage = resolveSpaceRuntimeImage(opts, runtime.env);
|
|
16186
16376
|
const agentName = variables.get("OPENCLAW_AGENT_NAME")?.value?.trim() || repoId.split("/")[1] || "openclaw";
|
|
16187
16377
|
runtime.stdout.log(`Generating current Space files into ${repoId}`);
|
|
16188
16378
|
const { templateRev } = await runtime.pushTemplateToSpace({
|
|
@@ -16192,6 +16382,12 @@ async function update(repoId, opts, hub, hfToken, runtime) {
|
|
|
16192
16382
|
});
|
|
16193
16383
|
await hub.addSpaceVariable(repoId, "MLCLAW_TEMPLATE_REV", templateRev);
|
|
16194
16384
|
await hub.addSpaceVariable(repoId, "MLCLAW_RUNTIME_IMAGE", runtimeImage ?? bundledSpaceRuntimeRef(templateRev));
|
|
16385
|
+
if (canonicalTemplate) {
|
|
16386
|
+
await hub.addSpaceVariable(repoId, "MLCLAW_CANONICAL_SPACE_ID", canonicalTemplateSpaceId(runtime.env));
|
|
16387
|
+
await doctor(repoId, { fix: true }, hub, runtime);
|
|
16388
|
+
await hub.restartSpace(repoId, true);
|
|
16389
|
+
return;
|
|
16390
|
+
}
|
|
16195
16391
|
await hub.addSpaceVariable(repoId, "MLCLAW_GATEWAY_LOCATION", "space");
|
|
16196
16392
|
await hub.addSpaceVariable(repoId, "MLCLAW_RUNTIME_ID", spaceRuntimeId(agentName));
|
|
16197
16393
|
await hub.addSpaceVariable(repoId, "MLCLAW_OPENCLAW_PORT", String(DEFAULT_SPACE_OPENCLAW_PORT));
|
|
@@ -16209,6 +16405,42 @@ async function doctor(repoId, opts, hub, runtime) {
|
|
|
16209
16405
|
const secrets = await hub.getSpaceSecrets(repoId);
|
|
16210
16406
|
const issues = [];
|
|
16211
16407
|
const fixed = [];
|
|
16408
|
+
const canonicalTemplate = isCanonicalTemplateSpace(repoId, runtime.env);
|
|
16409
|
+
if (canonicalTemplate) {
|
|
16410
|
+
const expectedCanonicalSpace = canonicalTemplateSpaceId(runtime.env);
|
|
16411
|
+
if ((variables.get("MLCLAW_CANONICAL_SPACE_ID")?.value ?? "") !== expectedCanonicalSpace) {
|
|
16412
|
+
if (fix) {
|
|
16413
|
+
await hub.addSpaceVariable(repoId, "MLCLAW_CANONICAL_SPACE_ID", expectedCanonicalSpace);
|
|
16414
|
+
fixed.push("set MLCLAW_CANONICAL_SPACE_ID");
|
|
16415
|
+
} else {
|
|
16416
|
+
issues.push(`MLCLAW_CANONICAL_SPACE_ID is not ${expectedCanonicalSpace}`);
|
|
16417
|
+
}
|
|
16418
|
+
}
|
|
16419
|
+
if (!variables.has("MLCLAW_TEMPLATE_REV") && !variables.has("OPENCLAW_HF_TEMPLATE_REV")) {
|
|
16420
|
+
issues.push("MLCLAW_TEMPLATE_REV is missing; run `mlclaw update` to refresh the template Space");
|
|
16421
|
+
}
|
|
16422
|
+
addRuntimeImageFindings(variables.get("MLCLAW_RUNTIME_IMAGE")?.value, issues);
|
|
16423
|
+
const runtimeInfo2 = await hub.getSpaceRuntime(repoId);
|
|
16424
|
+
runtime.stdout.log(`Space: ${repoId}`);
|
|
16425
|
+
runtime.stdout.log("Mode: template");
|
|
16426
|
+
runtime.stdout.log(`Stage: ${runtimeInfo2.stage ?? "unknown"}`);
|
|
16427
|
+
runtime.stdout.log(`Hardware: ${formatRuntimeValue(runtimeInfo2.requested_hardware ?? runtimeInfo2.hardware)}`);
|
|
16428
|
+
if (typeof runtimeInfo2.sleep_time === "number") {
|
|
16429
|
+
runtime.stdout.log(`Sleep time: ${runtimeInfo2.sleep_time}`);
|
|
16430
|
+
}
|
|
16431
|
+
if (fixed.length > 0) {
|
|
16432
|
+
runtime.stdout.log(`Fixed: ${fixed.join(", ")}`);
|
|
16433
|
+
}
|
|
16434
|
+
if (issues.length === 0) {
|
|
16435
|
+
runtime.stdout.log("Doctor: clean");
|
|
16436
|
+
} else {
|
|
16437
|
+
runtime.stdout.log("Doctor findings:");
|
|
16438
|
+
for (const issue of issues) {
|
|
16439
|
+
runtime.stdout.log(`- ${issue}`);
|
|
16440
|
+
}
|
|
16441
|
+
}
|
|
16442
|
+
return;
|
|
16443
|
+
}
|
|
16212
16444
|
const bucket = variables.get("OPENCLAW_HF_STATE_BUCKET")?.value ?? opts.bucket;
|
|
16213
16445
|
let signedInUser;
|
|
16214
16446
|
const currentUsername = async () => {
|
|
@@ -16248,9 +16480,7 @@ async function doctor(repoId, opts, hub, runtime) {
|
|
|
16248
16480
|
if ((variables.get("MLCLAW_GATEWAY_LOCATION")?.value ?? "") !== "space") {
|
|
16249
16481
|
issues.push("MLCLAW_GATEWAY_LOCATION is not set to space");
|
|
16250
16482
|
}
|
|
16251
|
-
|
|
16252
|
-
issues.push("MLCLAW_RUNTIME_IMAGE is missing");
|
|
16253
|
-
}
|
|
16483
|
+
addRuntimeImageFindings(variables.get("MLCLAW_RUNTIME_IMAGE")?.value, issues);
|
|
16254
16484
|
if ((variables.get("MLCLAW_OPENCLAW_PORT")?.value ?? "") !== String(DEFAULT_SPACE_OPENCLAW_PORT) && fix) {
|
|
16255
16485
|
await hub.addSpaceVariable(repoId, "MLCLAW_OPENCLAW_PORT", String(DEFAULT_SPACE_OPENCLAW_PORT));
|
|
16256
16486
|
fixed.push("set MLCLAW_OPENCLAW_PORT");
|
|
@@ -16315,6 +16545,26 @@ async function doctor(repoId, opts, hub, runtime) {
|
|
|
16315
16545
|
}
|
|
16316
16546
|
}
|
|
16317
16547
|
}
|
|
16548
|
+
function canonicalTemplateSpaceId(env) {
|
|
16549
|
+
return nonEmpty(env.MLCLAW_CANONICAL_SPACE_ID) ?? DEFAULT_CANONICAL_TEMPLATE_SPACE;
|
|
16550
|
+
}
|
|
16551
|
+
function isCanonicalTemplateSpace(repoId, env) {
|
|
16552
|
+
return repoId === canonicalTemplateSpaceId(env);
|
|
16553
|
+
}
|
|
16554
|
+
function addRuntimeImageFindings(value, issues) {
|
|
16555
|
+
const runtimeImage = value?.trim();
|
|
16556
|
+
if (!runtimeImage) {
|
|
16557
|
+
issues.push("MLCLAW_RUNTIME_IMAGE is missing; run `mlclaw update` to refresh the Space runtime");
|
|
16558
|
+
return;
|
|
16559
|
+
}
|
|
16560
|
+
if (runtimeImage.startsWith("ghcr.io/osolmaz/mlclaw-runtime:")) {
|
|
16561
|
+
issues.push(`MLCLAW_RUNTIME_IMAGE points at the legacy mlclaw-runtime package; run \`mlclaw update\` to use ${DEFAULT_RUNTIME_IMAGE}`);
|
|
16562
|
+
return;
|
|
16563
|
+
}
|
|
16564
|
+
if (runtimeImage.startsWith("bundled:")) {
|
|
16565
|
+
issues.push(`MLCLAW_RUNTIME_IMAGE uses a bundled runtime; run \`mlclaw update\` to use ${DEFAULT_RUNTIME_IMAGE}`);
|
|
16566
|
+
}
|
|
16567
|
+
}
|
|
16318
16568
|
async function settings(repoId, opts, hub, runtime) {
|
|
16319
16569
|
if (opts.gateway) {
|
|
16320
16570
|
throw new Error("gateway location changes must use `mlclaw gateway migrate` to preserve state");
|
|
@@ -16391,7 +16641,7 @@ async function promptAgentName(runtime) {
|
|
|
16391
16641
|
}
|
|
16392
16642
|
const value = await runtime.prompt.text({
|
|
16393
16643
|
message: "Agent name",
|
|
16394
|
-
placeholder: "
|
|
16644
|
+
placeholder: "mlclaw",
|
|
16395
16645
|
initialValue: "mlclaw"
|
|
16396
16646
|
});
|
|
16397
16647
|
return readPromptValue(value, "Agent name");
|