archondev 0.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,399 @@
1
+ // src/core/keys/manager.ts
2
+ import { homedir as homedir2 } from "os";
3
+ import { join } from "path";
4
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
5
+ import { existsSync } from "fs";
6
+
7
+ // src/core/keys/encryption.ts
8
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
9
+ import { homedir, hostname, userInfo } from "os";
10
+ var ALGORITHM = "aes-256-gcm";
11
+ var IV_LENGTH = 16;
12
+ var AUTH_TAG_LENGTH = 16;
13
+ var SALT_LENGTH = 32;
14
+ var KEY_LENGTH = 32;
15
+ function getDerivedKey(salt) {
16
+ const machineId = `${hostname()}-${userInfo().username}-${homedir()}`;
17
+ return scryptSync(machineId, salt, KEY_LENGTH);
18
+ }
19
+ function encrypt(plaintext) {
20
+ const salt = randomBytes(SALT_LENGTH);
21
+ const key = getDerivedKey(salt);
22
+ const iv = randomBytes(IV_LENGTH);
23
+ const cipher = createCipheriv(ALGORITHM, key, iv);
24
+ let encrypted = cipher.update(plaintext, "utf8", "hex");
25
+ encrypted += cipher.final("hex");
26
+ const authTag = cipher.getAuthTag();
27
+ const combined = Buffer.concat([
28
+ salt,
29
+ iv,
30
+ authTag,
31
+ Buffer.from(encrypted, "hex")
32
+ ]);
33
+ return combined.toString("base64");
34
+ }
35
+ function decrypt(encryptedData) {
36
+ const combined = Buffer.from(encryptedData, "base64");
37
+ const salt = combined.subarray(0, SALT_LENGTH);
38
+ const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
39
+ const authTag = combined.subarray(
40
+ SALT_LENGTH + IV_LENGTH,
41
+ SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
42
+ );
43
+ const encrypted = combined.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);
44
+ const key = getDerivedKey(salt);
45
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
46
+ decipher.setAuthTag(authTag);
47
+ let decrypted = decipher.update(encrypted.toString("hex"), "hex", "utf8");
48
+ decrypted += decipher.final("utf8");
49
+ return decrypted;
50
+ }
51
+
52
+ // src/core/keys/manager.ts
53
+ var CONFIG_DIR = join(homedir2(), ".archon");
54
+ var KEYS_FILE = join(CONFIG_DIR, "keys.json");
55
+ var CURRENT_VERSION = 2;
56
+ var KeyManager = class {
57
+ async ensureConfigDir() {
58
+ if (!existsSync(CONFIG_DIR)) {
59
+ await mkdir(CONFIG_DIR, { recursive: true, mode: 448 });
60
+ }
61
+ }
62
+ async loadKeys() {
63
+ try {
64
+ if (!existsSync(KEYS_FILE)) {
65
+ return { keys: [], version: CURRENT_VERSION };
66
+ }
67
+ const content = await readFile(KEYS_FILE, "utf-8");
68
+ const data = JSON.parse(content);
69
+ if (data.version === 1 && !Array.isArray(data.keys)) {
70
+ const oldKeys = data.keys;
71
+ const newKeys = Object.values(oldKeys).map((k) => ({
72
+ ...k,
73
+ label: k.label ?? "default",
74
+ isPrimary: k.isPrimary ?? true
75
+ }));
76
+ const migrated = { keys: newKeys, version: CURRENT_VERSION };
77
+ await this.saveKeys(migrated);
78
+ return migrated;
79
+ }
80
+ return data;
81
+ } catch {
82
+ return { keys: [], version: CURRENT_VERSION };
83
+ }
84
+ }
85
+ async saveKeys(stored) {
86
+ await this.ensureConfigDir();
87
+ const content = JSON.stringify(stored, null, 2);
88
+ await writeFile(KEYS_FILE, content, { mode: 384 });
89
+ await chmod(KEYS_FILE, 384);
90
+ }
91
+ async addKey(provider, apiKey, label = "default") {
92
+ try {
93
+ const stored = await this.loadKeys();
94
+ const now = (/* @__PURE__ */ new Date()).toISOString();
95
+ const existingIndex = stored.keys.findIndex((k) => k.provider === provider && k.label === label);
96
+ const providerKeys = stored.keys.filter((k) => k.provider === provider);
97
+ const isPrimary = providerKeys.length === 0 || existingIndex >= 0 && stored.keys[existingIndex]?.isPrimary === true;
98
+ const newKey = {
99
+ provider,
100
+ encryptedKey: encrypt(apiKey),
101
+ label,
102
+ isPrimary,
103
+ createdAt: now,
104
+ updatedAt: now
105
+ };
106
+ if (existingIndex >= 0) {
107
+ const existing = stored.keys[existingIndex];
108
+ if (existing) {
109
+ newKey.createdAt = existing.createdAt;
110
+ newKey.isPrimary = existing.isPrimary;
111
+ }
112
+ stored.keys[existingIndex] = newKey;
113
+ } else {
114
+ stored.keys.push(newKey);
115
+ }
116
+ await this.saveKeys(stored);
117
+ return { success: true };
118
+ } catch (error) {
119
+ return {
120
+ success: false,
121
+ error: `Failed to save key: ${error instanceof Error ? error.message : "Unknown error"}`
122
+ };
123
+ }
124
+ }
125
+ async getKey(provider, label) {
126
+ try {
127
+ const stored = await this.loadKeys();
128
+ let keyConfig;
129
+ if (label) {
130
+ keyConfig = stored.keys.find((k) => k.provider === provider && k.label === label);
131
+ } else {
132
+ keyConfig = stored.keys.find((k) => k.provider === provider && k.isPrimary);
133
+ if (!keyConfig) {
134
+ keyConfig = stored.keys.find((k) => k.provider === provider);
135
+ }
136
+ }
137
+ if (!keyConfig) {
138
+ return null;
139
+ }
140
+ return decrypt(keyConfig.encryptedKey);
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+ async removeKey(provider, label) {
146
+ try {
147
+ const stored = await this.loadKeys();
148
+ let index;
149
+ if (label) {
150
+ index = stored.keys.findIndex((k) => k.provider === provider && k.label === label);
151
+ } else {
152
+ index = stored.keys.findIndex((k) => k.provider === provider && k.isPrimary);
153
+ if (index < 0) {
154
+ index = stored.keys.findIndex((k) => k.provider === provider);
155
+ }
156
+ }
157
+ if (index < 0) {
158
+ const labelMsg = label ? ` with label "${label}"` : "";
159
+ return {
160
+ success: false,
161
+ error: `No API key found for provider: ${provider}${labelMsg}`
162
+ };
163
+ }
164
+ const removedKey = stored.keys[index];
165
+ stored.keys.splice(index, 1);
166
+ if (removedKey?.isPrimary) {
167
+ const nextKey = stored.keys.find((k) => k.provider === provider);
168
+ if (nextKey) {
169
+ nextKey.isPrimary = true;
170
+ }
171
+ }
172
+ await this.saveKeys(stored);
173
+ return { success: true };
174
+ } catch (error) {
175
+ return {
176
+ success: false,
177
+ error: `Failed to remove key: ${error instanceof Error ? error.message : "Unknown error"}`
178
+ };
179
+ }
180
+ }
181
+ async setPrimary(provider, label) {
182
+ try {
183
+ const stored = await this.loadKeys();
184
+ const targetKey = stored.keys.find((k) => k.provider === provider && k.label === label);
185
+ if (!targetKey) {
186
+ return {
187
+ success: false,
188
+ error: `No key found for ${provider} with label "${label}"`
189
+ };
190
+ }
191
+ for (const key of stored.keys) {
192
+ if (key.provider === provider) {
193
+ key.isPrimary = key.label === label;
194
+ }
195
+ }
196
+ await this.saveKeys(stored);
197
+ return { success: true };
198
+ } catch (error) {
199
+ return {
200
+ success: false,
201
+ error: `Failed to set primary: ${error instanceof Error ? error.message : "Unknown error"}`
202
+ };
203
+ }
204
+ }
205
+ async listProviders() {
206
+ try {
207
+ const stored = await this.loadKeys();
208
+ const providers = /* @__PURE__ */ new Set();
209
+ for (const key of stored.keys) {
210
+ providers.add(key.provider);
211
+ }
212
+ return Array.from(providers);
213
+ } catch {
214
+ return [];
215
+ }
216
+ }
217
+ async listKeys(provider) {
218
+ try {
219
+ const stored = await this.loadKeys();
220
+ if (provider) {
221
+ return stored.keys.filter((k) => k.provider === provider);
222
+ }
223
+ return stored.keys;
224
+ } catch {
225
+ return [];
226
+ }
227
+ }
228
+ async hasAnyKey() {
229
+ const stored = await this.loadKeys();
230
+ return stored.keys.length > 0;
231
+ }
232
+ async getKeyConfig(provider, label) {
233
+ try {
234
+ const stored = await this.loadKeys();
235
+ if (label) {
236
+ return stored.keys.find((k) => k.provider === provider && k.label === label) ?? null;
237
+ }
238
+ return stored.keys.find((k) => k.provider === provider && k.isPrimary) ?? stored.keys.find((k) => k.provider === provider) ?? null;
239
+ } catch {
240
+ return null;
241
+ }
242
+ }
243
+ async getAdversarialStatus() {
244
+ const providers = await this.listProviders();
245
+ const enabled = providers.length >= 2;
246
+ if (enabled) {
247
+ return {
248
+ enabled: true,
249
+ providerCount: providers.length,
250
+ providers,
251
+ message: `Adversarial features enabled (${providers.length} providers: ${providers.join(", ")})`
252
+ };
253
+ } else if (providers.length === 1) {
254
+ return {
255
+ enabled: false,
256
+ providerCount: 1,
257
+ providers,
258
+ message: `Adversarial features require keys from 2+ providers. Add another provider to unlock.`
259
+ };
260
+ } else {
261
+ return {
262
+ enabled: false,
263
+ providerCount: 0,
264
+ providers: [],
265
+ message: `No API keys configured. Add keys from 2+ providers to enable adversarial features.`
266
+ };
267
+ }
268
+ }
269
+ };
270
+ var keyManager = new KeyManager();
271
+
272
+ // src/core/keys/validator.ts
273
+ var KeyValidator = class {
274
+ async validateKey(provider, apiKey) {
275
+ switch (provider) {
276
+ case "anthropic":
277
+ return this.validateAnthropicKey(apiKey);
278
+ case "openai":
279
+ return this.validateOpenAIKey(apiKey);
280
+ case "google":
281
+ return this.validateGoogleKey(apiKey);
282
+ default:
283
+ return { valid: false, error: `Unknown provider: ${provider}`, provider };
284
+ }
285
+ }
286
+ async validateAnthropicKey(apiKey) {
287
+ try {
288
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
289
+ method: "POST",
290
+ headers: {
291
+ "Content-Type": "application/json",
292
+ "x-api-key": apiKey,
293
+ "anthropic-version": "2023-06-01"
294
+ },
295
+ body: JSON.stringify({
296
+ model: "claude-3-haiku-20240307",
297
+ max_tokens: 1,
298
+ messages: [{ role: "user", content: "Hi" }]
299
+ })
300
+ });
301
+ if (response.status === 401) {
302
+ return {
303
+ valid: false,
304
+ error: "Invalid API key. Please check your Anthropic API key.",
305
+ provider: "anthropic"
306
+ };
307
+ }
308
+ if (response.status === 400) {
309
+ const data = await response.json();
310
+ if (data.error?.message?.includes("credit")) {
311
+ return {
312
+ valid: false,
313
+ error: "API key valid but no credits. Please add credits to your Anthropic account.",
314
+ provider: "anthropic"
315
+ };
316
+ }
317
+ }
318
+ if (response.ok || response.status === 429 || response.status === 404) {
319
+ return { valid: true, provider: "anthropic" };
320
+ }
321
+ return {
322
+ valid: true,
323
+ provider: "anthropic"
324
+ };
325
+ } catch (error) {
326
+ return {
327
+ valid: false,
328
+ error: `Connection error: ${error instanceof Error ? error.message : "Unknown error"}`,
329
+ provider: "anthropic"
330
+ };
331
+ }
332
+ }
333
+ async validateOpenAIKey(apiKey) {
334
+ try {
335
+ const response = await fetch("https://api.openai.com/v1/models", {
336
+ method: "GET",
337
+ headers: {
338
+ Authorization: `Bearer ${apiKey}`
339
+ }
340
+ });
341
+ if (response.status === 401) {
342
+ return {
343
+ valid: false,
344
+ error: "Invalid API key. Please check your OpenAI API key.",
345
+ provider: "openai"
346
+ };
347
+ }
348
+ if (response.ok) {
349
+ return { valid: true, provider: "openai" };
350
+ }
351
+ return {
352
+ valid: false,
353
+ error: `Validation failed with status ${response.status}`,
354
+ provider: "openai"
355
+ };
356
+ } catch (error) {
357
+ return {
358
+ valid: false,
359
+ error: `Connection error: ${error instanceof Error ? error.message : "Unknown error"}`,
360
+ provider: "openai"
361
+ };
362
+ }
363
+ }
364
+ async validateGoogleKey(apiKey) {
365
+ try {
366
+ const response = await fetch(
367
+ `https://generativelanguage.googleapis.com/v1/models?key=${apiKey}`,
368
+ { method: "GET" }
369
+ );
370
+ if (response.status === 400 || response.status === 401 || response.status === 403) {
371
+ return {
372
+ valid: false,
373
+ error: "Invalid API key. Please check your Google AI API key.",
374
+ provider: "google"
375
+ };
376
+ }
377
+ if (response.ok) {
378
+ return { valid: true, provider: "google" };
379
+ }
380
+ return {
381
+ valid: false,
382
+ error: `Validation failed with status ${response.status}`,
383
+ provider: "google"
384
+ };
385
+ } catch (error) {
386
+ return {
387
+ valid: false,
388
+ error: `Connection error: ${error instanceof Error ? error.message : "Unknown error"}`,
389
+ provider: "google"
390
+ };
391
+ }
392
+ }
393
+ };
394
+
395
+ export {
396
+ KeyManager,
397
+ keyManager,
398
+ KeyValidator
399
+ };