插件协议 byk.json

每个 byk 插件的发布入口是仓库根目录下的 byk.json 文件。它定义了插件的安装方式、下载资源和注册的命令。本文档面向插件发布者,说明如何编写这个文件。

文件位置

byk.json 放在 GitHub 仓库的根目录。byk 通过 raw.githubusercontent.com 拉取该文件:

https://raw.githubusercontent.com/{owner}/{repo}/{branch}/byk.json

设计哲学

byk.json 是一个过程式操作脚本,按顺序声明操作块:

1. pip-keep              → 安装 Python 依赖到虚拟环境(卸载时保留)
2. pip                   → 安装 Python 依赖到虚拟环境(卸载时自动清理)
3. downloads             → 统一下载/拷贝文件到 plugins/<plugin_key>/
4. download-to-workdir   → 下载到当前工作目录
5. download-to-alias     → 下载到 ~/.byk/alias/
6. command               → 注册单个命令(命令名 = 插件 key)
7. commands              → 注册多个命令到路由表
8. alias                 → 部署别名到 *.byk.json 文件

推荐:单命令插件用 command,多命令插件用 commands。两者可以共存,但不要在同名 key 上重复定义。

JSON key 的声明顺序即执行顺序,阅读即执行。

整体结构

byk.json 是一个 JSON 对象,每个顶级 key 为一个插件 key(即 byk add <key> 中的 <key>),值为该插件的操作定义:

{
  "$default": "<plugin-key>",
  "<plugin-key>": {
    "pip": [...],
    "commands": { ... }
  }
}

一个 byk.json 可以包含多个插件 key,用户通过 byk add <key> 指定要安装哪个。

$default

$default 字段指定当用户不指定 key 时(byk add user/repo)默认安装哪个插件。

{
  "$default": "pym",
  "pym": "./plugins/py-module/byk.json",
  "pys": "./plugins/py-script/byk.json"
}
场景行为
$default使用 $default 指定的 key
$default,只有一个有效 key自动使用该 key(无需显式声明)
$default,多个有效 key报错,提示可用 key 列表

::::note

  • $default 的值必须是 byk.json 中存在的有效 key,否则安装时报错
  • 只有一个 key 时无需声明 $default,byk 会自动推断
  • 有效 key 指不以 $ 开头的顶级字段

::::

$var

$var 字段用于定义可复用的变量,减少路径或 URL 中的重复字符串。定义后可在任意位置的字符串值中使用 {变量名} 语法引用。

{
  "$var": {
    "p": "./plugins"
  },
  "pym": "{p}/py-module/byk.json",
  "pys": "{p}/py-script/byk.json",
  "pyb": "{p}/py-bin/byk.json"
}

上例中三个 plugin key 的路径前缀 ./plugins 被提取到 $var.p,通过 {p} 引用,等价于

{
  "pym": "./plugins/py-module/byk.json",
  "pys": "./plugins/py-script/byk.json",
  "pyb": "./plugins/py-bin/byk.json"
}

平台特定值

$var 的值不仅可以是字符串,还可以是平台映射对象。当值为一个对象时,byk 根据当前运行平台自动选择对应的字符串:

{
  "$var": {
    "BASE": {
      "linux-x86_64": "https://cdn.linux.example.com",
      "linux-arm64": "https://cdn.linux-arm.example.com",
      "darwin-x86_64": "https://cdn.darwin-intel.example.com",
      "darwin-arm64": "https://cdn.darwin-arm.example.com",
      "windows-x64": "https://cdn.windows.example.com"
    }
  },
  "my-tool": {
    "downloads": {
      "tool": "[exe] {BASE}/my-tool"
    }
  }
}

变量也常用于减少 pip 中的重复 URL:

{
  "$var": {
    "url": "https://github.com/fcbyk/byk/releases/download/assets"
  },
  "pym": {
    "pip": "byk_pym @ {url}/byk_pym-2026.6.21-py3-none-any.whl",
    "commands": { ... }
  }
}

规则

规则说明
作用域仅限当前 byk.json 文件,不穿透到 Ref 引用的文件
替换目标对原始 JSON 字符串逐变量替换,覆盖任意位置的字符串值
未定义变量静默保留原文,用户自行负责
$var 值类型字符串或平台对象(如 {"darwin-arm64": "/path"}),不接受数字、布尔、数组等
平台对象仅当顶层值直接为平台对象时生效,嵌套在数组或其他结构中的对象不会被识别

