diff --git a/.goreleaser.yml b/.goreleaser.yml index 8e473bb..190ac7c 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -47,6 +47,10 @@ archives: checksum: name_template: checksums.txt +release: + prerelease: auto + make_latest: "{{ if .Prerelease }}false{{ else }}true{{ end }}" + snapshot: version_template: "{{ incpatch .Version }}-snapshot" diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..a167746 --- /dev/null +++ b/README.en.md @@ -0,0 +1,189 @@ +# AI MediaKit CLI + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Go Version](https://img.shields.io/badge/go-%3E%3D1.22-blue.svg)](https://go.dev/) +[![npm version](https://img.shields.io/npm/v/@volcengine/mediakit-cli.svg)](https://www.npmjs.com/package/@volcengine/mediakit-cli) + +[中文版](./README.md) | [English](./README.en.md) + +The official Mediakit CLI — an FFmpeg-compatible command surface. The same command can run FFmpeg locally for editing operations such as trimming, concatenation, subtitling, mixing, and audio extraction, or switch to the cloud with a single flag to invoke AI capabilities that FFmpeg cannot deliver — quality enhancement, subtitle erasure, ASR, OCR, storyline analysis, and more. It already covers atomic capabilities across video, image, and audio modalities plus 5 AI Agent [Skills](./skills/), with 100+ audio/video atomic capabilities planned. + +[Installation](#installation--quick-start) · [AI Agent Skills](#agent-skills) · [Authentication](#authentication) · [Command Structure](#command-structure) · [Advanced Usage](#advanced-usage) · [License](#license) + +## Why choose mediakit-cli? + +- **Comprehensive capability matrix**: spans video, image, and audio modalities, from low-level processing such as trimming / concatenation / subtitling to high-level understanding such as quality enhancement, subtitle erasure, ASR, OCR, and storyline analysis — a single command covers the full pipeline from preprocessing to final output. +- **FFmpeg-compatible, seamless migration**: local mode is built on `ffmpeg` / `ffprobe`, covering common capabilities such as trimming, concatenation, image overlay, subtitle overlay, speed adjustment, volume adjustment, flipping, fade in/out, mixing, audio/video composition, audio extraction, green-screen keying, and metadata probing — aligned with FFmpeg command intuition. Complex / AI capabilities such as filters, image-to-video, and concatenation transitions are handled in the cloud. +- **Cloud is faster and more powerful**: append `--cloud` to the same command to unlock capabilities FFmpeg cannot deliver — quality enhancement / generative quality restoration, subtitle erasure (standard / fine-grained), ASR, video OCR, highlight clipping (short drama / mini-game), storyline analysis, scene segmentation, green-screen / portrait keying, and other AI atomic capabilities. Cloud elastic compute provides second-level concurrency. +- **One command, two modes**: `--local` / `--cloud` can be switched per command; local mode is zero-cost and cloud provides elastic compute, complementing each other. They share the same parameters and `--schema`, so Agents / scripts can switch with zero modification. +- **Cost-effective processing**: leverages cloud elastic resource scheduling and off-peak batch processing strategies to provide highly competitive pricing for large batches of media tasks, significantly reducing overall token consumption and operational cost for AI applications. + +## Features + +| Domain | Capabilities | Runtime | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| 🎬 **Editing** (17) | Video trim · Audio trim · Video concat · Audio concat · Video image overlay · Video subtitle overlay · Video speed · Audio speed · Video volume adjust · Video filter · Video flip · Video audio fade in/out · Audio fade in/out · Audio mix · Video + audio · Extract audio · Image to video | Cloud **or** Local | +| 🎚️ **Audio** (2) | Voice / background separation · Audio metadata | Cloud | +| 🖼️ **Image AI** (5) | Image quality enhancement · Image erase & inpaint · Image quality assessment · Image OCR · Image background removal | Cloud | +| 🎥 **Video AI** (14) | Quality enhancement · Generative quality enhancement · Subtitle erasure (standard) · Fine-grained subtitle erasure · Speech-to-subtitles (ASR) · Video subtitle OCR · Highlight clipping - short drama · Highlight clipping - mini-game · Highlight extraction · Storyline analysis · Scene segmentation · Video green-screen keying · Video portrait keying · Video metadata | Cloud | +| 🔧 **Common** (2) | Async task query · Remote file fetch | Local / Cloud | +| 🚧 **Coming soon** | Video translation · Narration generation · Manga-to-animation (rolling out) | Cloud | + +## Installation & Quick Start + +### Requirements + +Before you begin, make sure you have: + +- Node.js `>=18` (`npm` / `npx`) + +- Local mode: `ffmpeg` `5.1.x` and `ffprobe` + +### Quick Start (Human Users) + +#### Installation + +Choose **one** of the following methods: + +**Option 1 — one-click install:** + +```bash +npx @volcengine/mediakit-cli install -y +``` + +**Option 2 — build from source:** + +Requires Go `v1.22`+. + +```bash +git clone https://github.com/volcengine/mediakit-cli.git +cd mediakit-cli +make build # Artifact: .mediakit/build/dev/mediakit-cli + +# Install AI Agent Skills from local skills directory (required) +npx -y skills add ./skills -g -y +``` + +#### Configuration & Usage + +```bash +# 1. Initialize configuration (interactive wizard) +mediakit-cli init + +# 2. Environment self-check (cloud connectivity, local dependencies, install suggestions) +mediakit-cli doctor + +# 3. Local editing (synchronous, no API Key needed): run FFmpeg locally to trim +mediakit-cli --local editing trim-video --video-url ./in.mp4 --start-time 3 --end-time 8 + +# 4. Cloud AI (async): enhance a video to 1080p, then poll for the final result +mediakit-cli --cloud video enhance-video --video-url --resolution 1080p +mediakit-cli shared query-task --task-id --poll-complete +``` + +### Quick Start (AI Agent) + +> The following steps are designed for AI Agents and support fully unattended workflows. + +**Step 1 — Install** + +```bash +npx @volcengine/mediakit-cli install -y +``` + +**Step 2 — Non-interactive initialization (`--yes` mode)** + +```bash +# Get an API Key at: https://console.volcengine.com/imp/ai-mediakit/settings +mediakit-cli init \ + --mode cloud-first \ + --api-key \ + --yes +``` + +**Step 3 — Verify** + +```bash +mediakit-cli doctor +mediakit-cli version +``` + +## Agent Skills + +| Skill | Description | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `byted-mediakit-shared` | Common capabilities: task query — required by all other skills | +| `byted-mediakit-editing` | Editing: video trim, audio trim, video concat, audio concat, video image overlay, video subtitle overlay, video speed, audio speed, video volume adjust, video filter, video flip, video audio fade in/out, audio fade in/out, audio mix, video + audio, extract audio, image to video | +| `byted-mediakit-audio` | Audio: voice / background separation, audio metadata | +| `byted-mediakit-image` | Image AI: image quality enhancement, image erase & inpaint, image quality assessment, image OCR, image background removal | +| `byted-mediakit-video` | Video AI: quality enhancement, generative quality enhancement, subtitle erasure (standard), fine-grained subtitle erasure, speech-to-subtitles (ASR), video subtitle OCR, highlight clipping - short drama, highlight clipping - mini-game, highlight extraction, storyline analysis, scene segmentation, video green-screen keying, video portrait keying, video metadata | + +## Authentication + +`mediakit-cli` uses minimal authentication: just an API Key — no OAuth / STS / IAM role configuration required. + +```bash +# Option A: choose a storage method during init (config / shell / export) +mediakit-cli init --api-key --credential-store config --yes + +# Option B: inject temporarily via environment variables +export MEDIAKIT_API_KEY= +export MEDIAKIT_OUTPUT_PATH= +``` + +| Environment variable | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------- | +| `MEDIAKIT_API_KEY` | Cloud API Key ([get it from the console](https://console.volcengine.com/imp/ai-mediakit/settings)) | +| `MEDIAKIT_OUTPUT_PATH` | Local mode output directory, defaults to `~/.mediakit/temp` | + +## Command Structure + +``` +mediakit-cli [--cloud|--local] [flags] +``` + +- **Two modes, one command surface**: `--cloud` uses cloud elastic compute (asynchronously returns a `task_id`); `--local` uses local FFmpeg (synchronous, zero cost). Default is `cloud-first`, and it can be overridden per command with `--cloud` / `--local`. +- **Output**: cloud results are returned as URLs; local results land in `~/.mediakit/temp` (override with `--output-path` or `MEDIAKIT_OUTPUT_PATH`). + +System commands: + +| Command | Description | +| -------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `mediakit-cli init [--yes]` | Initialize configuration, interactive or non-interactive (Agent-friendly) | +| `mediakit-cli doctor` | Check cloud connectivity, local dependencies, and install suggestions | +| `mediakit-cli config` | View / modify configuration | +| `mediakit-cli version [--check]` | Show version; `--check` compares against the latest npm release | +| `mediakit-cli update [--check]` | Update the CLI and Skills via `npm install -g`; `--check` only checks without installing | +| `mediakit-cli --domains` | List all domains | +| `mediakit-cli --help-full` | List the full capability index | +| `mediakit-cli --schema` | Output the JSON Schema for the capability (Mode / Async / polling command metadata) | +| `mediakit-cli shared query-task --task-id X` | Query an async task; add `--poll-complete` to poll until terminal state | + +## Advanced Usage + +### Schema Introspection + +Every capability command supports `--schema`, which outputs the input / output schema plus Mode and Async information for Agents to discover tool capabilities dynamically: + +```bash +mediakit-cli video enhance-video --schema +mediakit-cli --local editing trim-video --schema +``` + +### Local Mode Output Naming + +Local mode output files are named by the following priority: + +1. Explicit `--output-path` with a complete file path (including extension) → used directly +2. Input filename available → `{original_filename}_{tool_name}.{ext}`; if a file with the same name already exists, a 6-digit random number is appended +3. No input filename → `{tool_name}-{timestamp}.{ext}` + +## License + +This project is open-sourced under the **MIT License**. + +At runtime this software calls MediaKit cloud APIs. Using those APIs is subject to the following agreements: + +- [Video Cloud Services Specific Terms](https://www.volcengine.com/docs/6448/79646?lang=zh) +- [Intelligent Processing Service Billing Rules](https://www.volcengine.com/docs/6448/104992?lang=zh) +- [Intelligent Processing Service SLA](https://www.volcengine.com/docs/6448/79648?lang=zh) diff --git a/README.md b/README.md index 699a828..fc8305f 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,189 @@ # AI MediaKit CLI -[简体中文](./README.zh.md) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Go Version](https://img.shields.io/badge/go-%3E%3D1.22-blue.svg)](https://go.dev/) +[![npm version](https://img.shields.io/npm/v/@volcengine/mediakit-cli.svg)](https://www.npmjs.com/package/@volcengine/mediakit-cli) -

