mlclaw 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.agents/skills/mlclaw/SKILL.md +214 -0
  2. package/.agents/skills/mlclaw/agents/openai.yaml +4 -0
  3. package/.gitattributes +35 -0
  4. package/Dockerfile +45 -0
  5. package/LICENSE +21 -0
  6. package/README.md +206 -0
  7. package/assets/mlclaw.svg +143 -0
  8. package/dist/hf-state-sync.js +9532 -0
  9. package/dist/mlclaw-space-runtime.js +5010 -0
  10. package/dist/mlclaw.mjs +16502 -0
  11. package/entrypoint.sh +87 -0
  12. package/mlclaw.ps1 +108 -0
  13. package/mlclaw.sh +117 -0
  14. package/openclaw.default.json +67 -0
  15. package/package.json +66 -0
  16. package/scripts/configure-huggingface-model.mjs +86 -0
  17. package/scripts/configure-telegram.mjs +55 -0
  18. package/scripts/report-telegram-probe.mjs +18 -0
  19. package/space/README.md +42 -0
  20. package/src/hf-bucket-client/client.ts +217 -0
  21. package/src/hf-state-sync/archive.ts +137 -0
  22. package/src/hf-state-sync/cli.ts +92 -0
  23. package/src/hf-state-sync/hub.ts +81 -0
  24. package/src/hf-state-sync/manifest.ts +67 -0
  25. package/src/hf-state-sync/paths.ts +73 -0
  26. package/src/hf-state-sync/restore.ts +109 -0
  27. package/src/hf-state-sync/snapshot.ts +133 -0
  28. package/src/hf-state-sync/sqlite.ts +57 -0
  29. package/src/hf-state-sync/supervise.ts +256 -0
  30. package/src/vendor/hfjs-xet/error.ts +52 -0
  31. package/src/vendor/hfjs-xet/types/public.ts +207 -0
  32. package/src/vendor/hfjs-xet/utils/ChunkCache.ts +102 -0
  33. package/src/vendor/hfjs-xet/utils/RangeList.ts +182 -0
  34. package/src/vendor/hfjs-xet/utils/SplicedBlob.ts +249 -0
  35. package/src/vendor/hfjs-xet/utils/XetBlob.ts +732 -0
  36. package/src/vendor/hfjs-xet/utils/checkCredentials.ts +21 -0
  37. package/src/vendor/hfjs-xet/utils/combineUint8Arrays.ts +13 -0
  38. package/src/vendor/hfjs-xet/utils/createXorbs.ts +782 -0
  39. package/src/vendor/hfjs-xet/utils/shardParser.ts +152 -0
  40. package/src/vendor/hfjs-xet/utils/sum.ts +9 -0
  41. package/src/vendor/hfjs-xet/utils/uploadShards.ts +443 -0
  42. package/src/vendor/hfjs-xet/utils/xetWriteToken.ts +101 -0
  43. package/src/vendor/hfjs-xet/vendor/lz4js/index.ts +540 -0
  44. package/src/vendor/hfjs-xet/vendor/lz4js/util.ts +57 -0
  45. package/src/vendor/hfjs-xet/vendor/lz4js/xxh32.ts +99 -0
  46. package/src/vendor/hfjs-xet/vendor/type-fest/basic.ts +34 -0
  47. package/tsconfig.json +16 -0