处理顺序

byk.json 文件
  → 1. 解析 JSON,提取 $var
  → 2. 在原始 JSON 字符串上逐变量替换 {var} 占位符
  → 3. 解析 Ref 引用(已替换为完整路径)
  → 4. Ref 引用的文件回到步骤 1(独立的 $var 作用域)

::::note

  • $var 的 value 同样参与替换,交叉引用(如 "a": "{b}/foo")会逐变量传递替换
  • $var 不会传递到 Ref 引用的子文件,每个 byk.json 拥有独立的变量作用域
  • 变量替换发生在 Ref 解析之前,因此 "{p}/py-module/byk.json" 会先变成 "./plugins/py-module/byk.json",再作为相对路径被解析
  • 平台对象在变量替换阶段解析为对应平台的字符串值

::::

操作块

pip-keep

pip-keep 是插件内部的保留 pip 依赖,卸载时保留。结构与 pip 完全相同,支持单字符串或字符串数组写法:

{
  "plugin-a": {
    "pip-keep": ["rich", "click>=8.0"],
    "pip": ["my-plugin-a"],
    "commands": { ... }
  }
}

也可以写为单字符串(只有一个包时):

{
  "plugin-a": {
    "pip-keep": "rich",
    "pip": "my-plugin-a",
    "commands": { ... }
  }
}
字段类型必填说明
pip-keepstring | string[]保留 pip 依赖,卸载插件时不会 pip uninstall

pip-keep 适合声明插件的基础共享依赖(如 richclick),卸载插件时这些包会被保留。与 pip 不同,pip-keep 中的包不会随插件卸载而删除。

pip

通过 pip 安装 Python 包到 byk 的隔离虚拟环境,卸载插件时自动执行 pip uninstall

{
  "hello": {
    "pip": ["byk-hello"],
    "commands": { ... }
  }
}

pip 字段支持两种写法:

  • 单字符串"pip": "requests" — 只有一个包时更简洁
  • 字符串数组"pip": ["rich", "click>=8.0"] — 多个包时使用

字段说明

字段类型必填说明
pipstring | string[]pip 包名或 URL 列表,卸载时自动清理

pip 中的每一项可以是:

  • 包名"rich""click>=8.0""requests==2.28.0" — 从 PyPI 安装
  • URL"https://github.com/.../xxx.whl" — 从远程 URL 安装
  • name @ url"my-pkg @ https://github.com/.../xxx.whl" — 指定包名 + URL,卸载时用包名执行 pip uninstall

纯 URL 无法卸载:如果 pip 中写的是纯 URL(无 name @ 前缀),卸载时静默跳过,因为无法知道包名。如需自动卸载,请使用 "name @ url" 格式。

byk add user/repo/hello

示例:单字符串写法

{
  "hello": {
    "pip": "my-hello @ https://github.com/fcbyk/byk/releases/download/plugins/byk_hello-2026.6.21-py3-none-any.whl",
    "commands": { ... }
  }
}

downloads

downloads 是统一下载区块,将文件下载到 plugins/<plugin_key>/ 目录。支持三种写法:

  • 裸 URL 字符串:文件名从 URL 自动提取
  • 对象映射{ "<文件名>": "<URL>" },支持 [tar] / [exe] 前缀
  • 目录树{ "<目录名>": { "<文件名>": "<URL>", ... } },递归嵌套

裸 URL 字符串

最简单的写法,文件名从 URL 的最后一个路径段自动提取:

{
  "my-tool": {
    "downloads": "[exe] https://github.com/xxx/releases/download/v1.0/my-tool",
    "command": {
      "type": "bin",
      "entry": "my-tool",
      "desc": "My binary tool"
    }
  }
}

对象映射

key 为保存的文件名,value 为 URL。URL 前缀控制下载后行为:

前缀行为
[tar] 下载 tar.gz/zip,解压到 plugins/<key>/
[exe] 下载单文件,chmod +x
无前缀普通文件下载
{
  "my-tool": {
    "downloads": {
      "my-tool": "[exe] https://github.com/xxx/releases/download/v1.0/my-tool",
      "runner.py": "https://example.com/runner.py"
    },
    "command": {
      "type": "bin",
      "entry": "my-tool",
      "desc": "My platform-specific binary tool"
    }
  }
}

