mlclaw 0.3.6 → 0.3.7
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 +1 -1
- package/README.md +6 -2
- package/dist/mlclaw.mjs +244 -85
- package/package.json +3 -3
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.
|
|
5
|
+
ARG MLCLAW_RUNTIME_IMAGE=ghcr.io/osolmaz/mlclaw:0.3.7-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`.
|
|
44
|
-
token
|
|
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
|
|
1206
|
-
var
|
|
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 (
|
|
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 =
|
|
2219
|
-
if (
|
|
2220
|
-
if (sourceExt.includes(
|
|
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) =>
|
|
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 =
|
|
2234
|
+
resolvedScriptPath = fs16.realpathSync(this._scriptPath);
|
|
2235
2235
|
} catch {
|
|
2236
2236
|
resolvedScriptPath = this._scriptPath;
|
|
2237
2237
|
}
|
|
2238
|
-
executableDir =
|
|
2239
|
-
|
|
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 =
|
|
2246
|
+
const legacyName = path16.basename(
|
|
2247
2247
|
this._scriptPath,
|
|
2248
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
3189
|
-
if (
|
|
3190
|
-
this._executableDir =
|
|
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
|
|
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
|
|
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 &&
|
|
9601
|
-
|
|
9602
|
-
|
|
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
|
|
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
|
|
9760
|
-
import
|
|
9761
|
-
import
|
|
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(([
|
|
13586
|
-
path:
|
|
13587
|
-
progress: processedBytes / this.fileSize[
|
|
13588
|
-
lastSentProgress: ((this.fileUploadedBytes[
|
|
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((
|
|
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(
|
|
14478
|
-
const url = `${this.hubUrl}/buckets/${this.bucket}/resolve/${encodeURIComponent(
|
|
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((
|
|
14912
|
+
...(params.deletePaths ?? []).map((path16) => ({
|
|
14763
14913
|
key: "deletedFile",
|
|
14764
|
-
value: { path:
|
|
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
|
|
14837
|
-
import
|
|
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 =
|
|
15026
|
+
let dir = path13.dirname(fileURLToPath2(import.meta.url));
|
|
14877
15027
|
while (true) {
|
|
14878
|
-
const candidate =
|
|
15028
|
+
const candidate = path13.join(dir, "package.json");
|
|
14879
15029
|
try {
|
|
14880
|
-
return JSON.parse(
|
|
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 =
|
|
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
|
|
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 =
|
|
14905
|
-
await
|
|
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
|
|
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
|
|
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(
|
|
15112
|
+
await copyExisting(path14.join(sourceDir, from), path14.join(outDir, to));
|
|
14963
15113
|
}
|
|
14964
|
-
const hfLogoPng = await
|
|
14965
|
-
await
|
|
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
|
|
14968
|
-
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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
|
|
15087
|
-
await
|
|
15236
|
+
const stat = await fs13.stat(from);
|
|
15237
|
+
await fs13.mkdir(path14.dirname(to), { recursive: true });
|
|
15088
15238
|
if (stat.isDirectory()) {
|
|
15089
|
-
await
|
|
15239
|
+
await fs13.cp(from, to, { recursive: true });
|
|
15090
15240
|
} else {
|
|
15091
|
-
await
|
|
15092
|
-
await
|
|
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
|
|
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 =
|
|
15107
|
-
const entries = await
|
|
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 =
|
|
15111
|
-
const absolutePath =
|
|
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
|
|
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
|
|
15190
|
-
import
|
|
15191
|
-
import
|
|
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
|
|
15349
|
+
return path15.join(xdg, "mlclaw");
|
|
15200
15350
|
}
|
|
15201
|
-
return
|
|
15351
|
+
return path15.join(os6.homedir(), ".config", "mlclaw");
|
|
15202
15352
|
}
|
|
15203
15353
|
function localConfigPaths(root) {
|
|
15204
15354
|
return {
|
|
15205
15355
|
root,
|
|
15206
|
-
deploymentsDir:
|
|
15207
|
-
secretsDir:
|
|
15356
|
+
deploymentsDir: path15.join(root, "deployments"),
|
|
15357
|
+
secretsDir: path15.join(root, "secrets")
|
|
15208
15358
|
};
|
|
15209
15359
|
}
|
|
15210
15360
|
function manifestPath(root, agent) {
|
|
15211
|
-
return
|
|
15361
|
+
return path15.join(localConfigPaths(root).deploymentsDir, `${agent}.json`);
|
|
15212
15362
|
}
|
|
15213
15363
|
function secretEnvPath(root, agent) {
|
|
15214
|
-
return
|
|
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
|
|
15219
|
-
await
|
|
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
|
|
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
|
|
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
|
|
15245
|
-
await
|
|
15246
|
-
await
|
|
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
|
|
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
|
|
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;
|
|
@@ -16972,7 +17131,7 @@ async function readOptionalTelegramToken(opts, runtime) {
|
|
|
16972
17131
|
return direct;
|
|
16973
17132
|
}
|
|
16974
17133
|
if (opts.telegramTokenFile) {
|
|
16975
|
-
const raw = await
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.3.7",
|
|
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 .",
|