Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Historical test snapshots must retain their original bytes on Windows.
/crates/bsk-cli/tests/fixtures/legacy-skills/*.md text eol=lf
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- name: Check Rust formatting
run: cargo fmt --all -- --check

- name: Check Cargo skill package contents
run: node scripts/check-crate-skill.mjs

- name: Run clippy
run: cargo clippy --workspace --all-targets --locked -- -D warnings

Expand Down Expand Up @@ -61,6 +64,21 @@ jobs:
- name: Run Windows process liveness tests
run: cargo test -p bsk --lib --locked daemon::lockfile::tests

- name: Run Windows skill bundle installation and migration tests
run: cargo test -p bsk --lib --test skill_install_legacy --locked skill_install

- uses: actions/setup-node@v6
with:
node-version: 22

- name: Validate Windows skill metadata, resources and Cargo package
run: |
node --test scripts/check-skill-bundles.test.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node scripts/check-skill-bundles.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node scripts/check-crate-skill.mjs

- name: Run Windows update tests
run: cargo test -p bsk --lib --locked cli::update::tests

Expand Down Expand Up @@ -119,6 +137,9 @@ jobs:
- name: Run lint checks
run: pnpm lint

- name: Build and verify the DSH npm skill package
run: pnpm --filter @wxg-prc-cpg/browser-skill-dsh-plugin build && node scripts/check-dsh-package.mjs

- name: Compile extension TypeScript
run: pnpm --filter @browser-skill/extension compile

Expand All @@ -134,6 +155,34 @@ jobs:
- name: Build extension
run: pnpm ext:build

windows-dsh-package:
name: Windows DSH npm package
runs-on: windows-latest

steps:
- uses: actions/checkout@v6

- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build the DSH plugin
run: pnpm --filter @wxg-prc-cpg/browser-skill-dsh-plugin build

- name: Verify the npm archive and runtime resource paths
env:
TEMP: ${{ runner.temp }}/bsk package & cache
TMP: ${{ runner.temp }}/bsk package & cache
run: |
New-Item -ItemType Directory -Force -Path $env:TEMP | Out-Null
node scripts/check-dsh-package.mjs

browser-input:
name: Browser input readiness regression
runs-on: ubuntu-latest
Expand Down Expand Up @@ -170,5 +219,8 @@ jobs:
with:
node-version: 22

- name: Validate skill references and entry point budgets
run: node scripts/check-skill-bundles.mjs

- name: Run script tests
run: node --test scripts/*.test.mjs
3 changes: 3 additions & 0 deletions .github/workflows/release-dsh-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ jobs:
- name: Test plugin
run: pnpm --filter "${{ env.PACKAGE_NAME }}" test

- name: Verify packaged skill resources
run: pnpm --filter "${{ env.PACKAGE_NAME }}" build && node scripts/check-dsh-package.mjs

- name: Publish to npm
working-directory: ${{ env.PACKAGE_DIR }}
env:
Expand Down
56 changes: 34 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,32 +148,44 @@ For non-interactive installation, specify the intended harness, for example
the harness is not detected. `--yes` alone installs into every detected harness
and fails when none are detected.

To install your own instructions, use `bsk install-skill --harness cursor --source ./SKILL.md`.
The installer copies the complete skill package: `SKILL.md` plus `references/`.
The entry point contains the core workflow and safety rules; agents read detailed
references only when the task needs them.

To install your own package, use `bsk install-skill --harness cursor --source ./my-skill`.
The directory must contain `SKILL.md`; a single `--source ./SKILL.md` is also supported.
The explicitly selected source may be a symbolic link; links inside directory packages
and installed resource paths are rejected.
An explicit `--source` stays custom even if its contents match the bundled skill.
Existing installations are skipped unless you add `--force`.

Daemon startup, `session start`, and `doctor` automatically update managed skills
only when their contents still match the last installed version. Local edits are
preserved and automatic updates pause. An older installation without a content
baseline is enrolled automatically only if it exactly matches the current bundled
skill; this writes the source marker without rewriting `SKILL.md`. Explicit custom
installations stay custom even when their contents match.

For differing historical files, local edits, or an unrecognized source marker,
`doctor` shows `WARN` with the reason and recovery options. These warnings do not
make the health check fail (`--json` reports `status: "warn"` and `ok: true`).
A concurrent install or sync is reported as deferred and retried on a later pass.

To keep your current instructions as an explicit customization, run
`bsk install-skill --harness cursor --source <existing-SKILL.md> --force`, replacing
`<existing-SKILL.md>` with the path to your existing file. To restore the bundled
skill and resume automatic updates, run `bsk install-skill --harness cursor --force`
without `--source`. This second command overwrites the existing instructions.

Other shell-capable agent harnesses are supported too. Copy
[`skill/SKILL.md`](skill/SKILL.md) into your harness's skills directory as
`browser-skill/SKILL.md` to install the skill manually. DeepSeek Harness uses a
dedicated plugin instead — see [DeepSeek Harness plugin](#deepseek-harness-plugin).
only when every managed file still matches its recorded checksum. Editing or deleting
`SKILL.md` or a reference pauses the whole update. Unrelated user files are preserved;
a conflicting new resource is never overwritten automatically. Unchanged retired
resources are removed. Interrupted updates resume when the same bundle is available
and no local changes are detected.

Old single-file installations with a valid checksum migrate automatically. Older
installations without a checksum migrate when their bytes match a known official
historical skill (LF or CRLF) or the current entry point. Explicit custom installations stay custom.
Unrecognized historical content, local edits, invalid metadata, or an unfinished update
from another version produce a `doctor` warning with recovery options. Warnings do not
fail the health check (`--json` reports `status: "warn"` and `ok: true`). Concurrent
installations defer synchronization until a later pass.

To keep your current package as an explicit customization, run
`bsk install-skill --harness cursor --source <existing-skill-directory> --force`.
To restore the bundled package and resume automatic updates, run
`bsk install-skill --harness cursor --force` without `--source`. This overwrites
files supplied by the bundled package, including references. Binaries using the previous checksum-based updater do not recognize the new bundle
marker and leave these installations untouched.

Other shell-capable agent harnesses are supported too. Copy the **entire**
[`crates/bsk-cli/skill/`](crates/bsk-cli/skill/) directory to your harness's skills
directory as `browser-skill/`, preserving `references/`. This is the only authored
CLI skill source. DeepSeek Harness uses its own packaged skill — see
[DeepSeek Harness plugin](#deepseek-harness-plugin).

#### 4. Verify the connection

Expand Down
46 changes: 29 additions & 17 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,25 +131,37 @@ bsk install-skill
即使未检测到该 harness,也可显式选择。单独使用 `--yes` 会安装到所有检测到的 harness,
一个也未检测到时会报错。

安装自定义指令可运行 `bsk install-skill --harness cursor --source ./SKILL.md`。
显式指定 `--source` 的安装始终视为自定义,即使内容与内置 skill 相同。
已有安装默认跳过,添加 `--force` 才会覆盖。

daemon 启动、`session start` 和 `doctor` 会检查已安装的 skill:只有文件内容仍与
上次安装或同步时的内容一致,才继续自动更新。检测到本地编辑时会保留文件并暂停更新。
没有内容基线的历史安装,只有与当前内置 skill 字节级一致时才自动纳入管理;此时只补齐
来源标记,不重写 `SKILL.md`。明确的自定义安装即使内容相同,也不会被自动纳入管理。
安装器会复制完整技能包:`SKILL.md` 和 `references/`。入口保留核心流程与安全规则,
Agent 只在任务需要时读取对应参考文件。

对于内容不同的历史文件、本地编辑或无法识别的来源标记,`doctor` 会显示 `WARN`,
说明暂停原因及恢复方法。这类警告不会让健康检查失败(`--json` 中为 `status: "warn"`、
`ok: true`)。其他安装或同步正在进行时,本次同步会推迟到后续再试。

如需将当前指令保留为明确的自定义安装,运行
`bsk install-skill --harness cursor --source <existing-SKILL.md> --force`,将
`<existing-SKILL.md>` 替换为现有文件路径。如需恢复内置 skill 并重新启用自动更新,运行
`bsk install-skill --harness cursor --force`,不带 `--source`。后一条命令会覆盖现有指令。
安装自定义技能包可运行 `bsk install-skill --harness cursor --source ./my-skill`,
目录中必须包含 `SKILL.md`;原来的单文件 `--source ./SKILL.md` 方式仍可使用。
显式指定的来源路径可以是符号链接;目录包内部和安装目标的资源路径仍拒绝符号链接。
显式指定 `--source` 始终视为自定义,即使内容与内置技能包相同。
已有安装默认跳过,添加 `--force` 才会覆盖。

其他支持 Shell 的 Agent harness 也可使用 BrowserSkill,但需手动将 [`skill/SKILL.md`](skill/SKILL.md) 复制到对应 skills 目录下的 `browser-skill/SKILL.md`。DeepSeek Harness 走独立插件,见 [DeepSeek Harness 插件](#deepseek-harness-插件)。
daemon 启动、`session start` 和 `doctor` 会检查已安装的技能包:所有受管理文件仍与
记录的校验值一致,才会自动更新。修改或删除 `SKILL.md`、任一 reference 都会暂停整个
技能包的更新。用户额外添加的文件会保留;新增资源遇到同名且内容不同的文件时,也会
暂停更新。已废弃且未被修改的受管理资源会删除。更新中断后,使用同一版本技能包且未
发现本地修改时,会继续完成更新。

带有效校验值的旧单文件安装会自动迁移;更早没有校验值的安装,内容与已知官方历史版本
(LF 或 CRLF 行尾)或当前入口完全一致时也会迁移。显式自定义安装始终保持自定义。无法识别的历史内容、
本地修改、无效标记或其他版本未完成的更新,会让 `doctor` 显示 `WARN` 并给出恢复方法,
但不会使健康检查失败(`--json` 中为 `status: "warn"`、`ok: true`)。其他安装或同步正在
进行时,本次同步会推迟到后续再试。

如需将当前技能包保留为明确的自定义安装,运行
`bsk install-skill --harness cursor --source <existing-skill-directory> --force`。
如需恢复内置技能包并重新启用自动更新,运行
`bsk install-skill --harness cursor --force`,不带 `--source`。后一条命令会覆盖内置技能包
提供的文件,包括 references。采用上一版校验值机制的 CLI 无法识别新的包标记,因此会保留这些安装,不会覆盖。

其他支持 Shell 的 Agent harness 可手动将整个
[`crates/bsk-cli/skill/`](crates/bsk-cli/skill/) 目录复制到对应 skills 目录,命名为
`browser-skill/`,保留 `references/`。通用版只维护这一套源文件。
DeepSeek Harness 使用插件内独立的技能包,见 [DeepSeek Harness 插件](#deepseek-harness-插件)。

#### 4. 验证连接

Expand Down
71 changes: 46 additions & 25 deletions crates/bsk-cli/build.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,53 @@
//! Keep the packaged `skill/SKILL.md` in sync with the repo-root skill during dev builds.
//! Embed the canonical skill directory, including resources, without modifying sources.

use std::env;
use std::fs;
use std::path::PathBuf;
use std::{env, fs, path::Path};

fn main() {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
let src = manifest.join("../../skill/SKILL.md");
let dst = manifest.join("skill/SKILL.md");

println!("cargo:rerun-if-changed={}", src.display());
println!("cargo:rerun-if-changed=build.rs");

if !src.is_file() {
// `cargo package` on crates.io ships `skill/SKILL.md` committed in-tree.
return;
}

// The repo-root skill may be a symlink to the packaged skill.
// Avoid copying a file onto itself through the symlink.
if let (Ok(src_real), Ok(dst_real)) = (src.canonicalize(), dst.canonicalize()) {
if src_real == dst_real {
return;
fn collect(root: &Path, dir: &Path, files: &mut Vec<String>) {
println!("cargo:rerun-if-changed={}", dir.display());
for entry in fs::read_dir(dir).expect("read skill directory") {
let entry = entry.expect("read skill entry");
let kind = entry.file_type().expect("read skill file type");
let path = entry.path();
if kind.is_dir() {
collect(root, &path, files);
} else {
assert!(kind.is_file(), "skill resources must be regular files");
let name = path
.strip_prefix(root)
.unwrap()
.to_str()
.unwrap()
.replace('\\', "/");
assert!(
!name.split('/').any(|part| part.starts_with('.')),
"hidden skill resource"
);
files.push(name);
}
}
}

if let Some(parent) = dst.parent() {
fs::create_dir_all(parent).expect("create skill/ directory");
fn main() {
let manifest = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
let root = Path::new(&manifest).join("skill");
let mut files = Vec::new();
collect(&root, &root, &mut files);
files.sort();
assert!(
files.iter().any(|name| name == "SKILL.md"),
"missing SKILL.md"
);
let mut output = String::from("pub const BUNDLED_FILES: &[(&str, &[u8])] = &[\n");
for name in files {
output.push_str(&format!(
"({name:?}, include_bytes!({:?})),\n",
root.join(&name)
));
}
fs::copy(&src, &dst).expect("sync skill/SKILL.md from repo root");
output.push_str("];\n");
fs::write(
Path::new(&env::var("OUT_DIR").unwrap()).join("skill_bundle.rs"),
output,
)
.expect("generate embedded skill bundle");
}
Loading
Loading