Add unified IM channel plugin

This commit is contained in:
xmanrui 2026-08-15 04:39:56 +08:00
commit b8bd681a79
26 changed files with 7901 additions and 0 deletions

95
bin/dsh-im.mjs Executable file
View file

@ -0,0 +1,95 @@
#!/usr/bin/env node
import { readFile } from 'node:fs/promises';
import { homedir, tmpdir } from 'node:os';
import { isAbsolute, join, resolve } from 'node:path';
import { spawnSync } from 'node:child_process';
const PACKAGE_NAME = '@xmanrui/dsh-im';
const DEFAULT_SOURCE = 'github:xmanrui/dsh-im';
const LEGACY_PACKAGES = ['@xmanrui/dsh-feishu', '@xmanrui/dsh-weixin'];
function usage() {
console.log(`Usage:
dsh-im install [--profile web] [--source <package-spec>]
dsh-im uninstall [--profile web]
Examples:
npx -y github:xmanrui/dsh-im install
dsh-im install --source .`);
}
function takeOption(args, name, fallback) {
const index = args.indexOf(name);
if (index < 0) return fallback;
const value = args[index + 1];
if (!value || value.startsWith('--')) throw new Error(`${name} requires a value`);
args.splice(index, 2);
return value;
}
function runDsh(args) {
const result = spawnSync('dsh', args, {
cwd: tmpdir(),
stdio: 'inherit',
shell: false,
});
if (result.error?.code === 'ENOENT') {
throw new Error('找不到 dsh,请先安装 DeepSeek Harness 并确保 dsh 在 PATH 中。');
}
if (result.error) throw result.error;
if (result.status !== 0) throw new Error(`dsh 退出,状态码 ${result.status ?? 1}`);
}
async function directProfilePackages(profile) {
const dshHome = process.env.DSH_HOME || join(homedir(), '.dsh');
const profilePackage = join(dshHome, 'profiles', profile, 'package.json');
try {
const manifest = JSON.parse(await readFile(profilePackage, 'utf8'));
const bundles = new Set(manifest.dsh?.profile?.bundles ?? []);
const dependencies = manifest.dependencies ?? {};
return new Set(LEGACY_PACKAGES.filter((name) => dependencies[name] || bundles.has(name)));
} catch (error) {
if (error?.code === 'ENOENT') return new Set();
throw new Error(`无法读取 Harness 配置 ${profilePackage}:${error.message}`);
}
}
const args = process.argv.slice(2);
const command = args.shift();
if (!command || command === '--help' || command === '-h') {
usage();
process.exit(0);
}
try {
const profile = takeOption(args, '--profile', 'web');
if (command === 'install') {
const requested = takeOption(args, '--source', DEFAULT_SOURCE);
const source = requested === '.' || requested === '..'
|| requested.startsWith('./') || requested.startsWith('../')
? resolve(process.cwd(), requested)
: (isAbsolute(requested) ? requested : requested);
if (args.length > 0) throw new Error(`无法识别的参数:${args.join(' ')}`);
const legacy = await directProfilePackages(profile);
runDsh(['plugin', '--profile', profile, 'add', '--save-exact', source]);
for (const packageName of legacy) {
runDsh(['plugin', '--profile', profile, 'remove', packageName]);
}
console.log('\nIM 机器人插件已安装。请重启 dsh web,然后打开「设置 → 插件 → IM机器人」。');
if (legacy.size > 0) {
console.log('已用 dsh-im 替换独立飞书/微信插件;原有凭据和扫码绑定保持不变。');
}
} else if (command === 'uninstall') {
if (args.length > 0) throw new Error(`无法识别的参数:${args.join(' ')}`);
runDsh(['plugin', '--profile', profile, 'remove', PACKAGE_NAME]);
console.log('\nIM 机器人插件已卸载。请重启 dsh web。');
} else {
throw new Error(`无法识别的命令:${command}`);
}
} catch (error) {
console.error(`dsh-im: ${error.message}`);
process.exit(1);
}