diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 12bf103..f7436f9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,10 +3,132 @@ name: release on: push: branches: - - main + - main + - dev + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: + versioning: + runs-on: ubuntu-latest + outputs: + shouldRelease: ${{ steps.versioning.outputs.should_release }} + packageName: ${{ steps.versioning.outputs.package_name }} + baseVersion: ${{ steps.versioning.outputs.base_version }} + fullVersion: ${{ steps.versioning.outputs.full_version }} + npmTag: ${{ steps.versioning.outputs.npm_tag }} + releaseTag: ${{ steps.versioning.outputs.release_tag }} + releaseTitle: ${{ steps.versioning.outputs.release_title }} + assetName: ${{ steps.versioning.outputs.asset_name }} + isPrerelease: ${{ steps.versioning.outputs.is_prerelease }} + notesStartTag: ${{ steps.versioning.outputs.notes_start_tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute release version + id: versioning + shell: bash + run: | + package_name=$(node -p "JSON.parse(require('fs').readFileSync('mcp/package.json', 'utf8')).name") + package_version=$(node -p "JSON.parse(require('fs').readFileSync('mcp/package.json', 'utf8')).version.split('-')[0]") + stable_tag_pattern='^v[0-9]+\.[0-9]+\.[0-9]+$' + release_tag_pattern='^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$' + + latest_stable_tag=$( + git tag --merged HEAD --list 'v*' \ + | grep -E "$stable_tag_pattern" \ + | sort -V \ + | tail -n 1 \ + || true + ) + + if [[ -n "$latest_stable_tag" ]]; then + current_stable_version="${latest_stable_tag#v}" + else + current_stable_version="${package_version}" + echo "::warning::No stable git tag found on origin. Falling back to mcp/package.json version ${package_version} as the release base." + fi + + if [[ -n "$current_stable_version" ]]; then + IFS='.' read -r major_version minor_version patch_version <<< "$current_stable_version" + next_patch_version="${major_version}.${minor_version}.$((patch_version + 1))" + fi + + should_release="true" + npm_tag="latest" + is_prerelease="false" + base_version="" + full_version="" + release_tag="" + release_title="" + asset_name="" + notes_start_tag="" + + if [[ "${GITHUB_REF_NAME}" == "dev" ]]; then + base_version="${next_patch_version}" + full_version="${base_version}-dev.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}" + release_tag="v${full_version}" + release_title="Release ${release_tag}" + asset_name="meting-agent-skill-${full_version}.zip" + npm_tag="prerelease" + is_prerelease="true" + notes_start_tag=$( + git tag --merged HEAD --list 'v*' \ + | grep -E "$release_tag_pattern" \ + | sort -V \ + | tail -n 1 \ + || true + ) + else + head_stable_tag=$( + git tag --points-at HEAD --list 'v*' \ + | grep -E "$stable_tag_pattern" \ + | sort -V \ + | tail -n 1 \ + || true + ) + + if [[ -n "$head_stable_tag" ]]; then + base_version="${head_stable_tag#v}" + notes_start_tag=$( + git tag --merged HEAD --list 'v*' \ + | grep -E "$stable_tag_pattern" \ + | grep -vx "$head_stable_tag" \ + | sort -V \ + | tail -n 1 \ + || true + ) + else + base_version="${next_patch_version}" + notes_start_tag="${latest_stable_tag}" + fi + + full_version="${base_version}" + release_tag="v${full_version}" + release_title="Release ${release_tag}" + asset_name="meting-agent-skill-${full_version}.zip" + fi + + echo "should_release=$should_release" >> "$GITHUB_OUTPUT" + echo "package_name=$package_name" >> "$GITHUB_OUTPUT" + echo "base_version=$base_version" >> "$GITHUB_OUTPUT" + echo "full_version=$full_version" >> "$GITHUB_OUTPUT" + echo "npm_tag=$npm_tag" >> "$GITHUB_OUTPUT" + echo "release_tag=$release_tag" >> "$GITHUB_OUTPUT" + echo "release_title=$release_title" >> "$GITHUB_OUTPUT" + echo "asset_name=$asset_name" >> "$GITHUB_OUTPUT" + echo "is_prerelease=$is_prerelease" >> "$GITHUB_OUTPUT" + echo "notes_start_tag=$notes_start_tag" >> "$GITHUB_OUTPUT" + publish-npm: + needs: versioning + if: needs.versioning.outputs.shouldRelease == 'true' runs-on: ubuntu-latest permissions: contents: read @@ -15,42 +137,78 @@ jobs: run: working-directory: mcp steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - registry-url: "https://registry.npmjs.org" - - run: node scripts/sync-mcp-core.mjs - - run: npm ci - - run: npm run verify - - run: npm publish --provenance --access public + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: "https://registry.npmjs.org" + - run: npm ci + - run: npm run verify + - name: Set package version for publish + shell: bash + run: | + node -e 'const fs=require("node:fs"); const packagePath="package.json"; const packageJson=JSON.parse(fs.readFileSync(packagePath, "utf8")); packageJson.version=process.env.RELEASE_VERSION; fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + "\n");' + env: + RELEASE_VERSION: ${{ needs.versioning.outputs.fullVersion }} + - name: Check npm version availability + id: npm-version-check + shell: bash + run: | + if npm view "${{ needs.versioning.outputs.packageName }}@${{ needs.versioning.outputs.fullVersion }}" version --registry https://registry.npmjs.org > /dev/null 2>&1; then + echo "should_publish=false" >> "$GITHUB_OUTPUT" + echo "Version ${{ needs.versioning.outputs.fullVersion }} already exists on npm, skipping publish." + else + echo "should_publish=true" >> "$GITHUB_OUTPUT" + fi + - name: Publish npm package + if: steps.npm-version-check.outputs.should_publish == 'true' + run: npm publish --provenance --access public --tag "${{ needs.versioning.outputs.npmTag }}" release-skill: + needs: versioning + if: needs.versioning.outputs.shouldRelease == 'true' runs-on: ubuntu-latest permissions: contents: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - - name: Build skill release bundle - run: node scripts/build-skill-release.mjs - - name: Smoke test built skill bundle - run: | - node dist/meting-agent-skill/scripts/meting-cli.mjs platforms - node dist/meting-agent-skill/scripts/meting-cli.mjs search --platform netease --keyword "我怀念的" --limit 1 - - name: Create zip archive - run: | - cd dist - zip -r "meting-agent-skill-${GITHUB_REF_NAME}.zip" meting-agent-skill - - name: Publish GitHub release asset - env: - GH_TOKEN: ${{ github.token }} - run: | - asset="dist/meting-agent-skill-${GITHUB_REF_NAME}.zip" - if gh release view "${GITHUB_REF_NAME}" > /dev/null 2>&1; then - gh release upload "${GITHUB_REF_NAME}" "$asset" --clobber + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + - name: Build skill release bundle + run: node scripts/build-skill-release.mjs + - name: Create zip archive + shell: bash + run: | + cd dist + zip -r "${{ needs.versioning.outputs.assetName }}" meting-agent-skill + - name: Publish GitHub release asset + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + asset="dist/${{ needs.versioning.outputs.assetName }}" + release_tag="${{ needs.versioning.outputs.releaseTag }}" + release_title="${{ needs.versioning.outputs.releaseTitle }}" + is_prerelease="${{ needs.versioning.outputs.isPrerelease }}" + notes_start_tag="${{ needs.versioning.outputs.notesStartTag }}" + + if gh release view "$release_tag" > /dev/null 2>&1; then + if [[ "$is_prerelease" == "true" ]]; then + gh release edit "$release_tag" --title "$release_title" --prerelease + else + gh release edit "$release_tag" --title "$release_title" --latest + fi + gh release upload "$release_tag" "$asset" --clobber + else + notes_args=() + if [[ -n "$notes_start_tag" ]]; then + notes_args+=(--notes-start-tag "$notes_start_tag") + fi + + if [[ "$is_prerelease" == "true" ]]; then + gh release create "$release_tag" "$asset" --target "${GITHUB_SHA}" --title "$release_title" --prerelease --generate-notes "${notes_args[@]}" else - gh release create "${GITHUB_REF_NAME}" "$asset" --title "${GITHUB_REF_NAME}" --generate-notes + gh release create "$release_tag" "$asset" --target "${GITHUB_SHA}" --title "$release_title" --generate-notes "${notes_args[@]}" fi + fi diff --git a/.gitignore b/.gitignore index 5bafc95..a594fcd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,5 @@ node_modules/ mcp/.inspect/ mcp/.probe/ mcp/node_modules/ -mcp/src/meting.js -mcp/src/providers/ -skills/meting-agent/node_modules/ -skills/meting-agent/scripts/meting.js -skills/meting-agent/scripts/providers/ +mcp/src/meting/meting.js +mcp/src/meting/providers/ diff --git a/.prettierignore b/.prettierignore index 05e3923..fd25ca2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,12 +1,10 @@ dist/ node_modules/ *.md +.github/workflows/release.yml mcp/.inspect/ mcp/.probe/ mcp/node_modules/ -mcp/src/meting.js -mcp/src/providers/ -skills/meting-agent/node_modules/ -skills/meting-agent/scripts/meting.js -skills/meting-agent/scripts/providers/ +mcp/src/meting/meting.js +mcp/src/meting/providers/ diff --git a/README.md b/README.md index 4f1e468..7a10dfb 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,33 @@ # Meting-Agent -`Meting-Agent` 维护两套运行时独立的交付物: +`Meting-Agent` 是基于 **[metowolf/Meting](https://github.com/metowolf/Meting)** 构建的多平台音乐能力封装,当前提供两类交付物: -- `mcp/`:可发布的 Node.js MCP Server,对外名称统一为 `Meting Agent`。 -- `skills/meting-agent/`:面向 Codex 的 skill 源文件,最终以 GitHub Release 资产分发。 +- **MCP**:[@eldment/meting-agent](https://www.npmjs.com/package/@eldment/meting-agent) +- **Skill**:[skills/meting-agent](https://github.com/ELDment/Meting-Agent/releases) -## 目录说明 +## 能力 -- `shared/core-src/`:唯一的核心源码来源,包含 `meting.js` 和全部 providers。 -- `mcp/`:MCP Server 运行时副本与发布配置。 -- `skills/meting-agent/`:skill 元数据与静态脚本源文件。 -- `scripts/sync-mcp-core.mjs`:把共享核心生成到 `mcp/src/`。 -- `scripts/build-skill-release.mjs`:构建可下载的 skill release bundle。 +- `search`:按关键字搜索歌曲、专辑、歌手或平台特定资源 +- `song`:按歌曲 ID 获取歌曲详情 +- `album`:按专辑 ID 获取专辑详情 +- `artist`:按歌手 ID 获取歌手作品列表 +- `playlist`:按歌单 ID 获取歌单详情 +- `url`:按歌曲 ID 获取可播放链接 +- `lyric`:按歌曲 ID 获取歌词内容 +- `pic`:按资源 ID 获取封面或图片链接 -## 常用命令 +支持平台:[网易云音乐](https://music.163.com/)(`netease`)、[腾讯音乐](https://y.qq.com/)(`tencent`)、[酷狗音乐](https://www.kugou.com/)(`kugou`)、[千千音乐](https://music.taihe.com/)(`baidu`)、[酷我音乐](https://www.kuwo.cn/)(`kuwo`) -同步共享核心到 MCP: +## 文档 -```powershell -node scripts/sync-mcp-core.mjs -``` +- MCP 配置说明见 [mcp/README.md](./mcp/README.md) +- Skill 配置说明见 [skills/meting-agent/README.md](./skills/meting-agent/README.md) +- 贡献流程与同步机制(编译时报错)说明见 [docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md) -构建 skill release bundle: +## 致谢 -```powershell -node scripts/build-skill-release.mjs -``` +感谢 **[metowolf/Meting](https://github.com/metowolf/Meting)** 提供跨平台统一接口与各平台 provider 实现 -验证 MCP 子项目: +--- -```powershell -cd mcp -npm install -npm run verify -``` - -本地验证 skill release bundle: - -```powershell -node scripts/build-skill-release.mjs -node dist/meting-agent-skill/scripts/meting-cli.mjs platforms -node dist/meting-agent-skill/scripts/meting-cli.mjs search --platform netease --keyword "我怀念的" --limit 3 -``` - -## 维护原则 - -- 只在 `shared/core-src/` 修改核心实现。 -- `mcp/src/meting.js` 和 `mcp/src/providers/` 由同步脚本生成,不直接维护。 -- skill 的运行时副本只在 `dist/meting-agent-skill/` 构建时生成,用户从 GitHub Release 下载,不克隆源码仓库。 +关键词: MCP Server | Model Context Protocol | Music API | Node.js MCP | AI Tool Integration | AI Skills | Reusable Skills | NetEase Cloud Music | Tencent QQ Music | KuGou Music | Baidu Music | Taihe Music | Qianqian Music | Kuwo Music | Lyrics API | Playlist API diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..258259f --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,56 @@ +# CONTRIBUTING + +本仓库采用“共享源码 + 生成副本”的维护方式。提交修改前,请先理解同步关系,否则很容易改到错误位置 + +## 为什么刚克隆后不能直接编译 + +仓库默认只保留共享源码,不提交 skill release 产物,也不把 `shared/meting/` 的全部运行时副本长期维护在每个目标目录中 + +直接克隆后会遇到两个现实限制: + +- skill 的可分发产物位于 `dist/meting-agent-skill/scripts/meting/`,这个目录需要运行构建脚本后才会生成 +- `mcp/` 的构建虽然会在其 npm scripts 中自动执行 `sync:core`,但如果你跳过同步前置步骤,直接按生成文件视角排查问题,很容易得到错误结论 + +简化理解就是:这个仓库不是“每个交付物目录都自带完整、静态、可直接维护的源码副本”,而是先维护 `shared/meting/`,再生成给 `mcp/src/meting/` 和 skill 使用的副本 + +## 推荐修改流程 + +1. 先在 `shared/meting/` 修改核心逻辑 +2. 运行同步脚本生成 `mcp` 副本 +3. 运行构建脚本生成 skill release bundle +4. 分别验证 `mcp` 和 skill 产物 + +## 常用命令 + +同步共享核心到 `mcp/`: + +```powershell +node scripts/sync-mcp-core.mjs +``` + +构建 skill release bundle: + +```powershell +node scripts/build-skill-release.mjs +``` + +验证根目录文档格式: + +```powershell +npm run format:check +``` + +验证 `mcp` 子项目: + +```powershell +cd mcp +npm install +npm run verify +``` + +## 提交前检查 + +- 核心改动是否发生在 `shared/meting/` +- 是否重新生成了需要提交的副本或产物 +- 是否完成 `mcp` 验证 +- 如果改了文档,是否通过 `npm run format:check` diff --git a/mcp/README.md b/mcp/README.md index 1ea1239..ed1b36d 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -1,20 +1,24 @@ -# Meting Agent +# Meting-Agent `Meting-Agent` 是基于 **[metowolf/Meting](https://github.com/metowolf/Meting)** 构建的 MCP Server,支持 [网易云音乐](https://music.163.com/)(`netease`)、[腾讯音乐](https://y.qq.com/)(`tencent`)、[酷狗音乐](https://www.kugou.com/)(`kugou`)、[千千音乐](https://music.taihe.com/)(`baidu`)、[酷我音乐](https://www.kuwo.cn/)(`kuwo`) 等音乐平台,提供搜索、歌曲、专辑、歌手、歌单、播放链接、歌词、封面等能力 +## 下载 + +通过 npm 发布,下载入口是 [@eldment/meting-agent](https://www.npmjs.com/package/@eldment/meting-agent) + ## MCP 工具 -- `platforms`:列出支持的平台和平台代号。 -- `search`:按关键字搜索歌曲、专辑、歌手或平台特定资源。 -- `song`:按歌曲 ID 获取详情。 -- `album`:按专辑 ID 获取详情。 -- `artist`:按歌手 ID 获取作品。 -- `playlist`:按歌单 ID 获取详情。 -- `url`:按歌曲 ID 获取播放地址。 -- `lyric`:按歌曲 ID 获取歌词。 -- `pic`:按资源 ID 获取封面地址。 +- `platforms`:列出支持的平台和平台代号 +- `search`:按关键字搜索歌曲、专辑、歌手或平台特定资源 +- `song`:按歌曲 ID 获取详情 +- `album`:按专辑 ID 获取详情 +- `artist`:按歌手 ID 获取作品 +- `playlist`:按歌单 ID 获取详情 +- `url`:按歌曲 ID 获取播放地址 +- `lyric`:按歌曲 ID 获取歌词 +- `pic`:按资源 ID 获取封面地址 -## 发布包名 +## MCP 接入 Claude 配置示例: @@ -23,7 +27,10 @@ Claude 配置示例: "mcpServers": { "meting": { "command": "npx", - "args": ["-y", "@eldment/meting-agent@latest"], + "args": [ + "-y", + "@eldment/meting-agent@latest" + ], "env": { "METING_NETEASE_COOKIE": "__csrf=...; MUSIC_U=...; NMTID=...; __remember_me=true;", "METING_TENCENT_COOKIE": "uin=...; qm_keyst=...; qqmusic_key=...;", @@ -55,7 +62,6 @@ env = { METING_KUWO_COOKIE = "...", } tool_timeout_sec = 60 -disabled = false ``` ## Cookie 优先级 @@ -73,4 +79,6 @@ disabled = false - `METING_KUGOU_COOKIE` - `METING_BAIDU_COOKIE` - `METING_KUWO_COOKIE` -- `METING_COOKIE` +- `METING_COOKIE`(通用) + +如果只需要给某一个平台带 cookie,优先使用对应的平台变量;如果想统一兜底,可以只设置 `METING_COOKIE` diff --git a/mcp/package.json b/mcp/package.json index 0cec4fe..be03550 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -18,11 +18,9 @@ "scripts": { "sync:core": "node ../scripts/sync-mcp-core.mjs", "start": "npm run sync:core && node src/index.js", - "lint": "npm run sync:core && node --check src/index.js && node --check src/mcp-server.js && node --check src/meting.js && node --check src/providers/index.js && node --check ../test/test.js && node --check ../test/example.js", - "test": "npm run sync:core && node ../test/test.js", - "example": "npm run sync:core && node ../test/example.js", + "lint": "npm run sync:core && node --check src/index.js && node --check src/mcp-server.js && node --check src/meting/meting.js && node --check src/meting/providers/index.js", "build": "npm run sync:core && npm pack --dry-run", - "verify": "npm run lint && npm test && npm run build", + "verify": "npm run lint && npm run build", "prepublishOnly": "npm run verify" }, "keywords": [ diff --git a/mcp/src/mcp-server.js b/mcp/src/mcp-server.js index 49cfef9..3fcb674 100644 --- a/mcp/src/mcp-server.js +++ b/mcp/src/mcp-server.js @@ -1,7 +1,7 @@ import { createRequire } from "module"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import * as z from "zod/v4"; -import Meting from "./meting.js"; +import Meting from "./meting/meting.js"; const require = createRequire(import.meta.url); const { name: packageName, version } = require("../package.json"); diff --git a/scripts/build-skill-release.mjs b/scripts/build-skill-release.mjs index 390f413..39bf25e 100644 --- a/scripts/build-skill-release.mjs +++ b/scripts/build-skill-release.mjs @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url"; const generatedBanner = [ "/**", - " * Generated from shared/core-src by scripts/build-skill-release.mjs.", + " * Generated from shared/meting by scripts/build-skill-release.mjs.", " * Do not edit this copy directly.", " */", "", @@ -22,10 +22,10 @@ const staticFiles = Object.freeze([ const scriptPath = fileURLToPath(import.meta.url); const scriptDirectory = dirname(scriptPath); const repositoryRoot = resolve(scriptDirectory, ".."); -const sharedRoot = resolve(repositoryRoot, "shared", "core-src"); +const sharedRoot = resolve(repositoryRoot, "shared", "meting"); const skillSourceRoot = resolve(repositoryRoot, "skills", "meting-agent"); const outputRoot = resolve(repositoryRoot, "dist", "meting-agent-skill"); -const outputCoreRoot = resolve(outputRoot, "scripts", "core"); +const outputMetingRoot = resolve(outputRoot, "scripts", "meting"); async function PathExists(path) { try { @@ -71,7 +71,7 @@ async function CopyStaticFile(relativePath) { async function CopyGeneratedCoreFile(relativePath) { const sourcePath = resolve(sharedRoot, relativePath); - const targetPath = resolve(outputCoreRoot, relativePath); + const targetPath = resolve(outputMetingRoot, relativePath); const sourceContent = NormalizeContent(await readFile(sourcePath, "utf8")); const nextContent = `${generatedBanner}${sourceContent}`; @@ -84,7 +84,7 @@ async function CopyGeneratedCoreFile(relativePath) { async function Main() { if (!(await PathExists(sharedRoot))) { - throw new Error("shared/core-src not found."); + throw new Error("shared/meting not found."); } if (!(await PathExists(skillSourceRoot))) { diff --git a/scripts/sync-mcp-core.mjs b/scripts/sync-mcp-core.mjs index a3960e3..37c6bf7 100644 --- a/scripts/sync-mcp-core.mjs +++ b/scripts/sync-mcp-core.mjs @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url"; const generatedBanner = [ "/**", - " * Generated from shared/core-src by scripts/sync-mcp-core.mjs.", + " * Generated from shared/meting by scripts/sync-mcp-core.mjs.", " * Do not edit this copy directly.", " */", "", @@ -15,8 +15,8 @@ const generatedBanner = [ const scriptPath = fileURLToPath(import.meta.url); const scriptDirectory = dirname(scriptPath); const repositoryRoot = resolve(scriptDirectory, ".."); -const sharedRoot = resolve(repositoryRoot, "shared", "core-src"); -const targetRoot = resolve(repositoryRoot, "mcp", "src"); +const sharedRoot = resolve(repositoryRoot, "shared", "meting"); +const targetRoot = resolve(repositoryRoot, "mcp", "src", "meting"); async function PathExists(path) { try { @@ -71,7 +71,7 @@ async function WriteGeneratedCopy(sourceRoot, destinationRoot, relativePath) { async function Main() { if (!(await PathExists(sharedRoot))) { - process.stdout.write("[sync-mcp] shared/core-src not found, skipping sync.\n"); + process.stdout.write("[sync-mcp] shared/meting not found, skipping sync.\n"); return; } diff --git a/scripts/test-meting.js b/scripts/test-meting.js index 60a71ab..e86ecea 100644 --- a/scripts/test-meting.js +++ b/scripts/test-meting.js @@ -1,4 +1,4 @@ -import Meting from "../mcp/src/meting.js"; +import Meting from "../shared/meting/meting.js"; const supportedPlatforms = ["netease", "tencent", "kugou", "baidu", "kuwo"]; @@ -53,7 +53,7 @@ async function RunPlatformTest(platform, testKeyword) { } } -async function runTests() { +async function RunTests() { console.log("=== Meting smoke test ===\n"); const platform = process.argv[2]; @@ -72,7 +72,7 @@ async function runTests() { console.log("\n=== Smoke test finished ==="); } -runTests().catch((error) => { +RunTests().catch((error) => { console.error("Unexpected test failure:", error); process.exitCode = 1; }); diff --git a/shared/core-src/meting.js b/shared/meting/meting.js similarity index 100% rename from shared/core-src/meting.js rename to shared/meting/meting.js diff --git a/shared/core-src/providers/baidu.js b/shared/meting/providers/baidu.js similarity index 100% rename from shared/core-src/providers/baidu.js rename to shared/meting/providers/baidu.js diff --git a/shared/core-src/providers/base.js b/shared/meting/providers/base.js similarity index 100% rename from shared/core-src/providers/base.js rename to shared/meting/providers/base.js diff --git a/shared/core-src/providers/index.js b/shared/meting/providers/index.js similarity index 100% rename from shared/core-src/providers/index.js rename to shared/meting/providers/index.js diff --git a/shared/core-src/providers/kugou.js b/shared/meting/providers/kugou.js similarity index 100% rename from shared/core-src/providers/kugou.js rename to shared/meting/providers/kugou.js diff --git a/shared/core-src/providers/kuwo.js b/shared/meting/providers/kuwo.js similarity index 100% rename from shared/core-src/providers/kuwo.js rename to shared/meting/providers/kuwo.js diff --git a/shared/core-src/providers/netease.js b/shared/meting/providers/netease.js similarity index 100% rename from shared/core-src/providers/netease.js rename to shared/meting/providers/netease.js diff --git a/shared/core-src/providers/tencent.js b/shared/meting/providers/tencent.js similarity index 100% rename from shared/core-src/providers/tencent.js rename to shared/meting/providers/tencent.js diff --git a/skills/meting-agent/README.md b/skills/meting-agent/README.md new file mode 100644 index 0000000..aa00967 --- /dev/null +++ b/skills/meting-agent/README.md @@ -0,0 +1,26 @@ +# Meting-Agent + +`Meting-Agent` 是基于 **[metowolf/Meting](https://github.com/metowolf/Meting)** 构建的 Skills 资产,支持 [网易云音乐](https://music.163.com/)(`netease`)、[腾讯音乐](https://y.qq.com/)(`tencent`)、[酷狗音乐](https://www.kugou.com/)(`kugou`)、[千千音乐](https://music.taihe.com/)(`baidu`)、[酷我音乐](https://www.kuwo.cn/)(`kuwo`) 等音乐平台,提供搜索、歌曲、专辑、歌手、歌单、播放链接、歌词、封面等能力 + +## 下载 + +不通过 npm 发布,下载入口是 [GitHub Releases](https://github.com/ELDment/Meting-Agent/releases) + +## Cookie 规则 + +运行时按以下优先级取 Cookie: + +1. `METING__COOKIE` +2. `METING_COOKIE` +3. 命令参数 `--cookie` + +支持的环境变量: + +- `METING_NETEASE_COOKIE` +- `METING_TENCENT_COOKIE` +- `METING_KUGOU_COOKIE` +- `METING_BAIDU_COOKIE` +- `METING_KUWO_COOKIE` +- `METING_COOKIE`(通用) + +如果只需要给某一个平台带 cookie,优先使用对应的平台变量;如果想统一兜底,可以只设置 `METING_COOKIE` diff --git a/skills/meting-agent/SKILL.md b/skills/meting-agent/SKILL.md index 87bbbe7..293b81d 100644 --- a/skills/meting-agent/SKILL.md +++ b/skills/meting-agent/SKILL.md @@ -1,23 +1,22 @@ --- name: meting-agent -description: Use when Codex needs direct music lookup capabilities through bundled Meting scripts in this skill, including song search, album lookup, artist lookup, playlist lookup, lyrics, cover URLs, and playback URLs, or when it needs to maintain this skill's standalone music client implementation. +description: Use when an AI agent needs direct music lookup capabilities through the bundled Meting scripts in this skill, including song search, album lookup, artist lookup, playlist lookup, lyrics, cover URLs, and playback URLs, or when it needs to maintain this skill's standalone music client implementation. --- # Meting Agent ## Use The Bundled Implementation -- In the release bundle, treat `./scripts/core` as the implementation root. +- In the release bundle, treat `./scripts/meting` as the implementation root. - Use `./scripts/meting-cli.mjs` for normal lookup tasks. -- Do not use `../../mcp` as a runtime dependency for this skill. -- In the repository source, `../../shared/core-src` is the source of truth and `../../scripts/build-skill-release.mjs` builds the downloadable bundle. +- Downloaded bundles are self-contained and should not rely on repository-relative paths. ## Core Workflow 1. Run `node scripts/meting-cli.mjs platforms` to inspect supported platforms. 2. Run `node scripts/meting-cli.mjs search --platform netease --keyword "我怀念的" --limit 3` for search tasks. 3. Run `node scripts/meting-cli.mjs song --platform netease --id ` and related commands for detail lookups. -4. Set `METING__COOKIE` or `METING_COOKIE` before commands that need authenticated access. +4. Remember the default cookie resolution order: environment variable `METING__COOKIE` first, environment variable `METING_COOKIE` second, and CLI option `--cookie` only as the final override. ## Commands @@ -33,11 +32,7 @@ description: Use when Codex needs direct music lookup capabilities through bundl ## Configuration Rules -- Prefer `METING__COOKIE` for per-platform cookies. -- Use `METING_COOKIE` as a shared fallback. -- Use `--cookie ` only when the call needs an explicit override. - -## Sync Rules - -- Runtime release bundles stay independent, but repository maintenance uses one shared source. -- Update `shared/core-src` first, then run `node scripts/build-skill-release.mjs` from the repository root. +- Follow the default cookie resolution order used by the program: `METING__COOKIE` first, `METING_COOKIE` second, and `--cookie ` last. +- Treat `METING__COOKIE` and `METING_COOKIE` as environment variable names, not CLI options. +- Available environment variables include `METING_NETEASE_COOKIE`, `METING_TENCENT_COOKIE`, `METING_KUGOU_COOKIE`, `METING_BAIDU_COOKIE`, `METING_KUWO_COOKIE`, and shared fallback `METING_COOKIE`. +- Use CLI option `--cookie ` only when the call needs an explicit one-off override. diff --git a/skills/meting-agent/scripts/meting-cli.mjs b/skills/meting-agent/scripts/meting-cli.mjs index 85424a1..e9f3d5a 100644 --- a/skills/meting-agent/scripts/meting-cli.mjs +++ b/skills/meting-agent/scripts/meting-cli.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import Meting from "./core/meting.js"; +import Meting from "./meting/meting.js"; const platformCatalog = Object.freeze([ { name: "NetEase Cloud Music", code: "netease" },