README.md

  1# Crush
  2
  3<p align="center">
  4    <a href="https://stuff.charm.sh/crush/charm-crush.png"><img width="450" alt="Charm Crush Logo" src="https://github.com/user-attachments/assets/cf8ca3ce-8b02-43f0-9d0f-5a331488da4b" /></a><br />
  5    <a href="https://github.com/charmbracelet/crush/releases"><img src="https://img.shields.io/github/release/charmbracelet/crush" alt="Latest Release"></a>
  6    <a href="https://github.com/charmbracelet/crush/actions"><img src="https://github.com/charmbracelet/crush/actions/workflows/build.yml/badge.svg" alt="Build Status"></a>
  7</p>
  8
  9<p align="center">Your new coding bestie, now available in your favourite terminal.<br />Your tools, your code, and your workflows, wired into your LLM of choice.</p>
 10<p align="center">终端里的编程新搭档,<br />无缝接入你的工具、代码与工作流,全面兼容主流 LLM 模型。</p>
 11
 12<p align="center"><img width="800" alt="Crush Demo" src="https://github.com/user-attachments/assets/58280caf-851b-470a-b6f7-d5c4ea8a1968" /></p>
 13
 14## Features
 15
 16- **Multi-Model:** choose from a wide range of LLMs or add your own via OpenAI- or Anthropic-compatible APIs
 17- **Flexible:** switch LLMs mid-session while preserving context
 18- **Session-Based:** maintain multiple work sessions and contexts per project
 19- **LSP-Enhanced:** Crush uses LSPs for additional context, just like you do
 20- **Extensible:** add capabilities via MCPs (`http`, `stdio`, and `sse`)
 21- **Works Everywhere:** first-class support in every terminal on macOS, Linux, Windows (PowerShell and WSL), Android, FreeBSD, OpenBSD, and NetBSD
 22- **Industrial Grade:** built on the Charm ecosystem, powering 25k+ applications, from leading open source projects to business-critical infrastructure
 23
 24## Installation
 25
 26Use a package manager:
 27
 28```bash
 29# Homebrew
 30brew install charmbracelet/tap/crush
 31
 32# NPM
 33npm install -g @charmland/crush
 34
 35# Arch Linux (btw)
 36yay -S crush-bin
 37
 38# Nix
 39nix run github:numtide/nix-ai-tools#crush
 40
 41# FreeBSD
 42pkg install crush
 43```
 44
 45Windows users:
 46
 47```bash
 48# Winget
 49winget install charmbracelet.crush
 50
 51# Scoop
 52scoop bucket add charm https://github.com/charmbracelet/scoop-bucket.git
 53scoop install crush
 54```
 55
 56<details>
 57<summary><strong>Nix (NUR)</strong></summary>
 58
 59Crush is available via the official Charm [NUR](https://github.com/nix-community/NUR) in `nur.repos.charmbracelet.crush`, which is the most up-to-date way to get Crush in Nix.
 60
 61You can also try out Crush via the NUR with `nix-shell`:
 62
 63```bash
 64# Add the NUR channel.
 65nix-channel --add https://github.com/nix-community/NUR/archive/main.tar.gz nur
 66nix-channel --update
 67
 68# Get Crush in a Nix shell.
 69nix-shell -p '(import <nur> { pkgs = import <nixpkgs> {}; }).repos.charmbracelet.crush'
 70```
 71
 72### NixOS & Home Manager Module Usage via NUR
 73
 74Crush provides NixOS and Home Manager modules via NUR.
 75You can use these modules directly in your flake by importing them from NUR. Since it auto detects whether its a home manager or nixos context you can use the import the exact same way :)
 76
 77```nix
 78{
 79  inputs = {
 80    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
 81    nur.url = "github:nix-community/NUR";
 82  };
 83
 84  outputs = { self, nixpkgs, nur, ... }: {
 85    nixosConfigurations.your-hostname = nixpkgs.lib.nixosSystem {
 86      system = "x86_64-linux";
 87      modules = [
 88        nur.modules.nixos.default
 89        nur.repos.charmbracelet.modules.crush
 90        {
 91          programs.crush = {
 92            enable = true;
 93            settings = {
 94              providers = {
 95                openai = {
 96                  id = "openai";
 97                  name = "OpenAI";
 98                  base_url = "https://api.openai.com/v1";
 99                  type = "openai";
100                  api_key = "sk-fake123456789abcdef...";
101                  models = [
102                    {
103                      id = "gpt-4";
104                      name = "GPT-4";
105                    }
106                  ];
107                };
108              };
109              lsp = {
110                go = { command = "gopls"; enabled = true; };
111                nix = { command = "nil"; enabled = true; };
112              };
113              options = {
114                context_paths = [ "/etc/nixos/configuration.nix" ];
115                tui = { compact_mode = true; };
116                debug = false;
117              };
118            };
119          };
120        }
121      ];
122    };
123  };
124}
125```
126
127</details>
128
129<details>
130<summary><strong>Debian/Ubuntu</strong></summary>
131
132```bash
133sudo mkdir -p /etc/apt/keyrings
134curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
135echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
136sudo apt update && sudo apt install crush
137```
138
139</details>
140
141<details>
142<summary><strong>Fedora/RHEL</strong></summary>
143
144```bash
145echo '[charm]
146name=Charm
147baseurl=https://repo.charm.sh/yum/
148enabled=1
149gpgcheck=1
150gpgkey=https://repo.charm.sh/yum/gpg.key' | sudo tee /etc/yum.repos.d/charm.repo
151sudo yum install crush
152```
153
154</details>
155
156Or, download it:
157
158- [Packages][releases] are available in Debian and RPM formats
159- [Binaries][releases] are available for Linux, macOS, Windows, FreeBSD, OpenBSD, and NetBSD
160
161[releases]: https://github.com/charmbracelet/crush/releases
162
163Or just install it with Go:
164
165```
166go install github.com/charmbracelet/crush@latest
167```
168
169> [!WARNING]
170> Productivity may increase when using Crush and you may find yourself nerd
171> sniped when first using the application. If the symptoms persist, join the
172> [Discord][discord] and nerd snipe the rest of us.
173
174## Getting Started
175
176The quickest way to get started is to grab an API key for your preferred
177provider such as Anthropic, OpenAI, Groq, OpenRouter, or Vercel AI Gateway and just start
178Crush. You'll be prompted to enter your API key.
179
180That said, you can also set environment variables for preferred providers.
181
182| Environment Variable        | Provider                                           |
183| --------------------------- | -------------------------------------------------- |
184| `ANTHROPIC_API_KEY`         | Anthropic                                          |
185| `OPENAI_API_KEY`            | OpenAI                                             |
186| `VERCEL_API_KEY`            | Vercel AI Gateway                                  |
187| `GEMINI_API_KEY`            | Google Gemini                                      |
188| `SYNTHETIC_API_KEY`         | Synthetic                                          |
189| `ZAI_API_KEY`               | Z.ai                                               |
190| `MINIMAX_API_KEY`           | MiniMax                                            |
191| `HF_TOKEN`                  | Hugging Face Inference                             |
192| `CEREBRAS_API_KEY`          | Cerebras                                           |
193| `OPENROUTER_API_KEY`        | OpenRouter                                         |
194| `GROQ_API_KEY`              | Groq                                               |
195| `VERTEXAI_PROJECT`          | Google Cloud VertexAI (Gemini)                     |
196| `VERTEXAI_LOCATION`         | Google Cloud VertexAI (Gemini)                     |
197| `AWS_ACCESS_KEY_ID`         | Amazon Bedrock (Claude)                            |
198| `AWS_SECRET_ACCESS_KEY`     | Amazon Bedrock (Claude)                            |
199| `AWS_REGION`                | Amazon Bedrock (Claude)                            |
200| `AWS_PROFILE`               | Amazon Bedrock (Custom Profile)                    |
201| `AWS_BEARER_TOKEN_BEDROCK`  | Amazon Bedrock                                     |
202| `AZURE_OPENAI_API_ENDPOINT` | Azure OpenAI models                                |
203| `AZURE_OPENAI_API_KEY`      | Azure OpenAI models (optional when using Entra ID) |
204| `AZURE_OPENAI_API_VERSION`  | Azure OpenAI models                                |
205
206### Subscriptions
207
208If you prefer subscription-based usage, here are some plans that work well in
209Crush:
210
211- [Synthetic](https://synthetic.new/pricing)
212- [GLM Coding Plan](https://z.ai/subscribe)
213- [Kimi Code](https://www.kimi.com/membership/pricing)
214- [MiniMax Coding Plan](https://platform.minimax.io/subscribe/coding-plan)
215
216### By the Way
217
218Is there a provider you’d like to see in Crush? Is there an existing model that needs an update?
219
220Crush’s default model listing is managed in [Catwalk](https://github.com/charmbracelet/catwalk), a community-supported, open source repository of Crush-compatible models, and you’re welcome to contribute.
221
222<a href="https://github.com/charmbracelet/catwalk"><img width="174" height="174" alt="Catwalk Badge" src="https://github.com/user-attachments/assets/95b49515-fe82-4409-b10d-5beb0873787d" /></a>
223
224## Configuration
225
226Crush runs great with no configuration. That said, if you do need or want to
227customize Crush, configuration can be added either local to the project itself,
228or globally, with the following priority:
229
2301. `.crush.json`
2312. `crush.json`
2323. `$HOME/.config/crush/crush.json`
233
234Configuration itself is stored as a JSON object:
235
236```json
237{
238  "this-setting": { "this": "that" },
239  "that-setting": ["ceci", "cela"]
240}
241```
242
243As an additional note, Crush also stores ephemeral data, such as application state, in one additional location:
244
245```bash
246# Unix
247$HOME/.local/share/crush/crush.json
248
249# Windows
250%LOCALAPPDATA%\crush\crush.json
251```
252
253> [!TIP]
254> You can override the user and data config locations by setting:
255> * `CRUSH_GLOBAL_CONFIG`
256> * `CRUSH_GLOBAL_DATA`
257
258### LSPs
259
260Crush can use LSPs for additional context to help inform its decisions, just
261like you would. LSPs can be added manually like so:
262
263```json
264{
265  "$schema": "https://charm.land/crush.json",
266  "lsp": {
267    "go": {
268      "command": "gopls",
269      "env": {
270        "GOTOOLCHAIN": "go1.24.5"
271      }
272    },
273    "typescript": {
274      "command": "typescript-language-server",
275      "args": ["--stdio"]
276    },
277    "nix": {
278      "command": "nil"
279    }
280  }
281}
282```
283
284### MCPs
285
286Crush also supports Model Context Protocol (MCP) servers through three
287transport types: `stdio` for command-line servers, `http` for HTTP endpoints,
288and `sse` for Server-Sent Events. Environment variable expansion is supported
289using `$(echo $VAR)` syntax.
290
291```json
292{
293  "$schema": "https://charm.land/crush.json",
294  "mcp": {
295    "filesystem": {
296      "type": "stdio",
297      "command": "node",
298      "args": ["/path/to/mcp-server.js"],
299      "timeout": 120,
300      "disabled": false,
301      "disabled_tools": ["some-tool-name"],
302      "env": {
303        "NODE_ENV": "production"
304      }
305    },
306    "github": {
307      "type": "http",
308      "url": "https://api.githubcopilot.com/mcp/",
309      "timeout": 120,
310      "disabled": false,
311      "disabled_tools": ["create_issue", "create_pull_request"],
312      "headers": {
313        "Authorization": "Bearer $GH_PAT"
314      }
315    },
316    "streaming-service": {
317      "type": "sse",
318      "url": "https://example.com/mcp/sse",
319      "timeout": 120,
320      "disabled": false,
321      "headers": {
322        "API-Key": "$(echo $API_KEY)"
323      }
324    }
325  }
326}
327```
328
329### Ignoring Files
330
331Crush respects `.gitignore` files by default, but you can also create a
332`.crushignore` file to specify additional files and directories that Crush
333should ignore. This is useful for excluding files that you want in version
334control but don't want Crush to consider when providing context.
335
336The `.crushignore` file uses the same syntax as `.gitignore` and can be placed
337in the root of your project or in subdirectories.
338
339### Allowing Tools
340
341By default, Crush will ask you for permission before running tool calls. If
342you'd like, you can allow tools to be executed without prompting you for
343permissions. Use this with care.
344
345```json
346{
347  "$schema": "https://charm.land/crush.json",
348  "permissions": {
349    "allowed_tools": [
350      "view",
351      "ls",
352      "grep",
353      "edit",
354      "mcp_context7_get-library-doc"
355    ]
356  }
357}
358```
359
360You can also skip all permission prompts entirely by running Crush with the
361`--yolo` flag. Be very, very careful with this feature.
362
363### Disabling Built-In Tools
364
365If you'd like to prevent Crush from using certain built-in tools entirely, you
366can disable them via the `options.disabled_tools` list. Disabled tools are
367completely hidden from the agent.
368
369```json
370{
371  "$schema": "https://charm.land/crush.json",
372  "options": {
373    "disabled_tools": [
374      "bash",
375      "sourcegraph"
376    ]
377  }
378}
379```
380
381To disable tools from MCP servers, see the [MCP config section](#mcps).
382
383### Agent Skills
384
385Crush supports the [Agent Skills](https://agentskills.io) open standard for
386extending agent capabilities with reusable skill packages. Skills are folders
387containing a `SKILL.md` file with instructions that Crush can discover and
388activate on demand.
389
390Skills are discovered from:
391
392- `~/.config/crush/skills/` on Unix (default, can be overridden with `CRUSH_SKILLS_DIR`)
393- `%LOCALAPPDATA%\crush\skills\` on Windows (default, can be overridden with `CRUSH_SKILLS_DIR`)
394- Additional paths configured via `options.skills_paths`
395
396```jsonc
397{
398  "$schema": "https://charm.land/crush.json",
399  "options": {
400    "skills_paths": [
401      "~/.config/crush/skills", // Windows: "%LOCALAPPDATA%\\crush\\skills",
402      "./project-skills"
403    ]
404  }
405}
406```
407
408You can get started with example skills from [anthropics/skills](https://github.com/anthropics/skills):
409
410```bash
411# Unix
412mkdir -p ~/.config/crush/skills
413cd ~/.config/crush/skills
414git clone https://github.com/anthropics/skills.git _temp
415mv _temp/skills/* . && rm -rf _temp
416```
417
418```powershell
419# Windows (PowerShell)
420mkdir -Force "$env:LOCALAPPDATA\crush\skills"
421cd "$env:LOCALAPPDATA\crush\skills"
422git clone https://github.com/anthropics/skills.git _temp
423mv _temp/skills/* . ; rm -r -force _temp
424```
425
426### Initialization
427
428When you initialize a project, Crush analyzes your codebase and creates
429a context file that helps it work more effectively in future sessions.
430By default, this file is named `AGENTS.md`, but you can customize the
431name and location with the `initialize_as` option:
432
433```json
434{
435  "$schema": "https://charm.land/crush.json",
436  "options": {
437    "initialize_as": "AGENTS.md"
438  }
439}
440```
441
442This is useful if you prefer a different naming convention or want to
443place the file in a specific directory (e.g., `CRUSH.md` or
444`docs/LLMs.md`). Crush will fill the file with project-specific context
445like build commands, code patterns, and conventions it discovered during
446initialization.
447
448### Attribution Settings
449
450By default, Crush adds attribution information to Git commits and pull requests
451it creates. You can customize this behavior with the `attribution` option:
452
453```json
454{
455  "$schema": "https://charm.land/crush.json",
456  "options": {
457    "attribution": {
458      "trailer_style": "co-authored-by",
459      "generated_with": true
460    }
461  }
462}
463```
464
465- `trailer_style`: Controls the attribution trailer added to commit messages
466  (default: `assisted-by`)
467	- `assisted-by`: Adds `Assisted-by: [Model Name] via Crush <crush@charm.land>`
468	  (includes the model name)
469	- `co-authored-by`: Adds `Co-Authored-By: Crush <crush@charm.land>`
470	- `none`: No attribution trailer
471- `generated_with`: When true (default), adds `💘 Generated with Crush` line to
472  commit messages and PR descriptions
473
474### Custom Providers
475
476Crush supports custom provider configurations for both OpenAI-compatible and
477Anthropic-compatible APIs.
478
479> [!NOTE]
480> Note that we support two "types" for OpenAI. Make sure to choose the right one
481> to ensure the best experience!
482> * `openai` should be used when proxying or routing requests through OpenAI.
483> * `openai-compat` should be used when using non-OpenAI providers that have OpenAI-compatible APIs.
484
485#### OpenAI-Compatible APIs
486
487Here’s an example configuration for Deepseek, which uses an OpenAI-compatible
488API. Don't forget to set `DEEPSEEK_API_KEY` in your environment.
489
490```json
491{
492  "$schema": "https://charm.land/crush.json",
493  "providers": {
494    "deepseek": {
495      "type": "openai-compat",
496      "base_url": "https://api.deepseek.com/v1",
497      "api_key": "$DEEPSEEK_API_KEY",
498      "models": [
499        {
500          "id": "deepseek-chat",
501          "name": "Deepseek V3",
502          "cost_per_1m_in": 0.27,
503          "cost_per_1m_out": 1.1,
504          "cost_per_1m_in_cached": 0.07,
505          "cost_per_1m_out_cached": 1.1,
506          "context_window": 64000,
507          "default_max_tokens": 5000
508        }
509      ]
510    }
511  }
512}
513```
514
515#### Anthropic-Compatible APIs
516
517Custom Anthropic-compatible providers follow this format:
518
519```json
520{
521  "$schema": "https://charm.land/crush.json",
522  "providers": {
523    "custom-anthropic": {
524      "type": "anthropic",
525      "base_url": "https://api.anthropic.com/v1",
526      "api_key": "$ANTHROPIC_API_KEY",
527      "extra_headers": {
528        "anthropic-version": "2023-06-01"
529      },
530      "models": [
531        {
532          "id": "claude-sonnet-4-20250514",
533          "name": "Claude Sonnet 4",
534          "cost_per_1m_in": 3,
535          "cost_per_1m_out": 15,
536          "cost_per_1m_in_cached": 3.75,
537          "cost_per_1m_out_cached": 0.3,
538          "context_window": 200000,
539          "default_max_tokens": 50000,
540          "can_reason": true,
541          "supports_attachments": true
542        }
543      ]
544    }
545  }
546}
547```
548
549### Amazon Bedrock
550
551Crush currently supports running Anthropic models through Bedrock, with caching disabled.
552
553- A Bedrock provider will appear once you have AWS configured, i.e. `aws configure`
554- Crush also expects the `AWS_REGION` or `AWS_DEFAULT_REGION` to be set
555- To use a specific AWS profile set `AWS_PROFILE` in your environment, i.e. `AWS_PROFILE=myprofile crush`
556- Alternatively to `aws configure`, you can also just set `AWS_BEARER_TOKEN_BEDROCK`
557
558### Vertex AI Platform
559
560Vertex AI will appear in the list of available providers when `VERTEXAI_PROJECT` and `VERTEXAI_LOCATION` are set. You will also need to be authenticated:
561
562```bash
563gcloud auth application-default login
564```
565
566To add specific models to the configuration, configure as such:
567
568```json
569{
570  "$schema": "https://charm.land/crush.json",
571  "providers": {
572    "vertexai": {
573      "models": [
574        {
575          "id": "claude-sonnet-4@20250514",
576          "name": "VertexAI Sonnet 4",
577          "cost_per_1m_in": 3,
578          "cost_per_1m_out": 15,
579          "cost_per_1m_in_cached": 3.75,
580          "cost_per_1m_out_cached": 0.3,
581          "context_window": 200000,
582          "default_max_tokens": 50000,
583          "can_reason": true,
584          "supports_attachments": true
585        }
586      ]
587    }
588  }
589}
590```
591
592### Local Models
593
594Local models can also be configured via OpenAI-compatible API. Here are two common examples:
595
596#### Ollama
597
598```json
599{
600  "providers": {
601    "ollama": {
602      "name": "Ollama",
603      "base_url": "http://localhost:11434/v1/",
604      "type": "openai-compat",
605      "models": [
606        {
607          "name": "Qwen 3 30B",
608          "id": "qwen3:30b",
609          "context_window": 256000,
610          "default_max_tokens": 20000
611        }
612      ]
613    }
614  }
615}
616```
617
618#### LM Studio
619
620```json
621{
622  "providers": {
623    "lmstudio": {
624      "name": "LM Studio",
625      "base_url": "http://localhost:1234/v1/",
626      "type": "openai-compat",
627      "models": [
628        {
629          "name": "Qwen 3 30B",
630          "id": "qwen/qwen3-30b-a3b-2507",
631          "context_window": 256000,
632          "default_max_tokens": 20000
633        }
634      ]
635    }
636  }
637}
638```
639
640## Logging
641
642Sometimes you need to look at logs. Luckily, Crush logs all sorts of
643stuff. Logs are stored in `./.crush/logs/crush.log` relative to the project.
644
645The CLI also contains some helper commands to make perusing recent logs easier:
646
647```bash
648# Print the last 1000 lines
649crush logs
650
651# Print the last 500 lines
652crush logs --tail 500
653
654# Follow logs in real time
655crush logs --follow
656```
657
658Want more logging? Run `crush` with the `--debug` flag, or enable it in the
659config:
660
661```json
662{
663  "$schema": "https://charm.land/crush.json",
664  "options": {
665    "debug": true,
666    "debug_lsp": true
667  }
668}
669```
670
671## Provider Auto-Updates
672
673By default, Crush automatically checks for the latest and greatest list of
674providers and models from [Catwalk](https://github.com/charmbracelet/catwalk),
675the open source Crush provider database. This means that when new providers and
676models are available, or when model metadata changes, Crush automatically
677updates your local configuration.
678
679### Disabling automatic provider updates
680
681For those with restricted internet access, or those who prefer to work in
682air-gapped environments, this might not be want you want, and this feature can
683be disabled.
684
685To disable automatic provider updates, set `disable_provider_auto_update` into
686your `crush.json` config:
687
688```json
689{
690  "$schema": "https://charm.land/crush.json",
691  "options": {
692    "disable_provider_auto_update": true
693  }
694}
695```
696
697Or set the `CRUSH_DISABLE_PROVIDER_AUTO_UPDATE` environment variable:
698
699```bash
700export CRUSH_DISABLE_PROVIDER_AUTO_UPDATE=1
701```
702
703### Manually updating providers
704
705Manually updating providers is possible with the `crush update-providers`
706command:
707
708```bash
709# Update providers remotely from Catwalk.
710crush update-providers
711
712# Update providers from a custom Catwalk base URL.
713crush update-providers https://example.com/
714
715# Update providers from a local file.
716crush update-providers /path/to/local-providers.json
717
718# Reset providers to the embedded version, embedded at crush at build time.
719crush update-providers embedded
720
721# For more info:
722crush update-providers --help
723```
724
725## Metrics
726
727Crush records pseudonymous usage metrics (tied to a device-specific hash),
728which maintainers rely on to inform development and support priorities. The
729metrics include solely usage metadata; prompts and responses are NEVER
730collected.
731
732Details on exactly what’s collected are in the source code ([here](https://github.com/charmbracelet/crush/tree/main/internal/event)
733and [here](https://github.com/charmbracelet/crush/blob/main/internal/llm/agent/event.go)).
734
735You can opt out of metrics collection at any time by setting the environment
736variable by setting the following in your environment:
737
738```bash
739export CRUSH_DISABLE_METRICS=1
740```
741
742Or by setting the following in your config:
743
744```json
745{
746  "options": {
747    "disable_metrics": true
748  }
749}
750```
751
752Crush also respects the `DO_NOT_TRACK` convention which can be enabled via
753`export DO_NOT_TRACK=1`.
754
755## Contributing
756
757See the [contributing guide](https://github.com/charmbracelet/crush?tab=contributing-ov-file#contributing).
758
759## Whatcha think?
760
761We’d love to hear your thoughts on this project. Need help? We gotchu. You can find us on:
762
763- [Twitter](https://twitter.com/charmcli)
764- [Slack](https://charm.land/slack)
765- [Discord][discord]
766- [The Fediverse](https://mastodon.social/@charmcli)
767- [Bluesky](https://bsky.app/profile/charm.land)
768
769[discord]: https://charm.land/discord
770
771## License
772
773[FSL-1.1-MIT](https://github.com/charmbracelet/crush/raw/main/LICENSE.md)
774
775---
776
777Part of [Charm](https://charm.land).
778
779<a href="https://charm.land/"><img alt="The Charm logo" width="400" src="https://stuff.charm.sh/charm-banner-next.jpg" /></a>
780
781<!--prettier-ignore-->
782Charm热爱开源 • Charm loves open source