Mason Voice Input 是一个面向 macOS 的轻量语音输入工具:按快捷键或执行命令开始录音,把音频发送到一个 兼容的语音转文字 HTTP 服务,拿到文字后自动写入剪贴板,并可 自动粘贴到当前光标位置。
它适合想要“像系统语音输入一样快速说话输入”,但又希望识别服务可以自由选择的人:你可以接 自建 faster-whisper/Whisper 服务,也可以通过一个轻量 adapter 接 第三方语音转文字服务。当前版本 不是完整输入法,也 不内置 Whisper 模型;它是一个本地客户端,需要配合一个 HTTP 语音转文字服务使用。
很多输入法都已经支持语音输入,但它们通常是一个完整的输入法产品:功能多、接入深、权限也更多。以常见商业输入法为例,语音转文字往往需要在使用时上传语音到服务端处理,部分增强功能还可能涉及输入内容、剪贴板、词库、跨设备同步等数据处理。
Mason Voice Input 想解决的是另一个更小、更透明的问题:
我只想在 macOS 上快速说一句话,把它变成文字,并粘贴到当前输入框;识别服务、数据流向和接口格式都由我自己决定。
所以这个项目刻意保持简单:
- 不是完整输入法:不接管系统输入法,不做候选词、词库、联想、账号体系。
- 客户端不内置云服务:默认只请求你配置的
api_url,可以是本机服务、远程自建服务,或你自己写的第三方 adapter。 - 接口透明:音频以
multipart/form-data上传到/upload,返回 JSON 文本;README 里写清楚请求和响应格式。 - 不把 API Key 放进客户端:如果接第三方 STT,推荐放在 adapter 或服务端环境变量里,而不是写进 App 或仓库。
- 默认不保存录音历史:临时音频使用后删除;只有你显式加
--keep才保留。 - 开源可审计:你可以直接看代码确认录音、上传、剪贴板和粘贴流程。
它不是为了替代所有输入法,而是给需要 可控、轻量、可自建、可审计 的用户一个选择。
你说话
↓
Mac 本地录音
↓
发送到语音转文字 HTTP API
↓
返回文字
↓
写入剪贴板 / 自动粘贴到当前应用
你可以用它在浏览器、Obsidian、微信、备忘录、编辑器等任何能输入文字的地方快速输入。
- macOS 本地录音。
- 支持固定时长录音:
record-once。 - 支持 VAD 自动停顿断句:
listen-auto。 - 支持转写本地音频文件:
transcribe-file。 - 支持把结果写入剪贴板。
- 支持自动模拟
Cmd+V粘贴到当前应用。 - 支持简单文本后处理:去掉明显重复片段和重复标点。
- 支持可选 SSH 本地端口转发,方便连接远程 Whisper 服务。
- 附带一个 macOS 菜单栏热键程序原型:
mason-voice-hotkey。
- 不是完整 macOS 输入法 IME。
- 不内置 Whisper / faster-whisper 模型。
- 不是真流式识别;
listen-auto是本地分段后上传。 - 不负责提供云服务账号。
- 不保存你的录音历史;默认临时音频会在使用后删除。
| 项目 | 要求 | 说明 |
|---|---|---|
| 系统 | macOS | 目前录音使用 ffmpeg avfoundation,主要面向 macOS。 |
| Go | 1.20+ 建议 | 用来编译 CLI。 |
| ffmpeg | 必需 | 用来录音、列出麦克风设备。 |
| pbcopy | macOS 自带 | 用来写剪贴板。 |
| cliclick | 可选 | 用来更稳定地模拟粘贴;没有时会降级到 AppleScript。 |
| ssh | 可选 | 如果你的 Whisper 服务在远程服务器上,可用 SSH tunnel。 |
| 语音转文字 HTTP API | 必需 | 需要提供兼容 /upload 的接口;第三方服务通常需要 adapter 转成该格式。 |
如果你还没有 Homebrew,先到官网按提示安装:
https://brew.sh/brew install ffmpeg检查是否安装成功:
ffmpeg -version能看到版本信息就说明成功。
cliclick 用来模拟按下 Cmd+V,自动粘贴会更稳定。
brew install cliclick不安装也能用,只是自动粘贴会降级到 AppleScript;如果粘贴失败,文字仍会在剪贴板里,你可以手动 Cmd+V。
本项目默认请求这个地址:
http://127.0.0.1:19000/upload
也就是说,你需要有一个本地或远程服务,能接收音频文件上传并返回 JSON 文字。这个服务可以是自建的 faster-whisper/Whisper 服务,也可以是你写的第三方服务 adapter。
当前 CLI 直连支持的是一个 multipart upload 格式,路径固定为 /upload。
请求方式:
POST /upload?task=transcribe&batch_size=5×tamp=chunk&diarise_audio=false
Content-Type: multipart/form-data表单字段:
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
file |
文件 | 是 | 要识别的音频文件,来自本地录音或 transcribe-file。 |
客户端会自动追加这些 query 参数:
| 参数 | 示例 | 说明 |
|---|---|---|
task |
transcribe |
表示转写,不做翻译。 |
batch_size |
5 |
传给后端的批处理大小。 |
timestamp |
chunk |
兼容部分 faster-whisper HTTP 服务的参数。 |
diarise_audio |
false |
不做说话人分离。 |
language |
zh / en |
仅当配置不是 auto 时才会追加。 |
返回 JSON 至少需要包含以下任意一种字段:
{"text":"识别出来的文字"}或:
{"output":{"text":"识别出来的文字"}}也支持:
{"result":{"text":"识别出来的文字"}}{"data":{"text":"识别出来的文字"}}当前客户端 不直接内置第三方厂商鉴权和各家专有 API 格式。为了避免把 API Key 写进客户端,推荐用一个本地或服务器上的 adapter 做转换:
Mason Voice Input
-> POST /upload multipart/form-data
-> 你的 adapter
-> OpenAI / Groq / Deepgram / AssemblyAI / 其他 STT 服务
-> adapter 返回 {"text":"..."}
| 服务类型 | 当前是否可直接调用 | 推荐接入方式 | adapter 需要做什么 |
|---|---|---|---|
| 自建 faster-whisper HTTP 服务 | 是,如果接口兼容 /upload |
直接把 api_url 指向服务地址 |
返回 text 或 output.text。 |
| 自建 Whisper 服务 | 是,如果你封装成 /upload |
直接接入或加一层 adapter | 接收 file,调用模型,返回 text。 |
| OpenAI Audio Transcriptions | 否,当前不直连 | 写 adapter | adapter 持有 API Key,把 /upload 转成厂商转写请求,再返回 text。 |
| Groq / OpenAI-compatible Whisper 类接口 | 否,当前不直连 | 写 adapter | 同上,隐藏 Key 和厂商差异。 |
| Deepgram / AssemblyAI 等第三方 STT | 否,当前不直连 | 写 adapter | 把 multipart 文件转换成厂商要求的请求,再统一返回 text。 |
一句话:客户端只认 /upload + file + JSON text。任何第三方服务只要通过 adapter 转成这个格式,就可以接入。
Mason Voice Input 最终会发出类似这样的请求:
curl -X POST \
-F "file=@demo.wav" \
"http://127.0.0.1:19000/upload?task=transcribe&batch_size=5×tamp=chunk&diarise_audio=false"一个最小可用响应长这样:
{
"text": "你好,这是一段语音识别结果。"
}或者长这样也可以:
{
"output": {
"text": "你好,这是一段语音识别结果。"
}
}只要你的服务能做到这件事,客户端就能直接接入。
很多第三方转写接口不是 /upload,而是类似:
POST https://example.com/v1/audio/transcriptions
Authorization: Bearer <你的 API Key>
multipart/form-data:
file=@demo.wav
model=whisper-large-v3
这时不要把 API Key 写进 Mason Voice Input 客户端。推荐做一个 adapter:
Mason Voice Input
-> http://127.0.0.1:19000/upload
-> 你的 adapter
-> 第三方 /v1/audio/transcriptions
-> adapter 返回 {"text":"..."}
下面是一个 Go 标准库版最小 adapter 模板。它接收 Mason Voice Input 的 /upload 请求,再转发到一个 OpenAI-compatible 的转写接口。
注意:这是模板。不同厂商的字段名、模型名、返回 JSON 可能不同,你只需要改环境变量和解析字段即可。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
)
func main() {
addr := env("ADAPTER_ADDR", ":19000")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("ok"))
})
http.HandleFunc("/upload", handleUpload)
log.Println("adapter listening on", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
func handleUpload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "missing form file: file", http.StatusBadRequest)
return
}
defer file.Close()
var body bytes.Buffer
mw := multipart.NewWriter(&body)
fw, err := mw.CreateFormFile("file", header.Filename)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err := io.Copy(fw, file); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// OpenAI/Groq-compatible 转写接口通常需要 model 字段。
_ = mw.WriteField("model", env("STT_MODEL", "whisper-large-v3"))
// 如果你的厂商支持 language,可以按需传;不需要就删掉。
if lang := os.Getenv("STT_LANGUAGE"); lang != "" && lang != "auto" {
_ = mw.WriteField("language", lang)
}
if err := mw.Close(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
upstreamURL := env("STT_TRANSCRIBE_URL", "https://example.com/v1/audio/transcriptions")
req, err := http.NewRequest(http.MethodPost, upstreamURL, &body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req.Header.Set("Content-Type", mw.FormDataContentType())
if key := os.Getenv("STT_API_KEY"); key != "" {
req.Header.Set("Authorization", "Bearer "+key)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
http.Error(w, fmt.Sprintf("upstream status=%d body=%s", resp.StatusCode, string(respBody)), http.StatusBadGateway)
return
}
// 兼容常见返回:{"text":"..."}
var parsed struct {
Text string `json:"text"`
}
if err := json.Unmarshal(respBody, &parsed); err != nil || parsed.Text == "" {
http.Error(w, "upstream response missing text", http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"text": parsed.Text})
}
func env(k, fallback string) string {
if v := os.Getenv(k); v != "" {
return v
}
return fallback
}运行方式示例:
export STT_TRANSCRIBE_URL="https://example.com/v1/audio/transcriptions"
# 不要把真实 Key 写进 README;请只在本机 shell 或服务端环境变量里设置
export STT_API_KEY="<YOUR_STT_API_KEY>"
export STT_MODEL="whisper-large-v3"
go run ./adapter.go然后 Mason Voice Input 配置成:
mason-voice-input setup \
--api-url http://127.0.0.1:19000 \
--device 0| 你的服务 | Mason Voice Input 发给 adapter | adapter 发给第三方 | adapter 返回给客户端 |
|---|---|---|---|
| 自建 faster-whisper | POST /upload + file |
可直接处理,或转内部模型 | {"text":"..."} |
| OpenAI-compatible 转写 | POST /upload + file |
POST /v1/audio/transcriptions + file + model |
{"text":"..."} |
| Groq-compatible Whisper | POST /upload + file |
类 OpenAI-compatible,模型名按厂商填写 | {"text":"..."} |
| Deepgram / AssemblyAI | POST /upload + file |
按厂商要求上传音频、带鉴权 Header | {"text":"..."} |
假设第三方返回:
{
"results": {
"channels": [
{
"alternatives": [
{"transcript": "你好世界"}
]
}
]
}
}adapter 要把它转换成:
{
"text": "你好世界"
}Mason Voice Input 不关心第三方原始格式,只关心最后有没有 text。
只要你的兼容服务或 adapter 监听在 127.0.0.1:19000,可以直接使用默认配置。
你有两种方式。
方式 A:你自己先开 SSH 隧道:
ssh -N -L 19000:127.0.0.1:9000 your-ssh-host这条命令的意思是:
| 部分 | 含义 |
|---|---|
ssh |
通过 SSH 连接服务器。 |
-N |
不执行远程命令,只做端口转发。 |
-L 19000:127.0.0.1:9000 |
把本机 19000 转发到服务器上的 127.0.0.1:9000。 |
your-ssh-host |
你的 SSH 主机名,可以是 user@example.com,也可以是 ~/.ssh/config 里的别名。 |
方式 B:把 ssh_host、ssh_remote_host、ssh_remote_port 写入配置,让工具自动尝试启动隧道。注意:ssh_remote_port 必须按你的服务实际端口显式设置,不会偷偷使用某台私有机器的默认端口。
进入项目目录:
git clone <你的仓库地址> mason-voice-input
cd mason-voice-input编译 CLI:
go build -buildvcs=false -o ./bin/mason-voice-input ./cmd/mason-voice-input可选但推荐:把 CLI 放到你的 PATH 里。
mkdir -p ~/bin
cp ./bin/mason-voice-input ~/bin/mason-voice-input如果你的 shell 还找不到 mason-voice-input,把这行加到 ~/.zshrc:
export PATH="$HOME/bin:$PATH"然后执行:
source ~/.zshrc检查是否安装成功:
mason-voice-input helpmason-voice-input list-devices你会看到类似输出:
AVFoundation audio devices:
[0] MacBook Pro Microphone
[1] Some External Microphone
记住你要用的音频设备编号,比如 0 或 1。
假设你的麦克风编号是 0,兼容语音转文字服务地址是默认的 http://127.0.0.1:19000:
mason-voice-input setup \
--device 0 \
--api-url http://127.0.0.1:19000 \
--duration 8如果你希望工具自动用 SSH 建隧道:
mason-voice-input setup \
--device 0 \
--api-url http://127.0.0.1:19000 \
--local-port 19000 \
--ssh-host your-ssh-host \
--ssh-remote-host 127.0.0.1 \
--ssh-remote-port 9000 \
--duration 8配置会保存到:
~/.config/mason-voice-input/config.json
查看当前配置:
mason-voice-input configmason-voice-input doctor它会检查:
ffmpeg是否存在。ffprobe是否存在。ssh是否存在。pbcopy是否存在。cliclick是否存在。- Whisper API 是否能访问。
第一次使用时,macOS 可能会弹权限提示。权限没给对时,录音或自动粘贴会失败。
用途:允许录音。
路径:
系统设置 → 隐私与安全性 → 麦克风
你需要给实际运行命令的程序授权。常见是:
- Terminal
- iTerm2
- 你使用的快捷键工具
- MasonVoiceHotkey.app(如果你使用菜单栏热键程序)
用途:允许自动按 Cmd+V 粘贴文字。
路径:
系统设置 → 隐私与安全性 → 辅助功能
你需要给实际负责粘贴的程序授权。常见是:
- Terminal
- iTerm2
- Raycast / Alfred / Keyboard Maestro
- MasonVoiceHotkey.app
如果没有辅助功能权限,通常 识别仍然能成功,文字也会写入剪贴板,只是 不能自动粘贴。你可以手动 Cmd+V。
把光标放在输入框里,然后执行:
mason-voice-input record-once --duration 8流程:
录音 8 秒 → 上传 → 转写 → 写剪贴板 → 自动粘贴
mason-voice-input record-once --duration 8 --no-paste适合你想先看看识别结果,或者当前 App 不适合自动粘贴。
mason-voice-input record-once --duration 8 --keep默认临时音频会删除。加 --keep 后会保留,方便排查录音质量。
listen-auto 是推荐的日常模式:它会持续监听麦克风,用本地 VAD 状态机判断你什么时候开始说话、什么时候停顿结束,然后把每个片段上传识别。
mason-voice-input listen-auto --once适合绑定快捷键:按一下快捷键,说一句话,停顿后自动识别并退出。
mason-voice-input listen-auto按 Ctrl+C 结束。
mason-voice-input listen-auto \
--silence-ms 750 \
--start-ms 180 \
--min-segment-ms 800 \
--max-segment-ms 25000 \
--energy-threshold 0.018参数解释:
| 参数 | 默认值 | 小白解释 | 什么时候要改 |
|---|---|---|---|
--start-ms |
180 |
连续检测到声音多久后,才认为你真的开始说话。 | 经常被键盘声触发就调大;开头容易漏字就调小。 |
--silence-ms |
750 |
你停顿多久后,认为一句话结束。 | 断句太早就调大;等太久才识别就调小。 |
--min-segment-ms |
800 |
太短的声音会被当成噪声丢掉。 | 咳嗽、敲键盘误识别就调大;短词总被丢掉就调小。 |
--max-segment-ms |
25000 |
单段最长时间,超过会强制切开。 | 长篇讲话被切太碎就调大;希望更快返回就调小。 |
--pre-roll-ms |
300 |
保留说话前一点点音频,避免漏掉第一个字。 | 开头第一个字经常没了就调大。 |
--energy-threshold |
0.018 |
声音能量阈值,超过才算“有声”。 | 环境吵就调高;说话轻总识别不到就调低。 |
--max-pending |
2 |
最多允许几个片段排队上传。 | 网络慢但不想占太多资源就保持默认。 |
--listen-seconds |
0 |
最多监听多久,0 表示一直监听。 | 想定时退出时设置。 |
--once |
false |
第一个有效片段识别完成后退出。 | 快捷键触发时建议加。 |
--no-paste |
false |
不自动粘贴,只写剪贴板。 | 调试或不想自动输入时加。 |
--keep |
false |
保留切分后的临时 wav 文件。 | 排查录音质量时加。 |
mason-voice-input transcribe-file /path/to/audio.mp3写剪贴板但不粘贴:
mason-voice-input transcribe-file /path/to/audio.mp3 --clip写剪贴板并自动粘贴:
mason-voice-input transcribe-file /path/to/audio.mp3 --paste配置文件位置:
~/.config/mason-voice-input/config.json
示例:
{
"api_url": "http://127.0.0.1:19000",
"ssh_host": "",
"local_port": 19000,
"ssh_remote_host": "127.0.0.1",
"ssh_remote_port": 0,
"device": "0",
"language": "auto",
"duration_seconds": 8,
"paste": true,
"batch_size": 5,
"audio_codec": "mp3",
"post_process": true
}逐项解释:
| 配置项 | 类型 | 默认值 | 解释 |
|---|---|---|---|
api_url |
字符串 | http://127.0.0.1:19000 |
兼容语音转文字 HTTP API 的基础地址。程序会请求 api_url + /upload。 |
ssh_host |
字符串 | 空 | 可选。SSH 主机名。如果 API 不通且这里不为空,程序会尝试自动执行 SSH 本地端口转发。 |
local_port |
数字 | 19000 |
本地端口。自动 SSH 转发时会使用它,例如监听本机 19000。 |
ssh_remote_host |
字符串 | 127.0.0.1 |
SSH 登录到远程机器后,要连接的远端服务 host。多数服务只监听远程机器本地地址,所以默认是 127.0.0.1。 |
ssh_remote_port |
数字 | 0 |
SSH 登录到远程机器后,要连接的远端服务端口。使用 SSH 自动隧道时必须改成你的 Whisper 服务实际端口,例如 9000。 |
device |
字符串 | 0 或你配置的编号 |
ffmpeg avfoundation 音频设备编号,通过 list-devices 查看。 |
language |
字符串 | auto |
识别语言。auto 表示让后端自动判断;如果写 zh/en,会传给后端。 |
duration_seconds |
数字 | 8 |
record-once 默认录音秒数。 |
paste |
布尔 | true |
是否默认自动粘贴。即使为 true,命令行也可以用 --no-paste 临时关闭。 |
batch_size |
数字 | 5 |
传给 Whisper API 的 batch size。显存越大通常可以越高;不懂就保持默认。 |
audio_codec |
字符串 | mp3 |
固定录音模式的音频格式配置,目前主要保留为配置项。 |
post_process |
布尔 | true |
是否启用本地轻度后处理,例如去掉重复标点、重复短句。 |
仓库里还有一个 Swift 写的菜单栏小程序:
cmd/mason-voice-hotkey/main.swift
它用于监听长按或双击 fn,然后执行:
mason-voice-input listen-auto --oncemkdir -p bin
swiftc cmd/mason-voice-hotkey/main.swift -o bin/mason-voice-hotkey./bin/mason-voice-hotkey --debugtail -f /tmp/mason-voice-hotkey.log
tail -f /tmp/mason-voice-input-launcher.logmacOS 的辅助功能权限不是简单看文件名,而是看程序身份。 裸二进制如果反复重新编译,可能出现“系统设置里看起来开了,但进程里仍然没有权限”的情况。
更稳定的做法是把热键程序包装成 .app,设置 Bundle ID,并签名后再授权这个 .app。
验收时不要只看系统设置开关,要看日志里是否有:
accessibility trusted=true
event tap enabled
如果你不想用 Swift 热键程序,可以用这些工具绑定命令:
- Raycast
- Alfred
- Keyboard Maestro
- macOS Shortcuts
建议绑定:
mason-voice-input listen-auto --once或者更简单的固定 8 秒录音:
mason-voice-input record-once --duration 8运行单元测试:
go test ./...重新编译:
go build -buildvcs=false -o ./bin/mason-voice-input ./cmd/mason-voice-input
swiftc cmd/mason-voice-hotkey/main.swift -o ./bin/mason-voice-hotkey建议每次发布新本地程序时,在启动日志、菜单或 --version 输出中带上 版本号、构建时间、commit、实际路径和 pid,避免不知道当前运行的是旧版还是新版。
通常是 辅助功能权限没给对。去:
系统设置 → 隐私与安全性 → 辅助功能
给实际运行程序授权,例如 Terminal、iTerm2、Raycast、Alfred 或 MasonVoiceHotkey.app。
macOS 辅助功能权限看的是程序身份,不只是名字。 裸二进制重新编译后身份可能变化。建议使用 .app + Bundle ID + codesign,然后授权 .app。最终以程序日志里的 trusted=true 和 event tap enabled 为准。
先确认你的服务是否真的在跑:
curl http://127.0.0.1:19000/如果服务在远程机器,确认 SSH 隧道是否建立:
ssh -N -L 19000:127.0.0.1:9000 your-ssh-host如果你的远程语音转文字服务或 adapter 不是 9000 端口,请把命令和配置里的 ssh_remote_port 改成实际端口。
再运行:
mason-voice-input doctor运行:
mason-voice-input list-devices在输出里找到你的麦克风名称,前面的 [数字] 就是设备编号。然后:
mason-voice-input setup --device 这个数字常见原因:
- 麦克风权限没给 Terminal/iTerm。
device设备编号选错。- 外接麦克风断开了。
- 当前 App 或系统正在独占麦克风。
先重新运行:
mason-voice-input list-devices
mason-voice-input setup --device 正确编号把阈值调高一点:
mason-voice-input listen-auto --once --energy-threshold 0.025也可以提高开始确认时间:
mason-voice-input listen-auto --once --start-ms 260加大 pre-roll:
mason-voice-input listen-auto --once --pre-roll-ms 500或者降低开始确认时间:
mason-voice-input listen-auto --once --start-ms 120把静音等待时间调大:
mason-voice-input listen-auto --once --silence-ms 1200默认开启 post_process=true,会做轻度去重。但它不是大模型纠错,只处理明显重复。你可以查看配置:
mason-voice-input config发到你配置的 api_url 对应的兼容服务或 adapter。默认是:
http://127.0.0.1:19000/upload
如果你把 api_url 指向远程服务,音频就会发到那个服务。请只连接你信任的后端。
默认保存在系统临时目录下的 mason-voice-input 子目录,用完会删除。加 --keep 才会保留。
当前不作为主要目标。录音部分使用 macOS 的 ffmpeg avfoundation 输入,Windows/Linux 需要替换录音参数和权限处理。
当前路线是先稳定可用:本地 VAD 断句 + 分段上传。很多 faster-whisper HTTP 服务本身不是严格真流式,直接改成 WebSocket 真流式会显著增加后端复杂度。
建议提交:
cmd/docs/scripts/go.modREADME.md.gitignore
不要提交:
.env/.env.*bin/编译产物- 私有服务器地址
- 私有日志
- API key / token / password
如果这个项目对你有帮助,欢迎给一个 Star。
MIT. See LICENSE.