mlclaw 0.3.6 → 0.3.8

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/Dockerfile CHANGED
@@ -2,7 +2,7 @@ ARG OPENCLAW_VERSION=2026.7.1
2
2
  ARG OPENCLAW_BASE_IMAGE=ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION}
3
3
  ARG BROKERKIT_PLUGIN_VERSION=0.2.1
4
4
  ARG BROKERKIT_VERSION=hf-broker/v0.1.0
5
- ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.3.6-openclaw-2026.7.1
5
+ ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.3.8-openclaw-2026.7.1
6
6
 
7
7
  FROM golang:1.26.5-bookworm AS hf-broker-build
8
8
  ARG BROKERKIT_VERSION
package/README.md CHANGED
@@ -40,8 +40,12 @@ irm https://raw.githubusercontent.com/osolmaz/mlclaw/main/mlclaw.ps1 | iex
40
40
  ```
41
41
 
42
42
  You need a Hugging Face account and a token available through `HF_TOKEN`,
43
- `HF_TOKEN_PATH`, `$HF_HOME/token`, or `hf auth login`. You never paste that
44
- token into someone else's app; the bootstrapper runs locally.
43
+ `HF_TOKEN_PATH`, `$HF_HOME/token`, or `hf auth login`. If an interactive
44
+ bootstrap cannot find a token, it offers to install the official Hugging Face
45
+ CLI, asks whether you need to create an account, opens the appropriate browser
46
+ flow, and resumes after sign-in. Non-interactive runs never install software and
47
+ still require a token up front. You never paste that token into someone else's
48
+ app; the bootstrapper runs locally.
45
49
 
46
50
  ML Claw places the active Hugging Face token behind an in-container HF Broker.
47
51
  The broker owns the real token; OpenClaw receives only a separate agent
package/dist/mlclaw.mjs CHANGED
@@ -1202,8 +1202,8 @@ var require_command = __commonJS({
1202
1202
  "node_modules/commander/lib/command.js"(exports) {
1203
1203
  var EventEmitter = __require("node:events").EventEmitter;
1204
1204
  var childProcess = __require("node:child_process");
1205
- var path15 = __require("node:path");
1206
- var fs15 = __require("node:fs");
1205
+ var path16 = __require("node:path");
1206
+ var fs16 = __require("node:fs");
1207
1207
  var process5 = __require("node:process");
1208
1208
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1209
1209
  var { CommanderError: CommanderError2 } = require_error();
@@ -2197,7 +2197,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2197
2197
  * @param {string} subcommandName
2198
2198
  */
2199
2199
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2200
- if (fs15.existsSync(executableFile)) return;
2200
+ if (fs16.existsSync(executableFile)) return;
2201
2201
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2202
2202
  const executableMissing = `'${executableFile}' does not exist
2203
2203
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2215,11 +2215,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2215
2215
  let launchWithNode = false;
