@vectorlingo/cli 0.1.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.
- package/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/cli-bin.d.ts +3 -0
- package/dist/cli-bin.d.ts.map +1 -0
- package/dist/cli-bin.js +4 -0
- package/dist/cli-bin.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +405 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +103 -0
- package/dist/client.js.map +1 -0
- package/dist/contracts.d.ts +127 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +105 -0
- package/dist/contracts.js.map +1 -0
- package/dist/credential-store.d.ts +112 -0
- package/dist/credential-store.d.ts.map +1 -0
- package/dist/credential-store.js +528 -0
- package/dist/credential-store.js.map +1 -0
- package/dist/http.d.ts +7 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +92 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +180 -0
- package/dist/mcp.js.map +1 -0
- package/dist/oauth.d.ts +43 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +369 -0
- package/dist/oauth.js.map +1 -0
- package/dist/safe-files.d.ts +26 -0
- package/dist/safe-files.d.ts.map +1 -0
- package/dist/safe-files.js +203 -0
- package/dist/safe-files.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { chmod, link, lstat, mkdir, open, readFile, rename, rm, } from 'node:fs/promises';
|
|
3
|
+
import { homedir, platform } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
6
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
|
7
|
+
import { AgentError } from './contracts.js';
|
|
8
|
+
export function defaultCredentialPath() {
|
|
9
|
+
const configRoot = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
10
|
+
return join(configRoot, 'vectorlingo', 'credentials.json');
|
|
11
|
+
}
|
|
12
|
+
export function createCredentialStore(options = {}) {
|
|
13
|
+
const mode = options.mode ?? 'auto';
|
|
14
|
+
const filePath = options.filePath ?? defaultCredentialPath();
|
|
15
|
+
if (mode === 'file')
|
|
16
|
+
return new FileCredentialStore(filePath);
|
|
17
|
+
const backendFor = platformKeychain(options.service ?? 'VectorLingo Agent Access', dirname(filePath));
|
|
18
|
+
if (backendFor)
|
|
19
|
+
return new KeychainCredentialStore(backendFor, `${filePath}.lock`);
|
|
20
|
+
if (mode === 'keychain') {
|
|
21
|
+
throw new AgentError('keychain_unavailable', 'No supported operating-system keychain is available');
|
|
22
|
+
}
|
|
23
|
+
throw new AgentError('keychain_unavailable', 'No supported keychain is available; explicitly choose the private file credential store');
|
|
24
|
+
}
|
|
25
|
+
export class FileCredentialStore {
|
|
26
|
+
filePath;
|
|
27
|
+
constructor(filePath) {
|
|
28
|
+
this.filePath = filePath;
|
|
29
|
+
}
|
|
30
|
+
async load() {
|
|
31
|
+
await validatePrivateFile(this.filePath);
|
|
32
|
+
try {
|
|
33
|
+
const value = JSON.parse(await readFile(this.filePath, 'utf8'));
|
|
34
|
+
return parseCredentialProfile(value);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (isNotFound(error))
|
|
38
|
+
return undefined;
|
|
39
|
+
if (error instanceof AgentError)
|
|
40
|
+
throw error;
|
|
41
|
+
throw new AgentError('credential_store_corrupt', 'Credential file could not be read', {
|
|
42
|
+
cause: error,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async save(profile) {
|
|
47
|
+
await atomicPrivateWrite(this.filePath, JSON.stringify(parseCredentialProfile(profile)));
|
|
48
|
+
}
|
|
49
|
+
async clear() {
|
|
50
|
+
await rm(this.filePath, { force: true });
|
|
51
|
+
}
|
|
52
|
+
async update(updater) {
|
|
53
|
+
return withFileLock(`${this.filePath}.lock`, async () => {
|
|
54
|
+
const next = await updater(await this.load());
|
|
55
|
+
if (next === undefined)
|
|
56
|
+
await this.clear();
|
|
57
|
+
else
|
|
58
|
+
await this.save(next);
|
|
59
|
+
return next;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One keychain entry per deployment (issuer + resource), so logging in to a second deployment
|
|
65
|
+
* never overwrites the first. A separate `active` entry names the deployment that commands
|
|
66
|
+
* without a deployment of their own (tenant, execute, logout) use: the last one logged in to.
|
|
67
|
+
*/
|
|
68
|
+
export class KeychainCredentialStore {
|
|
69
|
+
backendFor;
|
|
70
|
+
lockPath;
|
|
71
|
+
constructor(backendFor, lockPath) {
|
|
72
|
+
this.backendFor = backendFor;
|
|
73
|
+
this.lockPath = lockPath;
|
|
74
|
+
}
|
|
75
|
+
get active() {
|
|
76
|
+
return this.backendFor('active');
|
|
77
|
+
}
|
|
78
|
+
async load(deployment) {
|
|
79
|
+
const account = deployment ? credentialAccountFor(deployment.issuer, deployment.resource) : await this.active.load();
|
|
80
|
+
if (account === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
const value = await this.backendFor(account).load();
|
|
83
|
+
return value === undefined ? undefined : parseCredentialProfile(JSON.parse(value));
|
|
84
|
+
}
|
|
85
|
+
/** Locked like `update()`: two concurrent logins would otherwise race the sealing key (GLM W2). */
|
|
86
|
+
async save(profile) {
|
|
87
|
+
await withFileLock(this.lockPath, () => this.write(profile));
|
|
88
|
+
}
|
|
89
|
+
async write(profile) {
|
|
90
|
+
const account = credentialAccountFor(profile.issuer, profile.resource);
|
|
91
|
+
await this.backendFor(account).save(JSON.stringify(parseCredentialProfile(profile)));
|
|
92
|
+
await this.active.save(account);
|
|
93
|
+
}
|
|
94
|
+
async clear() {
|
|
95
|
+
const account = await this.active.load();
|
|
96
|
+
if (account !== undefined)
|
|
97
|
+
await this.backendFor(account).clear();
|
|
98
|
+
await this.active.clear();
|
|
99
|
+
}
|
|
100
|
+
async update(updater) {
|
|
101
|
+
return withFileLock(this.lockPath, async () => {
|
|
102
|
+
const next = await updater(await this.load());
|
|
103
|
+
if (next === undefined)
|
|
104
|
+
await this.clear();
|
|
105
|
+
else
|
|
106
|
+
await this.write(next);
|
|
107
|
+
return next;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* macOS keeps only a random 256-bit key per entry in the keychain; the value itself is sealed with
|
|
113
|
+
* AES-256-GCM in a 0600 file. `security` takes a secret only in argv, on a tty prompt, or on stdin
|
|
114
|
+
* in `-i` mode, and `-i` hangs on lines near 4 KB — too short for a token profile, fine for a key.
|
|
115
|
+
* No secret is ever in argv, where process telemetry can log it (Codex Gate 3 r2 A H2).
|
|
116
|
+
*/
|
|
117
|
+
export class SealedFileSecret {
|
|
118
|
+
key;
|
|
119
|
+
filePath;
|
|
120
|
+
constructor(key, filePath) {
|
|
121
|
+
this.key = key;
|
|
122
|
+
this.filePath = filePath;
|
|
123
|
+
}
|
|
124
|
+
async load() {
|
|
125
|
+
await validatePrivateFile(this.filePath);
|
|
126
|
+
let sealed;
|
|
127
|
+
try {
|
|
128
|
+
sealed = await readFile(this.filePath, 'utf8');
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (isNotFound(error))
|
|
132
|
+
return undefined;
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
const key = await this.key.load();
|
|
136
|
+
if (key === undefined)
|
|
137
|
+
throw corruptCredentialStore();
|
|
138
|
+
const [version, iv, tag, data] = sealed.split('.');
|
|
139
|
+
if (version !== 'v1' || !iv || !tag || data === undefined)
|
|
140
|
+
throw corruptCredentialStore();
|
|
141
|
+
try {
|
|
142
|
+
const decipher = createDecipheriv('aes-256-gcm', parseKey(key), Buffer.from(iv, 'base64'));
|
|
143
|
+
decipher.setAuthTag(Buffer.from(tag, 'base64'));
|
|
144
|
+
return Buffer.concat([decipher.update(Buffer.from(data, 'base64')), decipher.final()]).toString('utf8');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
throw new AgentError('credential_store_corrupt', 'Stored credentials are invalid', { cause: error });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async save(value) {
|
|
151
|
+
let key = await this.key.load();
|
|
152
|
+
// A non-key value is an entry from before sealing (the whole profile): replacing it with a key
|
|
153
|
+
// also scrubs that plaintext copy. The key is stored before the file, so a crash in between
|
|
154
|
+
// leaves no unreadable file.
|
|
155
|
+
if (key === undefined || !HEX_KEY.test(key)) {
|
|
156
|
+
key = randomBytes(32).toString('hex');
|
|
157
|
+
await this.key.save(key);
|
|
158
|
+
}
|
|
159
|
+
const iv = randomBytes(12);
|
|
160
|
+
const cipher = createCipheriv('aes-256-gcm', parseKey(key), iv);
|
|
161
|
+
const data = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);
|
|
162
|
+
await atomicPrivateWrite(this.filePath, ['v1', iv, cipher.getAuthTag(), data].map((part) => typeof part === 'string' ? part : part.toString('base64')).join('.'));
|
|
163
|
+
}
|
|
164
|
+
async clear() {
|
|
165
|
+
await rm(this.filePath, { force: true });
|
|
166
|
+
await this.key.clear();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const HEX_KEY = /^[0-9a-f]{64}$/;
|
|
170
|
+
function parseKey(hex) {
|
|
171
|
+
if (!HEX_KEY.test(hex))
|
|
172
|
+
throw corruptCredentialStore();
|
|
173
|
+
return Buffer.from(hex, 'hex');
|
|
174
|
+
}
|
|
175
|
+
/** A short secret (the sealing key) in the user's default macOS keychain, written via `security -i` on stdin. */
|
|
176
|
+
export class MacOsKeychain {
|
|
177
|
+
service;
|
|
178
|
+
account;
|
|
179
|
+
run;
|
|
180
|
+
constructor(service, account, run = runSecretCommand) {
|
|
181
|
+
this.service = service;
|
|
182
|
+
this.account = account;
|
|
183
|
+
this.run = run;
|
|
184
|
+
if (!/^[^"\\\s][^"\\\n]*$/.test(service) || !/^[^"\\\s]+$/.test(account)) {
|
|
185
|
+
throw new AgentError('keychain_error', 'Keychain service or account name is not safe to quote');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async load() {
|
|
189
|
+
const result = await this.run('security', ['find-generic-password', '-s', this.service, '-a', this.account, '-w']);
|
|
190
|
+
if (result.code === 44)
|
|
191
|
+
return undefined;
|
|
192
|
+
if (result.code !== 0)
|
|
193
|
+
throw keychainError('read', result.stderr);
|
|
194
|
+
return result.stdout.replace(/\r?\n$/, '');
|
|
195
|
+
}
|
|
196
|
+
async save(value) {
|
|
197
|
+
if (!/^[0-9a-f]{1,128}$/.test(value))
|
|
198
|
+
throw new AgentError('keychain_error', 'Only a short hex key may be written to the keychain');
|
|
199
|
+
const command = `add-generic-password -U -s "${this.service}" -a "${this.account}" -w ${value}\n`;
|
|
200
|
+
const result = await this.run('security', ['-i'], command);
|
|
201
|
+
// `-i` does not reliably report a failed inner command, so read the value back.
|
|
202
|
+
if (result.code !== 0 || (await this.load()) !== value)
|
|
203
|
+
throw keychainError('write', result.stderr);
|
|
204
|
+
}
|
|
205
|
+
async clear() {
|
|
206
|
+
const result = await this.run('security', ['delete-generic-password', '-s', this.service, '-a', this.account]);
|
|
207
|
+
if (result.code !== 0 && result.code !== 44)
|
|
208
|
+
throw keychainError('delete', result.stderr);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
class LinuxSecretService {
|
|
212
|
+
service;
|
|
213
|
+
account;
|
|
214
|
+
constructor(service, account) {
|
|
215
|
+
this.service = service;
|
|
216
|
+
this.account = account;
|
|
217
|
+
}
|
|
218
|
+
async load() {
|
|
219
|
+
const result = await runSecretCommand('secret-tool', [
|
|
220
|
+
'lookup',
|
|
221
|
+
'service',
|
|
222
|
+
this.service,
|
|
223
|
+
'account',
|
|
224
|
+
this.account,
|
|
225
|
+
]);
|
|
226
|
+
if (result.code === 1)
|
|
227
|
+
return undefined;
|
|
228
|
+
if (result.code !== 0)
|
|
229
|
+
throw keychainError('read', result.stderr);
|
|
230
|
+
return result.stdout.replace(/\r?\n$/, '');
|
|
231
|
+
}
|
|
232
|
+
async save(value) {
|
|
233
|
+
const result = await runSecretCommand('secret-tool', [
|
|
234
|
+
'store',
|
|
235
|
+
'--label=VectorLingo Agent Access',
|
|
236
|
+
'service',
|
|
237
|
+
this.service,
|
|
238
|
+
'account',
|
|
239
|
+
this.account,
|
|
240
|
+
], value);
|
|
241
|
+
if (result.code !== 0)
|
|
242
|
+
throw keychainError('write', result.stderr);
|
|
243
|
+
}
|
|
244
|
+
async clear() {
|
|
245
|
+
const result = await runSecretCommand('secret-tool', [
|
|
246
|
+
'clear',
|
|
247
|
+
'service',
|
|
248
|
+
this.service,
|
|
249
|
+
'account',
|
|
250
|
+
this.account,
|
|
251
|
+
]);
|
|
252
|
+
if (result.code !== 0 && result.code !== 1)
|
|
253
|
+
throw keychainError('delete', result.stderr);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function platformKeychain(service, directory) {
|
|
257
|
+
if (platform() === 'darwin' && commandExists('security')) {
|
|
258
|
+
return (account) => new SealedFileSecret(new MacOsKeychain(service, account), join(directory, `${account}.sealed`));
|
|
259
|
+
}
|
|
260
|
+
if (platform() === 'linux' && commandExists('secret-tool'))
|
|
261
|
+
return (account) => new LinuxSecretService(service, account);
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
function commandExists(command) {
|
|
265
|
+
const result = spawnSync(command, ['--help'], { stdio: 'ignore' });
|
|
266
|
+
return !result.error;
|
|
267
|
+
}
|
|
268
|
+
async function atomicPrivateWrite(filePath, contents) {
|
|
269
|
+
const directory = dirname(filePath);
|
|
270
|
+
await ensurePrivateDirectory(directory);
|
|
271
|
+
const temporary = `${filePath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
|
|
272
|
+
const handle = await open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
|
|
273
|
+
try {
|
|
274
|
+
await handle.writeFile(contents, 'utf8');
|
|
275
|
+
await handle.sync();
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
await handle.close();
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
await rename(temporary, filePath);
|
|
282
|
+
await chmod(filePath, 0o600);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
await rm(temporary, { force: true });
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Create the credential directory 0700 when missing. An existing directory (for example the
|
|
291
|
+
* parent of a `--credential-file` path) is never re-permissioned; it is refused when other
|
|
292
|
+
* users could write to it, because they could then replace the credential file.
|
|
293
|
+
*/
|
|
294
|
+
async function ensurePrivateDirectory(directory) {
|
|
295
|
+
const created = await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
296
|
+
const info = await lstat(directory);
|
|
297
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
298
|
+
throw new AgentError('unsafe_credential_path', 'Credential directory must be a real directory');
|
|
299
|
+
}
|
|
300
|
+
if (created !== undefined)
|
|
301
|
+
await chmod(directory, 0o700);
|
|
302
|
+
else if ((info.mode & 0o022) !== 0) {
|
|
303
|
+
throw new AgentError('unsafe_credential_permissions', `Credential directory ${directory} must not be group- or world-writable`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async function validatePrivateFile(filePath) {
|
|
307
|
+
try {
|
|
308
|
+
const info = await lstat(filePath);
|
|
309
|
+
if (!info.isFile() || info.isSymbolicLink()) {
|
|
310
|
+
throw new AgentError('unsafe_credential_path', 'Credential path must be a regular file');
|
|
311
|
+
}
|
|
312
|
+
if ((info.mode & 0o077) !== 0) {
|
|
313
|
+
throw new AgentError('unsafe_credential_permissions', 'Credential file permissions must be 0600');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
if (!isNotFound(error))
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function withFileLock(lockPath, task) {
|
|
322
|
+
await ensurePrivateDirectory(dirname(lockPath));
|
|
323
|
+
// Resolved before the lock exists, so a failed `ps` cannot leave an ownerless lock behind.
|
|
324
|
+
const owner = `${lockOwner(process.pid)}\n`;
|
|
325
|
+
const deadline = Date.now() + 10_000;
|
|
326
|
+
let lock;
|
|
327
|
+
while (!lock) {
|
|
328
|
+
try {
|
|
329
|
+
lock = await open(lockPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (!isExists(error))
|
|
333
|
+
throw error;
|
|
334
|
+
if (await reclaimStaleLock(lockPath))
|
|
335
|
+
continue;
|
|
336
|
+
if (Date.now() >= deadline) {
|
|
337
|
+
throw new AgentError('credential_store_busy', `Timed out waiting for the credential store lock ${lockPath}`);
|
|
338
|
+
}
|
|
339
|
+
await new Promise((resolve) => setTimeout(resolve, 25 + Math.floor(Math.random() * 25)));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
try {
|
|
343
|
+
await lock.writeFile(owner, 'utf8');
|
|
344
|
+
return await task();
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
await lock.close();
|
|
348
|
+
await rm(lockPath, { force: true });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const STALE_LOCK_MS = 60_000;
|
|
352
|
+
/**
|
|
353
|
+
* A lock records `<pid> <process start time>`. It is stale when its owner has exited (Ctrl-C
|
|
354
|
+
* skips `finally`), when its PID now belongs to a different process (PID reuse after a crash:
|
|
355
|
+
* the start time differs), or when it is ownerless and older than a minute (its writer died
|
|
356
|
+
* before writing). A live owner is never reclaimed: a slow refresh must not be joined, and a
|
|
357
|
+
* hung keychain command is bounded by `runSecretCommand`'s timeout instead. A stale lock is
|
|
358
|
+
* renamed away before removal, so of two processes reclaiming the same stale lock only one
|
|
359
|
+
* rename succeeds and the loser retries. A loser that judged the old lock
|
|
360
|
+
* stale but renames after the winner re-locked has moved the winner's fresh lock: the identity
|
|
361
|
+
* check (inode, mtime and content — Linux reuses a freed inode at once) sees that and links it
|
|
362
|
+
* back (the sealed save writes a random key, so two holders would leave the profile unreadable).
|
|
363
|
+
* ponytail: a third process that locks inside that rename/link gap still overlaps the winner.
|
|
364
|
+
*/
|
|
365
|
+
async function reclaimStaleLock(lockPath) {
|
|
366
|
+
let identity;
|
|
367
|
+
try {
|
|
368
|
+
identity = await readLockIdentity(lockPath);
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
if (isNotFound(error))
|
|
372
|
+
return true;
|
|
373
|
+
throw error;
|
|
374
|
+
}
|
|
375
|
+
const [pidText, ...startParts] = identity.content.trim().split(' ');
|
|
376
|
+
const owner = Number.parseInt(pidText ?? '', 10);
|
|
377
|
+
const recordedStart = startParts.join(' ');
|
|
378
|
+
const ageMs = Date.now() - identity.mtimeMs;
|
|
379
|
+
// An empty PID is a lock still being written; only its age can make it stale. A lock without
|
|
380
|
+
// a start time (older CLI, or Windows) falls back to PID liveness alone.
|
|
381
|
+
if (Number.isInteger(owner) && owner > 0) {
|
|
382
|
+
if (processIsAlive(owner) && (!recordedStart || processStartTime(owner) === recordedStart))
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
else if (ageMs < STALE_LOCK_MS) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
await claimStaleLock(lockPath, identity);
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
/** Inode, mtime and content read through one handle, so they describe the same file. */
|
|
392
|
+
export async function readLockIdentity(lockPath) {
|
|
393
|
+
const handle = await open(lockPath, 'r');
|
|
394
|
+
try {
|
|
395
|
+
const stat = await handle.stat();
|
|
396
|
+
return { ino: stat.ino, mtimeMs: stat.mtimeMs, content: await handle.readFile('utf8') };
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
await handle.close();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/** Remove the lock at `lockPath` only if it is still the file judged stale. Exported for tests. */
|
|
403
|
+
export async function claimStaleLock(lockPath, stale) {
|
|
404
|
+
const claimed = `${lockPath}.${process.pid}.${randomBytes(8).toString('hex')}.stale`;
|
|
405
|
+
try {
|
|
406
|
+
await rename(lockPath, claimed);
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
if (isNotFound(error))
|
|
410
|
+
return;
|
|
411
|
+
throw error;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const moved = await readLockIdentity(claimed);
|
|
415
|
+
if (moved.ino !== stale.ino || moved.mtimeMs !== stale.mtimeMs || moved.content !== stale.content) {
|
|
416
|
+
try {
|
|
417
|
+
await link(claimed, lockPath);
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
if (!isExists(error))
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
finally {
|
|
426
|
+
await rm(claimed, { force: true });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function lockOwner(pid) {
|
|
430
|
+
const start = processStartTime(pid);
|
|
431
|
+
return start ? `${pid} ${start}` : String(pid);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The process's start time from `ps` (same `lstart` format on macOS and Linux under `LC_ALL=C`),
|
|
435
|
+
* or `undefined` when no such process exists. `lstart` is wall-clock time, so `TZ=UTC` keeps
|
|
436
|
+
* two CLIs with different time zones from reading one live owner as a reused PID.
|
|
437
|
+
* ponytail: Windows has no `ps`, so its locks record the PID alone and PID reuse there still
|
|
438
|
+
* needs the lock file removed by hand; add a Windows process-identity lookup if it ships there.
|
|
439
|
+
*/
|
|
440
|
+
function processStartTime(pid) {
|
|
441
|
+
if (platform() === 'win32')
|
|
442
|
+
return undefined;
|
|
443
|
+
const result = spawnSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
|
|
444
|
+
encoding: 'utf8',
|
|
445
|
+
env: { ...process.env, LC_ALL: 'C', TZ: 'UTC' },
|
|
446
|
+
});
|
|
447
|
+
if (result.error)
|
|
448
|
+
throw result.error;
|
|
449
|
+
const start = result.stdout.trim();
|
|
450
|
+
if (result.status === 0 && start)
|
|
451
|
+
return start;
|
|
452
|
+
if (result.status === 1 && !start)
|
|
453
|
+
return undefined;
|
|
454
|
+
throw new Error(`ps could not read process ${pid} (exit ${result.status}): ${result.stderr.slice(0, 200)}`);
|
|
455
|
+
}
|
|
456
|
+
function processIsAlive(pid) {
|
|
457
|
+
try {
|
|
458
|
+
process.kill(pid, 0);
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
// EPERM: the process exists but belongs to another user.
|
|
463
|
+
return error.code !== 'ESRCH';
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function parseCredentialProfile(value) {
|
|
467
|
+
if (typeof value !== 'object' || value === null)
|
|
468
|
+
throw corruptCredentialStore();
|
|
469
|
+
const profile = value;
|
|
470
|
+
const tokens = profile.tokens;
|
|
471
|
+
if (typeof profile.baseUrl !== 'string' ||
|
|
472
|
+
typeof profile.issuer !== 'string' ||
|
|
473
|
+
typeof profile.resource !== 'string' ||
|
|
474
|
+
typeof profile.clientId !== 'string' ||
|
|
475
|
+
typeof tokens?.accessToken !== 'string' ||
|
|
476
|
+
tokens.tokenType !== 'Bearer' ||
|
|
477
|
+
typeof tokens.expiresAt !== 'number' ||
|
|
478
|
+
(tokens.refreshToken !== undefined && typeof tokens.refreshToken !== 'string') ||
|
|
479
|
+
(profile.selectedTenantId !== undefined && typeof profile.selectedTenantId !== 'string')) {
|
|
480
|
+
throw corruptCredentialStore();
|
|
481
|
+
}
|
|
482
|
+
return profile;
|
|
483
|
+
}
|
|
484
|
+
function corruptCredentialStore() {
|
|
485
|
+
return new AgentError('credential_store_corrupt', 'Stored credentials are invalid');
|
|
486
|
+
}
|
|
487
|
+
function keychainError(action, stderr) {
|
|
488
|
+
return new AgentError('keychain_error', `Operating-system keychain ${action} failed`, {
|
|
489
|
+
details: stderr ? { diagnostic: stderr.slice(0, 200) } : undefined,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Run a keychain helper. The timeout bounds a hung helper, so the caller fails and releases the
|
|
494
|
+
* credential lock instead of holding it forever; 60 s leaves room for a macOS Keychain prompt.
|
|
495
|
+
* Exported for tests.
|
|
496
|
+
*/
|
|
497
|
+
export function runSecretCommand(command, args, stdin, timeoutMs = 60_000) {
|
|
498
|
+
return new Promise((resolve, reject) => {
|
|
499
|
+
const child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], timeout: timeoutMs, killSignal: 'SIGKILL' });
|
|
500
|
+
const stdout = [];
|
|
501
|
+
const stderr = [];
|
|
502
|
+
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
|
503
|
+
child.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
504
|
+
child.on('error', reject);
|
|
505
|
+
child.on('close', (code, signal) => {
|
|
506
|
+
if (signal) {
|
|
507
|
+
reject(new AgentError('keychain_error', 'Operating-system keychain command timed out'));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
resolve({
|
|
511
|
+
code: code ?? 1,
|
|
512
|
+
stdout: Buffer.concat(stdout).toString('utf8'),
|
|
513
|
+
stderr: Buffer.concat(stderr).toString('utf8'),
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
child.stdin.end(stdin);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function isNotFound(error) {
|
|
520
|
+
return error?.code === 'ENOENT';
|
|
521
|
+
}
|
|
522
|
+
function isExists(error) {
|
|
523
|
+
return error?.code === 'EEXIST';
|
|
524
|
+
}
|
|
525
|
+
export function credentialAccountFor(issuer, resource) {
|
|
526
|
+
return createHash('sha256').update(`${issuer}\n${resource}`).digest('hex').slice(0, 32);
|
|
527
|
+
}
|
|
528
|
+
//# sourceMappingURL=credential-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../src/credential-store.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type FetchLike = typeof globalThis.fetch;
|
|
2
|
+
export declare function validateServiceUrl(rawUrl: string, label?: string): URL;
|
|
3
|
+
export declare function validateLoopbackRedirect(rawUrl: string): URL;
|
|
4
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
5
|
+
export declare function parseAgentResponse<T>(response: Response): Promise<T>;
|
|
6
|
+
export declare function joinUrl(baseUrl: URL, path: string): URL;
|
|
7
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC,KAAK,CAAA;AAE/C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAgB,GAAG,GAAG,CAmB7E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAS5D;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAsB1E;AAqCD,wBAAgB,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CAGvD"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AgentError } from './contracts.js';
|
|
2
|
+
export function validateServiceUrl(rawUrl, label = 'Service URL') {
|
|
3
|
+
let url;
|
|
4
|
+
try {
|
|
5
|
+
url = new URL(rawUrl);
|
|
6
|
+
}
|
|
7
|
+
catch (cause) {
|
|
8
|
+
throw new AgentError('invalid_configuration', `${label} is not a valid URL`, { cause });
|
|
9
|
+
}
|
|
10
|
+
if (url.username || url.password || url.hash) {
|
|
11
|
+
throw new AgentError('invalid_configuration', `${label} must not contain credentials or a fragment`);
|
|
12
|
+
}
|
|
13
|
+
if (url.protocol === 'https:')
|
|
14
|
+
return url;
|
|
15
|
+
if (url.protocol === 'http:' && isLoopbackHostname(url.hostname))
|
|
16
|
+
return url;
|
|
17
|
+
throw new AgentError('insecure_url', `${label} must use HTTPS (HTTP is allowed only for a loopback address)`);
|
|
18
|
+
}
|
|
19
|
+
export function validateLoopbackRedirect(rawUrl) {
|
|
20
|
+
const url = validateServiceUrl(rawUrl, 'OAuth redirect URL');
|
|
21
|
+
if (url.protocol !== 'http:' || !isLoopbackHostname(url.hostname)) {
|
|
22
|
+
throw new AgentError('insecure_redirect', 'OAuth redirect URL must use HTTP on a loopback address');
|
|
23
|
+
}
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
export function isLoopbackHostname(hostname) {
|
|
27
|
+
const value = hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
28
|
+
return value === 'localhost' || value === '127.0.0.1' || value === '::1';
|
|
29
|
+
}
|
|
30
|
+
export async function parseAgentResponse(response) {
|
|
31
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
32
|
+
const body = contentType.includes('application/json')
|
|
33
|
+
? await response.json().catch(() => undefined)
|
|
34
|
+
: undefined;
|
|
35
|
+
if (response.ok)
|
|
36
|
+
return body;
|
|
37
|
+
const remote = isAgentErrorResponse(body) ? body.error : undefined;
|
|
38
|
+
const retryAfter = parseRetryAfter(response.headers.get('retry-after'));
|
|
39
|
+
const operationId = remote?.operation_id;
|
|
40
|
+
const retryAfterSeconds = remote?.retry_after_seconds ?? retryAfter;
|
|
41
|
+
throw new AgentError(remote?.code ?? httpErrorCode(response.status), remote?.message ?? safeStatus(response), {
|
|
42
|
+
...(remote?.details === undefined ? {} : { details: remote.details }),
|
|
43
|
+
httpStatus: response.status,
|
|
44
|
+
...(operationId === undefined ? {} : { operationId }),
|
|
45
|
+
...(retryAfterSeconds === undefined ? {} : { retryAfterSeconds }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function isAgentErrorResponse(value) {
|
|
49
|
+
if (typeof value !== 'object' || value === null || !('error' in value))
|
|
50
|
+
return false;
|
|
51
|
+
const error = value.error;
|
|
52
|
+
return (typeof error === 'object' &&
|
|
53
|
+
error !== null &&
|
|
54
|
+
typeof error.code === 'string' &&
|
|
55
|
+
typeof error.message === 'string');
|
|
56
|
+
}
|
|
57
|
+
function safeStatus(response) {
|
|
58
|
+
return `VectorLingo request failed with HTTP ${response.status}`;
|
|
59
|
+
}
|
|
60
|
+
function httpErrorCode(status) {
|
|
61
|
+
if (status === 400)
|
|
62
|
+
return 'invalid_request';
|
|
63
|
+
if (status === 401)
|
|
64
|
+
return 'authentication_required';
|
|
65
|
+
if (status === 403)
|
|
66
|
+
return 'forbidden';
|
|
67
|
+
if (status === 404)
|
|
68
|
+
return 'not_found';
|
|
69
|
+
if (status === 409)
|
|
70
|
+
return 'conflict';
|
|
71
|
+
if (status === 413)
|
|
72
|
+
return 'payload_too_large';
|
|
73
|
+
if (status === 429)
|
|
74
|
+
return 'rate_limited';
|
|
75
|
+
return status >= 500 ? 'service_error' : 'request_failed';
|
|
76
|
+
}
|
|
77
|
+
function parseRetryAfter(value) {
|
|
78
|
+
if (value === null)
|
|
79
|
+
return undefined;
|
|
80
|
+
const seconds = Number(value);
|
|
81
|
+
if (Number.isInteger(seconds) && seconds >= 0)
|
|
82
|
+
return seconds;
|
|
83
|
+
const date = Date.parse(value);
|
|
84
|
+
if (Number.isNaN(date))
|
|
85
|
+
return undefined;
|
|
86
|
+
return Math.max(0, Math.ceil((date - Date.now()) / 1000));
|
|
87
|
+
}
|
|
88
|
+
export function joinUrl(baseUrl, path) {
|
|
89
|
+
const root = baseUrl.href.endsWith('/') ? baseUrl : new URL(`${baseUrl.href}/`);
|
|
90
|
+
return new URL(path.replace(/^\//, ''), root);
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=http.js.map
|