Commit graph

279 commits

Author SHA1 Message Date
Daniel Han
92e7563fa2
Document install env vars in README advanced launch options (#5972)
Add copyable per-platform examples for UNSLOTH_NO_TORCH, UNSLOTH_PYTHON and
UNSLOTH_STUDIO_HOME (curl | sh after the pipe; $env: before irm | iex), and
move the UNSLOTH_CPU_THREADS note to the end of the section.
2026-06-03 05:39:38 -07:00
Michael Han
6bf101107a
Removing unsloth studio update.md 2026-05-31 07:31:27 -07:00
Daniel Han
998feaf9d0 Update README.md 2026-05-31 07:29:24 -07:00
mervivian
faea796c35
fix: wrong code block language for nightly Windows install (#5877)
Co-authored-by: danielhanchen <michaelhan2050@gmail.com>
2026-05-30 21:15:40 -07:00
Daniel Han
131fa4fcae
Trim README Advanced launch options blurb (#5809) 2026-05-27 05:15:45 -07:00
alkinun
9222ffd9b4
Studio: add configurable CPU thread pool limit (#5760)
* Studio: add configurable CPU thread pool limit

* Studio: report invalid CPU thread setting cleanly

* Studio: also cover uvicorn main:app, harden tests, move docs to advanced

---------

Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com>
Co-authored-by: danielhanchen <danielhanchen@gmail.com>
2026-05-27 05:09:34 -07:00
Michael Han
b180ae7dd6
Adding Connect a Provider README changes 2026-05-21 05:08:59 -07:00
Daniel Han
a74a1080e0
Move uninstall scripts into scripts/ and fix references (#5644)
* Move uninstall scripts into scripts/ and fix all references

Relocates `uninstall.sh` and `uninstall.ps1` from the repo root into
the existing `scripts/` directory, alongside the other helper scripts.

Reference fixes:
* `README.md`: Studio uninstall instructions now point at the raw
  GitHub URLs under `scripts/`. The previous `unsloth.ai/uninstall.*`
  short URLs currently 404 (unlike `unsloth.ai/install.sh`, which
  301s to the raw github URL), so the raw URL is the working entry
  point until that redirect is configured.
* `scripts/uninstall.sh` header `Usage:` example updated to the new
  raw GitHub path.
* `scripts/uninstall.ps1` header `Usage:` example updated to the new
  raw GitHub path.
* `.github/workflows/studio-update-smoke.yml`: `paths:` trigger and
  round-trip exec/exists checks now use `scripts/uninstall.sh`.
* `.github/workflows/studio-mac-update-smoke.yml`: same.
* `.github/workflows/studio-windows-update-smoke.yml`: `paths:`
  trigger and round-trip exec/exists checks now use
  `scripts/uninstall.ps1`.

The in-script help hints (e.g. `sh uninstall.sh`, `.\uninstall.ps1`)
are left unchanged because they are user-facing examples shown after
the user already has the file locally, and the basename form works
regardless of which directory the user downloaded the script into.

Follow-up note for unsloth.ai: once this lands, please add the
`unsloth.ai/uninstall.sh` and `unsloth.ai/uninstall.ps1` short-URL
redirects to `raw.githubusercontent.com/unslothai/unsloth/main/scripts/...`
(matching the existing `unsloth.ai/install.sh` redirect pattern).

* Update remaining uninstall script help hints for new scripts/ path

Three user-facing strings inside the uninstall scripts still showed
the old basename form, which became misleading after the move:

* `scripts/uninstall.ps1` header `# Local:` example: now references
  `.\scripts\uninstall.ps1` (the actual path from the cloned repo
  root).
* `scripts/uninstall.sh` env-var re-run hint: now shows the canonical
  curl-pipe form documented in README, since callers who came via
  `curl -fsSL ... | sh` never had a local `uninstall.sh` to invoke.
* `scripts/uninstall.ps1` env-var re-run hint: same, switched to the
  `irm ... | iex` form documented in README.

Pure string changes, no behavior change.

---------

Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
2026-05-20 04:42:03 -07:00
Daniel Han
47167885b0
studio: add uninstall.ps1 for Windows (#5513)
* studio: add uninstall.ps1 and document it in README for Windows

The previous Windows uninstall guidance was Remove-Item -Recurse -Force on
$HOME\.unsloth\studio, which only deletes the install dir and leaves
behind:

  * %LOCALAPPDATA%\Unsloth Studio                 (data dir)
  * Desktop\Unsloth Studio.lnk                    (Desktop shortcut)
  * %APPDATA%\Microsoft\Windows\Start Menu\Programs\Unsloth Studio.lnk
  * Custom UNSLOTH_STUDIO_HOME / STUDIO_HOME roots
  * Running unsloth_studio venv processes
  * User PATH entry under .unsloth\studio
  * HKCU\Software\Unsloth\PathBackup

This script mirrors uninstall.sh for Windows. It stops listening backends
by reading the port from share\studio.port (with a Win32_Process sweep
anchored on \unsloth_studio\ as a fallback), removes the install dir,
data dir, both shortcuts, the Studio PATH entry, and the PathBackup
registry key. Custom roots discovered from env vars or share\studio.conf
are accepted only if they contain a Studio sentinel (share\studio.conf,
unsloth_studio\.unsloth-studio-owned, or bin\unsloth.exe) and are not
on a hard deny list (drive root, %USERPROFILE%, parent of %USERPROFILE%,
or top-level system paths).

README now points Windows users at the script.

* Scope port-file kill and PATH cleanup to known Studio roots for PR #5513

Three findings from the reviewer round:

1. _StopByPortFile killed whatever owned the recorded port without proving
   the PID belonged to this Studio install. A stale studio.port pointing
   at a port a different local service later bound would force-kill that
   service. New _PidUnderKnownRoot checks the listening PID's exe path
   against the same $KnownRoots that _StopStudioProcesses already uses.

2. The netstat.exe fallback matched ":$port " anywhere in the line, so a
   stale port file with 443 (or any common port) could match an
   ESTABLISHED row whose remote endpoint was that port, killing an
   unrelated process (browser, IDE). Now requires the row contain
   LISTENING, and applies the same _PidUnderKnownRoot ownership check.

3. PATH cleanup removed any entry whose expanded path contained
   \unsloth_studio\, which would also clobber an unrelated user virtualenv
   that shared the name. Now only removes entries that resolve inside a
   known Studio root (default %USERPROFILE%\.unsloth\studio plus any
   custom roots discovered from UNSLOTH_STUDIO_HOME / STUDIO_HOME /
   share\studio.conf).

* Expand tilde and honor UNSLOTH_STUDIO_HOME precedence for PR #5513

Two findings from the latest review round:

1. install.ps1 (lines 152-154) expands ~ and ~\path to $env:USERPROFILE
   before resolving the install root, but uninstall.ps1 was passing the
   raw env value to [System.IO.Path]::GetFullPath. That resolved ~\foo
   relative to the current directory rather than the user profile, so a
   user who installed with UNSLOTH_STUDIO_HOME='~\custom' could not
   uninstall through the same variable. New _ExpandTilde helper matches
   install.ps1's behavior.

2. Mirror install.ps1's env-var precedence: UNSLOTH_STUDIO_HOME wins,
   STUDIO_HOME is ignored when both are set. Otherwise uninstalling
   install A could also touch install B if the user has a stale
   STUDIO_HOME pointing at B.

---------

Co-authored-by: Daniel Han <info@unsloth.ai>
2026-05-18 02:32:24 -07:00
Michael Han
c41ce170ec
studio: add uninstall.sh and document it in README (#5497)
* studio: add uninstall.sh and document it in README

The current uninstall guidance in README.md is `rm -rf ~/.unsloth/studio`,
which leaves behind everything that lives outside that path:

  - ~/.local/share/unsloth/ (launcher script, studio.conf, studio.log,
    icon assets)
  - ~/Applications/Unsloth Studio.app (macOS bundle, orphaned and
    pointing nowhere on next reinstall)
  - ~/Desktop/Unsloth Studio (broken symlink after the bundle is gone)
  - ~/Desktop/unsloth-studio.desktop (Linux)
  - ~/.local/share/applications/unsloth-studio.desktop (Linux)
  - /tmp/unsloth-studio-launcher-<uid>*.lock (lock dir, possibly stale)
  - Launch Services cache entry for ai.unsloth.studio on macOS
  - Any running `unsloth studio -p N` processes

Users who follow the documented uninstall and reinstall end up with the
new launcher layered on top of stale state from the previous install,
which has produced concrete bugs (e.g. self-referential symlink inside
the .app bundle after a reinstall over leftover state).

Add uninstall.sh at the repo root that handles all of the above, and
update README.md to point at it as the recommended path. The plain
`rm -rf ~/.unsloth/studio` line is kept as a "partial uninstall, keep
launcher for a later reinstall" alternative. The model cache at
~/.cache/huggingface is intentionally left untouched, with a note in
the script suggesting how to remove it if desired.

Script is POSIX sh, idempotent (every removal is gated on existence
and uses `2>/dev/null || true`), and handles macOS, Linux, and WSL.
Windows is intentionally not covered here; the existing PowerShell
Remove-Item line in README is kept for that.

* studio: trim uninstall.sh header

* studio: address PR review feedback on uninstall.sh

Four findings from automated review, all verified real:

1. pkill pattern only matched `-p N`, not `--port N`. Studio
   instances launched with the long option form survived the
   uninstall. Fix: run two pkill passes, one for each form, with
   `[ =]` covering both space and `=` separators.

2. CLI shim at ~/.local/bin/unsloth (symlink into the venv created
   by install.sh:2167) was left behind, becoming a broken symlink
   after the venv directory is removed. Fix: add it to the removals.

3. Custom install roots via UNSLOTH_STUDIO_HOME / STUDIO_HOME were
   not removed. install.sh records the install location in
   ~/.local/share/unsloth/studio.conf as UNSLOTH_EXE; parse it,
   derive the root as three dirnames up, and remove the root if it
   is non-default.

4. On WSL the installer creates 'Unsloth Studio.lnk' on the Windows
   Desktop and Start Menu Programs folder via powershell.exe.
   Mirror that path on uninstall by invoking powershell.exe to
   Remove-Item the same two locations. Best-effort, gated on
   powershell.exe being available.

Tests (T2.8b, T2.15, T2.16, T2.17, T2.18, T2.5b) added behind the
scenes; all pass on macOS Darwin 25.3 with `dash -n`, `sh -n`,
shellcheck-clean (SC2016 suppressed on the PowerShell single-quoted
heredoc since the $env: expansions must remain literal to the
shell so PowerShell receives them verbatim).

* studio: harden uninstall.sh against env-mode and shim collisions

- Honor UNSLOTH_STUDIO_HOME / STUDIO_HOME at uninstall time and read
  env-mode studio.conf at $<root>/share/studio.conf, not just the
  default-mode conf under $HOME/.local/share/unsloth/. Without this,
  installs done with a custom STUDIO_HOME leak the install tree even
  when the env var is re-exported.
- Guard the custom-root resolver against "/" and empty so a corrupted
  studio.conf (UNSLOTH_EXE='/etc/passwd' or similar) or an
  UNSLOTH_STUDIO_HOME=/ cannot trick the script into rm -rf'ing root.
- Only remove $HOME/.local/bin/unsloth when it is a symlink resolving
  to a Studio venv. pyproject.toml declares unsloth as a console
  script, so pip install --user unsloth places a regular file at the
  same path; the previous unconditional rm wiped that unrelated CLI.
- When neither env var is set, print a tail hint so users with custom
  install roots know to re-run with the variable.

Verified with a sandboxed harness covering 24 scenarios (default and
env-mode installs across macOS / Linux / WSL, idempotency, hostile
lockfile names, path-traversal attempts, malformed conf, pkill long
and short forms, pip-conflict shim, broken-symlink bundle path).
Script remains POSIX (shellcheck -s sh clean, runs under /bin/dash).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Refuse non-Studio uninstall roots and tighten process matching for PR #5497

Three issues found while testing custom-root paths and process cleanup:

1. UNSLOTH_STUDIO_HOME=$HOME sh uninstall.sh rm -rf'd $HOME (same for
   STUDIO_HOME and parent-of-$HOME). install.sh accepts any writable
   directory for STUDIO_HOME, so the uninstaller must validate ownership
   before deletion. _is_studio_root accepts a candidate root only if it
   contains share/studio.conf, an unsloth_studio/ directory, or a
   bin/unsloth shim pointing into unsloth_studio/bin. _is_unsafe_root is
   a defense-in-depth deny list (/, $HOME, $HOME's parent, system paths).

2. pkill -f patterns "unsloth studio.*-p[ =][0-9]" over-matched on argv
   substrings. A user running `less notes.md` whose filename contained
   "unsloth studio ... -p N" had their less killed. New patterns anchor
   on /unsloth_studio/bin/ so only processes whose actual exe lives in a
   Studio venv match.

3. pkill missed processes that exec into studio/backend/run.py --port N
   (the post-exec form when the unsloth CLI replaces itself). Added a
   third pattern for that shape, and prefer PID files written by
   install.sh's _spawn_terminal (studio-$port.pid in DATA_DIR) over
   argv matching for installs that have them.

* Tighten ownership guards from review round for PR #5497

Three findings from the second reviewer round:

1. _is_studio_root accepted any directory containing an unsloth_studio/
   subdir as Studio-owned. A user workspace that happens to contain a
   folder named unsloth_studio/ would be deleted. install.sh's env-mode
   guard at install.sh:1358-1361 already requires .unsloth-studio-owned
   before treating the venv as replaceable. Mirror that: require the
   owner marker, share/studio.conf, or the bin/unsloth shim target.

2. The pkill -f fallback patterns were global, so uninstalling install A
   would also kill install B's running server. Scope each pattern to the
   actual install root being removed by interpolating the root path into
   the regex. Also adds a third pattern shape for `unsloth studio` with
   no -p / --port flag (the CLI default-port form).

3. Desktop/Unsloth Studio is created by install.sh as a symlink to the
   .app bundle. If a user has a regular directory by that name (photos,
   notes, etc.), the previous _remove_path call rm -rf'd it. Now we only
   remove it when it is a symlink or does not exist.

* Canonicalize env roots and honor UNSLOTH_STUDIO_HOME precedence for PR #5497

Two findings from the latest review round:

1. Canonicalize env-derived roots before the safety check. The deny list
   only string-compares against $HOME, so a syntactic variant like
   UNSLOTH_STUDIO_HOME=$HOME/../$USER (or trailing slash, or relative
   path) bypassed _is_unsafe_root even though it resolves to $HOME. Now
   _emit runs CDPATH= cd -P -- + pwd -P first, so all variants normalize
   to the same canonical path before the deny check. Also added the same
   tilde expansion install.sh's _resolve_studio_destinations does.

2. Mirror install.sh's env-var precedence (install.sh:282-290). When
   both UNSLOTH_STUDIO_HOME and STUDIO_HOME are set, install.sh resolves
   only UNSLOTH_STUDIO_HOME and ignores STUDIO_HOME. Uninstall was
   emitting both, so running uninstall.sh for install A would also
   delete install B if the user had a stale STUDIO_HOME pointing at B.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Han <info@unsloth.ai>
2026-05-18 02:11:05 -07:00
Michael Han
9bab3b955e
Add API Inference endpoint 2026-05-05 06:13:35 -07:00
Roland Tannous
35ab5da93c
Default Studio host to 127.0.0.1 and prompt before auto-start (#5267)
Studio bound to 0.0.0.0 by default and the installer silently auto-started
a server at end of install, exposing it on the network without consent and
contradicting the privacy-first / local-only guarantee.

- studio/backend/run.py: run_server() and argparse --host default to 127.0.0.1
- unsloth_cli/commands/studio.py: studio_default() and run() --host default to 127.0.0.1
- install.sh: drop -H 0.0.0.0 from generated launcher template; replace silent
  auto-start with a [Y/n] prompt; add cloud/network note to manual hint
- install.ps1: drop -H 0.0.0.0 from PowerShell launcher template; replace
  silent auto-start with a Read-Host [Y/n] prompt; add cloud/network note
- studio/setup.sh: drop -H 0.0.0.0 from launch hint; add cloud/network note
- README.md: simplify launch examples to `unsloth studio -p 8888`; note
  -H 0.0.0.0 is available for cloud/LAN use

Tests:
- studio/backend/tests/test_host_defaults.py
- tests/studio/test_cli_studio_defaults.py
- tests/sh/test_install_host_defaults.sh
2026-05-04 13:03:16 +04:00
Michael Han
b24f3f61b8
Update README.md 2026-04-20 00:37:40 -07:00
Michael Han
f5eec8a6f2
Qwen3.6 and ReadMe revamp.md 2026-04-19 23:16:36 -07:00
Michael Han
c1685b9459
Gemma 4 update.md 2026-04-02 22:54:03 -07:00
Michael Han
fbfcbc69f2
Update README.md 2026-03-30 01:34:36 -07:00
Michael Han
d2b8ed8def
Update install.md 2026-03-30 01:33:33 -07:00
Michael Han
0ffac92cf4
Update Install instructions.md 2026-03-27 03:04:07 -07:00
Michael Han
19298a0b41
Update Uninstall instructions.md 2026-03-27 02:56:34 -07:00
Michael Han
d4e9b708bb
Update Install instructions.md 2026-03-25 19:55:30 -07:00
Michael Han
d3049db427
Update install instructions.md 2026-03-25 19:04:10 -07:00
Roland Tannous
88a6dfc5cd Revert "Update README.md"
This reverts commit c30e1d2029.
2026-03-25 19:54:12 +00:00
Roland Tannous
c30e1d2029
Update README.md
remove newline from windows command
2026-03-25 23:26:37 +04:00
Daniel Han
9fa67809e6 Update README.md 2026-03-25 09:43:55 -07:00
Roland Tannous
c23c3a17e9
Update README.md (#4604)
Update install instructions for studio
2026-03-25 09:42:32 -07:00
Michael Han
a41dbb6ab2
Add r/unsloth Reddit.md 2026-03-24 04:13:38 -07:00
Michael Han
381f509695
Adding Qwen3.5 RL.md 2026-03-24 04:06:23 -07:00
Michael Han
d50e605f08
Update README.md 2026-03-21 14:55:18 -07:00
Sridhar Nandigam
a20c824711
FIX: Broken link to NVIDIA DataDesigner in README (#4500) 2026-03-21 14:41:09 -07:00
Michael Han
f113f3511d
Update Install method.md 2026-03-20 05:17:05 -07:00
Michael Han
07aabf45c0
Update Install instructions.md 2026-03-19 21:51:10 -07:00
Michael Han
29270a3726
Data recipes now works for Mac and CPU.md 2026-03-19 07:26:28 -07:00
Daniel Han
709a611356 Update README.md 2026-03-19 02:28:53 -07:00
Daniel Han
2b8bfa5b19 Update README.md 2026-03-19 02:26:18 -07:00
Manan Shah
6f129a214b
Fix Install commands for Windows + 1 line installs (#4447)
* One liner setup for unsloth studio

* Fix install scripts: system deps, activation bugs, curl/wget support

- install.sh: detect platform (macOS/Linux/WSL) and check for missing
  system dependencies (cmake, git, build-essential, libcurl4-openssl-dev).
  Prompt user once for permission to install all missing packages via
  brew (macOS) or sudo apt-get (Linux/WSL). Add wget fallback via
  download() helper since curl is not always present on minimal Linux
  installs. Fix nested curl|sh stdin stealing by downloading uv installer
  to a tempfile first. Replace venv activation (no-op in a pipe subshell)
  with explicit --python flag for uv pip install and direct venv binary
  invocation. Add idempotency guard for venv creation. Redirect stdin
  on unsloth studio setup to prevent pipe consumption. On macOS, check
  for Xcode Command Line Tools and trigger install if missing.

- install.ps1: wrap script body in Install-UnslothStudio function so
  that errors use return instead of exit (exit kills the terminal when
  run via irm|iex). Remove activate.ps1 invocation entirely -- use
  explicit --python path for uv pip install and & $UnslothExe for
  studio setup. This avoids both the child-scope activation bug (& vs
  dot-source) and the execution policy error on default Windows systems.
  Add winget availability check with clear error message. Fix PATH
  refresh to append registry paths instead of replacing the session PATH.
  Add uv installer fallback via astral.sh PowerShell script if winget
  install does not put uv on PATH. Broaden Python version check to
  accept 3.11-3.13. Add idempotency guard for venv creation.

- README.md: add wget one-liner alternative for systems without curl.

* Fix Tailwind CSS v4 .gitignore bug on Windows (#4444)

- Add .gitignore hiding workaround to setup.ps1 (matching existing
  setup.sh logic) so venv .gitignore files containing "*" don't prevent
  Tailwind's oxide scanner from finding .tsx source files
- Add CSS size validation to setup.sh, setup.ps1, and build.sh to catch
  truncated Tailwind builds early
- Remove stray force-rebuild overrides that made the "skip build if
  current" cache check dead code in both setup scripts
- Add rm -rf dist to build.sh to force clean rebuilds for wheel packaging

* Change default port 8000 to 8888, fix installer bugs, improve UX

- Change default Studio port from 8000 to 8888 across all entry points
  (run.py, studio.py, ui.py, colab.py, vite.config.ts, setup scripts)
- Update launch banner: "Launching with studio venv..." to
  "Launching Unsloth Studio... Please wait..."
- Add "Open your web browser" banner and rename labels
  (Local -> Local Access, External -> Worldwide Web Address)
- Fix venv idempotency: check for bin/python instead of just directory
  existence, clean up partial venvs on retry
- Fix build.sh CSS validation: handle empty CSS case that silently
  bypassed the check with "integer expression expected"
- Fix install.sh sudo handling: try apt-get without sudo first (works
  when root), then escalate with per-package tracking and user prompt
- Fix install.ps1: check exit code from studio setup, fail on error
- Add pciutils to WSL GGUF build dependencies
- Apply same smart apt-get escalation pattern to studio/setup.sh

* Use detected Python version for venv, abort on non-apt Linux

- install.ps1: detect existing Python 3.11/3.12/3.13 and use that
  version for venv creation instead of always forcing 3.13
- install.sh: exit with error on non-apt Linux distros when required
  packages cannot be auto-installed, instead of silently continuing

* Make sudo permission prompt more prominent with warning banner

* Add Accept [Y/n] sudo prompt to studio/setup.sh for consistency

* Fix native command exit code handling and sudo decline flow

install.ps1: Add $LASTEXITCODE checks after winget (Python), uv venv,
and uv pip install calls. $ErrorActionPreference only catches PowerShell
cmdlet errors, not native executable failures. The Python check also
handles winget returning non-zero for "already installed".

setup.sh: Skip llama-server build when user declines sudo or sudo is
unavailable. Previously the script continued to section 8 which would
fail with confusing errors (e.g. "gcc: command not found") since
build-essential was never installed.

* Move rm -rf llama.cpp inside build branch to preserve existing install

When _SKIP_GGUF_BUILD is set (user declined sudo or sudo unavailable),
the previous rm -rf would destroy an already-working llama-server before
the skip check ran. Move it inside the else branch so existing builds
are preserved when the rebuild is skipped.

---------

Co-authored-by: Daniel Han <danielhanchen@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-03-19 02:09:09 -07:00
Michael Han
e6a42d0073
Update Install instructions.md 2026-03-18 20:13:48 -07:00
Michael Han
6f9d8ad4c3
Add BETA in README.md 2026-03-18 17:15:10 -07:00
Daniel Han
8b4a0f2191 Update README.md 2026-03-18 11:13:12 -07:00
Daniel Han
e0a9e772d1 Update README.md 2026-03-18 09:57:44 -07:00
Datta Nimmaturi
d4c8c0cb84
Make instructions mac friendly (#4432) 2026-03-18 09:48:02 -07:00
Michael Han
7d270825fb
Update README.md 2026-03-18 08:30:53 -07:00
Daniel Han
11b5e7abf3 Update README.md 2026-03-18 08:15:07 -07:00
Daniel Han
d45abae5b3 Update README.md 2026-03-18 08:12:20 -07:00
Daniel Han
2a7646c4ca Update README.md 2026-03-18 07:27:04 -07:00
Daniel Han
6bf81e4a48 Update README.md 2026-03-18 06:59:37 -07:00
Daniel Han
38217bcdcc Update README.md 2026-03-18 06:58:42 -07:00
Daniel Han
9c89d7b22b Update README.md 2026-03-18 06:52:27 -07:00
Daniel Han
7517e0fb2f Update README.md 2026-03-18 06:33:54 -07:00
Michael Han
0922a2bb17
Update README.md 2026-03-18 04:21:18 -07:00
Michael Han
67d3519cab
Update README.md 2026-03-17 23:04:54 -07:00