2216
2216
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2217
2217
  function findFile(baseDir, baseName) {
2218
- const localBin = path15.resolve(baseDir, baseName);
2219
- if (fs15.existsSync(localBin)) return localBin;
2220
- if (sourceExt.includes(path15.extname(baseName))) return void 0;
2218
+ const localBin = path16.resolve(baseDir, baseName);
2219
+ if (fs16.existsSync(localBin)) return localBin;
2220
+ if (sourceExt.includes(path16.extname(baseName))) return void 0;
2221
2221
  const foundExt = sourceExt.find(
2222
- (ext) => fs15.existsSync(`${localBin}${ext}`)
2222
+ (ext) => fs16.existsSync(`${localBin}${ext}`)
2223
2223
  );
2224
2224
  if (foundExt) return `${localBin}${foundExt}`;
2225
2225
  return void 0;
@@ -2231,21 +2231,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2231
2231
  if (this._scriptPath) {
2232
2232
  let resolvedScriptPath;
2233
2233
  try {
2234
- resolvedScriptPath = fs15.realpathSync(this._scriptPath);
2234
+ resolvedScriptPath = fs16.realpathSync(this._scriptPath);
2235
2235
  } catch {
2236
2236
  resolvedScriptPath = this._scriptPath;
2237
2237
  }
2238
- executableDir = path15.resolve(
2239
- path15.dirname(resolvedScriptPath),
2238
+ executableDir = path16.resolve(
2239
+ path16.dirname(resolvedScriptPath),
2240
2240
  executableDir
2241
2241
  );
2242
2242
  }
2243
2243
  if (executableDir) {
2244
2244
  let localFile = findFile(executableDir, executableFile);
2245
2245
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2246
- const legacyName = path15.basename(
2246
+ const legacyName = path16.basename(
2247
2247
  this._scriptPath,
2248
- path15.extname(this._scriptPath)
2248
+ path16.extname(this._scriptPath)
2249
2249
  );
2250
2250
  if (legacyName !== this._name) {
2251
2251
  localFile = findFile(
@@ -2256,7 +2256,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2256
2256
  }
2257
2257
  executableFile = localFile || executableFile;
2258
2258
  }
2259
- launchWithNode = sourceExt.includes(path15.extname(executableFile));
2259
+ launchWithNode = sourceExt.includes(path16.extname(executableFile));
2260
2260
  let proc;
2261
2261
  if (process5.platform !== "win32") {
2262
2262
  if (launchWithNode) {
@@ -3171,7 +3171,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3171
3171
  * @return {Command}
3172
3172
  */
3173
3173
  nameFromFilename(filename) {
3174
- this._name = path15.basename(filename, path15.extname(filename));
3174
+ this._name = path16.basename(filename, path16.extname(filename));
3175
3175
  return this;
3176
3176
  }
3177
3177
  /**
@@ -3185,9 +3185,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3185
3185
  * @param {string} [path]
3186
3186
  * @return {(string|null|Command)}
3187
3187
  */
3188
- executableDir(path16) {
3189
- if (path16 === void 0) return this._executableDir;
3190
- this._executableDir = path16;
3188
+ executableDir(path17) {
3189
+ if (path17 === void 0) return this._executableDir;
3190
+ this._executableDir = path17;
3191
3191
  return this;
3192
3192
  }
3193
3193
  /**
@@ -8952,7 +8952,7 @@ var init_cli = __esm({
8952
8952
  });
8953
8953
 
8954
8954
  // src/mlclaw/cli.ts
8955
- import fs14 from "node:fs/promises";
8955
+ import fs15 from "node:fs/promises";
8956
8956
  import { realpathSync } from "node:fs";
8957
8957
  import process4 from "node:process";
8958
8958
  import { randomBytes } from "node:crypto";
@@ -9587,9 +9587,103 @@ async function handleSkillflag(argv, opts) {
9587
9587
  }
9588
9588
 
9589
9589
  // src/mlclaw/auth.ts
9590
- import fs10 from "node:fs/promises";
9590
+ import fs11 from "node:fs/promises";
9591
+ import os4 from "node:os";
9592
+ import path12 from "node:path";
9593
+
9594
+ // src/mlclaw/hf-cli.ts
9595
+ import { spawn } from "node:child_process";
9596
+ import fs10, { constants as fsConstants } from "node:fs/promises";
9591
9597
  import os3 from "node:os";
9592
9598
  import path11 from "node:path";
9599
+ var HF_CLI_INSTALL_URL = "https://hf.co/cli/install.sh";
9600
+ var HF_ACCOUNT_CREATE_URL = "https://huggingface.co/join";
9601
+ var HF_CLI_INSTALL_COMMAND = `curl -LsSf ${HF_CLI_INSTALL_URL} | bash`;
9602
+ function createSystemHfCli(env = process.env) {
9603
+ return {
9604
+ findExecutable: async () => await findHfExecutable(env),
9605
+ install: async () => await installHfCli(env),
9606
+ login: async (executable) => await runInherited(executable, ["auth", "login"], env),
9607
+ openUrl: async (url) => await openUrl(url, env)
9608
+ };
9609
+ }
9610
+ async function findHfExecutable(env) {
9611
+ const fromPath = await hfCommandPath(env);
9612
+ if (fromPath) {
9613
+ return fromPath;
9614
+ }
9615
+ const home = env.HOME || os3.homedir();
9616
+ const candidates = [
9617
+ env.HF_CLI_BIN_DIR && path11.join(env.HF_CLI_BIN_DIR, "hf"),
9618
+ path11.join(home, ".local", "bin", "hf")
9619
+ ].filter((candidate) => Boolean(candidate));
9620
+ for (const candidate of candidates) {
9621
+ try {
9622
+ await fs10.access(candidate, fsConstants.X_OK);
9623
+ return candidate;
9624
+ } catch {
9625
+ }
9626
+ }
9627
+ return void 0;
9628
+ }
9629
+ async function hfCommandPath(env) {
9630
+ return await new Promise((resolve) => {
9631
+ const child = spawn("sh", ["-c", "command -v hf"], {
9632
+ env,
9633
+ stdio: ["ignore", "pipe", "ignore"]
9634
+ });
9635
+ let output = "";
9636
+ child.stdout?.setEncoding("utf8");
9637
+ child.stdout?.on("data", (chunk) => {
9638
+ output += chunk;
9639
+ });
9640
+ child.once("error", () => resolve(void 0));
9641
+ child.once("close", (code) => resolve(code === 0 ? output.trim() || void 0 : void 0));
9642
+ });
9643
+ }
9644
+ async function installHfCli(env) {
9645
+ if (process.platform !== "darwin" && process.platform !== "linux") {
9646
+ throw new Error(`automatic Hugging Face CLI installation is not supported on ${process.platform}`);
9647
+ }
9648
+ const response = await fetch(HF_CLI_INSTALL_URL, { redirect: "follow" });
9649
+ if (!response.ok) {
9650
+ throw new Error(`failed to download the Hugging Face CLI installer: HTTP ${response.status}`);
9651
+ }
9652
+ const temporaryDirectory = await fs10.mkdtemp(path11.join(os3.tmpdir(), "mlclaw-hf-cli-"));
9653
+ const installerPath = path11.join(temporaryDirectory, "install.sh");
9654
+ try {
9655
+ await fs10.writeFile(installerPath, await response.text(), { mode: 448 });
9656
+ await runInherited("bash", [installerPath], env);
9657
+ } finally {
9658
+ await fs10.rm(temporaryDirectory, { recursive: true, force: true });
9659
+ }
9660
+ }
9661
+ async function runInherited(command, args, env) {
9662
+ await new Promise((resolve, reject) => {
9663
+ const child = spawn(command, args, { env, stdio: "inherit" });
9664
+ child.once("error", reject);
9665
+ child.once("close", (code, signal) => {
9666
+ if (code === 0) {
9667
+ resolve();
9668
+ return;
9669
+ }
9670
+ reject(new Error(`${command} exited ${signal ? `after signal ${signal}` : `with status ${code ?? "unknown"}`}`));
9671
+ });
9672
+ });
9673
+ }
9674
+ async function openUrl(url, env) {
9675
+ const command = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : void 0;
9676
+ if (!command) {
9677
+ return false;
9678
+ }
9679
+ return await new Promise((resolve) => {
9680
+ const child = spawn(command, [url], { env, stdio: "ignore" });
9681
+ child.once("error", () => resolve(false));
9682
+ child.once("close", (code) => resolve(code === 0));
9683
+ });
9684
+ }
9685
+
9686
+ // src/mlclaw/auth.ts
9593
9687
  async function readToken(env = process.env) {
9594
9688
  const fromEnv = env.HF_TOKEN?.trim();
9595
9689
  if (fromEnv) {
@@ -9597,13 +9691,13 @@ async function readToken(env = process.env) {
9597
9691
  }
9598
9692
  const candidates = [
9599
9693
  env.HF_TOKEN_PATH,
9600
- env.HF_HOME && path11.join(env.HF_HOME, "token"),
9601
- path11.join(os3.homedir(), ".cache", "huggingface", "token"),
9602
- path11.join(os3.homedir(), ".huggingface", "token")
9694
+ env.HF_HOME && path12.join(env.HF_HOME, "token"),
9695
+ path12.join(os4.homedir(), ".cache", "huggingface", "token"),
9696
+ path12.join(os4.homedir(), ".huggingface", "token")
9603
9697
  ].filter((value) => Boolean(value));
9604
9698
  for (const candidate of candidates) {
9605
9699
  try {
9606
- const token = (await fs10.readFile(candidate, "utf8")).trim();
9700
+ const token = (await fs11.readFile(candidate, "utf8")).trim();
9607
9701
  if (token) {
9608
9702
  return token;
9609
9703
  }
@@ -9612,6 +9706,62 @@ async function readToken(env = process.env) {
9612
9706
  }
9613
9707
  throw new Error("HF token not found. Set HF_TOKEN or run `hf auth login` once.");
9614
9708
  }
9709
+ async function ensureHfToken(params) {
9710
+ let missingTokenError;
9711
+ try {
9712
+ return await params.readToken();
9713
+ } catch (error) {
9714
+ missingTokenError = error;
9715
+ }
9716
+ if (!params.prompt.isInteractive()) {
9717
+ throw missingTokenError;
9718
+ }
9719
+ let executable = await params.hfCli.findExecutable();
9720
+ if (!executable) {
9721
+ params.prompt.note(
9722
+ `ML Claw uses the official Hugging Face CLI to sign you in.
9723
+
9724
+ Manual install command:
9725
+ ${HF_CLI_INSTALL_COMMAND}`,
9726
+ "Hugging Face CLI required"
9727
+ );
9728
+ const install = await params.prompt.confirm("Install the Hugging Face CLI now?", true);
9729
+ if (!install) {
9730
+ throw new Error(`Hugging Face CLI installation was declined. Install it with: ${HF_CLI_INSTALL_COMMAND}`);
9731
+ }
9732
+ await params.hfCli.install();
9733
+ executable = await params.hfCli.findExecutable();
9734
+ if (!executable) {
9735
+ throw new Error(
9736
+ `Hugging Face CLI was installed but could not be found. Open a new terminal or run: ${HF_CLI_INSTALL_COMMAND}`
9737
+ );
9738
+ }
9739
+ }
9740
+ const hasAccount = await params.prompt.confirm("Do you already have a Hugging Face account?", true);
9741
+ if (!hasAccount) {
9742
+ const opened = await params.hfCli.openUrl(HF_ACCOUNT_CREATE_URL);
9743
+ params.prompt.note(
9744
+ `${opened ? "A browser was opened for account creation." : "Create your account in a browser."}
9745
+
9746
+ ${HF_ACCOUNT_CREATE_URL}`,
9747
+ "Create a Hugging Face account"
9748
+ );
9749
+ const accountCreated = await params.prompt.confirm("Have you created your Hugging Face account?", false);
9750
+ if (!accountCreated) {
9751
+ throw new Error(`Create a Hugging Face account at ${HF_ACCOUNT_CREATE_URL}, then run ML Claw again`);
9752
+ }
9753
+ }
9754
+ params.prompt.note(
9755
+ "Complete Hugging Face sign-in in the browser. ML Claw will resume automatically afterward.",
9756
+ "Hugging Face sign-in"
9757
+ );
9758
+ await params.hfCli.login(executable);
9759
+ try {
9760
+ return await params.readToken();
9761
+ } catch {
9762
+ throw new Error("Hugging Face sign-in completed, but no local token was found. Run `hf auth login` and try again.");
9763
+ }
9764
+ }
9615
9765
 
9616
9766
  // src/mlclaw/docker.ts
9617
9767
  import { execFile } from "node:child_process";
@@ -9756,9 +9906,9 @@ function parseGatewayLocation(value) {
9756
9906
 
9757
9907
  // src/mlclaw/git.ts
9758
9908
  import { execFile as execFile2 } from "node:child_process";
9759
- import fs12 from "node:fs/promises";
9760
- import os4 from "node:os";
9761
- import path13 from "node:path";
9909
+ import fs13 from "node:fs/promises";
9910
+ import os5 from "node:os";
9911
+ import path14 from "node:path";
9762
9912
  import { fileURLToPath as fileURLToPath3 } from "node:url";
9763
9913
  import { promisify as promisify2 } from "node:util";
9764
9914
 
@@ -13582,10 +13732,10 @@ var CurrentXorbInfo = class {
13582
13732
  hash: computeXorbHash(xorbChunksCleaned),
13583
13733
  chunks: xorbChunksCleaned,
13584
13734
  id: this.id,
13585
- files: Object.entries(this.fileProcessedBytes).map(([path15, processedBytes]) => ({
13586
- path: path15,
13587
- progress: processedBytes / this.fileSize[path15],
13588
- lastSentProgress: ((this.fileUploadedBytes[path15] ?? 0) + (processedBytes - (this.fileUploadedBytes[path15] ?? 0)) * PROCESSING_PROGRESS_RATIO) / this.fileSize[path15]
13735
+ files: Object.entries(this.fileProcessedBytes).map(([path16, processedBytes]) => ({
13736
+ path: path16,
13737
+ progress: processedBytes / this.fileSize[path16],
13738
+ lastSentProgress: ((this.fileUploadedBytes[path16] ?? 0) + (processedBytes - (this.fileUploadedBytes[path16] ?? 0)) * PROCESSING_PROGRESS_RATIO) / this.fileSize[path16]
13589
13739
  }))
13590
13740
  };
13591
13741
  }
@@ -14458,7 +14608,7 @@ var BucketClient = class {
14458
14608
  if (paths.length === 0) {
14459
14609
  return;
14460
14610
  }
14461
- await this.batch(paths.map((path15) => ({ type: "deleteFile", path: path15 })));
14611
+ await this.batch(paths.map((path16) => ({ type: "deleteFile", path: path16 })));
14462
14612
  }
14463
14613
  async batch(operations) {
14464
14614
  const body = `${operations.map((op) => JSON.stringify(op)).join("\n")}
@@ -14474,8 +14624,8 @@ var BucketClient = class {
14474
14624
  * any other failure (including bucket/auth errors), so a missing object is
14475
14625
  * never conflated with an unreachable bucket.
14476
14626
  */
14477
- async downloadFile(path15) {
14478
- const url = `${this.hubUrl}/buckets/${this.bucket}/resolve/${encodeURIComponent(path15)}`;
14627
+ async downloadFile(path16) {
14628
+ const url = `${this.hubUrl}/buckets/${this.bucket}/resolve/${encodeURIComponent(path16)}`;
14479
14629
  const response = await this.fetchWithRetry(url);
14480
14630
  if (response.status === 404) {
14481
14631
  await this.assertBucketAccessible();
@@ -14759,9 +14909,9 @@ var HubApi = class {
14759
14909
  encoding: "base64"
14760
14910
  }
14761
14911
  })),
14762
- ...(params.deletePaths ?? []).map((path15) => ({
14912
+ ...(params.deletePaths ?? []).map((path16) => ({
14763
14913
  key: "deletedFile",
14764
- value: { path: path15 }
14914
+ value: { path: path16 }
14765
14915
  }))
14766
14916
  ].map((entry) => JSON.stringify(entry)).join("\n");
14767
14917
  await this.request(`/api/spaces/${repoId}/commit/${encodeURIComponent(params.branch ?? "main")}`, {
@@ -14833,8 +14983,8 @@ function sseDataToText(raw) {
14833
14983
  }
14834
14984
 
14835
14985
  // src/mlclaw/runtime-image.ts
14836
- import fs11 from "node:fs";
14837
- import path12 from "node:path";
14986
+ import fs12 from "node:fs";
14987
+ import path13 from "node:path";
14838
14988
  import { fileURLToPath as fileURLToPath2 } from "node:url";
14839
14989
  var DEFAULT_OPENCLAW_VERSION = "2026.7.1";
14840
14990
  var DEFAULT_BROKERKIT_PLUGIN_VERSION = "0.2.1";
@@ -14873,17 +15023,17 @@ function packageConfigString(key, fallback) {
14873
15023
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
14874
15024
  }
14875
15025
  function readPackageMetadata() {
14876
- let dir = path12.dirname(fileURLToPath2(import.meta.url));
15026
+ let dir = path13.dirname(fileURLToPath2(import.meta.url));
14877
15027
  while (true) {
14878
- const candidate = path12.join(dir, "package.json");
15028
+ const candidate = path13.join(dir, "package.json");
14879
15029
  try {
14880
- return JSON.parse(fs11.readFileSync(candidate, "utf8"));
15030
+ return JSON.parse(fs12.readFileSync(candidate, "utf8"));
14881
15031
  } catch (err) {
14882
15032
  if (!isMissingFileError(err)) {
14883
15033
  throw err;
14884
15034
  }
14885
15035
  }
14886
- const parent = path12.dirname(dir);
15036
+ const parent = path13.dirname(dir);
14887
15037
  if (parent === dir) {
14888
15038
  throw new Error("could not find package.json while resolving default runtime image");
14889
15039
  }
@@ -14897,12 +15047,12 @@ function isMissingFileError(err) {
14897
15047
  // src/mlclaw/git.ts
14898
15048
  var execFileAsync2 = promisify2(execFile2);
14899
15049
  async function pushTemplateToSpace(params) {
14900
- const tempRoot = await fs12.mkdtemp(path13.join(os4.tmpdir(), "mlclaw-space-"));
15050
+ const tempRoot = await fs13.mkdtemp(path14.join(os5.tmpdir(), "mlclaw-space-"));
14901
15051
  try {
14902
15052
  const sourceDir = params.sourceDir ?? process.env.MLCLAW_SOURCE_DIR ?? await findPackagedSourceRoot();
14903
15053
  const templateRev = await currentTemplateRev(sourceDir);
14904
- const outDir = path13.join(tempRoot, "space");
14905
- await fs12.mkdir(outDir, { recursive: true });
15054
+ const outDir = path14.join(tempRoot, "space");
15055
+ await fs13.mkdir(outDir, { recursive: true });
14906
15056
  await generateSpaceRepo(sourceDir, outDir, {
14907
15057
  ...params.runtimeImage ? { runtimeImage: params.runtimeImage } : {}
14908
15058
  });
@@ -14920,7 +15070,7 @@ async function pushTemplateToSpace(params) {
14920
15070
  });
14921
15071
  return { templateRev };
14922
15072
  } finally {
14923
- await fs12.rm(tempRoot, { recursive: true, force: true });
15073
+ await fs13.rm(tempRoot, { recursive: true, force: true });
14924
15074
  }
14925
15075
  }
14926
15076
  async function currentTemplateRev(sourceDir) {
@@ -14933,7 +15083,7 @@ async function currentTemplateRev(sourceDir) {
14933
15083
  }
14934
15084
  } catch {
14935
15085
  }
14936
- const pkg = JSON.parse(await fs12.readFile(path13.join(sourceDir, "package.json"), "utf8"));
15086
+ const pkg = JSON.parse(await fs13.readFile(path14.join(sourceDir, "package.json"), "utf8"));
14937
15087
  return `npm:${pkg.name ?? "mlclaw"}@${pkg.version ?? "unknown"}`;
14938
15088
  }
14939
15089
  async function generateSpaceRepo(sourceDir, outDir, options = {}) {
@@ -14959,13 +15109,13 @@ async function generateSpaceRepo(sourceDir, outDir, options = {}) {
14959
15109
  );
14960
15110
  }
14961
15111
  for (const [from, to] of copies) {
14962
- await copyExisting(path13.join(sourceDir, from), path13.join(outDir, to));
15112
+ await copyExisting(path14.join(sourceDir, from), path14.join(outDir, to));
14963
15113
  }
14964
- const hfLogoPng = await fs12.readFile(path13.join(sourceDir, "assets/hf-logo.png"));
14965
- await fs12.writeFile(path13.join(outDir, "assets/hf-logo.png.base64"), `${hfLogoPng.toString("base64")}
15114
+ const hfLogoPng = await fs13.readFile(path14.join(sourceDir, "assets/hf-logo.png"));
15115
+ await fs13.writeFile(path14.join(outDir, "assets/hf-logo.png.base64"), `${hfLogoPng.toString("base64")}
14966
15116
  `, "utf8");
14967
- await fs12.writeFile(
14968
- path13.join(outDir, "Dockerfile"),
15117
+ await fs13.writeFile(
15118
+ path14.join(outDir, "Dockerfile"),
14969
15119
  options.runtimeImage ? imageDockerfile(options.runtimeImage) : bundledDockerfile(),
14970
15120
  "utf8"
14971
15121
  );
@@ -15053,13 +15203,13 @@ CMD ["/app/entrypoint.sh"]
15053
15203
  `;
15054
15204
  }
15055
15205
  async function findPackagedSourceRoot() {
15056
- const start = path13.dirname(fileURLToPath3(import.meta.url));
15206
+ const start = path14.dirname(fileURLToPath3(import.meta.url));
15057
15207
  let dir = start;
15058
15208
  while (true) {
15059
15209
  if (await hasPackagedSourceFiles(dir)) {
15060
15210
  return dir;
15061
15211
  }
15062
- const parent = path13.dirname(dir);
15212
+ const parent = path14.dirname(dir);
15063
15213
  if (parent === dir) {
15064
15214
  throw new Error("Could not find packaged ML Claw source files. Reinstall the mlclaw npm package.");
15065
15215
  }
@@ -15076,20 +15226,20 @@ async function hasPackagedSourceFiles(dir) {
15076
15226
  "src/hf-bucket-client/client.ts"
15077
15227
  ];
15078
15228
  try {
15079
- await Promise.all(required.map((file) => fs12.access(path13.join(dir, file))));
15229
+ await Promise.all(required.map((file) => fs13.access(path14.join(dir, file))));
15080
15230
  return true;
15081
15231
  } catch {
15082
15232
  return false;
15083
15233
  }
15084
15234
  }
15085
15235
  async function copyExisting(from, to) {
15086
- const stat = await fs12.stat(from);
15087
- await fs12.mkdir(path13.dirname(to), { recursive: true });
15236
+ const stat = await fs13.stat(from);
15237
+ await fs13.mkdir(path14.dirname(to), { recursive: true });
15088
15238
  if (stat.isDirectory()) {
15089
- await fs12.cp(from, to, { recursive: true });
15239
+ await fs13.cp(from, to, { recursive: true });
15090
15240
  } else {
15091
- await fs12.copyFile(from, to);
15092
- await fs12.chmod(to, stat.mode);
15241
+ await fs13.copyFile(from, to);
15242
+ await fs13.chmod(to, stat.mode);
15093
15243
  }
15094
15244
  }
15095
15245
  async function readFilesForCommit(root) {
@@ -15097,24 +15247,24 @@ async function readFilesForCommit(root) {
15097
15247
  for (const relativePath of await listFiles(root)) {
15098
15248
  files.push({
15099
15249
  path: relativePath,
15100
- content: await fs12.readFile(path13.join(root, relativePath))
15250
+ content: await fs13.readFile(path14.join(root, relativePath))
15101
15251
  });
15102
15252
  }
15103
15253
  return files;
15104
15254
  }
15105
15255
  async function listFiles(root, dir = "") {
15106
- const absoluteDir = path13.join(root, dir);
15107
- const entries = await fs12.readdir(absoluteDir, { withFileTypes: true });
15256
+ const absoluteDir = path14.join(root, dir);
15257
+ const entries = await fs13.readdir(absoluteDir, { withFileTypes: true });
15108
15258
  const files = [];
15109
15259
  for (const entry of entries) {
15110
- const relativePath = path13.posix.join(dir.split(path13.sep).join(path13.posix.sep), entry.name);
15111
- const absolutePath = path13.join(root, relativePath);
15260
+ const relativePath = path14.posix.join(dir.split(path14.sep).join(path14.posix.sep), entry.name);
15261
+ const absolutePath = path14.join(root, relativePath);
15112
15262
  if (entry.isDirectory()) {
15113
15263
  files.push(...await listFiles(root, relativePath));
15114
15264
  } else if (entry.isFile()) {
15115
15265
  files.push(relativePath);
15116
15266
  } else {
15117
- const stat = await fs12.stat(absolutePath);
15267
+ const stat = await fs13.stat(absolutePath);
15118
15268
  if (stat.isFile()) {
15119
15269
  files.push(relativePath);
15120
15270
  }
@@ -15186,9 +15336,9 @@ var DEFAULT_MODEL_PROVIDER = "fireworks-ai";
15186
15336
  var DEFAULT_MODEL = `huggingface/${DEFAULT_MODEL_ID}:${DEFAULT_MODEL_PROVIDER}`;
15187
15337
 
15188
15338
  // src/mlclaw/local-config.ts
15189
- import fs13 from "node:fs/promises";
15190
- import os5 from "node:os";
15191
- import path14 from "node:path";
15339
+ import fs14 from "node:fs/promises";
15340
+ import os6 from "node:os";
15341
+ import path15 from "node:path";
15192
15342
  function defaultConfigRoot(env = process.env) {
15193
15343
  const explicit = env.MLCLAW_CONFIG_HOME?.trim();
15194
15344
  if (explicit) {
@@ -15196,32 +15346,32 @@ function defaultConfigRoot(env = process.env) {
15196
15346
  }
15197
15347
  const xdg = env.XDG_CONFIG_HOME?.trim();
15198
15348
  if (xdg) {
15199
- return path14.join(xdg, "mlclaw");
15349
+ return path15.join(xdg, "mlclaw");
15200
15350
  }
15201
- return path14.join(os5.homedir(), ".config", "mlclaw");
15351
+ return path15.join(os6.homedir(), ".config", "mlclaw");
15202
15352
  }
15203
15353
  function localConfigPaths(root) {
15204
15354
  return {
15205
15355
  root,
15206
- deploymentsDir: path14.join(root, "deployments"),
15207
- secretsDir: path14.join(root, "secrets")
15356
+ deploymentsDir: path15.join(root, "deployments"),
15357
+ secretsDir: path15.join(root, "secrets")
15208
15358
  };
15209
15359
  }
15210
15360
  function manifestPath(root, agent) {
15211
- return path14.join(localConfigPaths(root).deploymentsDir, `${agent}.json`);
15361
+ return path15.join(localConfigPaths(root).deploymentsDir, `${agent}.json`);
15212
15362
  }
15213
15363
  function secretEnvPath(root, agent) {
15214
- return path14.join(localConfigPaths(root).secretsDir, `${agent}.env`);
15364
+ return path15.join(localConfigPaths(root).secretsDir, `${agent}.env`);
15215
15365
  }
15216
15366
  async function writeManifest(root, manifest) {
15217
15367
  const file = manifestPath(root, manifest.agent);
15218
- await fs13.mkdir(path14.dirname(file), { recursive: true });
15219
- await fs13.writeFile(file, `${JSON.stringify(manifest, null, 2)}
15368
+ await fs14.mkdir(path15.dirname(file), { recursive: true });
15369
+ await fs14.writeFile(file, `${JSON.stringify(manifest, null, 2)}
15220
15370
  `, "utf8");
15221
15371
  }
15222
15372
  async function readManifest(root, agent) {
15223
15373
  const file = manifestPath(root, agent);
15224
- const parsed = JSON.parse(await fs13.readFile(file, "utf8"));
15374
+ const parsed = JSON.parse(await fs14.readFile(file, "utf8"));
15225
15375
  if (parsed.version !== 1) {
15226
15376
  throw new Error(`unsupported deployment manifest version in ${file}`);
15227
15377
  }
@@ -15229,7 +15379,7 @@ async function readManifest(root, agent) {
15229
15379
  }
15230
15380
  async function manifestExists(root, agent) {
15231
15381
  try {
15232
- await fs13.access(manifestPath(root, agent));
15382
+ await fs14.access(manifestPath(root, agent));
15233
15383
  return true;
15234
15384
  } catch {
15235
15385
  return false;
@@ -15241,12 +15391,12 @@ function renderSecretEnv(values) {
15241
15391
  }
15242
15392
  async function writeSecretEnv(root, agent, values) {
15243
15393
  const file = secretEnvPath(root, agent);
15244
- await fs13.mkdir(path14.dirname(file), { recursive: true, mode: 448 });
15245
- await fs13.writeFile(file, renderSecretEnv(values), { encoding: "utf8", mode: 384 });
15246
- await fs13.chmod(file, 384);
15394
+ await fs14.mkdir(path15.dirname(file), { recursive: true, mode: 448 });
15395
+ await fs14.writeFile(file, renderSecretEnv(values), { encoding: "utf8", mode: 384 });
15396
+ await fs14.chmod(file, 384);
15247
15397
  }
15248
15398
  async function readSecretEnv(root, agent) {
15249
- return parseSecretEnv(await fs13.readFile(secretEnvPath(root, agent), "utf8"));
15399
+ return parseSecretEnv(await fs14.readFile(secretEnvPath(root, agent), "utf8"));
15250
15400
  }
15251
15401
  function parseSecretEnv(raw) {
15252
15402
  const out = {};
@@ -15357,6 +15507,7 @@ function createRuntime(overrides = {}) {
15357
15507
  stdout: overrides.stdout ?? console,
15358
15508
  stderr: overrides.stderr ?? console,
15359
15509
  readToken: overrides.readToken ?? readToken,
15510
+ hfCli: overrides.hfCli ?? createSystemHfCli(overrides.env ?? process4.env),
15360
15511
  hubFactory: overrides.hubFactory ?? ((token) => new HubApi({ token })),
15361
15512
  pushTemplateToSpace: overrides.pushTemplateToSpace ?? pushTemplateToSpace,
15362
15513
  getTelegramBot: overrides.getTelegramBot ?? getTelegramBot,
@@ -15435,7 +15586,15 @@ async function main(argv = process4.argv.slice(2), runtimeOverrides = {}) {
15435
15586
  async function bootstrap(opts, runtime) {
15436
15587
  runtime.prompt.intro("ML Claw bootstrap");
15437
15588
  const requestedGatewayLocation = opts.gateway ? parseGatewayLocation(opts.gateway) : void 0;
15438
- const hfToken = await runtime.readToken(runtime.env);
15589
+ const hfToken = await ensureHfToken({
15590
+ readToken: async () => await runtime.readToken(runtime.env),
15591
+ hfCli: runtime.hfCli,
15592
+ prompt: {
15593
+ isInteractive: runtime.prompt.isInteractive,
15594
+ note: runtime.prompt.note,
15595
+ confirm: async (message, initialValue) => await promptConfirm(message, initialValue, runtime)
15596
+ }
15597
+ });
15439
15598
  const hub = runtime.hubFactory(hfToken);
15440
15599
  const me2 = await hub.whoami();
15441
15600
  const owner = opts.owner ?? me2.name;
@@ -15561,7 +15720,7 @@ async function bootstrap(opts, runtime) {
15561
15720
  if (deployedSpaceRuntime) {
15562
15721
  runtime.stdout.log(`Space runtime: ${deployedSpaceRuntime}`);
15563
15722
  }
15564
- runtime.prompt.outro(gatewayLocation === "space" ? `Restart requested. Your agent will soon be available at ${spacePublicUrl(names.space)}.` : "Local gateway start requested.");
15723
+ runtime.prompt.outro(gatewayLocation === "space" ? `Deployment requested. Your agent will soon be available at ${spacePublicUrl(names.space)}.` : "Local gateway start requested.");
15565
15724
  }
15566
15725
  function spacePublicUrl(repoId) {
15567
15726
  return `https://${repoId.replace("/", "-")}.hf.space`;
@@ -15963,7 +16122,7 @@ async function deploySpaceGateway(params) {
15963
16122
  } else {
15964
16123
  runtime.stdout.log("Keeping legacy broad Hub token secrets until an HF Broker or Router credential is configured");
15965
16124
  }
15966
- await hub.restartSpace(manifest.space, true);
16125
+ runtime.stdout.log(`Space deployment triggered: ${manifest.space}`);
15967
16126
  return { runtimeImage: spaceRuntimeRef };
15968
16127
  }
15969
16128
  async function startLocalGateway(params) {
@@ -16549,7 +16708,7 @@ async function update(repoId, opts, hub, hfToken, runtime) {
16549
16708
  if (canonicalTemplate) {
16550
16709
  await hub.addSpaceVariable(repoId, "MLCLAW_CANONICAL_SPACE_ID", canonicalTemplateSpaceId(runtime.env));
16551
16710
  await doctor(repoId, { fix: true }, hub, runtime);
16552
- await hub.restartSpace(repoId, true);
16711
+ runtime.stdout.log(`Space deployment triggered: ${repoId}`);
16553
16712
  return;
16554
16713
  }
16555
16714
  await hub.addSpaceVariable(repoId, "MLCLAW_GATEWAY_LOCATION", "space");
@@ -16564,7 +16723,7 @@ async function update(repoId, opts, hub, hfToken, runtime) {
16564
16723
  await ensureSpaceStateVolume(hub, repoId, bucket);
16565
16724
  }
16566
16725
  await doctor(repoId, { fix: true }, hub, runtime);
16567
- await hub.restartSpace(repoId, true);
16726
+ runtime.stdout.log(`Space deployment triggered: ${repoId}`);
16568
16727
  }
16569
16728
  async function ensureUpdateRouterToken(params) {
16570
16729
  if (!isHuggingFaceRouterModel(params.model)) {
@@ -16972,7 +17131,7 @@ async function readOptionalTelegramToken(opts, runtime) {
16972
17131
  return direct;
16973
17132
  }
16974
17133
  if (opts.telegramTokenFile) {
16975
- const raw = await fs14.readFile(opts.telegramTokenFile, "utf8");
17134
+ const raw = await fs15.readFile(opts.telegramTokenFile, "utf8");
16976
17135
  const match = raw.match(/(?:^|\n)\s*TELEGRAM_BOT_TOKEN\s*=\s*['"]?([^'"\n]+)['"]?/);
16977
17136
  return (match?.[1] ?? raw.trim()).trim();
16978
17137
  }
@@ -16994,7 +17153,7 @@ async function readOptionalRouterTokenFile(file) {
16994
17153
  if (!file) {
16995
17154
  return void 0;
16996
17155
  }
16997
- const raw = await fs14.readFile(file, "utf8");
17156
+ const raw = await fs15.readFile(file, "utf8");
16998
17157
  const parsed = parseSecretEnv(raw);
16999
17158
  return nonEmpty(parsed.MLCLAW_ROUTER_TOKEN) ?? nonEmpty(parsed.HF_ROUTER_TOKEN) ?? nonEmpty(raw);
17000
17159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlclaw",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "license": "MIT",
5
5
  "config": {
6
6
  "openclawVersion": "2026.7.1",
@@ -55,8 +55,8 @@
55
55
  "pack:check": "node scripts/check-package.mjs",
56
56
  "prepack": "npm run build",
57
57
  "check:secrets": "node scripts/check-secrets.mjs",
58
- "format": "prettier --check eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
59
- "format:write": "prettier --write eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
58
+ "format": "prettier --check eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw/auth.ts src/mlclaw/hf-cli.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.auth.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
59
+ "format:write": "prettier --write eslint.config.mjs vitest.config.ts vitest.stryker.config.ts package.json README.md src/hf-state-sync/archive.ts src/hf-state-sync/cli.ts src/hf-state-sync/paths.ts src/hf-state-sync/prepare.ts src/hf-state-sync/stage-worker.ts src/hf-state-sync/supervise.ts src/mlclaw/auth.ts src/mlclaw/hf-cli.ts src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts src/mlclaw-space-runtime/app.ts src/mlclaw-space-runtime/config.ts src/mlclaw-space-runtime/openai-credentials.ts src/mlclaw-space-runtime/server.ts src/mlclaw-space-runtime/shell.ts src/mlclaw-control-ui/src/main.tsx src/mlclaw-control-ui/src/styles.css src/mlclaw-control-ui/src/components/ui/utils.ts src/mlclaw/git.ts test/hf-state-sync.archive.test.ts test/hf-state-sync.roundtrip.test.ts test/hf-state-sync.supervise.test.ts test/mlclaw.auth.test.ts test/mlclaw.brokerkit-agent-tools.test.ts test/mlclaw.bundle.test.ts test/mlclaw.delegated-brokerkit.test.ts test/mlclaw.generate-space.test.ts test/mlclaw.operator-brokers.test.ts test/mlclaw.runtime-image.test.ts test/mlclaw.space-runtime.test.ts docs/operator-brokers-config.md",
60
60
  "lint": "eslint --max-warnings 0 src/mlclaw-space-runtime/delegated-brokerkit.ts src/mlclaw-space-runtime/delegated-revisions.ts src/mlclaw-space-runtime/operator-brokers.ts src/mlclaw-space-runtime/openclaw-config.ts",
61
61
  "coverage": "vitest run --coverage",
62
62
  "dry": "slophammer-ts dry .",