package/entrypoint.sh ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ LIVE_DIR="${OPENCLAW_LIVE_DIR:-/tmp/openclaw-live}"
5
+
6
+ if [ "${MLCLAW_GATEWAY_DISABLED:-0}" = "1" ]; then
7
+ echo "[mlclaw] gateway disabled"
8
+ exit 0
9
+ fi
10
+
11
+ if [ -z "${HUGGINGFACE_HUB_TOKEN:-}" ] && [ -n "${HF_TOKEN:-}" ]; then
12
+ export HUGGINGFACE_HUB_TOKEN="$HF_TOKEN"
13
+ fi
14
+
15
+ # State, workspace, and config paths are ALWAYS derived from the live dir,
16
+ # never inherited: older deployments set OPENCLAW_STATE_DIR=/data/... as Space
17
+ # variables, and any state written outside the live dir would be invisible to
18
+ # snapshot/restore — the bucket would back up an empty tree.
19
+ export OPENCLAW_STATE_DIR="$LIVE_DIR/.openclaw"
20
+ export OPENCLAW_WORKSPACE_DIR="$LIVE_DIR/workspace"
21
+ export OPENCLAW_CONFIG_PATH="$LIVE_DIR/.openclaw/openclaw.json"
22
+ export OPENCLAW_GATEWAY_PORT="${MLCLAW_OPENCLAW_PORT:-7861}"
23
+ STATE_DIR="$OPENCLAW_STATE_DIR"
24
+ WORKSPACE_DIR="$OPENCLAW_WORKSPACE_DIR"
25
+ CONFIG_PATH="$OPENCLAW_CONFIG_PATH"
26
+
27
+ # Restore durable state from the bucket BEFORE creating any live dirs: the
28
+ # restore target is the live dir itself and must not exist yet. Fails the boot
29
+ # if the bucket has state but its manifest or every snapshot is corrupt (never
30
+ # silently start fresh then snapshot over a bucket that still holds data).
31
+ echo "[hf-state-sync] starting restore"
32
+ RESTORE_TIMEOUT_SECONDS="${MLCLAW_RESTORE_TIMEOUT_SECONDS:-180}"
33
+ if command -v timeout >/dev/null 2>&1; then
34
+ timeout "${RESTORE_TIMEOUT_SECONDS}s" node /app/hf-state-sync.js restore
35
+ else
36
+ node /app/hf-state-sync.js restore
37
+ fi
38
+ echo "[hf-state-sync] restore complete"
39
+
40
+ mkdir -p "$LIVE_DIR" "$WORKSPACE_DIR" "$STATE_DIR"
41
+ chown -R node:node "$LIVE_DIR"
42
+
43
+ if [ -n "${OPENCLAW_AGENT_NAME:-}" ]; then
44
+ printf "%s\n" "$OPENCLAW_AGENT_NAME" > "$STATE_DIR/agent-name.txt"
45
+ fi
46
+
47
+ if [ ! -f "$CONFIG_PATH" ]; then
48
+ cp /app/openclaw.default.json "$CONFIG_PATH"
49
+ fi
50
+ chown -R node:node "$LIVE_DIR"
51
+
52
+ if [ -n "${OPENCLAW_MODEL:-}" ]; then
53
+ echo "[huggingface-config] configuring selected Hugging Face model"
54
+ gosu node node /app/scripts/configure-huggingface-model.mjs "$CONFIG_PATH"
55
+ echo "[huggingface-config] Hugging Face model configured"
56
+ fi
57
+
58
+ if [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_ALLOWED_USERS:-}" ]; then
59
+ echo "[telegram-config] configuring Telegram channel"
60
+ gosu node node /app/scripts/configure-telegram.mjs "$CONFIG_PATH" "$TELEGRAM_ALLOWED_USERS"
61
+ echo "[telegram-config] Telegram channel configured"
62
+ fi
63
+
64
+ if [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ "${OPENCLAW_TELEGRAM_CONNECTIVITY_PROBE:-0}" = "1" ]; then
65
+ if command -v curl >/dev/null 2>&1; then
66
+ PROBE_OUT="/tmp/openclaw-telegram-probe.json"
67
+ PROBE_PROXY=()
68
+ PROBE_API_ROOT="${TELEGRAM_API_ROOT:-https://api.telegram.org}"
69
+ PROBE_API_ROOT="${PROBE_API_ROOT%/}"
70
+ if [ -n "${TELEGRAM_PROXY:-}" ]; then
71
+ PROBE_PROXY=(--proxy "$TELEGRAM_PROXY")
72
+ fi
73
+ if curl -fsS --connect-timeout 20 --max-time 30 "${PROBE_PROXY[@]}" \
74
+ "${PROBE_API_ROOT}/bot${TELEGRAM_BOT_TOKEN}/getMe" \
75
+ -o "$PROBE_OUT"; then
76
+ gosu node node /app/scripts/report-telegram-probe.mjs "$PROBE_OUT" || true
77
+ else
78
+ echo "[telegram-probe] curl getMe failed"
79
+ fi
80
+ rm -f "$PROBE_OUT"
81
+ else
82
+ echo "[telegram-probe] curl is unavailable; skipping"
83
+ fi
84
+ fi
85
+
86
+ chown -R node:node "$LIVE_DIR"
87
+ exec gosu node node /app/hf-state-sync.js supervise -- node /app/mlclaw-space-runtime.js
package/mlclaw.ps1 ADDED
@@ -0,0 +1,108 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ $MinNodeMajor = if ($env:MLCLAW_MIN_NODE_MAJOR) { [int]$env:MLCLAW_MIN_NODE_MAJOR } else { 22 }
4
+ $NodeVersion = if ($env:MLCLAW_NODE_VERSION) { $env:MLCLAW_NODE_VERSION } else { "v24.16.0" }
5
+ $PackageSpec = if ($env:MLCLAW_NPM_SPEC) { $env:MLCLAW_NPM_SPEC } else { "mlclaw" }
6
+ $CacheRoot = if ($env:MLCLAW_CACHE_DIR) {
7
+ $env:MLCLAW_CACHE_DIR
8
+ } elseif ($env:LOCALAPPDATA) {
9
+ Join-Path $env:LOCALAPPDATA "mlclaw"
10
+ } else {
11
+ Join-Path $HOME ".cache\mlclaw"
12
+ }
13
+
14
+ function Get-NodeMajor($NodeBin) {
15
+ try {
16
+ $major = & $NodeBin -p 'Number(process.versions.node.split(".")[0])'
17
+ return [int]$major
18
+ } catch {
19
+ return 0
20
+ }
21
+ }
22
+
23
+ function Test-CompatibleNode($NodeBin) {
24
+ return (Get-NodeMajor $NodeBin) -ge $MinNodeMajor
25
+ }
26
+
27
+ function Get-SystemNode {
28
+ $cmd = Get-Command node -ErrorAction SilentlyContinue
29
+ if ($cmd -and (Test-CompatibleNode $cmd.Source)) {
30
+ return $cmd.Source
31
+ }
32
+ return $null
33
+ }
34
+
35
+ function Get-NodeArch {
36
+ if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq "Arm64") {
37
+ return "arm64"
38
+ }
39
+ return "x64"
40
+ }
41
+
42
+ function Install-CachedNode {
43
+ $arch = Get-NodeArch
44
+ $archiveName = "node-$NodeVersion-win-$arch"
45
+ $targetDir = Join-Path $CacheRoot "node\$NodeVersion-win-$arch"
46
+ $nodeBin = Join-Path $targetDir "node.exe"
47
+ if ((Test-Path $nodeBin) -and (Test-CompatibleNode $nodeBin)) {
48
+ return $targetDir
49
+ }
50
+
51
+ $parent = Split-Path $targetDir -Parent
52
+ New-Item -ItemType Directory -Force -Path $parent | Out-Null
53
+ $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("mlclaw-node-" + [System.Guid]::NewGuid().ToString("N"))
54
+ New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
55
+ try {
56
+ $zipPath = Join-Path $tempDir "node.zip"
57
+ $url = "https://nodejs.org/dist/$NodeVersion/$archiveName.zip"
58
+ Write-Error "mlclaw: installing Node.js $NodeVersion into $targetDir" -ErrorAction Continue
59
+ Invoke-WebRequest -Uri $url -OutFile $zipPath
60
+ Expand-Archive -Path $zipPath -DestinationPath $tempDir -Force
61
+ if (Test-Path $targetDir) {
62
+ Remove-Item -Recurse -Force $targetDir
63
+ }
64
+ Move-Item (Join-Path $tempDir $archiveName) $targetDir
65
+ } finally {
66
+ Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
67
+ }
68
+ return $targetDir
69
+ }
70
+
71
+ function Invoke-MLClaw($NodeBin, [string[]]$CliArgs) {
72
+ $nodeDir = Split-Path $NodeBin -Parent
73
+ $env:PATH = "$nodeDir;$env:PATH"
74
+ $npmCli = Join-Path $nodeDir "node_modules\npm\bin\npm-cli.js"
75
+ if (-not (Test-Path $npmCli)) {
76
+ $npmCommand = Get-Command npm -ErrorAction SilentlyContinue
77
+ if (-not $npmCommand) {
78
+ throw "npm was not found for Node runtime $NodeBin"
79
+ }
80
+ & $npmCommand.Source exec --yes --package $PackageSpec -- mlclaw @CliArgs
81
+ exit $LASTEXITCODE
82
+ }
83
+ & $NodeBin $npmCli exec --yes --package $PackageSpec -- mlclaw @CliArgs
84
+ exit $LASTEXITCODE
85
+ }
86
+
87
+ function Test-NodeHasNpm($NodeBin) {
88
+ $nodeDir = Split-Path $NodeBin -Parent
89
+ $npmCli = Join-Path $nodeDir "node_modules\npm\bin\npm-cli.js"
90
+ if (Test-Path $npmCli) {
91
+ return $true
92
+ }
93
+ $oldPath = $env:PATH
94
+ try {
95
+ $env:PATH = "$nodeDir;$env:PATH"
96
+ return [bool](Get-Command npm -ErrorAction SilentlyContinue)
97
+ } finally {
98
+ $env:PATH = $oldPath
99
+ }
100
+ }
101
+
102
+ $node = Get-SystemNode
103
+ if ($node -and (Test-NodeHasNpm $node)) {
104
+ Invoke-MLClaw $node $args
105
+ }
106
+
107
+ $nodeDir = Install-CachedNode
108
+ Invoke-MLClaw (Join-Path $nodeDir "node.exe") $args
package/mlclaw.sh ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ MIN_NODE_MAJOR="${MLCLAW_MIN_NODE_MAJOR:-22}"
5
+ NODE_VERSION="${MLCLAW_NODE_VERSION:-v24.16.0}"
6
+ PACKAGE_SPEC="${MLCLAW_NPM_SPEC:-mlclaw}"
7
+ CACHE_ROOT="${MLCLAW_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/mlclaw}"
8
+
9
+ die() {
10
+ printf 'mlclaw: %s\n' "$*" >&2
11
+ exit 1
12
+ }
13
+
14
+ node_major() {
15
+ "$1" -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || printf '0\n'
16
+ }
17
+
18
+ node_is_compatible() {
19
+ local node_bin="$1"
20
+ local major
21
+ major="$(node_major "$node_bin")"
22
+ [ "$major" -ge "$MIN_NODE_MAJOR" ]
23
+ }
24
+
25
+ system_node() {
26
+ if command -v node >/dev/null 2>&1 && node_is_compatible "$(command -v node)"; then
27
+ command -v node
28
+ fi
29
+ }
30
+
31
+ platform_name() {
32
+ case "$(uname -s)" in
33
+ Darwin) printf 'darwin' ;;
34
+ Linux) printf 'linux' ;;
35
+ *) die "unsupported operating system: $(uname -s)" ;;
36
+ esac
37
+ }
38
+
39
+ arch_name() {
40
+ case "$(uname -m)" in
41
+ x86_64 | amd64) printf 'x64' ;;
42
+ arm64 | aarch64) printf 'arm64' ;;
43
+ *) die "unsupported CPU architecture: $(uname -m)" ;;
44
+ esac
45
+ }
46
+
47
+ cached_node_dir() {
48
+ printf '%s/node/%s-%s-%s\n' "$CACHE_ROOT" "$NODE_VERSION" "$(platform_name)" "$(arch_name)"
49
+ }
50
+
51
+ install_node() {
52
+ command -v curl >/dev/null 2>&1 || die "curl is required to install Node.js"
53
+ command -v tar >/dev/null 2>&1 || die "tar is required to install Node.js"
54
+
55
+ local os arch archive_name url temp_dir target_dir
56
+ os="$(platform_name)"
57
+ arch="$(arch_name)"
58
+ archive_name="node-${NODE_VERSION}-${os}-${arch}"
59
+ url="https://nodejs.org/dist/${NODE_VERSION}/${archive_name}.tar.xz"
60
+ target_dir="$(cached_node_dir)"
61
+
62
+ if [ -x "$target_dir/bin/node" ] && node_is_compatible "$target_dir/bin/node"; then
63
+ printf '%s\n' "$target_dir"
64
+ return
65
+ fi
66
+
67
+ temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/mlclaw-node.XXXXXX")"
68
+ mkdir -p "$(dirname "$target_dir")"
69
+ printf 'mlclaw: installing Node.js %s into %s\n' "$NODE_VERSION" "$target_dir" >&2
70
+ if ! curl -fsSL "$url" -o "$temp_dir/node.tar.xz"; then
71
+ rm -rf "$temp_dir"
72
+ die "failed to download Node.js from $url"
73
+ fi
74
+ if ! tar -xJf "$temp_dir/node.tar.xz" -C "$temp_dir"; then
75
+ rm -rf "$temp_dir"
76
+ die "failed to unpack Node.js archive"
77
+ fi
78
+ rm -rf "$target_dir"
79
+ mv "$temp_dir/$archive_name" "$target_dir"
80
+ rm -rf "$temp_dir"
81
+ printf '%s\n' "$target_dir"
82
+ }
83
+
84
+ run_with_node() {
85
+ local node_bin="$1"
86
+ shift
87
+ local npm_cli node_path
88
+ node_path="$(dirname "$node_bin")"
89
+ npm_cli="$(dirname "$node_bin")/../lib/node_modules/npm/bin/npm-cli.js"
90
+ if [ -f "$npm_cli" ]; then
91
+ PATH="$node_path:$PATH" exec "$node_bin" "$npm_cli" exec --yes --package "$PACKAGE_SPEC" -- mlclaw "$@"
92
+ fi
93
+ if command -v npm >/dev/null 2>&1; then
94
+ PATH="$node_path:$PATH" exec npm exec --yes --package "$PACKAGE_SPEC" -- mlclaw "$@"
95
+ fi
96
+ die "npm was not found for Node runtime $node_bin"
97
+ }
98
+
99
+ node_has_npm() {
100
+ local node_bin="$1"
101
+ local node_path npm_cli
102
+ node_path="$(dirname "$node_bin")"
103
+ npm_cli="$(dirname "$node_bin")/../lib/node_modules/npm/bin/npm-cli.js"
104
+ [ -f "$npm_cli" ] || PATH="$node_path:$PATH" command -v npm >/dev/null 2>&1
105
+ }
106
+
107
+ main() {
108
+ local node_bin node_dir
109
+ node_bin="$(system_node || true)"
110
+ if [ -n "$node_bin" ] && node_has_npm "$node_bin"; then
111
+ run_with_node "$node_bin" "$@"
112
+ fi
113
+ node_dir="$(install_node)"
114
+ run_with_node "$node_dir/bin/node" "$@"
115
+ }
116
+
117
+ main "$@"
@@ -0,0 +1,67 @@
1
+ {
2
+ "agents": {
3
+ "defaults": {
4
+ "model": {
5
+ "primary": "${OPENCLAW_MODEL}"
6
+ }
7
+ }
8
+ },
9
+ "gateway": {
10
+ "mode": "local",
11
+ "bind": "loopback",
12
+ "port": 7861,
13
+ "auth": {
14
+ "mode": "trusted-proxy",
15
+ "trustedProxy": {
16
+ "userHeader": "x-forwarded-user",
17
+ "requiredHeaders": ["x-forwarded-proto", "x-forwarded-host"],
18
+ "allowLoopback": true
19
+ }
20
+ },
21
+ "trustedProxies": ["127.0.0.1", "::1"],
22
+ "controlUi": {
23
+ "dangerouslyDisableDeviceAuth": true,
24
+ "allowedOrigins": ["${MLCLAW_PUBLIC_URL}"]
25
+ }
26
+ },
27
+ "channels": {
28
+ "telegram": {
29
+ "enabled": false,
30
+ "botToken": "${TELEGRAM_BOT_TOKEN}",
31
+ "dmPolicy": "allowlist",
32
+ "allowFrom": [],
33
+ "timeoutSeconds": 45,
34
+ "pollingStallThresholdMs": 60000,
35
+ "commands": {
36
+ "native": false
37
+ },
38
+ "network": {
39
+ "autoSelectFamily": false,
40
+ "dnsResultOrder": "ipv4first"
41
+ }
42
+ }
43
+ },
44
+ "messages": {
45
+ "queue": {
46
+ "byChannel": {
47
+ "telegram": "collect"
48
+ },
49
+ "debounceMsByChannel": {
50
+ "telegram": 1500
51
+ },
52
+ "cap": 20,
53
+ "drop": "summarize"
54
+ }
55
+ },
56
+ "session": {
57
+ "dmScope": "per-channel-peer",
58
+ "reset": {
59
+ "mode": "daily",
60
+ "atHour": 4,
61
+ "idleMinutes": 1440
62
+ }
63
+ },
64
+ "cron": {
65
+ "enabled": true
66
+ }
67
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "mlclaw",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/osolmaz/mlclaw"
8
+ },
9
+ "homepage": "https://github.com/osolmaz/mlclaw#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/osolmaz/mlclaw/issues"
12
+ },
13
+ "type": "module",
14
+ "bin": {
15
+ "mlclaw": "dist/mlclaw.mjs"
16
+ },
17
+ "files": [
18
+ ".agents/skills/",
19
+ ".gitattributes",
20
+ "assets/",
21
+ "dist/hf-state-sync.js",
22
+ "dist/mlclaw.mjs",
23
+ "dist/mlclaw-space-runtime.js",
24
+ "Dockerfile",
25
+ "entrypoint.sh",
26
+ "mlclaw.ps1",
27
+ "mlclaw.sh",
28
+ "LICENSE",
29
+ "openclaw.default.json",
30
+ "README.md",
31
+ "scripts/configure-telegram.mjs",
32
+ "scripts/configure-huggingface-model.mjs",
33
+ "scripts/report-telegram-probe.mjs",
34
+ "space/",
35
+ "src/hf-bucket-client/",
36
+ "src/hf-state-sync/",
37
+ "src/vendor/",
38
+ "tsconfig.json"
39
+ ],
40
+ "scripts": {
41
+ "build": "npm run build:state-sync && npm run build:space-runtime && npm run build:mlclaw",
42
+ "build:state-sync": "esbuild src/hf-state-sync/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/hf-state-sync.js --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\"",
43
+ "build:space-runtime": "esbuild src/mlclaw-space-runtime/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/mlclaw-space-runtime.js --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\"",
44
+ "build:mlclaw": "esbuild src/mlclaw/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/mlclaw.mjs --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\"",
45
+ "build:probe": "esbuild scripts/parity-probe.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/parity-probe.mjs --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\"",
46
+ "pack:check": "node scripts/check-package.mjs",
47
+ "prepack": "npm run build",
48
+ "check:secrets": "node scripts/check-secrets.mjs",
49
+ "test": "vitest run",
50
+ "typecheck": "tsc --noEmit"
51
+ },
52
+ "dependencies": {
53
+ "@clack/prompts": "^1.4.0",
54
+ "@huggingface/splitmix64-wasm": "^0.0.1",
55
+ "@huggingface/xetchunk-wasm": "^0.0.6",
56
+ "commander": "^14.0.3",
57
+ "skillflag": "^0.2.0",
58
+ "zod": "^3.24.0"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^24.0.0",
62
+ "esbuild": "^0.25.0",
63
+ "typescript": "^5.7.0",
64
+ "vitest": "^3.0.0"
65
+ }
66
+ }
@@ -0,0 +1,86 @@
1
+ import fs from "node:fs";
2
+
3
+ const [configPath] = process.argv.slice(2);
4
+
5
+ if (!configPath) {
6
+ console.error("Usage: configure-huggingface-model.mjs <config-path>");
7
+ process.exit(2);
8
+ }
9
+
10
+ const modelRef = (process.env.OPENCLAW_MODEL || "").trim();
11
+ const prefix = "huggingface/";
12
+
13
+ if (!modelRef.startsWith(prefix)) {
14
+ process.exit(0);
15
+ }
16
+
17
+ const providerModelId = modelRef.slice(prefix.length).trim();
18
+ if (!providerModelId) {
19
+ process.exit(0);
20
+ }
21
+
22
+ function displayNameFromModelId(id) {
23
+ const withoutPolicy = id.replace(/:(cheapest|fastest)$/i, "");
24
+ const base = withoutPolicy.split("/").pop() || withoutPolicy;
25
+ return base.replace(/[-_]+/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
26
+ }
27
+
28
+ function isLikelyImageModel(id) {
29
+ const lower = id.toLowerCase();
30
+ return (
31
+ lower.includes("-vl") ||
32
+ lower.includes("vision") ||
33
+ lower.includes("multimodal") ||
34
+ lower.includes("gemma-3") ||
35
+ lower.includes("gemma-4") ||
36
+ lower.includes("llama-4")
37
+ );
38
+ }
39
+
40
+ function contextWindowForModel(id) {
41
+ const lower = id.toLowerCase();
42
+ if (lower.includes("gemma-4") || lower.includes("qwen3.6")) return 262144;
43
+ if (lower.includes("qwen3-8b") || lower.includes("qwen3-14b")) return 40960;
44
+ return 131072;
45
+ }
46
+
47
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
48
+ config.models ||= {};
49
+ config.models.providers ||= {};
50
+ config.models.providers.huggingface ||= {};
51
+
52
+ const provider = config.models.providers.huggingface;
53
+ provider.baseUrl ||= "https://router.huggingface.co/v1";
54
+ provider.api ||= "openai-completions";
55
+ provider.models = Array.isArray(provider.models) ? provider.models : [];
56
+
57
+ const defaultEntry = {
58
+ id: providerModelId,
59
+ name: displayNameFromModelId(providerModelId),
60
+ input: isLikelyImageModel(providerModelId) ? ["text", "image"] : ["text"],
61
+ contextWindow: contextWindowForModel(providerModelId),
62
+ maxTokens: 8192,
63
+ reasoning: /r1|reason|thinking|reasoner|qwq/i.test(providerModelId),
64
+ cost: {
65
+ input: 0,
66
+ output: 0,
67
+ cacheRead: 0,
68
+ cacheWrite: 0,
69
+ },
70
+ api: "openai-completions",
71
+ };
72
+
73
+ const existingIndex = provider.models.findIndex(
74
+ (entry) => entry && typeof entry === "object" && entry.id === providerModelId,
75
+ );
76
+
77
+ if (existingIndex >= 0) {
78
+ provider.models[existingIndex] = {
79
+ ...defaultEntry,
80
+ ...provider.models[existingIndex],
81
+ };
82
+ } else {
83
+ provider.models.push(defaultEntry);
84
+ }
85
+
86
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
@@ -0,0 +1,55 @@
1
+ import fs from "node:fs";
2
+
3
+ const [configPath, allowedUsersRaw] = process.argv.slice(2);
4
+
5
+ if (!configPath) {
6
+ console.error("Usage: configure-telegram.mjs <config-path> <allowed-users>");
7
+ process.exit(2);
8
+ }
9
+
10
+ const allowedUsers = (allowedUsersRaw || "")
11
+ .split(",")
12
+ .map((value) => value.trim())
13
+ .filter(Boolean);
14
+ const telegramProxy = (process.env.TELEGRAM_PROXY || "").trim();
15
+ const telegramApiRoot = (process.env.TELEGRAM_API_ROOT || "").trim().replace(/\/+$/, "");
16
+
17
+ if (allowedUsers.length === 0) {
18
+ process.exit(0);
19
+ }
20
+
21
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
22
+ config.channels ||= {};
23
+ config.channels.telegram ||= {};
24
+ config.messages ||= {};
25
+ config.messages.queue ||= {};
26
+ config.messages.queue.byChannel ||= {};
27
+ config.messages.queue.debounceMsByChannel ||= {};
28
+
29
+ config.messages.queue.byChannel.telegram ||= "collect";
30
+ config.messages.queue.debounceMsByChannel.telegram ??= 1500;
31
+ config.messages.queue.cap ??= 20;
32
+ config.messages.queue.drop ||= "summarize";
33
+
34
+ Object.assign(config.channels.telegram, {
35
+ enabled: true,
36
+ botToken: "${TELEGRAM_BOT_TOKEN}",
37
+ dmPolicy: "allowlist",
38
+ allowFrom: allowedUsers,
39
+ timeoutSeconds: 45,
40
+ pollingStallThresholdMs: 60000,
41
+ commands: {
42
+ ...(config.channels.telegram.commands || {}),
43
+ native: false
44
+ },
45
+ network: {
46
+ ...(config.channels.telegram.network || {}),
47
+ autoSelectFamily: false,
48
+ dnsResultOrder: "ipv4first"
49
+ },
50
+ ...(telegramProxy ? { proxy: "${TELEGRAM_PROXY}" } : {}),
51
+ ...(telegramApiRoot ? { apiRoot: "${TELEGRAM_API_ROOT}" } : {}),
52
+ groups: config.channels.telegram.groups || {}
53
+ });
54
+
55
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
@@ -0,0 +1,18 @@
1
+ import fs from "node:fs";
2
+
3
+ const [probePath] = process.argv.slice(2);
4
+
5
+ if (!probePath) {
6
+ process.exit(0);
7
+ }
8
+
9
+ try {
10
+ const payload = JSON.parse(fs.readFileSync(probePath, "utf8"));
11
+ if (payload?.ok && payload?.result?.username) {
12
+ console.log(`[telegram-probe] curl getMe ok (@${payload.result.username})`);
13
+ } else {
14
+ console.log("[telegram-probe] curl getMe returned an unexpected response");
15
+ }
16
+ } catch {
17
+ console.log("[telegram-probe] curl getMe response could not be parsed");
18
+ }
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: ML Claw
3
+ emoji: 🦞
4
+ colorFrom: yellow
5
+ colorTo: red
6
+ sdk: docker
7
+ app_port: 7860
8
+ hf_oauth: true
9
+ hf_oauth_expiration_minutes: 43200
10
+ pinned: false
11
+ secrets:
12
+ - HF_TOKEN
13
+ - MLCLAW_SESSION_SECRET
14
+ - OPENAI_API_KEY
15
+ ---
16
+
17
+ # ML Claw
18
+
19
+ <p align="center">
20
+ <img src="assets/mlclaw.svg" alt="ML Claw" width="160">
21
+ </p>
22
+
23
+ ML Claw runs OpenClaw in a Hugging Face Space with a browser gateway protected
24
+ by Hugging Face OAuth. Duplicate this Space or use `mlclaw bootstrap` to create
25
+ your own deployment.
26
+
27
+ The public Space process is an ML Claw proxy. It authenticates the signed-in
28
+ Hugging Face user, then forwards browser traffic to OpenClaw on loopback using
29
+ OpenClaw trusted-proxy auth. The browser never receives an OpenClaw gateway
30
+ token.
31
+
32
+ Durable state lives in a private Hugging Face Storage Bucket configured by
33
+ `OPENCLAW_HF_STATE_BUCKET`. The bucket is not mounted as a live filesystem;
34
+ `hf-state-sync` restores verified snapshots on boot and uploads new snapshots
35
+ during runtime and shutdown.
36
+
37
+ Manage an existing deployment from your machine with:
38
+
39
+ ```bash
40
+ mlclaw doctor <owner/space> --fix
41
+ mlclaw update <owner/space>
42
+ ```