压缩包解压([tar] 前缀)

{
  "my-tool": {
    "downloads": {
      "my-tool": "[tar] https://github.com/xxx/releases/download/v1.0/my-tool.tar.gz"
    },
    "command": {
      "type": "bin",
      "entry": "my-tool/my-tool",
      "desc": "My tool from archive"
    }
  }
}

解压后目录结构:plugins/my-tool/my-tool/my-toolentry 对应 my-tool/my-tool

目录树

当需要下载一组文件到子目录时,可以使用嵌套对象结构:

{
  "my-app": {
    "downloads": {
      "mylib": {
        "main.py": "https://example.com/main.py",
        "utils": {
          "helper.py": "https://example.com/helper.py"
        }
      }
    }
  }
}

下载后文件结构:

plugins/my-app/
  └── mylib/
      ├── main.py
      └── utils/
          └── helper.py

目录树中的叶子节点同样支持 [tar] / [exe] 前缀。

字段说明

字段类型必填说明
downloadsstring | Record<string, string | DownloadsTree>下载到 plugins/<plugin_key>/ 的文件

::::note

  • downloads 按声明顺序逐一处理,下载失败立即终止并报错
  • 相对路径仅支持 ./xxxxxx 两种写法,..//xxx~ 不被允许
  • 使用 --cdn 时,raw.githubusercontent.com 的 URL 自动转换为 jsDelivr CDN 加速
  • [tar] 使用系统 tar 命令解压,支持 .tar.gz / .tgz / .zip 格式,要求 Windows 10 1803+(内置 tar
  • [exe] 在非 Windows 系统自动设置执行权限(chmod 755
  • 解压时一个 key 对应一个独立子目录,不同插件互不干扰,卸载时直接删除整个目录

::::

download-to-workdir

download-to-workdirdownloads 结构相同,但下载到当前工作目录而非 plugins/。适合下载配置文件、模板等需要放在项目目录中的资源。

{
  "config-tool": {
    "download-to-workdir": {
      "$name": "my-configs",
      "settings.json": "https://example.com/settings.json",
      "templates": {
        "default.tmpl": "https://example.com/default.tmpl"
      }
    }
  }
}

下载后文件结构(当前工作目录):

./
└── my-configs/
    ├── settings.json
    └── templates/
        └── default.tmpl

$name

Map 模式下支持 $name 键指定容器目录名。$name 不会创建实体文件,仅用作目录前缀。

  • 指定 $name:文件放入 <cwd>/<name>/ 目录
  • 不指定 $name:默认容器目录名为 "downloads"

字段说明

字段类型必填说明
download-to-workdirstring | Record<string, string | DownloadsTree>下载到当前工作目录的文件。Map 模式下支持 $name 键指定容器目录名

download-to-alias

download-to-aliasdownloads 结构相同,但下载到 ~/.byk/alias/ 目录。适合部署别名脚本等全局可重用的资源。

{
  "my-aliases": {
    "download-to-alias": {
      "my-script.py": "https://example.com/my-script.py"
    }
  }
}
字段类型必填说明
download-to-aliasstring | Record<string, string | DownloadsTree>下载到 ~/.byk/alias/ 的文件

command

为单命令插件注册命令,命令名自动取插件 key。推荐只有一个命令的插件使用此字段。

{
  "hello": {
    "pip": "byk-hello",
    "command": {
      "type": "python-m",
      "entry": "hello.one",
      "desc": "Say hello"
    }
  }
}

安装后,用户通过 byk hello 即可执行该命令。

字段说明

字段类型必填说明
typestring命令类型:python-m(模块)、python(脚本)、pip-bin(pip 安装的可执行文件)或 bin(普通二进制)
entrystring入口点:python-m 为模块路径,python 为脚本文件名(相对于 plugins/<plugin_key>/),pip-bin 为 venv/bin 下的可执行文件名,bin 为二进制文件名(相对于 plugins/<plugin_key>/
descstring命令描述,显示在帮助信息中

commands

声明插件提供的多个命令。commands 中的内容会被零解析直接合并到 plugins.cmd.json,无需中间转换。

{
  "commands": {
    "hello": {
      "type": "python-m",
      "entry": "hello.one",
      "desc": "Example subcommand to verify dynamic registration."
    },
    "world": {
      "type": "python-m",
      "entry": "hello.two",
      "desc": "Second command to verify dynamic registration."
    }
  }
}

字段说明

字段类型必填说明
typestring命令类型:python-m(模块)、python(脚本)、pip-bin(pip 安装的可执行文件)或 bin(普通二进制)
entrystring入口点:python-m 为模块路径,python 为脚本文件名(相对于 plugins/<plugin_key>/),pip-bin 为 venv/bin 下的可执行文件名,bin 为二进制文件名(相对于 plugins/<plugin_key>/
descstring命令描述,显示在帮助信息中

命令类型

类型执行方式示例
python-mpython -m <entry>"entry": "hello.one"
pythonpython plugins/<plugin_key>/<entry>"entry": "runner.py"
pip-bin直接执行 venv/bin/<entry>"entry": "my-tool"
bin直接执行 plugins/<plugin_key>/<entry>"entry": "my-tool""entry": "my-tool/bin/my-tool"

一个插件可以注册多个命令,每个命令对应一个独立的入口点。

command 共存commandcommands 可以同时使用。command 注册的命令名 = 插件 key,commands 注册额外的命名命令。但 commands 中不能定义与插件 key 同名的子命令,否则安装时报错。

python 入口点

对于 python 类型的命令,entry 是相对于 plugins/<plugin_key>/ 的文件路径,指代通过 downloads 下载的脚本文件:

{
  "jiahao": {
    "downloads": {
      "jiahao.py": "https://raw.githubusercontent.com/user/repo/main/scripts/jiahao.py"
    },
    "command": {
      "type": "python",
      "entry": "jiahao.py",
      "desc": "装 x 命令"
    }
  }
}
字段职责
downloads下载/拷贝脚本到 plugins/<plugin_key>/jiahao.py
commands/command注册命令,entrydownloads 中定义的相对路径

bin 入口点

对于 bin 类型的命令,entry 指代 plugins/<plugin_key>/ 目录下的相对路径。可以是纯文件名,也可以带子目录路径。二进制文件通过 downloads 下载:

{
  "my-tool": {
    "downloads": {
      "my-tool": "[exe] https://github.com/xxx/releases/download/v1.0/my-tool"
    },
    "command": {
      "type": "bin",
      "entry": "my-tool",
      "desc": "My binary tool"
    }
  }
}

alias

alias 字段用于在安装插件时自动部署别名到 *.byk.json 文件。

{
  "my-plugin": {
    "pip": "my-plugin",
    "commands": { ... },
    "alias": {
      "@myapp": {
        "run": "python main.py",
        "test": "pytest"
      },
      "@@global-tool": {
        "lint": "ruff check ."
      }
    }
  }
}

解析规则

Key 模式目标位置示例
@filename当前工作目录 → filename.byk.json"@myapp"./myapp.byk.json
@@filename~/.byk/alias/filename.byk.json"@@global-tool"~/.byk/alias/global-tool.byk.json
  • 文件不存在 → 创建,直接写入
  • 文件已存在 → 顶层 key 直接覆盖,不影响其他 key
  • alias 会在所有下载和命令注册完成后执行

文件名不能包含 .@ 字符。

Ref 引用

当一个 key 的值为字符串时,byk 会将其视为引用,从该位置拉取另一个 byk.json,并在其中查找同名 key。

URL 引用

如果字符串以 http://https:// 开头,byk 直接将其作为 URL 拉取:

{
  "lansend": "https://raw.githubusercontent.com/fcbyk/byk-lansend/main/byk.json",
  "hello": {
    "pip": "byk-hello",
    "commands": { ... }
  }
}

当用户执行 byk add user/repo/lansend 时,byk 会:

  1. 发现 lansend 的值是一个 URL
  2. 从该 URL 拉取完整的 byk.json
  3. 在新拉取的内容中查找 key 为 lansend 的条目
  4. 使用该条目的操作定义执行安装

相对路径引用

如果字符串不以 http 开头,byk 将其视为相对路径,相对于当前 byk.json 所在目录解析:

  • 远程模式byk add user/repo):拼接到 https://raw.githubusercontent.com/{user}/{repo}/{branch}/
  • 本地模式--file):相对于当前 byk.json 所在目录解析
{
  "hello": "./plugins/python/byk.json"
}

当用户执行 byk add user/repo/hello 时,byk 会:

  1. 发现 hello 的值是相对路径 ./plugins/python/byk.json
  2. 拼接为 https://raw.githubusercontent.com/user/repo/main/plugins/python/byk.json
  3. 拉取该文件,查找 key 为 hello 的条目
  4. 使用该条目的操作定义执行安装

路径规则

写法说明
./xxxxxx相对于 byk.json 所在目录 ✅
https://...远程 URL ✅
..//xxx~不允许 ❌

::::note

  • ./ 前缀可选,plugins/python/byk.json./plugins/python/byk.json 等效
  • URL 引用不受安装模式影响,始终直接拉取
  • ../ 等路径逃逸写法不被允许,如需引用外部资源请使用完整 URL
  • 相对路径引用适用于将插件定义拆分到子目录的场景
  • 每个 byk.json 自治:嵌套 byk.json 中的相对路径始终相对于自身所在目录

::::

配合 $var 使用:$var 可以进一步减少 Ref 路径中的重复前缀,详见 $var — 变量替换

Ref 适合将插件定义分散到各自仓库或子目录中维护,主仓库只做索引。

远程仓库

任何 GitHub 仓库只要在根目录放一个 byk.json,即可作为 byk 插件源使用:

github.com/myuser/my-byk-plugins/blob/main/byk.json
{
  "my-tool": {
    "pip": "my-byk-tool",
    "command": {
      "type": "python-m",
      "entry": "mybyktool.cli",
      "desc": "My custom tool"
    }
  }
}

用户安装方式:

byk add myuser/my-byk-plugins/my-tool    # 指定 key
byk add myuser/my-byk-plugins            # 安装 byk.json 中的默认 key

完整示例

一个包含多个插件、使用 Ref 引用和完整操作块的 byk.json

{
  "lansend": "https://raw.githubusercontent.com/fcbyk/byk-lansend/main/byk.json",

  "hello": {
    "pip": "byk_hello @ https://github.com/fcbyk/byk/releases/download/plugins/byk_hello-2026.6.21-py3-none-any.whl",
    "commands": {
      "hello": {
        "type": "python-m",
        "entry": "hello.one",
        "desc": "Example subcommand to verify dynamic registration."
      },
      "world": {
        "type": "python-m",
        "entry": "hello.two",
        "desc": "Second command to verify dynamic registration."
      }
    }
  },

  "jiahao": {
    "downloads": {
      "jiahao.py": "https://raw.githubusercontent.com/fcbyk/pyodd/refs/heads/main/scripts/jiahao.py"
    },
    "pip": ["rich", "click"],
    "command": {
      "type": "python",
      "entry": "jiahao.py",
      "desc": "装 x 命令"
    }
  },

  "my-tool": {
    "downloads": {
      "my-tool": "[exe] https://github.com/xxx/releases/download/v1.0/my-tool"
    },
    "download-to-workdir": {
      "$name": "configs",
      "default.json": "https://example.com/default.json"
    },
    "command": {
      "type": "bin",
      "entry": "my-tool",
      "desc": "My platform-specific binary tool"
    },
    "alias": {
      "@tool": {
        "run": "byk my-tool",
        "config": "byk my-tool --config"
      }
    }
  }
}

注意事项

  • byk.json 中的 key 不能包含 /,因为 / 在 spec 语法中用于分隔仓库地址和 key
  • Ref 引用支持 URL(http(s)://)和相对路径两种写法,相对路径遵循 ./xxx 规则
  • 一个插件的 commandcommands 可以共存,但 commands 中不能定义与插件 key 同名的子命令
  • 单命令插件推荐使用 command,多命令插件使用 commands
  • 操作块按固定顺序执行:pip-keeppipdownloadsdownload-to-workdirdownload-to-aliascommands / commandalias
  • 使用 --cdn 时,所有 raw.githubusercontent.com 的 URL 自动转换为 jsDelivr CDN 加速
  • 顶层 $var/$default 为系统字段,不会被视为有效 plugin key