@agentuity/cli 0.0.66 → 0.0.68
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.
- package/dist/cmd/ai/prompt/agent.js +2 -2
- package/dist/cmd/ai/prompt/api.js +1 -1
- package/dist/cmd/build/ast.js +4 -4
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/bundler.js +2 -2
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +8 -11
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/dev/index.js +2 -2
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/project/download.js +11 -11
- package/dist/cmd/project/download.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +48 -87
- package/dist/config.js.map +1 -1
- package/dist/keychain.d.ts +31 -0
- package/dist/keychain.d.ts.map +1 -0
- package/dist/keychain.js +131 -0
- package/dist/keychain.js.map +1 -0
- package/dist/utils/detectSubagent.d.ts +1 -1
- package/dist/utils/detectSubagent.js +3 -3
- package/dist/utils/detectSubagent.js.map +1 -1
- package/package.json +3 -3
- package/src/cmd/ai/prompt/agent.ts +2 -2
- package/src/cmd/ai/prompt/api.ts +1 -1
- package/src/cmd/build/ast.ts +4 -4
- package/src/cmd/build/bundler.ts +2 -2
- package/src/cmd/build/plugin.ts +8 -11
- package/src/cmd/dev/index.ts +2 -2
- package/src/cmd/project/download.ts +11 -11
- package/src/config.ts +54 -96
- package/src/keychain.ts +178 -0
- package/src/utils/detectSubagent.ts +3 -3
package/src/keychain.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Keychain integration for secure auth token storage
|
|
3
|
+
*
|
|
4
|
+
* Stores auth tokens encrypted in the macOS Keychain using a per-device AES-256 key.
|
|
5
|
+
* No user prompts required - fully automatic and secure.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const SERVICE_PREFIX = "com.agentuity.cli";
|
|
9
|
+
const KEY_ACCOUNT = "aes-encryption-key";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if we're running on macOS
|
|
13
|
+
*/
|
|
14
|
+
export function isMacOS(): boolean {
|
|
15
|
+
return process.platform === 'darwin';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get or create an AES encryption key stored in the macOS Keychain
|
|
20
|
+
*/
|
|
21
|
+
async function ensureEncryptionKey(service: string): Promise<Uint8Array> {
|
|
22
|
+
// Try to read existing key
|
|
23
|
+
const find = Bun.spawn(
|
|
24
|
+
['security', 'find-generic-password', '-s', service, '-a', KEY_ACCOUNT, '-w'],
|
|
25
|
+
{ stderr: 'ignore' }
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const stdout = await new Response(find.stdout).text();
|
|
29
|
+
|
|
30
|
+
if (stdout.length > 0) {
|
|
31
|
+
const b64 = stdout.trim();
|
|
32
|
+
return Uint8Array.from(Buffer.from(b64, 'base64'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create a new 32-byte (256-bit) AES key
|
|
36
|
+
const key = crypto.getRandomValues(new Uint8Array(32));
|
|
37
|
+
const b64 = Buffer.from(key).toString('base64');
|
|
38
|
+
|
|
39
|
+
// Store in macOS Keychain (no user prompts with -U flag)
|
|
40
|
+
const add = Bun.spawn([
|
|
41
|
+
'security',
|
|
42
|
+
'add-generic-password',
|
|
43
|
+
'-s',
|
|
44
|
+
service,
|
|
45
|
+
'-a',
|
|
46
|
+
KEY_ACCOUNT,
|
|
47
|
+
'-w',
|
|
48
|
+
b64,
|
|
49
|
+
'-U', // Update without user confirmation
|
|
50
|
+
]);
|
|
51
|
+
await add.exited;
|
|
52
|
+
|
|
53
|
+
return key;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Encrypt data using AES-256-GCM
|
|
58
|
+
*/
|
|
59
|
+
async function encrypt(data: string, keyBytes: Uint8Array): Promise<Uint8Array> {
|
|
60
|
+
const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, ['encrypt']);
|
|
61
|
+
|
|
62
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
63
|
+
const plaintext = new TextEncoder().encode(data);
|
|
64
|
+
|
|
65
|
+
const ciphertext = new Uint8Array(
|
|
66
|
+
await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, plaintext)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Combine IV + ciphertext
|
|
70
|
+
const combined = new Uint8Array(iv.length + ciphertext.length);
|
|
71
|
+
combined.set(iv, 0);
|
|
72
|
+
combined.set(ciphertext, iv.length);
|
|
73
|
+
|
|
74
|
+
return combined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Decrypt data using AES-256-GCM
|
|
79
|
+
*/
|
|
80
|
+
async function decrypt(combined: Uint8Array, keyBytes: Uint8Array): Promise<string> {
|
|
81
|
+
const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, ['decrypt']);
|
|
82
|
+
|
|
83
|
+
const iv = combined.slice(0, 12);
|
|
84
|
+
const ciphertext = combined.slice(12);
|
|
85
|
+
|
|
86
|
+
const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
|
|
87
|
+
|
|
88
|
+
return new TextDecoder().decode(plaintext);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Store auth data in macOS Keychain
|
|
93
|
+
*/
|
|
94
|
+
export async function saveAuthToKeychain(
|
|
95
|
+
profileName: string,
|
|
96
|
+
authData: { api_key: string; user_id: string; expires: number }
|
|
97
|
+
): Promise<void> {
|
|
98
|
+
const service = `${SERVICE_PREFIX}.${profileName}`;
|
|
99
|
+
const account = 'auth-token';
|
|
100
|
+
|
|
101
|
+
// Get or create encryption key
|
|
102
|
+
const key = await ensureEncryptionKey(service);
|
|
103
|
+
|
|
104
|
+
// Encrypt the auth data
|
|
105
|
+
const json = JSON.stringify(authData);
|
|
106
|
+
const encrypted = await encrypt(json, key);
|
|
107
|
+
const b64 = Buffer.from(encrypted).toString('base64');
|
|
108
|
+
|
|
109
|
+
// Store encrypted auth in keychain
|
|
110
|
+
// First try to delete if exists, then add
|
|
111
|
+
const del = Bun.spawn(
|
|
112
|
+
['security', 'delete-generic-password', '-s', service, '-a', account],
|
|
113
|
+
{ stderr: 'ignore' }
|
|
114
|
+
);
|
|
115
|
+
await del.exited;
|
|
116
|
+
|
|
117
|
+
const add = Bun.spawn([
|
|
118
|
+
'security',
|
|
119
|
+
'add-generic-password',
|
|
120
|
+
'-s',
|
|
121
|
+
service,
|
|
122
|
+
'-a',
|
|
123
|
+
account,
|
|
124
|
+
'-w',
|
|
125
|
+
b64,
|
|
126
|
+
'-U',
|
|
127
|
+
]);
|
|
128
|
+
await add.exited;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Retrieve auth data from macOS Keychain
|
|
133
|
+
*/
|
|
134
|
+
export async function getAuthFromKeychain(
|
|
135
|
+
profileName: string
|
|
136
|
+
): Promise<{ api_key: string; user_id: string; expires: number } | null> {
|
|
137
|
+
const service = `${SERVICE_PREFIX}.${profileName}`;
|
|
138
|
+
const account = 'auth-token';
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Get the encrypted auth data
|
|
142
|
+
const find = Bun.spawn(
|
|
143
|
+
['security', 'find-generic-password', '-s', service, '-a', account, '-w'],
|
|
144
|
+
{ stderr: 'ignore' }
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const stdout = await new Response(find.stdout).text();
|
|
148
|
+
if (stdout.length === 0) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const b64 = stdout.trim();
|
|
153
|
+
const encrypted = Uint8Array.from(Buffer.from(b64, 'base64'));
|
|
154
|
+
|
|
155
|
+
// Get the encryption key
|
|
156
|
+
const key = await ensureEncryptionKey(service);
|
|
157
|
+
|
|
158
|
+
// Decrypt the auth data
|
|
159
|
+
const json = await decrypt(encrypted, key);
|
|
160
|
+
return JSON.parse(json);
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Delete auth data from macOS Keychain
|
|
168
|
+
*/
|
|
169
|
+
export async function deleteAuthFromKeychain(profileName: string): Promise<void> {
|
|
170
|
+
const service = `${SERVICE_PREFIX}.${profileName}`;
|
|
171
|
+
const account = 'auth-token';
|
|
172
|
+
|
|
173
|
+
const del = Bun.spawn(
|
|
174
|
+
['security', 'delete-generic-password', '-s', service, '-a', account],
|
|
175
|
+
{ stderr: 'ignore' }
|
|
176
|
+
);
|
|
177
|
+
await del.exited;
|
|
178
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Detects if a file path represents a subagent based on path structure.
|
|
3
3
|
*
|
|
4
|
-
* Subagents follow the pattern:
|
|
4
|
+
* Subagents follow the pattern: agent/parent/child/agent.ts or agent/parent/child/route.ts
|
|
5
5
|
* The path structure is currently hardcoded to 4 segments but could be made configurable later.
|
|
6
6
|
*
|
|
7
7
|
* @param filePath - The file path to analyze (can include leading './')
|
|
@@ -22,9 +22,9 @@ export function detectSubagent(
|
|
|
22
22
|
// Strip leading './' and split into parts, filtering out empty segments
|
|
23
23
|
const pathParts = normalizedPath.replace(/^\.\//, '').split('/').filter(Boolean);
|
|
24
24
|
|
|
25
|
-
// Path structure assumption: ['
|
|
25
|
+
// Path structure assumption: ['agent', 'parent', 'child', 'agent.ts' | 'route.ts' | 'route']
|
|
26
26
|
// Currently hardcoded to 4 segments - consider making configurable in the future
|
|
27
|
-
const isSubagent = pathParts.length === 4 && pathParts[0] === '
|
|
27
|
+
const isSubagent = pathParts.length === 4 && pathParts[0] === 'agent';
|
|
28
28
|
const parentName = isSubagent ? pathParts[1] : null;
|
|
29
29
|
|
|
30
30
|
return { isSubagent, parentName };
|