- AI MediaKit CLI — interface rebuilt for LLMs · agent-native · cloud + local -

+[中文版](./README.md) | [English](./README.en.md) -> The agent-native command-line toolkit for audio & video. Run Volcengine's cloud AI and local editing through **one unified command** — built to be driven by AI agents (Claude Code, Trae, Cursor …) or by you in the terminal. +Mediakit 官方 CLI —— 兼容 FFmpeg 的命令面,同一条命令既能在本地跑 FFmpeg 完成裁剪 / 拼接 / 加字幕 / 混音 / 提取音频等剪辑操作,也能一键切到云端调用画质增强、字幕擦除、ASR、OCR、剧情线分析等 FFmpeg 做不到的 AI 能力。目前已覆盖视频、图像、音频等多模态原子能力和 5 个 AI Agent [Skills](./skills/),未来预计提供 100+ 音视频原子能力。 -`mediakit-cli` packs video enhancement, subtitle removal, and a full editing toolbox into a single tool. Heavy AI runs in the cloud; lightweight editing runs locally — switch with one flag, same command surface. +[安装](#安装与快速开始) · [AI Agent Skills](#agent-skills) · [鉴权](#鉴权) · [命令结构](#命令结构) · [进阶用法](#进阶用法) · [许可证](#许可证) ---- +## 为什么选 mediakit-cli? -## ✨ What it can do +- **完备的能力矩阵**:横跨视频、图像、音频三大模态,从裁剪 / 拼接 / 加字幕等底层处理,到画质增强、字幕擦除、ASR、OCR、剧情线分析等上层理解,一条命令覆盖预处理到成片输出的全链路。 +- **兼容 FFmpeg,无缝迁移**:本地模式基于 `ffmpeg` / `ffprobe`,覆盖裁剪、拼接、加图片、加字幕、调速、调音量、翻转、淡入淡出、混音、音视频合成、提取音频、绿幕抠图、元信息探测等常用能力,与 FFmpeg 命令直觉对齐;滤镜、图片转视频、拼接转场等复杂 / AI 能力交由云端处理。 +- **云端更快更强**:同一条命令加 `--cloud` 就能升维到 FFmpeg 做不到的能力 —— 画质增强 / 生成式画质修复、字幕擦除(标准版 / 精细化)、ASR、视频 OCR、高光智剪(短剧 / 小游戏)、剧情故事线分析、场景切分、绿幕 / 人像抠图等 AI 原子能力,云端弹性算力提供秒级并发。 +- **一命令双模态**:`--local` / `--cloud` 可逐命令切换,本地零成本 + 云端弹性算力互补;共用同一份参数与 `--schema`,Agent / 脚本零改造切换。 +- **高性价比处理**:依托云端弹性资源调度与闲时批量处理策略,为大批量媒体任务提供极具竞争力的价格,显著降低 AI 应用的整体 Token 消耗与运行成本。 -**40+ capabilities across 5 domains** — run `mediakit-cli --help-full` to list them all. +## 功能 -| Domain | Capabilities | Runs on | Status | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------ | -| 🎬 **Editing** (17) | trim · concat · watermark · subtitle · speed · volume · filter · flip · fade · mix · mux · extract audio · image-to-video | cloud **or** local | ✅ Available | -| 🎚️ **Audio** (2) | voice / background separation · audio metadata probe | cloud | ✅ Available | -| 🖼️ **Image AI** (5) | enhance · object / text erase · quality scoring · OCR · background removal | cloud | ✅ Available | -| 🎥 **Video AI** (14) | enhancement (+ generative restore) · subtitle removal · ASR subtitles · OCR · highlight clipping (short-drama / mini-game) · storyline analysis · scene split · portrait & green-screen matting · metadata probe | cloud | ✅ Available | -| 🔧 **Shared** (2) | async task query · remote-file fetch | local / cloud | ✅ Available | -| 🚧 **Coming** | video translation · commentary generation · anime restyling | cloud | Rolling out | +| 领域 | 能力 | 运行 | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| 🎬 **剪辑** (17) | 视频裁剪 · 音频裁剪 · 视频拼接 · 音频拼接 · 视频加图片 · 视频加字幕 · 视频调速 · 音频调速 · 调整视频音量 · 视频添加滤镜 · 视频画面翻转 · 视频声音淡入淡出 · 音频声音淡入淡出 · 音频混合 · 视频加音频 · 提取音频 · 图片转视频 | 云端 **或** 本地 | +| 🎚️ **音频** (2) | 人声背景音分离 · 音频元信息获取 | 云端 | +| 🖼️ **图像 AI** (5) | 图像画质增强 · 图像擦除修复 · 图像画质评估 · 图像文字识别 OCR · 图像背景移除 | 云端 | +| 🎥 **视频 AI** (14) | 画质增强 · 生成式画质增强 · 字幕擦除(标准版)· 精细化字幕擦除 · 语音转字幕(ASR)· 视频识别字幕(OCR)· 高光智剪-短剧 · 高光智剪-小游戏 · 高光片段提取 · 剧情故事线分析 · 场景切分 · 视频绿幕抠图 · 视频人像抠图 · 视频元信息获取 | 云端 | +| 🔧 **通用** (2) | 异步任务查询 · 远程文件拉取 | 本地 / 云端 | +| 🚧 **即将上线** | 视频翻译 · 解说生成 · 漫剧转绘(陆续上线) | 云端 | -> AI capabilities run in the cloud (elastic compute, async). Editing runs **either** in the cloud **or** locally (sync, zero cost) — pick per command with `--cloud` / `--local`. +## 安装与快速开始 ---- +### 环境要求 -## 🚀 Quick Start +开始之前,请确保具备以下条件: -```bash -npm install -g @volcengine/mediakit-cli -npx skills add volcengine/mediakit-cli -g -y # optional — install agent Skills (Claude Code / Trae / Cursor …) -export MEDIAKIT_API_KEY= # from the AI MediaKit console +- Node.js `>=18`(`npm` / `npx`) -# Cloud AI (async): enhance to 1080p, then poll for the result -mediakit-cli --cloud video enhance-video --video-url --resolution 1080p -mediakit-cli shared query-task --task-id --poll-complete +- 本地模式:`ffmpeg` `5.1.x` 与 `ffprobe` -# Local editing (sync, no key needed): runs on your machine -mediakit-cli --local editing trim-video --video-url ./in.mp4 --start-time 3 --end-time 8 -``` +### 快速开始(人类用户) + +#### 安装 ---- +以下两种方式**任选其一**: -## 📦 Install +**方式一 — 一键安装:** ```bash -# One-click install (CLI + AI agent Skills) npx @volcengine/mediakit-cli install -y +``` + +**方式二 — 从源码构建:** -# npm only (CLI, recommended, cross-platform — pulls the right build for your OS / arch) -npm install -g @volcengine/mediakit-cli +需要 Go `v1.22`+。 -# npx (no install) -npx @volcengine/mediakit-cli version +```bash +git clone https://github.com/volcengine/mediakit-cli.git +cd mediakit-cli +make build # 产物:.mediakit/build/dev/mediakit-cli -# curl (macOS / Linux) -curl -fsSL https://raw.githubusercontent.com/volcengine/mediakit-cli/main/scripts/install.sh | bash +# 从本地 skills 目录安装 AI Agent Skills(必需) +npx -y skills add ./skills -g -y ``` -Pin a version or path: `VERSION= INSTALL_DIR="$HOME/.local/bin" curl -fsSL …/install.sh | bash` +#### 配置与使用 -Verify: `mediakit-cli doctor` (checks cloud readiness + local tool deps + install hints). +```bash +# 1. 初始化配置(交互式引导) +mediakit-cli init -### Update +# 2. 环境自检(检查云端连通性、本地依赖、安装建议) +mediakit-cli doctor -The CLI checks the npm registry for new releases once a day (TTL 24h). When an update is available you'll see a hint in `stderr` and a `_notice.update` field in the stdout JSON. +# 3. 本地剪辑(同步、无需 API Key):本机运行 FFmpeg 完成裁剪 +mediakit-cli --local editing trim-video --video-url ./in.mp4 --start-time 3 --end-time 8 -```bash -mediakit-cli version --check # report current vs latest as JSON -mediakit-cli update --check # check only, no install -mediakit-cli update # install the latest via `npm install -g` +# 4. 云端 AI(异步):将视频画质增强至 1080p,然后轮询获取最终结果 +mediakit-cli --cloud video enhance-video --video-url --resolution 1080p +mediakit-cli shared query-task --task-id --poll-complete ``` -Suppress the check with `MEDIAKIT_DISABLE_UPDATE_CHECK=1` or in CI (`CI` env set). +### 快速开始(AI Agent) ---- +> 以下步骤面向 AI Agent,全流程支持无人值守。 -## 🤖 Use with AI Agents +**第 1 步 — 安装** -`mediakit-cli` ships **AI agent Skills** that teach an agent how to call it — so a user can just say _"enhance this video to 1080p and trim the best 5 seconds"_ and the agent orchestrates the commands. +```bash +npx @volcengine/mediakit-cli install -y +``` + +**第 2 步 — 非交互式初始化(`--yes` 模式)** ```bash -# One command installs the Skills into every supported agent on your machine -npx skills add volcengine/mediakit-cli -g -y +# API Key 获取地址:https://console.volcengine.com/imp/ai-mediakit/settings +mediakit-cli init \ + --mode cloud-first \ + --api-key \ + --yes ``` -This auto-detects and installs to **10+ runtimes** — Claude Code, Trae (CN & Global), Cursor, Codex, Gemini CLI, GitHub Copilot, OpenCode, OpenClaw, Antigravity, and more. +**第 3 步 — 验证** -Every capability is also **MCP-compatible** — `mediakit-cli --schema` emits a JSON Schema for MCP / Anthropic Tool Use / function-calling, no hand-written adapter needed. +```bash +mediakit-cli doctor +mediakit-cli version +``` ---- +## Agent Skills -## 🧩 How it works +| Skill | 说明 | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `byted-mediakit-shared` | 通用能力:查询任务,其它 skill 依赖此项 | +| `byted-mediakit-editing` | 剪辑:视频裁剪、音频裁剪、视频拼接、音频拼接、视频加图片、视频加字幕、视频调速、音频调速、调整视频音量、视频添加滤镜、视频画面翻转、视频声音淡入淡出、音频声音淡入淡出、音频混合、视频加音频、提取音频、图片转视频 | +| `byted-mediakit-audio` | 音频:人声背景音分离、音频元信息获取 | +| `byted-mediakit-image` | 图像 AI:图像画质增强、图像擦除修复、图像画质评估、图像文字识别 OCR、图像背景移除 | +| `byted-mediakit-video` | 视频 AI:画质增强、生成式画质增强、字幕擦除(标准版)、精细化字幕擦除、语音转字幕(ASR)、视频识别字幕(OCR)、高光智剪-短剧、高光智剪-小游戏、高光片段提取、剧情故事线分析、场景切分、视频绿幕抠图、视频人像抠图、视频元信息获取 | -- **Two modes, one command surface.** `--cloud` runs heavy AI in Volcengine's cloud (elastic compute, async `task_id`); `--local` runs deterministic editing locally (sync, zero cloud cost). Default mode is `cloud-first`; per-command flags override it. -- **Command structure:** `mediakit-cli [--cloud|--local] [flags]` — domains are `editing` · `audio` · `image` · `video` · `shared`. -- **Outputs:** cloud results are returned as URLs; local results write to `~/.mediakit/temp` (override with `--output-path` or `MEDIAKIT_OUTPUT_PATH`). +## 鉴权 ---- +`mediakit-cli` 采用极简鉴权:只需一个 API Key,无需 OAuth / STS / IAM 角色配置。 -## 📖 Documentation +```bash +# 方式 A:init 时选择存储方式(config / shell / export) +mediakit-cli init --api-key --credential-store config --yes -- Volcengine AI MediaKit product docs & pricing: https://www.volcengine.com/docs/6448 -- Full command reference & FAQ: see the docs site. -- [Error codes & exit code contract](./docs/error-codes.md) — stdout JSON protocol and exit-code rules. +# 方式 B:临时通过环境变量注入 +export MEDIAKIT_API_KEY= +export MEDIAKIT_OUTPUT_PATH= +``` ---- +| 环境变量 | 说明 | +| ---------------------- | ------------------------------------------------------------------------------------- | +| `MEDIAKIT_API_KEY` | 云端 API Key([控制台获取](https://console.volcengine.com/imp/ai-mediakit/settings)) | +| `MEDIAKIT_OUTPUT_PATH` | 本地模式输出目录,默认 `~/.mediakit/temp` | -## 🛠 Development +## 命令结构 -```bash -make build # local build → .mediakit/build/dev/mediakit-cli -make build-all # all platforms -make snapshot # snapshot release +``` +mediakit-cli [--cloud|--local] [flags] ``` -Releases are produced via `.goreleaser.yml`; npm distribution via `package.json` + `scripts/install.js`; curl install via `scripts/install.sh`. +- **两种模式,同一命令面**:`--cloud` 走云端弹性算力(异步返回 `task_id`);`--local` 走本地 FFmpeg(同步、零成本)。默认为 `cloud-first`,可用 `--cloud` / `--local` 逐命令覆盖。 +- **输出**:云端结果以 URL 返回;本地结果落到 `~/.mediakit/temp`(可用 `--output-path` 或 `MEDIAKIT_OUTPUT_PATH` 覆盖)。 -
-Local Tool Admission (FFmpeg policy) +系统命令: + +| 命令 | 说明 | +| -------------------------------------------- | ----------------------------------------------------------- | +| `mediakit-cli init [--yes]` | 初始化配置,支持交互式或非交互式(Agent 友好) | +| `mediakit-cli doctor` | 检查云端连通性、本地依赖与安装建议 | +| `mediakit-cli config` | 查看 / 修改配置项 | +| `mediakit-cli version [--check]` | 显示版本;`--check` 对比 npm 最新版 | +| `mediakit-cli update [--check]` | 通过 `npm install -g` 更新 CLI 和 Skills;`--check` 只检查不安装 | +| `mediakit-cli --domains` | 列出所有域 | +| `mediakit-cli --help-full` | 列出全部能力索引 | +| `mediakit-cli --schema` | 输出该能力的 JSON Schema(Mode / Async / 轮询命令等元信息) | +| `mediakit-cli shared query-task --task-id X` | 查询异步任务;加 `--poll-complete` 轮询至终态 | + +## 进阶用法 + +### Schema 自省 + +每个能力命令都支持 `--schema`,输出输入 / 输出 schema、Mode 与 Async 信息,供 Agent 动态发现工具能力: + +```bash +mediakit-cli video enhance-video --schema +mediakit-cli --local editing trim-video --schema +``` -- `ffmpeg` / `ffprobe`: required, `5.1.x`, `LGPL v2.1 or later`, commercial use allowed -- Optional FFmpeg features: `openh264`, `libmp3lame`, `libass`, `libfreetype`, `libfontconfig`, `libfribidi`, `libharfbuzz`, `zlib`, `libpng`, `libjpeg-turbo` -- Boundary: external process execution only (no static/dynamic linking of local tools into the Go binary); FFmpeg stays in LGPL mode by default; no `non-free` components; no local intermediate artifacts retained (only final outputs + `fetch-file` downloads). +### 本地模式输出命名 -
+本地模式输出文件按以下优先级命名: ---- +1. 显式 `--output-path` 指定完整文件路径(含扩展名)→ 直接使用 +2. 有输入文件名 → `{原文件名}_{工具名}.{ext}`;同名文件已存在时追加 6 位随机数字 +3. 无输入文件名 → `{工具名}-{时间戳}.{ext}` -## License +## 许可证 -This project is open-sourced under the [MIT License](./LICENSE). +本项目基于 **MIT 许可证** 开源。 -This software calls MediaKit APIs at runtime. Use of these APIs is subject to the following terms and privacy policies: +该软件运行时会调用 MediaKit 云端 API,使用这些 API 需要遵守如下协议: -- [Video Cloud Service Special Terms](https://www.volcengine.com/docs/6448/79646?lang=zh) -- [Intelligent Processing Service Billing Rules](https://www.volcengine.com/docs/6448/104992?lang=zh) -- [Intelligent Processing Service Level Agreement](https://www.volcengine.com/docs/6448/79648?lang=zh) +- [视频云服务专有条款](https://www.volcengine.com/docs/6448/79646?lang=zh) +- [智能处理服务计费规则](https://www.volcengine.com/docs/6448/104992?lang=zh) +- [智能处理服务 SLA](https://www.volcengine.com/docs/6448/79648?lang=zh) diff --git a/README.zh.md b/README.zh.md deleted file mode 100644 index 59e26cd..0000000 --- a/README.zh.md +++ /dev/null @@ -1,143 +0,0 @@ -# AI MediaKit CLI - -[English](./README.md) - -

