chore: archive AgentDock v1 implementation
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user