feat: 新增卸载脚本与统一版本管理
uninstall.mjs 一键清理 installed.json/managed 副本/含密钥配置目录(--yes/--dry-run,幂等);scripts/bump-version.mjs 以 package.json 为唯一版本来源同步 kimi.plugin.json,挂 npm version 钩子;vision.mjs VERSION 运行时读 package.json,并修复 modelsEndpoint 对 /chat/completions 结尾的边界。1.0.5 → 1.0.6。
This commit is contained in:
1 parent
8a5d41ebca
commit
839a66ff5d
5 files changed
+263
-5
No files matched your search
+11
-3
@@ -1,8 +1,14 @@
|
||||
{
|
||||
"name": "kimi-eyes",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.6",
|
||||
"description": "Give non-multimodal models vision: analyze local images and clipboard screenshots through a user-configured VLM API (OpenAI-compatible or Anthropic protocol).",
|
||||
"keywords": ["vision", "image", "screenshot", "multimodal", "vlm"],
|
||||
"keywords": [
|
||||
"vision",
|
||||
"image",
|
||||
"screenshot",
|
||||
"multimodal",
|
||||
"vlm"
|
||||
],
|
||||
"author": "kimi-eyes",
|
||||
"license": "MIT",
|
||||
"interface": {
|
||||
@@ -14,7 +20,9 @@
|
||||
"mcpServers": {
|
||||
"kimi-eyes": {
|
||||
"command": "node",
|
||||
"args": ["./mcp/server.mjs"],
|
||||
"args": [
|
||||
"./mcp/server.mjs"
|
||||
],
|
||||
"cwd": "./"
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -10,7 +10,19 @@ import { execFileSync } from 'node:child_process';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
export const MODELS_DB_PATH = path.join(__dirname, 'models-db.json');
|
||||
|
||||
export const VERSION = '1.0.2';
|
||||
// Single source of truth for the version: package.json.
|
||||
// Shared by the MCP server (serverInfo.version) and any consumer of this module.
|
||||
function readPackageVersion() {
|
||||
try {
|
||||
const pkg = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
|
||||
);
|
||||
return typeof pkg.version === 'string' && pkg.version ? pkg.version : '0.0.0';
|
||||
} catch {
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
export const VERSION = readPackageVersion();
|
||||
|
||||
export const EXT_MEDIA = {
|
||||
png: 'image/png',
|
||||
@@ -145,6 +157,8 @@ export function anthropicEndpoint(baseUrl) {
|
||||
export function modelsEndpoint(baseUrl, protocol) {
|
||||
let b = String(baseUrl || '').trim().replace(/\/+$/, '');
|
||||
if (!b) return '';
|
||||
// 用户可能把完整 chat/completions 端点当 BaseUrl 填(与 openaiEndpoint 处理保持一致)
|
||||
if (/\/chat\/completions$/i.test(b)) b = b.replace(/\/chat\/completions$/i, '');
|
||||
if (/\/v\d+$/i.test(b)) return `${b}/models`;
|
||||
return `${b}/v1/models`;
|
||||
}
|
||||
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kimi-eyes",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"description": "Give non-multimodal models in Kimi Code the ability to analyze images and screenshots via a user-configured VLM (OpenAI-compatible or Anthropic protocol).",
|
||||
"keywords": [
|
||||
"kimi",
|
||||
@@ -18,6 +18,7 @@
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"kimi-eyes": "setup.mjs",
|
||||
"kimi-eyes-uninstall": "uninstall.mjs",
|
||||
"kimi-eyes-mcp": "mcp/server.mjs"
|
||||
},
|
||||
"files": [
|
||||
@@ -26,6 +27,7 @@
|
||||
"SYSTEM.md",
|
||||
"kimi.plugin.json",
|
||||
"setup.mjs",
|
||||
"uninstall.mjs",
|
||||
"README.md",
|
||||
"README.en.md",
|
||||
"LICENSE"
|
||||
@@ -35,7 +37,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"setup": "node setup.mjs",
|
||||
"uninstall": "node uninstall.mjs",
|
||||
"sync:models": "node scripts/sync-models.mjs",
|
||||
"version": "node scripts/bump-version.mjs",
|
||||
"prepublishOnly": "node scripts/sync-models.mjs --allow-fail"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
// kimi-eyes — 版本同步脚本。package.json 是唯一版本来源。
|
||||
//
|
||||
// 用法:
|
||||
// node scripts/bump-version.mjs 从 package.json 同步 kimi.plugin.json
|
||||
// node scripts/bump-version.mjs 1.0.6 更新 package.json 并同步 kimi.plugin.json
|
||||
//
|
||||
// 作为 npm "version" 钩子运行时(npm version xxx 改完包版本后自动触发),
|
||||
// 无需参数,自动把新版本同步到 kimi.plugin.json。
|
||||
// mcp/vision.mjs 的 VERSION 在运行时直接读取 package.json,无需同步。
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const pkgPath = path.join(root, 'package.json');
|
||||
const pluginPath = path.join(root, 'kimi.plugin.json');
|
||||
|
||||
const SEMVER = /^\d+\.\d+\.\d+$/;
|
||||
|
||||
function readJson(p) {
|
||||
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
||||
}
|
||||
function writeJson(p, obj) {
|
||||
fs.writeFileSync(p, `${JSON.stringify(obj, null, 2)}\n`, 'utf8');
|
||||
}
|
||||
|
||||
const arg = process.argv[2];
|
||||
const pkg = readJson(pkgPath);
|
||||
|
||||
if (arg) {
|
||||
if (!SEMVER.test(arg)) {
|
||||
console.error(`[bump-version] 非法版本号: ${arg}(需要 semver 格式,如 1.0.6)`);
|
||||
process.exit(1);
|
||||
}
|
||||
pkg.version = arg;
|
||||
writeJson(pkgPath, pkg);
|
||||
console.log(`[bump-version] package.json -> ${arg}`);
|
||||
} else {
|
||||
console.log(`[bump-version] package.json 当前版本: ${pkg.version}`);
|
||||
}
|
||||
|
||||
// 同步到 kimi.plugin.json
|
||||
const plugin = readJson(pluginPath);
|
||||
if (plugin.version === pkg.version) {
|
||||
console.log(`[bump-version] kimi.plugin.json 已一致 (${pkg.version})`);
|
||||
} else {
|
||||
plugin.version = pkg.version;
|
||||
writeJson(pluginPath, plugin);
|
||||
console.log(`[bump-version] kimi.plugin.json -> ${pkg.version}`);
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env node
|
||||
// kimi-eyes uninstall — 撤销 setup.mjs + `/plugins install` 的所有落盘痕迹。
|
||||
// Zero-dependency: 仅内置模块;交互确认 TTY 用 readline,非 TTY 走默认值。
|
||||
//
|
||||
// 清理范围(KIMI_CODE_HOME 或 ~/.kimi-code):
|
||||
// [1] plugins/installed.json 中的 kimi-eyes 条目(改前备份)
|
||||
// [2] plugins/managed/kimi-eyes/ 安装副本
|
||||
// [3] kimi-eyes/config.json 配置目录(含 VLM API Key,需二次确认)
|
||||
//
|
||||
// 用法:
|
||||
// node uninstall.mjs 交互确认
|
||||
// node uninstall.mjs --yes 跳过全部确认(脚本化)
|
||||
// node uninstall.mjs --dry-run 只预览将要执行的操作
|
||||
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import readline from 'node:readline/promises';
|
||||
import process from 'node:process';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const YES = args.includes('--yes');
|
||||
const DRY_RUN = args.includes('--dry-run');
|
||||
|
||||
const PLUGIN_ID = 'kimi-eyes';
|
||||
const home = process.env.KIMI_CODE_HOME || path.join(os.homedir(), '.kimi-code');
|
||||
const installedJsonPath = path.join(home, 'plugins', 'installed.json');
|
||||
const managedDir = path.join(home, 'plugins', 'managed', PLUGIN_ID);
|
||||
const configDir = path.join(home, PLUGIN_ID);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 交互确认(TTY 用 readline;非 TTY / --yes 返回默认值)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function confirm(query, def = false) {
|
||||
if (YES) return true;
|
||||
if (!process.stdin.isTTY) return def;
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
try {
|
||||
const ans = (await rl.question(query)).trim().toLowerCase();
|
||||
if (ans === 'y' || ans === 'yes') return true;
|
||||
if (ans === 'n' || ans === 'no') return false;
|
||||
return def;
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 检查残留
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function installedEntry() {
|
||||
if (!fs.existsSync(installedJsonPath)) return null;
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(installedJsonPath, 'utf8'));
|
||||
const list = Array.isArray(data?.plugins) ? data.plugins : [];
|
||||
return list.find((p) => p?.id === PLUGIN_ID) ?? null;
|
||||
} catch {
|
||||
return null; // JSON 无效时按无条目处理,但保留文件不动
|
||||
}
|
||||
}
|
||||
|
||||
const hasInstalledEntry = () => installedEntry() !== null;
|
||||
const hasManagedDir = () => fs.existsSync(managedDir);
|
||||
const hasConfigDir = () => fs.existsSync(configDir);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 执行清理
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function removeInstalledEntry() {
|
||||
if (!hasInstalledEntry()) return { status: 'skip', detail: 'installed.json 无 kimi-eyes 条目' };
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(installedJsonPath, 'utf8'));
|
||||
if (!Array.isArray(data?.plugins)) throw new Error('plugins 字段缺失或非数组');
|
||||
const before = data.plugins.length;
|
||||
data.plugins = data.plugins.filter((p) => p?.id !== PLUGIN_ID);
|
||||
if (data.plugins.length === before) return { status: 'skip', detail: 'installed.json 无 kimi-eyes 条目' };
|
||||
if (DRY_RUN) return { status: 'would-remove', detail: `移除 ${before} → ${data.plugins.length} 个插件条目` };
|
||||
const backup = `${installedJsonPath}.bak.uninstall-${new Date().toISOString().replace(/[:.]/g, '-')}`;
|
||||
fs.copyFileSync(installedJsonPath, backup);
|
||||
fs.writeFileSync(installedJsonPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
||||
return { status: 'done', detail: `已移除条目,备份: ${path.basename(backup)}` };
|
||||
} catch (err) {
|
||||
return { status: 'error', detail: `installed.json 处理失败,未改动: ${err.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
function removeManagedDir() {
|
||||
if (!hasManagedDir()) return { status: 'skip', detail: 'managed 安装副本不存在' };
|
||||
if (DRY_RUN) return { status: 'would-remove', detail: `删除目录 ${managedDir}` };
|
||||
try {
|
||||
fs.rmSync(managedDir, { recursive: true, force: true });
|
||||
if (fs.existsSync(managedDir)) {
|
||||
// 目录壳被占用(如插件正被会话加载),内容通常已清空
|
||||
return { status: 'warn', detail: `目录被占用未完全删除,重启 kimi-code 会话后执行: rmdir "${managedDir}"` };
|
||||
}
|
||||
return { status: 'done', detail: '已删除安装副本' };
|
||||
} catch (err) {
|
||||
return { status: 'warn', detail: `删除失败(可能被会话占用),重启后手动删除: ${err.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
function removeConfigDir() {
|
||||
if (!hasConfigDir()) return { status: 'skip', detail: '配置目录不存在' };
|
||||
if (DRY_RUN) return { status: 'would-remove', detail: `删除配置目录 ${configDir}(含 VLM API Key)` };
|
||||
try {
|
||||
fs.rmSync(configDir, { recursive: true, force: true });
|
||||
return { status: 'done', detail: '已删除配置目录(含 VLM API Key)' };
|
||||
} catch (err) {
|
||||
return { status: 'warn', detail: `删除失败: ${err.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function main() {
|
||||
console.log('=== Kimi Eyes 卸载 ===');
|
||||
console.log(`操作目录: ${home}\n`);
|
||||
|
||||
if (DRY_RUN) console.log('(--dry-run 模式:仅预览,不执行任何修改)\n');
|
||||
|
||||
const pending = [];
|
||||
if (hasInstalledEntry()) pending.push(' [1] plugins/installed.json 中的 kimi-eyes 条目(改前备份)');
|
||||
if (hasManagedDir()) pending.push(` [2] 安装副本 ${managedDir}`);
|
||||
if (hasConfigDir()) pending.push(` [3] 配置目录 ${configDir}(含 VLM API Key)`);
|
||||
|
||||
if (pending.length === 0) {
|
||||
console.log('已卸载或无需清理(installed.json / managed 副本 / 配置目录均无残留)。');
|
||||
console.log('如在 Claude Code 中挂载过,请手动执行: claude mcp remove kimi-eyes');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('将清理:');
|
||||
console.log(pending.join('\n'));
|
||||
|
||||
if (DRY_RUN) {
|
||||
console.log('\n(--dry-run 结束,未做任何修改)');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await confirm('\n确认卸载?[y/N] ', false))) {
|
||||
console.log('已取消,未做任何修改。');
|
||||
return;
|
||||
}
|
||||
|
||||
const results = [];
|
||||
results.push(removeInstalledEntry());
|
||||
results.push(removeManagedDir());
|
||||
|
||||
if (hasConfigDir()) {
|
||||
if (await confirm(' 配置目录含你的 VLM API Key,确认一并删除?[y/N] ', false)) {
|
||||
results.push(removeConfigDir());
|
||||
} else {
|
||||
results.push({ status: 'skip', detail: '用户选择保留配置目录(请自行处理其中密钥)' });
|
||||
}
|
||||
} else {
|
||||
results.push({ status: 'skip', detail: '配置目录不存在' });
|
||||
}
|
||||
|
||||
console.log('\n执行结果:');
|
||||
for (const r of results) {
|
||||
const icon = r.status === 'done' ? '✓' : r.status === 'warn' ? '⚠' : r.status === 'error' ? '✗' : '·';
|
||||
console.log(` ${icon} ${r.detail}`);
|
||||
}
|
||||
|
||||
console.log('\n完成。请重启 kimi-code 会话(或 /reload)使卸载完全生效。');
|
||||
console.log('如在 Claude Code 中挂载过,请手动执行: claude mcp remove kimi-eyes');
|
||||
|
||||
if (results.some((r) => r.status === 'error')) process.exitCode = 1;
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(`\n[uninstall] 出错: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in new issue
Block a user