feat: 个人定制版 —— API Key 模式显示剩余积分 + 悬浮卡片

基于 @axiaohungry/dsh-llm-workbuddy 1.3.21 (MIT)。
详见 README.CUSTOM.md。
This commit is contained in:
dsh-custom committed 2026-10-10 13:59:09 +08:00
commit a44227b69a
14 files changed
+5517

No files matched your search

+315
View File
@@ -0,0 +1,315 @@
#!/usr/bin/env node
import { copyFileSync, existsSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import { createRequire } from "node:module";
import { delimiter, dirname, join, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import { parseDocument } from "yaml";
import {
WORKBUDDY_SESSION_REF,
WORKBUDDY_SESSIONS_REF,
LEGACY_SESSION_REF,
LEGACY_SESSIONS_REF,
createWorkBuddySessionStore,
loginWorkBuddy,
parseWorkBuddySession,
parseWorkBuddySessions,
serializeWorkBuddySession,
serializeWorkBuddySessions,
upsertWorkBuddySession,
} from "./workbuddy-auth.js";
const PACKAGE = "@axiaohungry/dsh-llm-workbuddy";
const LEGACY_PACKAGES = ["dsh-llm-workbuddy", "dsh-llm-codebuddy"];
const PACKAGE_VERSION = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version;
const PACKAGE_SPEC = `${PACKAGE}@${PACKAGE_VERSION}`;
const PROVIDER_PATH = ["llm-pi-ai", "providers", "workbuddy-cn"];
const LEGACY_PROVIDER_PATH = ["llm-pi-ai", "providers", "codebuddy-cn"];
const IGNORED_BUILDS = ["@google/genai", "protobufjs"];
const require = createRequire(import.meta.url);
function dshHome() {
return resolve(process.env.DSH_HOME || join(homedir(), ".dsh"));
}
function profileHasPackage(home, profile, packageName) {
const file = join(home, "profiles", profile, "package.json");
if (!existsSync(file)) return false;
const json = JSON.parse(readFileSync(file, "utf8"));
return Boolean(json.dependencies?.[packageName] || json.devDependencies?.[packageName]);
}
function profileHasPlugin(home, profile) {
return profileHasPackage(home, profile, PACKAGE);
}
function dshEnv() {
const pnpmPackageDir = dirname(require.resolve("pnpm"));
const pnpmBinDir = join(dirname(pnpmPackageDir), ".bin");
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") || "PATH";
return {
...process.env,
[pathKey]: `${pnpmBinDir}${delimiter}${process.env[pathKey] || ""}`,
npm_config_ignore_workspace_root_check: "true",
};
}
function runDsh(args) {
const result = spawnSync(process.platform === "win32" ? "dsh.cmd" : "dsh", args, {
stdio: "inherit",
shell: process.platform === "win32",
env: dshEnv(),
});
if (result.error) throw result.error;
if (result.status !== 0) throw new Error(`dsh ${args.join(" ")} 执行失败(退出码 ${result.status})`);
}
function entrySettingsVersion(output) {
const version = String(output).match(/\b(\d+)\.(\d+)\.(\d+)(?:[-.]|\b)/);
if (!version) return undefined;
const [, major, minor, patch] = version.map(Number);
return major > 0 || minor > 1 || (minor === 1 && patch >= 7);
}
function usesEntrySettings() {
const result = spawnSync(process.platform === "win32" ? "dsh.cmd" : "dsh", ["--version"], {
encoding: "utf8",
shell: process.platform === "win32",
env: dshEnv(),
});
if (result.error || result.status !== 0) throw new Error("无法检测 DSH 版本,请确认 dsh 命令可用");
const modern = entrySettingsVersion(result.stdout);
if (modern === undefined) throw new Error("无法识别 DSH 版本,令牌已保存;请在模型设置中手动选择令牌登录");
return modern;
}
function writeYamlDocument(file, document) {
const temporary = join(dirname(file), `.workbuddy-${process.pid}.tmp`);
writeFileSync(temporary, String(document), "utf8");
renameSync(temporary, file);
}
function withPnpmBuildPolicy(file, action) {
const document = parseDocument(readFileSync(file, "utf8"));
if (document.errors.length) throw new Error(`无法解析 ${file}:${document.errors[0].message}`);
const changes = [];
for (const packageName of IGNORED_BUILDS) {
const path = ["allowBuilds", packageName];
if (typeof document.getIn(path) !== "boolean") {
changes.push({ packageName, existed: document.hasIn(path), value: document.getIn(path) });
document.setIn(path, false);
}
}
if (changes.length) writeYamlDocument(file, document);
try {
return action();
} finally {
if (changes.length) {
const current = parseDocument(readFileSync(file, "utf8"));
for (const change of changes) {
const path = ["allowBuilds", change.packageName];
if (change.existed) current.setIn(path, change.value);
else current.deleteIn(path);
}
if (current.getIn(["allowBuilds"])?.items?.length === 0) current.deleteIn(["allowBuilds"]);
writeYamlDocument(file, current);
}
}
}
function cleanPnpmWorkspace(file) {
if (!existsSync(file)) return;
const document = parseDocument(readFileSync(file, "utf8"));
if (document.errors.length) throw new Error(`无法解析 ${file}:${document.errors[0].message}`);
const entries = document.getIn(["minimumReleaseAgeExclude"])?.items;
if (!entries) return;
const packageNames = [PACKAGE, ...LEGACY_PACKAGES];
const remaining = entries.map((entry) => entry.value).filter((entry) => !packageNames.some((packageName) => String(entry).startsWith(`${packageName}@`)));
if (remaining.length === entries.length) return;
if (remaining.length) document.setIn(["minimumReleaseAgeExclude"], remaining);
else document.deleteIn(["minimumReleaseAgeExclude"]);
writeYamlDocument(file, document);
}
function cleanSettings(file) {
if (!existsSync(file)) return undefined;
const source = readFileSync(file, "utf8");
const document = parseDocument(source);
if (document.errors.length) throw new Error(`无法解析 ${file}:${document.errors[0].message}`);
const paths = [PROVIDER_PATH, LEGACY_PROVIDER_PATH].filter((path) => document.hasIn(path));
if (paths.length === 0) return undefined;
for (const path of paths) document.deleteIn(path);
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
const backup = `${file}.workbuddy-backup-${stamp}`;
const temporary = join(dirname(file), `.settings-workbuddy-${process.pid}.tmp`);
copyFileSync(file, backup);
writeFileSync(temporary, String(document), "utf8");
renameSync(temporary, file);
return backup;
}
function enableTokenLogin(home = dshHome()) {
const file = join(home, "settings.yaml");
const source = existsSync(file) ? readFileSync(file, "utf8") : "{}\n";
const document = parseDocument(source);
if (document.errors.length) throw new Error(`无法解析 ${file}:${document.errors[0].message}`);
if (document.hasIn(PROVIDER_PATH)) document.deleteIn([...PROVIDER_PATH, "apiKeyEnv"]);
else if (document.hasIn(LEGACY_PROVIDER_PATH)) {
const legacy = document.getIn(LEGACY_PROVIDER_PATH);
const value = legacy && typeof legacy.toJSON === "function" ? legacy.toJSON() : legacy;
document.setIn(PROVIDER_PATH, document.createNode(value && typeof value === "object" ? value : {}));
document.deleteIn([...PROVIDER_PATH, "apiKeyEnv"]);
document.deleteIn(LEGACY_PROVIDER_PATH);
}
else document.setIn(PROVIDER_PATH, {});
if (existsSync(file)) {
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
copyFileSync(file, `${file}.workbuddy-backup-${stamp}`);
}
writeYamlDocument(file, document);
}
function storeLoginSession(session, home = dshHome()) {
const file = join(home, ".credentials.yaml");
const document = parseDocument(existsSync(file) ? readFileSync(file, "utf8") : "{}\n");
if (document.errors.length) throw new Error(`无法解析 ${file}:${document.errors[0].message}`);
const stored = document.get(WORKBUDDY_SESSIONS_REF)
?? document.get(LEGACY_SESSIONS_REF)
?? document.get(WORKBUDDY_SESSION_REF)
?? document.get(LEGACY_SESSION_REF);
const current = stored ? parseWorkBuddySessions(stored) : createWorkBuddySessionStore();
const next = upsertWorkBuddySession(current, session);
document.set(WORKBUDDY_SESSIONS_REF, serializeWorkBuddySessions(next));
document.set(WORKBUDDY_SESSION_REF, serializeWorkBuddySession(next.sessions.find((entry) => entry.id === next.activeId)));
document.delete(LEGACY_SESSIONS_REF);
document.delete(LEGACY_SESSION_REF);
if (existsSync(file)) {
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
copyFileSync(file, `${file}.workbuddy-backup-${stamp}`);
}
writeYamlDocument(file, document);
}
async function login() {
console.log("正在打开 WorkBuddy 中国站网页登录(无需安装 WorkBuddy CLI)……");
const session = await loginWorkBuddy((url, opened) => {
if (opened) console.log("浏览器登录页已打开,请在浏览器中完成登录。");
else console.log(`无法自动打开浏览器,请手动访问:${url}`);
});
storeLoginSession(session);
if (usesEntrySettings()) {
console.log("WorkBuddy 令牌登录成功。新版 DSH 按插件条目保存配置,请在模型设置的 WorkBuddy 卡片中选择“令牌登录”;无需安装 WorkBuddy CLI。");
} else {
enableTokenLogin();
console.log("WorkBuddy 令牌登录成功,Provider 已切换为令牌模式。请重启 DSH。");
}
}
function install() {
for (const profile of ["web", "headless"]) {
runDsh(["plugin", "--profile", profile, "list", "--depth", "0"]);
const workspace = join(dshHome(), "profiles", profile, "pnpm-workspace.yaml");
withPnpmBuildPolicy(workspace, () => {
for (const legacyPackage of LEGACY_PACKAGES) {
if (profileHasPackage(dshHome(), profile, legacyPackage)) {
runDsh(["plugin", "--profile", profile, "remove", legacyPackage]);
}
}
runDsh(["plugin", "--profile", profile, "add", PACKAGE_SPEC]);
});
}
console.log("WorkBuddy Provider 已安装。请重启 DSH 后进行配置。");
}
function uninstall(home = dshHome()) {
const backup = cleanSettings(join(home, "settings.yaml"));
for (const profile of ["web", "headless"]) {
const workspace = join(home, "profiles", profile, "pnpm-workspace.yaml");
const installedPackages = [PACKAGE, ...LEGACY_PACKAGES].filter((packageName) => profileHasPackage(home, profile, packageName));
if (installedPackages.length) {
withPnpmBuildPolicy(workspace, () => {
for (const packageName of installedPackages) {
runDsh(["plugin", "--profile", profile, "remove", packageName]);
}
});
}
cleanPnpmWorkspace(workspace);
}
console.log(backup ? `WorkBuddy 配置已清理,备份:${backup}` : "未发现 WorkBuddy Provider 配置。");
console.log("插件已卸载,API Key 和登录令牌凭据保持不变。请重启 DSH。");
}
function selfTest() {
const root = mkdtempSync(join(tmpdir(), "dsh-workbuddy-cli-"));
try {
if (entrySettingsVersion("dsh 0.1.6") !== false || entrySettingsVersion("dsh 0.1.7-rc.1") !== true || entrySettingsVersion("dsh 0.2.0-rc.2") !== true) {
throw new Error("DSH version detection self-test failed");
}
const file = join(root, "settings.yaml");
writeFileSync(file, "llm-pi-ai:\n providers:\n opencode-go:\n apiKeyEnv: OPENCODE_GO_API_KEY\n codebuddy-cn:\n apiKeyEnv: WORKBUDDY_CN_API_KEY\n models:\n - id: legacy-model\n", "utf8");
enableTokenLogin(root);
const tokenMode = parseDocument(readFileSync(file, "utf8"));
if (tokenMode.hasIn([...PROVIDER_PATH, "apiKeyEnv"]) || !tokenMode.hasIn(PROVIDER_PATH) || tokenMode.hasIn(LEGACY_PROVIDER_PATH) || !tokenMode.hasIn([...PROVIDER_PATH, "models", 0, "id"])) {
throw new Error("token login settings self-test failed");
}
const backup = cleanSettings(file);
const result = parseDocument(readFileSync(file, "utf8"));
if (!backup || !existsSync(backup) || result.hasIn(PROVIDER_PATH) || !result.hasIn(["llm-pi-ai", "providers", "opencode-go"])) {
throw new Error("uninstall settings cleanup self-test failed");
}
enableTokenLogin(root);
if (!parseDocument(readFileSync(file, "utf8")).hasIn(PROVIDER_PATH)) {
throw new Error("token login provider creation self-test failed");
}
const sampleSession = {
auth: { accessToken: "test-access-token", refreshToken: "test-refresh-token", expiresAt: Date.now() + 60_000 },
account: { userId: "test-user" },
};
storeLoginSession(sampleSession, root);
const storedSession = parseDocument(readFileSync(join(root, ".credentials.yaml"), "utf8")).get(WORKBUDDY_SESSION_REF);
if (parseWorkBuddySession(storedSession).account.userId !== "test-user") {
throw new Error("token credential storage self-test failed");
}
const storedSessions = parseDocument(readFileSync(join(root, ".credentials.yaml"), "utf8")).get(WORKBUDDY_SESSIONS_REF);
const sessionStore = parseWorkBuddySessions(storedSessions);
if (sessionStore.sessions.length !== 1 || sessionStore.activeId !== sessionStore.sessions[0].id) {
throw new Error("token account list self-test failed");
}
const workspace = join(root, "pnpm-workspace.yaml");
writeFileSync(workspace, "packages:\n - .\nallowBuilds:\n '@google/genai': true\n protobufjs: pending\n", "utf8");
withPnpmBuildPolicy(workspace, () => {
const active = parseDocument(readFileSync(workspace, "utf8"));
if (active.getIn(["allowBuilds", "@google/genai"]) !== true || active.getIn(["allowBuilds", "protobufjs"]) !== false) {
throw new Error("pnpm build policy activation self-test failed");
}
});
const restored = parseDocument(readFileSync(workspace, "utf8"));
if (restored.getIn(["allowBuilds", "@google/genai"]) !== true || restored.getIn(["allowBuilds", "protobufjs"]) !== "pending") {
throw new Error("pnpm build policy self-test failed");
}
restored.setIn(["minimumReleaseAgeExclude"], ["other@1.0.0", `${PACKAGE}@1.3.1`]);
writeYamlDocument(workspace, restored);
cleanPnpmWorkspace(workspace);
const cleaned = parseDocument(readFileSync(workspace, "utf8")).getIn(["minimumReleaseAgeExclude"])?.items?.map((entry) => entry.value);
if (cleaned?.join(",") !== "other@1.0.0") throw new Error("pnpm workspace cleanup self-test failed");
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") || "PATH";
if (!dshEnv()[pathKey].split(delimiter)[0].endsWith(join("node_modules", ".bin"))) {
throw new Error("bundled pnpm PATH self-test failed");
}
console.log("CLI-SELF-TEST-OK");
} finally {
rmSync(root, { recursive: true, force: true });
}
}
const command = process.argv[2];
if (command === "install") install();
else if (command === "uninstall") uninstall();
else if (command === "login") await login();
else if (command === "--self-test") selfTest();
else {
console.log("用法:dsh-llm-workbuddy <install|login|uninstall>");
process.exitCode = 1;
}