chore: archive AgentDock v1 implementation

This commit is contained in:
leefer
2026-08-24 17:14:08 +08:00
commit f512bd58ec
66 changed files with 10663 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
export const SECRET_DEFINITIONS = Object.freeze([
{ name: "OPENAI_API_KEY", label: "OpenAI API Key", tools: ["codex", "opencode"] },
{ name: "ANTHROPIC_API_KEY", label: "Anthropic API Key", tools: ["claude", "opencode"] },
{ name: "CODEBUDDY_API_KEY", label: "CodeBuddy API Key", tools: ["codebuddy"] },
{ name: "MOONSHOT_API_KEY", label: "Moonshot API Key", tools: ["kimi"] },
{ name: "DASHSCOPE_API_KEY", label: "DashScope API Key", tools: ["qwen"] },
{ name: "DEEPSEEK_API_KEY", label: "DeepSeek API Key", tools: ["dsh", "opencode"] },
{ name: "GOOGLE_GENERATIVE_AI_API_KEY", label: "Google AI API Key", tools: ["opencode"] },
]);
const DEFINITION_BY_NAME = new Map(SECRET_DEFINITIONS.map((item) => [item.name, item]));
function mask(value) {
if (!value) return null;
if (value.length < 9) return "********";
return `${value.slice(0, 3)}...${value.slice(-4)}`;
}
export class SettingsStore {
constructor(file, encryptionSecret) {
this.file = file;
this.key = crypto.createHash("sha256").update(encryptionSecret).digest();
this.data = { secrets: {}, preferences: { models: {}, dshProfile: "headless", ccSwitchManaged: false } };
}
async init() {
await fs.mkdir(path.dirname(this.file), { recursive: true, mode: 0o700 });
try {
const payload = JSON.parse(await fs.readFile(this.file, "utf8"));
const iv = Buffer.from(payload.iv, "base64");
const tag = Buffer.from(payload.tag, "base64");
const decipher = crypto.createDecipheriv("aes-256-gcm", this.key, iv);
decipher.setAuthTag(tag);
const plain = Buffer.concat([
decipher.update(Buffer.from(payload.data, "base64")),
decipher.final(),
]);
const parsed = JSON.parse(plain.toString("utf8"));
this.data = {
secrets: parsed.secrets || {},
preferences: { models: {}, dshProfile: "headless", ccSwitchManaged: false, ...(parsed.preferences || {}) },
};
} catch (error) {
if (error.code !== "ENOENT") throw new Error(`Cannot decrypt console settings: ${error.message}`);
}
}
publicView() {
return {
secrets: SECRET_DEFINITIONS.map((definition) => ({
...definition,
configured: Boolean(this.data.secrets[definition.name]),
masked: mask(this.data.secrets[definition.name]),
})),
preferences: this.data.preferences,
};
}
environmentForTool(toolId) {
const environment = {};
if (this.data.preferences.ccSwitchManaged && ["claude", "codex", "opencode"].includes(toolId)) {
return environment;
}
for (const definition of SECRET_DEFINITIONS) {
if (definition.tools.includes(toolId) && this.data.secrets[definition.name]) {
environment[definition.name] = this.data.secrets[definition.name];
}
}
return environment;
}
async setSecret(name, value) {
if (!DEFINITION_BY_NAME.has(name)) throw new Error("Unsupported secret name");
if (value) this.data.secrets[name] = value;
else delete this.data.secrets[name];
await this.save();
}
async setPreferences(preferences) {
const models = typeof preferences.models === "object" && preferences.models
? preferences.models
: this.data.preferences.models;
const cleanModels = Object.fromEntries(Object.entries(models).map(([key, value]) => [
String(key).slice(0, 32),
String(value).slice(0, 128),
]));
const dshProfile = "headless";
const ccSwitchManaged = typeof preferences.ccSwitchManaged === "boolean"
? preferences.ccSwitchManaged
: this.data.preferences.ccSwitchManaged;
this.data.preferences = { models: cleanModels, dshProfile, ccSwitchManaged };
await this.save();
}
async save() {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-256-gcm", this.key, iv);
const encrypted = Buffer.concat([
cipher.update(JSON.stringify(this.data), "utf8"),
cipher.final(),
]);
const payload = `${JSON.stringify({
version: 1,
iv: iv.toString("base64"),
tag: cipher.getAuthTag().toString("base64"),
data: encrypted.toString("base64"),
}, null, 2)}\n`;
const temp = `${this.file}.tmp`;
await fs.writeFile(temp, payload, { mode: 0o600 });
await fs.rename(temp, this.file);
}
}