- AI MediaKit CLI — 接口为模型重构 · Agent 原生 · 端云一体 -

- -> 面向 Agent 的音视频命令行工具集。用**一个统一的命令**调用火山引擎的云端 AI 能力、跑本地剪辑——既能让 AI Agent(Claude Code、Trae、Cursor……)自然语言调度,也能你自己在终端直接用。 - -`mediakit-cli` 把画质增强、字幕擦除和一整套剪辑能力收进一个工具:重算力 AI 跑云端,轻量剪辑在本地跑——一个 flag 切换,命令写法不变。 - ---- - -## ✨ 能做什么 - -**5 大领域、40+ 能力** —— 运行 `mediakit-cli --help-full` 可列全。 - -| 领域 | 能力 | 运行 | 状态 | -| --- | --- | --- | --- | -| 🎬 **剪辑** (17) | 裁剪 · 拼接 · 加水印 · 加字幕 · 调速 · 调音量 · 滤镜 · 翻转 · 淡入淡出 · 混音 · 合成 · 提取音频 · 图片转视频 | 云端 **或** 本地 | ✅ 已上线 | -| 🎚️ **音频** (2) | 人声 / 背景音分离 · 音频元信息探测 | 云端 | ✅ 已上线 | -| 🖼️ **图像 AI** (5) | 画质增强 · 擦除修复 · 画质评分 · OCR · 背景移除 | 云端 | ✅ 已上线 | -| 🎥 **视频 AI** (14) | 画质增强(含生成式修复)· 字幕擦除 · ASR 字幕 · OCR · 高光智剪(短剧 / 小游戏)· 剧情线分析 · 场景切分 · 人像 & 绿幕抠图 · 元信息探测 | 云端 | ✅ 已上线 | -| 🔧 **通用** (2) | 异步任务查询 · 远程文件拉取 | 本地 / 云端 | ✅ 已上线 | -| 🚧 **即将上线** | 视频翻译 · 解说生成 · 漫剧转绘 | 云端 | 陆续上线 | - -> AI 能力跑在云端(弹性算力、异步);剪辑能力**云端或本地**皆可(本地跑,同步、零成本)—— 每条命令用 `--cloud` / `--local` 选。 - ---- - -## 🚀 快速开始 - -```bash -npm install -g @volcengine/mediakit-cli -npx skills add volcengine/mediakit-cli -g -y # 可选 —— 装 Agent Skill(Claude Code / Trae / Cursor …) -export MEDIAKIT_API_KEY=<你的 API Key> # 在 AI MediaKit 控制台获取 - -# 云端 AI(异步):增强到 1080p,再轮询拿结果 -mediakit-cli --cloud video enhance-video --video-url --resolution 1080p -mediakit-cli shared query-task --task-id --poll-complete - -# 本地剪辑(同步、无需 Key):在本机跑 -mediakit-cli --local editing trim-video --video-url ./in.mp4 --start-time 3 --end-time 8 -``` - ---- - -## 📦 安装 - -```bash -# 一键安装(CLI + AI Agent Skill) -npx @volcengine/mediakit-cli install -y - -# 仅装 CLI(推荐,跨平台——自动拉取对应平台 / 架构的构建产物) -npm install -g @volcengine/mediakit-cli - -# npx(免安装) -npx @volcengine/mediakit-cli version - -# curl(macOS / Linux) -curl -fsSL https://raw.githubusercontent.com/volcengine/mediakit-cli/main/scripts/install.sh | bash -``` - -指定版本 / 路径:`VERSION= INSTALL_DIR="$HOME/.local/bin" curl -fsSL …/install.sh | bash` - -验证环境:`mediakit-cli doctor`(检查云端就绪 + 本地工具依赖 + 安装指引)。 - -### 更新 - -CLI 每天会向 npm registry 检查一次新版本(TTL 24h)。有更新时,`stderr` 会出现提示,stdout JSON 会带上 `_notice.update` 字段。 - -```bash -mediakit-cli version --check # 以 JSON 输出当前版本 vs 最新版本 -mediakit-cli update --check # 只检查,不安装 -mediakit-cli update # 通过 `npm install -g` 安装最新版 -``` - -如需关闭自动检查,设置 `MEDIAKIT_DISABLE_UPDATE_CHECK=1`,或在 CI 中运行(`CI` 环境变量被设置时也会自动抑制)。 - ---- - -## 🤖 配合 AI Agent 使用 - -`mediakit-cli` 自带 **AI Agent Skill**——教 Agent 怎么调它。于是用户只需说一句*"把这个视频增强到 1080p,再剪出最精彩的 5 秒"*,Agent 就能自动编排命令。 - -```bash -# 一个命令把 Skill 装进本机所有支持的 Agent -npx skills add volcengine/mediakit-cli -g -y -``` - -它会自动检测并安装到 **10+ 种 runtime**——Claude Code、Trae(国内 & 海外)、Cursor、Codex、Gemini CLI、GitHub Copilot、OpenCode、OpenClaw、Antigravity 等。 - -每个能力还**MCP 兼容**——`mediakit-cli --schema` 吐出 JSON Schema,直接喂 MCP / Anthropic Tool Use / function calling,无需手写适配器。 - ---- - -## 🧩 工作原理 - -- **两种模式,同一套命令。** `--cloud` 把重算力 AI 跑在火山引擎云端(弹性算力、异步 `task_id`);`--local` 在本机跑确定性剪辑(同步、零云端成本)。默认 `cloud-first`,单命令 flag 可覆盖。 -- **命令结构:** `mediakit-cli [--cloud|--local] [flags]`——domain 为 `editing` · `audio` · `image` · `video` · `shared`。 -- **输出:** 云端结果以 URL 返回;本地结果写到 `~/.mediakit/temp`(可用 `--output-path` 或 `MEDIAKIT_OUTPUT_PATH` 覆盖)。 - ---- - -## 📖 文档 - -- Volcengine AI MediaKit 产品文档 & 定价:https://www.volcengine.com/docs/6448 -- 完整命令参考 & FAQ:见文档站。 -- [错误码与退出码契约](./docs/error-codes.md) —— stdout JSON 协议与退出码规则。 - ---- - -## 🛠 开发 - -```bash -make build # 本地构建 → .mediakit/build/dev/mediakit-cli -make build-all # 全平台 -make snapshot # snapshot release -``` - -发布走 `.goreleaser.yml`;npm 分发走 `package.json` + `scripts/install.js`;curl 安装走 `scripts/install.sh`。 - -
-本地工具 Admission(FFmpeg 策略) - -- `ffmpeg` / `ffprobe`:必需,`5.1.x`,`LGPL v2.1 或更高`,允许商用 -- 可选 FFmpeg 能力:`openh264`、`libmp3lame`、`libass`、`libfreetype`、`libfontconfig`、`libfribidi`、`libharfbuzz`、`zlib`、`libpng`、`libjpeg-turbo` -- 边界:仅通过外部进程调用(不把本地工具静态 / 动态链接进 Go 二进制);FFmpeg 默认保持 LGPL 模式;默认不引入 `non-free` 组件;不保留本地中间产物(仅保留最终产物 + `fetch-file` 下载)。 - -
- ---- - -## License - -本项目基于 [MIT 许可证](./LICENSE) 开源。 - -该软件运行时会调用 MediaKit 的 API,使用这些 API 需要遵守如下协议和隐私政策: - -- [视频云服务专用条款](https://www.volcengine.com/docs/6448/79646?lang=zh) -- [智能处理服务计费结算规则](https://www.volcengine.com/docs/6448/104992?lang=zh) -- [智能处理服务等级协议](https://www.volcengine.com/docs/6448/79648?lang=zh) diff --git a/docs/error-codes.md b/docs/error-codes.md index 6fa1b8f..2b895e6 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -85,8 +85,8 @@ ```json { "error": { - "type": "invalid_parameter | security_violation | environment_error | execution_error", - "code": "missing_required_param | invalid_param_type | param_out_of_range | param_insufficient | unsupported_value | forbidden_operation | not_whitelisted | unsafe_characters | handler_not_implemented | local_unsupported | dependency_missing | execution_failed | download_failed | unknown", + "type": "InvalidParameter | SecurityViolation | EnvironmentError | ExecutionError", + "code": "MissingRequiredParam | InvalidParamType | ParamOutOfRange | ParamInsufficient | UnsupportedValue | ForbiddenOperation | NotWhitelisted | UnsafeCharacters | HandlerNotImplemented | LocalUnsupported | DependencyMissing | ExecutionFailed | DownloadFailed | Unknown", "message": "<原始错误信息(不截断、不摘要)>" } } diff --git a/internal/cloud/request.go b/internal/cloud/request.go index d345af7..1852283 100644 --- a/internal/cloud/request.go +++ b/internal/cloud/request.go @@ -9,6 +9,7 @@ import ( "os" "strings" + "mediakit-cli/internal/build" cliconfig "mediakit-cli/internal/config" ) @@ -86,8 +87,9 @@ func (c *Client) newRequest(method string, path string, query map[string]any, bo req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") req.Header.Set("x-surface", resolveSurface(c.Surface)) - req.Header.Set("x-runtime", resolveRuntime(c.Runtime)) + req.Header.Set("X-Amk-Task-Runtime", resolveRuntime(c.Runtime)) req.Header.Set("X-Amk-Task-Source", "cli") + req.Header.Set("X-Amk-Cli-Version", build.Version) if c.APIKey != "" { req.Header.Set("Authorization", "Bearer "+c.APIKey) } diff --git a/internal/commands/registry.go b/internal/commands/registry.go index a9fed83..367c68c 100644 --- a/internal/commands/registry.go +++ b/internal/commands/registry.go @@ -3171,7 +3171,7 @@ func classifyErrorType(err error) string { case strings.Contains(msg, "禁止") || strings.Contains(msg, "不在白名单") || strings.Contains(msg, "不安全字符"): - return "security_violation" + return "SecurityViolation" case strings.Contains(msg, "必填参数") || strings.Contains(msg, "必须是") || strings.Contains(msg, "取值范围") || @@ -3179,13 +3179,13 @@ func classifyErrorType(err error) string { strings.Contains(msg, "仅支持") || strings.Contains(msg, "必须大于") || strings.Contains(msg, "必须大于等于"): - return "invalid_parameter" + return "InvalidParameter" case strings.Contains(msg, "本地处理器未实现") || strings.Contains(msg, "不支持本地执行") || strings.Contains(msg, "本地依赖"): - return "environment_error" + return "EnvironmentError" default: - return "execution_error" + return "ExecutionError" } } @@ -3193,37 +3193,37 @@ func classifyErrorCode(err error) string { msg := err.Error() switch { case strings.Contains(msg, "禁止"): - return "forbidden_operation" + return "ForbiddenOperation" case strings.Contains(msg, "不在白名单"): - return "not_whitelisted" + return "NotWhitelisted" case strings.Contains(msg, "不安全字符"): - return "unsafe_characters" + return "UnsafeCharacters" case strings.Contains(msg, "必填参数"): - return "missing_required_param" + return "MissingRequiredParam" case strings.Contains(msg, "必须大于等于"): - return "param_out_of_range" + return "ParamOutOfRange" case strings.Contains(msg, "必须大于"): - return "param_out_of_range" + return "ParamOutOfRange" case strings.Contains(msg, "取值范围"): - return "param_out_of_range" + return "ParamOutOfRange" case strings.Contains(msg, "必须是"): - return "invalid_param_type" + return "InvalidParamType" case strings.Contains(msg, "至少需要"): - return "param_insufficient" + return "ParamInsufficient" case strings.Contains(msg, "仅支持"): - return "unsupported_value" + return "UnsupportedValue" case strings.Contains(msg, "本地处理器未实现"): - return "handler_not_implemented" + return "HandlerNotImplemented" case strings.Contains(msg, "不支持本地执行"): - return "local_unsupported" + return "LocalUnsupported" case strings.Contains(msg, "本地依赖"): - return "dependency_missing" + return "DependencyMissing" case strings.Contains(msg, "download failed"): - return "download_failed" + return "DownloadFailed" case strings.Contains(msg, "执行失败"): - return "execution_failed" + return "ExecutionFailed" default: - return "unknown" + return "Unknown" } } diff --git a/internal/commands/root.go b/internal/commands/root.go index f6a38a5..6a08218 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -20,15 +20,15 @@ func Execute() error { func newRootCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "mediakit-cli", - Short: "MediaKit command line interface", + Use: "mediakit-cli", + Short: "MediaKit command line interface", Long: `MediaKit CLI provides system commands, domain navigation, and generated capability commands. One-click install (CLI + AI agent Skills): npx @volcengine/mediakit-cli install -y Update: - mediakit-cli update # install latest via npm install -g + mediakit-cli update # update CLI and AI Agent Skills via npm mediakit-cli update --check # only report status mediakit-cli version --check # show current vs latest @@ -36,15 +36,8 @@ AI Agent Skills: mediakit-cli pairs with AI agent skills (Claude Code, etc.) that teach the agent MediaKit CLI patterns, best practices, and workflows. - Install all skills: - npx skills add volcengine/mediakit-cli -g -y - - Or pick specific domains: - npx skills add volcengine/mediakit-cli -s byted-mediakit-editing -y - npx skills add volcengine/mediakit-cli -s byted-mediakit-audio -y - npx skills add volcengine/mediakit-cli -s byted-mediakit-image -y - npx skills add volcengine/mediakit-cli -s byted-mediakit-video -y - npx skills add volcengine/mediakit-cli -s byted-mediakit-shared -y`, + Reinstall all skills: + npx @volcengine/mediakit-cli install --skills-only -y`, SilenceUsage: true, SilenceErrors: true, DisableAutoGenTag: true, diff --git a/internal/commands/update.go b/internal/commands/update.go index de35867..e1e765a 100644 --- a/internal/commands/update.go +++ b/internal/commands/update.go @@ -5,24 +5,37 @@ import ( "fmt" "os" "os/exec" + "strings" "time" buildinfo "mediakit-cli/internal/build" + cliconfig "mediakit-cli/internal/config" + "mediakit-cli/internal/skillstate" "mediakit-cli/internal/updatecheck" "github.com/spf13/cobra" ) +var ( + checkNow = func() *updatecheck.Result { + return updatecheck.CheckNow(3 * time.Second) + } + runNpmInstall = runNpmInstallLatest + runSkillsInstall = runSkillsInstallFromPackage +) + func newUpdateCmd() *cobra.Command { var checkOnly bool + var force bool + var asJSON bool cmd := &cobra.Command{ Use: "update", - Short: "Check and apply mediakit-cli updates from the npm registry", - Long: "Check the npm registry for the latest @volcengine/mediakit-cli release and optionally install it via npm install -g.", + Short: "Update mediakit-cli", + Long: "Update @volcengine/mediakit-cli from npm.", Args: cobra.NoArgs, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { - r := updatecheck.CheckNow(3 * time.Second) + r := checkNow() if r == nil { return writeUpdatePayload(cmd, map[string]any{ "current": buildinfo.Version, @@ -40,27 +53,55 @@ func newUpdateCmd() *cobra.Command { payload["action"] = "skipped" return writeUpdatePayload(cmd, payload) } - if !r.HasUpdate { - payload["action"] = "noop" - return writeUpdatePayload(cmd, payload) - } - payload["upgrade_command"] = fmt.Sprintf("npm install -g %s@latest", updatecheck.PackageName) + payload["upgrade_command"] = "mediakit-cli update" if checkOnly { payload["action"] = "check" + applySkillsStatus(payload, r.Current) + if !asJSON { + return writeUpdateCheckText(cmd, r) + } return writeUpdatePayload(cmd, payload) } - payload["action"] = "install" - if err := runNpmInstallLatest(cmd); err != nil { - payload["install_status"] = "failed" - payload["error"] = err.Error() - _ = writeUpdatePayload(cmd, payload) - return err + + if r.HasUpdate { + payload["action"] = "install" + if !asJSON { + fmt.Fprintf(cmd.OutOrStdout(), "Updating mediakit-cli %s → %s via npm ...\n", r.Current, r.Latest) + } + if err := runNpmInstall(cmd); err != nil { + payload["install_status"] = "failed" + payload["error"] = err.Error() + if asJSON { + _ = writeUpdatePayload(cmd, payload) + } + return err + } + payload["install_status"] = "ok" + } else { + payload["action"] = "noop" + } + + shouldInstallSkills := !r.HasUpdate && shouldInstallSkillsForVersion(r.Current, force) + if shouldInstallSkills { + if err := runSkillsInstall(cmd); err != nil { + payload["skills_action"] = "failed" + payload["skills_error"] = err.Error() + if asJSON { + _ = writeUpdatePayload(cmd, payload) + } + return err + } + payload["skills_action"] = "installed" + } + if !asJSON { + return writeUpdateText(cmd, r, shouldInstallSkills) } - payload["install_status"] = "ok" return writeUpdatePayload(cmd, payload) }, } cmd.Flags().BoolVar(&checkOnly, "check", false, "Only check for updates; do not install") + cmd.Flags().BoolVar(&force, "force", false, "Force reinstall skills from the current npm package even when CLI is already up to date") + cmd.Flags().BoolVar(&asJSON, "json", false, "Output structured JSON") return cmd } @@ -71,6 +112,33 @@ func writeUpdatePayload(cmd *cobra.Command, payload map[string]any) error { return encoder.Encode(payload) } +func writeUpdateCheckText(cmd *cobra.Command, r *updatecheck.Result) error { + if r.HasUpdate { + _, err := fmt.Fprintf(cmd.OutOrStdout(), + "Update available: %s → %s\n Release: https://www.npmjs.com/package/%s/v/%s\n\nRun `mediakit-cli update` to install.\n", + r.Current, r.Latest, updatecheck.PackageName, r.Latest) + return err + } + _, err := fmt.Fprintf(cmd.OutOrStdout(), "mediakit-cli %s is already up to date\n", r.Current) + return err +} + +func writeUpdateText(cmd *cobra.Command, r *updatecheck.Result, skillsInstalled bool) error { + if r.HasUpdate { + fmt.Fprintf(cmd.OutOrStdout(), "\n✓ Successfully updated mediakit-cli from %s to %s\n\n", r.Current, r.Latest) + fmt.Fprintln(cmd.OutOrStdout(), "Updating skills ...") + fmt.Fprintln(cmd.OutOrStdout(), "✓ Skills updated") + } else { + fmt.Fprintf(cmd.OutOrStdout(), "mediakit-cli %s is already up to date\n", r.Current) + } + if skillsInstalled { + fmt.Fprintln(cmd.OutOrStdout(), "\nUpdating skills ...") + _, err := fmt.Fprintln(cmd.OutOrStdout(), "✓ Skills updated") + return err + } + return nil +} + func runNpmInstallLatest(cmd *cobra.Command) error { target := fmt.Sprintf("%s@latest", updatecheck.PackageName) c := exec.Command("npm", "install", "-g", target) @@ -79,18 +147,68 @@ func runNpmInstallLatest(cmd *cobra.Command) error { return c.Run() } +func runSkillsInstallFromPackage(cmd *cobra.Command) error { + c := exec.Command("npx", "-y", currentPackageSpec(), "install", "--skills-only", "-y") + c.Stdout = os.Stderr + c.Stderr = os.Stderr + return c.Run() +} + +func currentPackageSpec() string { + version := normalizeVersion(buildinfo.Version) + if version == "" { + return updatecheck.PackageName + } + return fmt.Sprintf("%s@%s", updatecheck.PackageName, version) +} + +func shouldInstallSkillsForVersion(version string, force bool) bool { + if force { + return true + } + home, err := cliconfig.ResolveHomeDir() + if err != nil { + return false + } + return !skillstate.InSync(home, version) +} + +func applySkillsStatus(payload map[string]any, target string) { + home, err := cliconfig.ResolveHomeDir() + if err != nil { + return + } + status, err := skillstate.ReadStatus(home, target) + if err != nil || status == nil { + return + } + payload["skills_status"] = map[string]any{ + "current": status.Current, + "target": status.Target, + "in_sync": status.InSync, + "missing": status.Missing, + "command": status.Command, + } +} + +func normalizeVersion(version string) string { + version = strings.TrimSpace(version) + version = strings.TrimPrefix(version, "v") + return strings.TrimPrefix(version, "V") +} + // newUpdateRefreshCmd is the hidden entry the detached refresh subprocess runs. // It fetches the latest version and writes the cache, with no output. The empty // persistent hooks override the root's update-notice hooks so the child never // re-spawns itself or prints a nag. func newUpdateRefreshCmd() *cobra.Command { return &cobra.Command{ - Use: "__update-refresh", - Hidden: true, - Args: cobra.NoArgs, - DisableAutoGenTag: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) {}, - PersistentPostRun: func(cmd *cobra.Command, args []string) {}, + Use: "__update-refresh", + Hidden: true, + Args: cobra.NoArgs, + DisableAutoGenTag: true, + PersistentPreRun: func(cmd *cobra.Command, args []string) {}, + PersistentPostRun: func(cmd *cobra.Command, args []string) {}, RunE: func(cmd *cobra.Command, args []string) error { updatecheck.RunRefresh() return nil diff --git a/internal/commands/version.go b/internal/commands/version.go index f8b0236..47cc6c1 100644 --- a/internal/commands/version.go +++ b/internal/commands/version.go @@ -32,7 +32,7 @@ func newVersionCmd() *cobra.Command { payload["error"] = r.Err.Error() } if r.HasUpdate { - payload["upgrade_command"] = fmt.Sprintf("npm install -g %s@latest", updatecheck.PackageName) + payload["upgrade_command"] = "mediakit-cli update" } } encoder := json.NewEncoder(cmd.OutOrStdout()) diff --git a/internal/local/executor.go b/internal/local/executor.go index 5adcc5b..506d4a7 100644 --- a/internal/local/executor.go +++ b/internal/local/executor.go @@ -231,8 +231,8 @@ func (e *DependencyError) Error() string { func (e *DependencyError) StructuredError() map[string]any { return map[string]any{ "error": map[string]any{ - "type": "environment_error", - "code": "dependency_missing", + "type": "EnvironmentError", + "code": "DependencyMissing", "message": fmt.Sprintf("命令 %s 所需本地依赖缺失: %s", e.Command, strings.Join(e.Missing, ", ")), "install_guide": cliconfig.InstallGuide(e.Missing), }, diff --git a/internal/skillstate/state.go b/internal/skillstate/state.go new file mode 100644 index 0000000..210edc4 --- /dev/null +++ b/internal/skillstate/state.go @@ -0,0 +1,94 @@ +package skillstate + +import ( + "encoding/json" + "errors" + "os" + "path/filepath" + "strings" + "time" + + cliconfig "mediakit-cli/internal/config" +) + +const ( + FileName = "skills-state.json" + PackageName = "@volcengine/mediakit-cli" +) + +type State struct { + PackageName string `json:"package_name"` + Version string `json:"version"` + SkillsDir string `json:"skills_dir,omitempty"` + InstalledAt time.Time `json:"installed_at,omitempty"` +} + +type Status struct { + Current string `json:"current,omitempty"` + Target string `json:"target"` + InSync bool `json:"in_sync"` + Command string `json:"command"` + Missing bool `json:"missing,omitempty"` +} + +func File(home string) string { + return filepath.Join(cliconfig.ConfigDir(home), FileName) +} + +func Load(home string) (*State, error) { + data, err := os.ReadFile(File(home)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + return nil, err + } + if len(data) == 0 { + return nil, nil + } + var state State + if err := json.Unmarshal(data, &state); err != nil { + return nil, err + } + return &state, nil +} + +func Save(home string, state *State) error { + if state == nil { + return nil + } + if state.PackageName == "" { + state.PackageName = PackageName + } + return cliconfig.WriteJSONAtomic(File(home), state) +} + +func ReadStatus(home, target string) (*Status, error) { + target = normalizeVersion(target) + state, err := Load(home) + if err != nil { + return nil, err + } + status := &Status{ + Target: target, + Command: "mediakit-cli update --force", + } + if state == nil || strings.TrimSpace(state.Version) == "" { + status.Missing = true + return status, nil + } + status.Current = normalizeVersion(state.Version) + status.InSync = status.Current == target + return status, nil +} + +func InSync(home, target string) bool { + status, err := ReadStatus(home, target) + return err == nil && status != nil && status.InSync +} + +func normalizeVersion(version string) string { + version = strings.TrimSpace(version) + version = strings.TrimPrefix(version, "v") + return strings.TrimPrefix(version, "V") +} diff --git a/internal/updatecheck/notice.go b/internal/updatecheck/notice.go index b9c4ae3..c3c476c 100644 --- a/internal/updatecheck/notice.go +++ b/internal/updatecheck/notice.go @@ -4,6 +4,10 @@ import ( "fmt" "io" "os" + + "mediakit-cli/internal/build" + cliconfig "mediakit-cli/internal/config" + "mediakit-cli/internal/skillstate" ) // NoticePayload returns a map suitable to be embedded into the JSON stdout @@ -16,7 +20,7 @@ func NoticePayload() map[string]any { return map[string]any{ "current": r.Current, "latest": r.Latest, - "command": fmt.Sprintf("npm install -g %s@latest", PackageName), + "command": "mediakit-cli update", "message": fmt.Sprintf("New %s release available: %s -> %s", PackageName, r.Current, r.Latest), } } @@ -27,18 +31,47 @@ func InjectNotice(result map[string]any) { if result == nil { return } - payload := NoticePayload() - if payload == nil { - return - } notice, _ := result["_notice"].(map[string]any) if notice == nil { notice = map[string]any{} } - notice["update"] = payload + if payload := NoticePayload(); payload != nil { + notice["update"] = payload + } + if payload := SkillsNoticePayload(); payload != nil { + notice["skills"] = payload + } + if len(notice) == 0 { + return + } result["_notice"] = notice } +func SkillsNoticePayload() map[string]any { + home, err := cliconfig.ResolveHomeDir() + if err != nil { + return nil + } + status, err := skillstate.ReadStatus(home, build.Version) + if err != nil || status == nil || status.InSync { + return nil + } + return map[string]any{ + "current": status.Current, + "target": status.Target, + "command": status.Command, + "message": fmt.Sprintf("MediaKit skills are not synced: current %s, target %s, run: %s", + displayVersion(status.Current), status.Target, status.Command), + } +} + +func displayVersion(version string) string { + if version == "" { + return "missing" + } + return version +} + // PrintStderrNag prints an unobtrusive update hint to stderr when an update is // available and stderr is a TTY (character device). func PrintStderrNag(w io.Writer) { @@ -53,8 +86,8 @@ func PrintStderrNag(w io.Writer) { if !isCharDevice(f) { return } - fmt.Fprintf(w, "\n[mediakit-cli] new version available: %s -> %s\n run: npm install -g %s@latest\n", - r.Current, r.Latest, PackageName) + fmt.Fprintf(w, "\n[mediakit-cli] new version available: %s -> %s\n run: mediakit-cli update\n", + r.Current, r.Latest) } func isCharDevice(f *os.File) bool { diff --git a/local/generated/.gitkeep b/local/generated/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/local/manual/.gitkeep b/local/manual/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/package.json b/package.json index 175a803..f928264 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@volcengine/mediakit-cli", - "version": "0.1.7", + "version": "0.1.8", "description": "MediaKit CLI with multi-platform binary distribution via npm", "license": "MIT", "bin": { diff --git a/scripts/install-wizard.js b/scripts/install-wizard.js index 6148a85..8984e27 100644 --- a/scripts/install-wizard.js +++ b/scripts/install-wizard.js @@ -1,12 +1,15 @@ #!/usr/bin/env node const { spawnSync } = require('node:child_process') +const fs = require('node:fs') +const os = require('node:os') const path = require('node:path') const pkg = require('../package.json') const PACKAGE_NAME = pkg.name || '@volcengine/mediakit-cli' -const SKILL_REPO = 'volcengine/mediakit-cli' +const PACKAGE_SPEC = `${PACKAGE_NAME}@${pkg.version}` +const SKILLS_DIR = path.join(__dirname, '..', 'skills') function parseArgs(argv) { const opts = { @@ -14,7 +17,6 @@ function parseArgs(argv) { skillsOnly: false, skills: [], yes: false, - versionTag: pkg.version || 'latest', } for (let i = 0; i < argv.length; i++) { const a = argv[i] @@ -38,17 +40,11 @@ function parseArgs(argv) { } break } - case '--version': { - const next = argv[i + 1] - if (next && !next.startsWith('-')) { - opts.versionTag = next - i++ - } - break - } + case '--version': + throw new Error('--version is not supported; install the npm package version you want directly') default: if (a.startsWith('--version=')) { - opts.versionTag = a.slice('--version='.length) + throw new Error('--version is not supported; install the npm package version you want directly') } else if (a.startsWith('--skills=')) { opts.skills.push( ...a @@ -69,17 +65,33 @@ function log(msg) { } function whichSync(cmd) { - const probe = process.platform === 'win32' ? 'where' : 'command' - const probeArgs = process.platform === 'win32' ? [cmd] : ['-v', cmd] - const result = spawnSync(probe, probeArgs, { stdio: 'ignore', shell: true }) - return result.status === 0 + const pathValue = process.env.PATH || '' + const extensions = + process.platform === 'win32' + ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';') + : [''] + for (const dir of pathValue.split(path.delimiter)) { + if (!dir) continue + for (const ext of extensions) { + const candidate = path.join(dir, `${cmd}${ext}`) + if (fs.existsSync(candidate)) { + return true + } + } + } + return false } function runNpmInstall(target) { log(`installing ${target} via npm install -g`) - const result = spawnSync('npm', ['install', '-g', target], { - stdio: 'inherit', - }) + log('Installing skills ...') + const result = spawnSync( + 'npm', + ['install', '-g', target, '--foreground-scripts', '--ignore-scripts=false'], + { + stdio: 'inherit', + }, + ) if (result.error) { throw result.error } @@ -88,17 +100,18 @@ function runNpmInstall(target) { `npm install -g ${target} failed with exit code ${result.status}`, ) } + log(`✓ Successfully installed mediakit-cli ${pkg.version}`) + log('✓ Skills installed') } function runSkillsAdd(opts) { - const args = ['-y', 'skills', 'add', SKILL_REPO] - if (opts.skills.length === 0) { - args.push('-g') - } else { + const args = ['-y', 'skills', 'add', SKILLS_DIR] + if (opts.skills.length > 0) { for (const skill of opts.skills) { args.push('-s', skill) } } + args.push('-g') if (opts.yes) { args.push('-y') } @@ -110,6 +123,26 @@ function runSkillsAdd(opts) { if (result.status !== 0) { throw new Error(`npx skills add failed with exit code ${result.status}`) } + writeSkillsState() + log('✓ Skills installed') +} + +function writeSkillsState() { + const stateFile = path.join(os.homedir(), '.mediakit', 'skills-state.json') + fs.mkdirSync(path.dirname(stateFile), { recursive: true }) + fs.writeFileSync( + stateFile, + `${JSON.stringify( + { + package_name: PACKAGE_NAME, + version: pkg.version, + skills_dir: SKILLS_DIR, + installed_at: new Date().toISOString(), + }, + null, + 2, + )}\n`, + ) } async function runInstallWizard(rawArgs) { @@ -120,10 +153,10 @@ async function runInstallWizard(rawArgs) { } if (!opts.skillsOnly) { - runNpmInstall(`${PACKAGE_NAME}@${opts.versionTag}`) + runNpmInstall(PACKAGE_SPEC) } - if (!opts.cliOnly) { + if (opts.skillsOnly && !opts.cliOnly) { if (!whichSync('npx')) { throw new Error('npx is required to install skills but not found in PATH') } diff --git a/scripts/install.js b/scripts/install.js index 5715de1..2233a39 100755 --- a/scripts/install.js +++ b/scripts/install.js @@ -36,6 +36,7 @@ const RELEASE_BASE_URL = const TMP_PREFIX = "mediakit-cli-install-"; const packageRoot = path.resolve(__dirname, ".."); const binDir = path.join(packageRoot, "bin"); +const skillsDir = path.join(packageRoot, "skills"); const platformMap = { darwin: "darwin", @@ -198,9 +199,46 @@ function findBinary(rootDir, targetName) { return null; } +function installSkills() { + console.log("[mediakit-cli] Installing skills ..."); + console.log(`[mediakit-cli] skills source: ${skillsDir}`); + const result = spawnSync( + "npx", + ["-y", "skills", "add", skillsDir, "-g", "-y"], + { stdio: "inherit" } + ); + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + throw new Error(`npx skills add failed with exit code ${result.status}`); + } + writeSkillsState(); + console.log("[mediakit-cli] ✓ Skills installed"); +} + +function writeSkillsState() { + const stateFile = path.join(os.homedir(), ".mediakit", "skills-state.json"); + fs.mkdirSync(path.dirname(stateFile), { recursive: true }); + fs.writeFileSync( + stateFile, + `${JSON.stringify( + { + package_name: pkg.name || "@volcengine/mediakit-cli", + version: pkg.version, + skills_dir: skillsDir, + installed_at: new Date().toISOString(), + }, + null, + 2 + )}\n` + ); +} + async function install(options = {}) { if (readEnv("MEDIAKIT_CLI_SKIP_DOWNLOAD", "MEDIKIT_CLI_SKIP_DOWNLOAD") === "1") { console.log("[mediakit-cli] skip download because MEDIAKIT_CLI_SKIP_DOWNLOAD=1"); + installSkills(); return; } @@ -211,6 +249,7 @@ async function install(options = {}) { const targetBinary = path.join(binDir, binaryName()); if (!options.force && fs.existsSync(targetBinary)) { + installSkills(); return; } @@ -246,6 +285,7 @@ async function install(options = {}) { } console.log(`[mediakit-cli] installed ${targetBinary}`); + installSkills(); } if (require.main === module) {