@agentrix/shared 2.10.0 → 2.13.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/dist/node.cjs CHANGED
@@ -4,6 +4,8 @@ var node_fs = require('node:fs');
4
4
  var node_path = require('node:path');
5
5
  var errors = require('./errors-myQvpVrM.cjs');
6
6
  var os = require('node:os');
7
+ var gitlabWebhook = require('./gitlabWebhook.cjs');
8
+ var node_crypto = require('node:crypto');
7
9
  require('zod');
8
10
 
9
11
  function _interopNamespaceDefault(e) {
@@ -213,11 +215,14 @@ async function loadSystemPrompt(claudeDir, promptFile) {
213
215
  }
214
216
  }
215
217
  function replacePromptPlaceholders(template, cwd, extra) {
218
+ const now = /* @__PURE__ */ new Date();
216
219
  const vars = {
217
220
  WORKING_DIR: cwd,
218
221
  PLATFORM: process.platform,
219
222
  OS_VERSION: `${os__namespace.type()} ${os__namespace.release()}`,
220
- DATE: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
223
+ DATE: now.toISOString().split("T")[0],
224
+ TIME: now.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false }),
225
+ TIMEZONE: Intl.DateTimeFormat().resolvedOptions().timeZone,
221
226
  ...extra
222
227
  };
223
228
  let result = template.replace(
@@ -234,6 +239,296 @@ function replacePromptPlaceholders(template, cwd, extra) {
234
239
  return result;
235
240
  }
236
241
 
242
+ const ALGORITHM = "aes-256-gcm";
243
+ const DEFAULT_PAT_VALIDATE_TIMEOUT_MS = 1e4;
244
+ const DEFAULT_GITLAB_PAT_REQUIRED_SCOPES = ["api", "read_repository", "write_repository"];
245
+ function hmacSha512(key, data) {
246
+ const hmac = node_crypto.createHmac("sha512", key);
247
+ hmac.update(data);
248
+ return new Uint8Array(hmac.digest());
249
+ }
250
+ function deriveLocalGitServerEncryptionKey(masterSecret) {
251
+ const decoded = Buffer.from(masterSecret, "base64");
252
+ const rootSeedKey = new TextEncoder().encode("Agentrix EnCoder Master Seed");
253
+ const rootI = hmacSha512(rootSeedKey, decoded);
254
+ const chainCode = rootI.slice(32);
255
+ const childData = new Uint8Array([0, ...new TextEncoder().encode("content")]);
256
+ const childI = hmacSha512(chainCode, childData);
257
+ return childI.slice(0, 32);
258
+ }
259
+ function parseScopes(rawScopes) {
260
+ if (!rawScopes) return [];
261
+ return rawScopes.split(",").map((scope) => scope.trim()).filter(Boolean);
262
+ }
263
+ function makeFailedResult(status, error, extras = {}) {
264
+ return { valid: false, status, error, ...extras };
265
+ }
266
+ function validateRequiredScopes(scopes, requiredScopes) {
267
+ if (!requiredScopes.length) return [];
268
+ const actual = new Set(scopes);
269
+ return requiredScopes.filter((scope) => !actual.has(scope));
270
+ }
271
+ async function fetchPatSelfInfo(apiUrl, pat, timeoutMs, log) {
272
+ let response;
273
+ try {
274
+ response = await fetch(`${apiUrl}/personal_access_tokens/self`, {
275
+ headers: { Authorization: `Bearer ${pat}` },
276
+ signal: AbortSignal.timeout(timeoutMs)
277
+ });
278
+ } catch (err) {
279
+ log?.warn?.("[PAT] Failed to fetch PAT expiry:", err);
280
+ return void 0;
281
+ }
282
+ if (!response.ok) {
283
+ return void 0;
284
+ }
285
+ try {
286
+ const tokenInfo = await response.json();
287
+ return {
288
+ expiresAt: tokenInfo.expires_at ?? void 0,
289
+ scopes: Array.isArray(tokenInfo.scopes) ? tokenInfo.scopes.map((scope) => scope.trim()).filter(Boolean) : void 0
290
+ };
291
+ } catch (err) {
292
+ log?.warn?.("[PAT] Failed to parse PAT self response:", err);
293
+ return void 0;
294
+ }
295
+ }
296
+ async function validateGitLabPatToken(apiUrl, pat, options = {}) {
297
+ const requiredScopes = options.requiredScopes ?? DEFAULT_GITLAB_PAT_REQUIRED_SCOPES;
298
+ const timeoutMs = options.timeoutMs ?? DEFAULT_PAT_VALIDATE_TIMEOUT_MS;
299
+ if (!pat.trim()) {
300
+ return { result: makeFailedResult("invalid", "Token is empty") };
301
+ }
302
+ let response;
303
+ try {
304
+ response = await fetch(`${apiUrl}/user`, {
305
+ headers: { Authorization: `Bearer ${pat}` },
306
+ signal: AbortSignal.timeout(timeoutMs)
307
+ });
308
+ } catch (err) {
309
+ options.log?.error?.("[PAT] GitLab API validation failed:", err);
310
+ return { result: makeFailedResult("network_error", `Cannot reach GitLab: ${err.message}`) };
311
+ }
312
+ if (!response.ok) {
313
+ if (response.status === 401 || response.status === 403) {
314
+ return { result: makeFailedResult("expired", `GitLab returned ${response.status}`) };
315
+ }
316
+ return { result: makeFailedResult("invalid", `GitLab returned ${response.status}`) };
317
+ }
318
+ let user;
319
+ try {
320
+ user = await response.json();
321
+ } catch {
322
+ return { result: makeFailedResult("invalid", "GitLab returned an invalid response") };
323
+ }
324
+ if (!user?.username) {
325
+ return { result: makeFailedResult("invalid", "GitLab response missing username") };
326
+ }
327
+ const scopesFromHeader = parseScopes(response.headers.get("x-oauth-scopes"));
328
+ const selfInfo = await fetchPatSelfInfo(apiUrl, pat, timeoutMs, options.log);
329
+ const scopesFromSelf = selfInfo?.scopes ?? [];
330
+ const effectiveScopes = scopesFromHeader.length > 0 ? scopesFromHeader : scopesFromSelf;
331
+ const expiresAt = selfInfo?.expiresAt;
332
+ const shouldEnforceScopes = effectiveScopes.length > 0;
333
+ const missingScopes = shouldEnforceScopes ? validateRequiredScopes(effectiveScopes, requiredScopes) : [];
334
+ if (shouldEnforceScopes && missingScopes.length > 0) {
335
+ return {
336
+ result: makeFailedResult(
337
+ "insufficient_scope",
338
+ `Token is missing required scopes: ${missingScopes.join(", ")}`,
339
+ { username: user.username, scopes: effectiveScopes, missingScopes, expiresAt }
340
+ ),
341
+ user
342
+ };
343
+ }
344
+ return {
345
+ result: { valid: true, status: "valid", username: user.username, scopes: effectiveScopes, expiresAt },
346
+ user
347
+ };
348
+ }
349
+ class GitServerLocalStore {
350
+ credentialsDir;
351
+ constructor(options) {
352
+ this.credentialsDir = options.credentialsDir;
353
+ }
354
+ ensureCredentialsDir() {
355
+ if (!node_fs.existsSync(this.credentialsDir)) {
356
+ node_fs.mkdirSync(this.credentialsDir, { recursive: true });
357
+ }
358
+ }
359
+ getPatFilePath(gitServerId) {
360
+ return node_path.join(this.credentialsDir, `${gitServerId}.pat.enc`);
361
+ }
362
+ getPatMetaFilePath(gitServerId) {
363
+ return node_path.join(this.credentialsDir, `${gitServerId}.pat.meta.json`);
364
+ }
365
+ getGitServerConfigFilePath(gitServerId) {
366
+ return node_path.join(this.credentialsDir, `${gitServerId}.git-server.json`);
367
+ }
368
+ getGitLabWebhookBridgeFilePath(gitServerId) {
369
+ return node_path.join(this.credentialsDir, `${gitServerId}.gitlab-webhook.enc`);
370
+ }
371
+ encryptJsonFile(filePath, value, encryptionKey) {
372
+ this.ensureCredentialsDir();
373
+ const iv = node_crypto.randomBytes(16);
374
+ const key = encryptionKey.slice(0, 32);
375
+ const cipher = node_crypto.createCipheriv(ALGORITHM, key, iv);
376
+ let encrypted = cipher.update(JSON.stringify(value), "utf8", "hex");
377
+ encrypted += cipher.final("hex");
378
+ const authTag = cipher.getAuthTag();
379
+ const payload = {
380
+ iv: iv.toString("hex"),
381
+ authTag: authTag.toString("hex"),
382
+ encrypted
383
+ };
384
+ node_fs.writeFileSync(filePath, JSON.stringify(payload), { mode: 384 });
385
+ }
386
+ decryptJsonFile(filePath, encryptionKey) {
387
+ if (!node_fs.existsSync(filePath)) return null;
388
+ try {
389
+ const raw = node_fs.readFileSync(filePath, "utf8");
390
+ const { iv, authTag, encrypted } = JSON.parse(raw);
391
+ const key = encryptionKey.slice(0, 32);
392
+ const decipher = node_crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, "hex"));
393
+ decipher.setAuthTag(Buffer.from(authTag, "hex"));
394
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
395
+ decrypted += decipher.final("utf8");
396
+ return JSON.parse(decrypted);
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
401
+ saveGitServerConfig(gitServerId, input) {
402
+ this.ensureCredentialsDir();
403
+ const existing = this.loadGitServerConfig(gitServerId) ?? {};
404
+ const next = {
405
+ ...existing,
406
+ ...input
407
+ };
408
+ node_fs.writeFileSync(this.getGitServerConfigFilePath(gitServerId), JSON.stringify(next, null, 2), { mode: 384 });
409
+ }
410
+ loadGitServerConfig(gitServerId) {
411
+ const filePath = this.getGitServerConfigFilePath(gitServerId);
412
+ if (!node_fs.existsSync(filePath)) return null;
413
+ try {
414
+ return JSON.parse(node_fs.readFileSync(filePath, "utf8"));
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ deleteGitServerConfig(gitServerId) {
420
+ const configPath = this.getGitServerConfigFilePath(gitServerId);
421
+ if (node_fs.existsSync(configPath)) {
422
+ node_fs.unlinkSync(configPath);
423
+ }
424
+ const bridgePath = this.getGitLabWebhookBridgeFilePath(gitServerId);
425
+ if (node_fs.existsSync(bridgePath)) {
426
+ node_fs.unlinkSync(bridgePath);
427
+ }
428
+ }
429
+ listGitServerIds() {
430
+ this.ensureCredentialsDir();
431
+ const ids = /* @__PURE__ */ new Set();
432
+ for (const file of node_fs.readdirSync(this.credentialsDir)) {
433
+ if (file.endsWith(".git-server.json")) {
434
+ ids.add(file.slice(0, -".git-server.json".length));
435
+ } else if (file.endsWith(".pat.enc")) {
436
+ ids.add(file.slice(0, -".pat.enc".length));
437
+ } else if (file.endsWith(".gitlab-webhook.enc")) {
438
+ ids.add(file.slice(0, -".gitlab-webhook.enc".length));
439
+ }
440
+ }
441
+ return [...ids].sort();
442
+ }
443
+ listPats() {
444
+ this.ensureCredentialsDir();
445
+ const files = node_fs.readdirSync(this.credentialsDir).filter((file) => file.endsWith(".pat.enc"));
446
+ return files.map((file) => ({
447
+ gitServerId: file.replace(".pat.enc", ""),
448
+ exists: true
449
+ }));
450
+ }
451
+ loadGitLabWebhookBridgeSecrets(gitServerId, encryptionKey) {
452
+ return this.decryptJsonFile(
453
+ this.getGitLabWebhookBridgeFilePath(gitServerId),
454
+ encryptionKey
455
+ );
456
+ }
457
+ saveGitLabWebhookBridgeSecrets(gitServerId, secrets, encryptionKey) {
458
+ this.encryptJsonFile(this.getGitLabWebhookBridgeFilePath(gitServerId), secrets, encryptionKey);
459
+ }
460
+ ensureGitLabWebhookSecret(gitServerId, encryptionKey) {
461
+ const current = this.loadGitLabWebhookBridgeSecrets(gitServerId, encryptionKey);
462
+ if (current?.webhookSecret) {
463
+ return current.webhookSecret;
464
+ }
465
+ const webhookSecret = node_crypto.randomBytes(32).toString("hex");
466
+ this.saveGitLabWebhookBridgeSecrets(gitServerId, {
467
+ ...current ?? {},
468
+ webhookSecret,
469
+ projectTriggerTokens: current?.projectTriggerTokens ?? {}
470
+ }, encryptionKey);
471
+ return webhookSecret;
472
+ }
473
+ loadPatMeta(gitServerId) {
474
+ const filePath = this.getPatMetaFilePath(gitServerId);
475
+ if (!node_fs.existsSync(filePath)) return null;
476
+ try {
477
+ return JSON.parse(node_fs.readFileSync(filePath, "utf8"));
478
+ } catch {
479
+ return null;
480
+ }
481
+ }
482
+ savePatMeta(gitServerId, meta) {
483
+ this.ensureCredentialsDir();
484
+ node_fs.writeFileSync(this.getPatMetaFilePath(gitServerId), JSON.stringify(meta, null, 2), { mode: 384 });
485
+ }
486
+ savePat(gitServerId, pat, encryptionKey) {
487
+ this.ensureCredentialsDir();
488
+ const iv = node_crypto.randomBytes(16);
489
+ const key = encryptionKey.slice(0, 32);
490
+ const cipher = node_crypto.createCipheriv(ALGORITHM, key, iv);
491
+ let encrypted = cipher.update(pat, "utf8", "hex");
492
+ encrypted += cipher.final("hex");
493
+ const authTag = cipher.getAuthTag();
494
+ const payload = {
495
+ iv: iv.toString("hex"),
496
+ authTag: authTag.toString("hex"),
497
+ encrypted
498
+ };
499
+ node_fs.writeFileSync(this.getPatFilePath(gitServerId), JSON.stringify(payload), { mode: 384 });
500
+ }
501
+ loadPat(gitServerId, encryptionKey) {
502
+ const filePath = this.getPatFilePath(gitServerId);
503
+ if (!node_fs.existsSync(filePath)) return null;
504
+ try {
505
+ const raw = node_fs.readFileSync(filePath, "utf8");
506
+ const { iv, authTag, encrypted } = JSON.parse(raw);
507
+ const key = encryptionKey.slice(0, 32);
508
+ const decipher = node_crypto.createDecipheriv(ALGORITHM, key, Buffer.from(iv, "hex"));
509
+ decipher.setAuthTag(Buffer.from(authTag, "hex"));
510
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
511
+ decrypted += decipher.final("utf8");
512
+ return decrypted;
513
+ } catch {
514
+ return null;
515
+ }
516
+ }
517
+ hasPat(gitServerId) {
518
+ return node_fs.existsSync(this.getPatFilePath(gitServerId));
519
+ }
520
+ deletePat(gitServerId) {
521
+ const filePath = this.getPatFilePath(gitServerId);
522
+ if (node_fs.existsSync(filePath)) {
523
+ node_fs.unlinkSync(filePath);
524
+ }
525
+ const metaPath = this.getPatMetaFilePath(gitServerId);
526
+ if (node_fs.existsSync(metaPath)) {
527
+ node_fs.unlinkSync(metaPath);
528
+ }
529
+ }
530
+ }
531
+
237
532
  exports.AgentConfigValidationError = errors.AgentConfigValidationError;
238
533
  exports.AgentError = errors.AgentError;
239
534
  exports.AgentLoadError = errors.AgentLoadError;
@@ -245,10 +540,16 @@ exports.FrameworkNotSupportedError = errors.FrameworkNotSupportedError;
245
540
  exports.MissingAgentFileError = errors.MissingAgentFileError;
246
541
  exports.getAgentContext = errors.getAgentContext;
247
542
  exports.setAgentContext = errors.setAgentContext;
543
+ exports.buildGitLabWebhookEndpointPath = gitlabWebhook.buildGitLabWebhookEndpointPath;
544
+ exports.buildGitLabWebhookUrl = gitlabWebhook.buildGitLabWebhookUrl;
545
+ exports.DEFAULT_GITLAB_PAT_REQUIRED_SCOPES = DEFAULT_GITLAB_PAT_REQUIRED_SCOPES;
546
+ exports.GitServerLocalStore = GitServerLocalStore;
248
547
  exports.assertAgentExists = assertAgentExists;
249
548
  exports.assertFileExists = assertFileExists;
549
+ exports.deriveLocalGitServerEncryptionKey = deriveLocalGitServerEncryptionKey;
250
550
  exports.discoverPlugins = discoverPlugins;
251
551
  exports.loadAgentConfig = loadAgentConfig;
252
552
  exports.replacePromptPlaceholders = replacePromptPlaceholders;
253
553
  exports.validateAgentDirectory = validateAgentDirectory;
254
554
  exports.validateFrameworkDirectory = validateFrameworkDirectory;
555
+ exports.validateGitLabPatToken = validateGitLabPatToken;
package/dist/node.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- import { dC as LoadAgentOptions, dA as AgentConfig, dB as ValidationResult, dx as FrameworkType } from './errors-CjePCqwd.cjs';
2
- export { dK as AgentConfigValidationError, dt as AgentContext, dI as AgentError, dM as AgentLoadError, dy as AgentMetadata, dG as AgentMetadataSchema, dJ as AgentNotFoundError, du as AgentrixContext, dz as ClaudeAgentConfig, dH as ClaudeConfigSchema, dF as FRAMEWORK_TYPES, dL as FrameworkNotSupportedError, dE as HookFactory, dN as MissingAgentFileError, dD as RepositoryInitHookInput, dw as getAgentContext, dv as setAgentContext } from './errors-CjePCqwd.cjs';
1
+ import { dE as LoadAgentOptions, dC as AgentConfig, dD as ValidationResult, dz as FrameworkType } from './errors-B5CcMP8s.cjs';
2
+ export { dM as AgentConfigValidationError, dv as AgentContext, dK as AgentError, dO as AgentLoadError, dA as AgentMetadata, dI as AgentMetadataSchema, dL as AgentNotFoundError, dw as AgentrixContext, dB as ClaudeAgentConfig, dJ as ClaudeConfigSchema, dH as FRAMEWORK_TYPES, dN as FrameworkNotSupportedError, dG as HookFactory, dP as MissingAgentFileError, dF as RepositoryInitHookInput, dy as getAgentContext, dx as setAgentContext } from './errors-B5CcMP8s.cjs';
3
+ export { buildGitLabWebhookEndpointPath, buildGitLabWebhookUrl } from './gitlabWebhook.cjs';
3
4
  import '@anthropic-ai/claude-agent-sdk';
4
5
  import 'zod';
5
6
 
@@ -49,4 +50,77 @@ declare function assertAgentExists(agentDir: string, agentId: string): void;
49
50
  */
50
51
  declare function discoverPlugins(pluginsDir: string): string[];
51
52
 
52
- export { AgentConfig, FrameworkType, LoadAgentOptions, ValidationResult, assertAgentExists, assertFileExists, discoverPlugins, loadAgentConfig, replacePromptPlaceholders, validateAgentDirectory, validateFrameworkDirectory };
53
+ declare const DEFAULT_GITLAB_PAT_REQUIRED_SCOPES: readonly ["api", "read_repository", "write_repository"];
54
+ interface GitServerLocalConfig {
55
+ baseUrl?: string;
56
+ apiUrl?: string;
57
+ }
58
+ interface GitLabWebhookBridgeSecrets {
59
+ webhookSecret?: string;
60
+ projectTriggerTokens?: Record<string, string>;
61
+ }
62
+ interface PatMeta {
63
+ username: string;
64
+ email: string;
65
+ lastValidatedAt?: string;
66
+ expiresAt?: string | null;
67
+ }
68
+ interface PatValidationResult {
69
+ valid: boolean;
70
+ status: 'valid' | 'invalid' | 'expired' | 'insufficient_scope' | 'network_error';
71
+ username?: string;
72
+ scopes?: string[];
73
+ missingScopes?: string[];
74
+ expiresAt?: string | null;
75
+ error?: string;
76
+ }
77
+ interface GitServerLocalStoreOptions {
78
+ credentialsDir: string;
79
+ }
80
+ interface GitLabPatValidationOptions {
81
+ requiredScopes?: readonly string[];
82
+ timeoutMs?: number;
83
+ log?: {
84
+ warn?: (message: string, error?: unknown) => void;
85
+ error?: (message: string, error?: unknown) => void;
86
+ };
87
+ }
88
+ declare function deriveLocalGitServerEncryptionKey(masterSecret: string): Uint8Array;
89
+ declare function validateGitLabPatToken(apiUrl: string, pat: string, options?: GitLabPatValidationOptions): Promise<{
90
+ result: PatValidationResult;
91
+ user?: {
92
+ username: string;
93
+ email?: string;
94
+ };
95
+ }>;
96
+ declare class GitServerLocalStore {
97
+ private readonly credentialsDir;
98
+ constructor(options: GitServerLocalStoreOptions);
99
+ private ensureCredentialsDir;
100
+ private getPatFilePath;
101
+ private getPatMetaFilePath;
102
+ private getGitServerConfigFilePath;
103
+ private getGitLabWebhookBridgeFilePath;
104
+ private encryptJsonFile;
105
+ private decryptJsonFile;
106
+ saveGitServerConfig(gitServerId: string, input: GitServerLocalConfig): void;
107
+ loadGitServerConfig(gitServerId: string): GitServerLocalConfig | null;
108
+ deleteGitServerConfig(gitServerId: string): void;
109
+ listGitServerIds(): string[];
110
+ listPats(): Array<{
111
+ gitServerId: string;
112
+ exists: boolean;
113
+ }>;
114
+ loadGitLabWebhookBridgeSecrets(gitServerId: string, encryptionKey: Uint8Array): GitLabWebhookBridgeSecrets | null;
115
+ saveGitLabWebhookBridgeSecrets(gitServerId: string, secrets: GitLabWebhookBridgeSecrets, encryptionKey: Uint8Array): void;
116
+ ensureGitLabWebhookSecret(gitServerId: string, encryptionKey: Uint8Array): string;
117
+ loadPatMeta(gitServerId: string): PatMeta | null;
118
+ savePatMeta(gitServerId: string, meta: PatMeta): void;
119
+ savePat(gitServerId: string, pat: string, encryptionKey: Uint8Array): void;
120
+ loadPat(gitServerId: string, encryptionKey: Uint8Array): string | null;
121
+ hasPat(gitServerId: string): boolean;
122
+ deletePat(gitServerId: string): void;
123
+ }
124
+
125
+ export { AgentConfig, DEFAULT_GITLAB_PAT_REQUIRED_SCOPES, FrameworkType, GitServerLocalStore, LoadAgentOptions, ValidationResult, assertAgentExists, assertFileExists, deriveLocalGitServerEncryptionKey, discoverPlugins, loadAgentConfig, replacePromptPlaceholders, validateAgentDirectory, validateFrameworkDirectory, validateGitLabPatToken };
126
+ export type { GitLabPatValidationOptions, GitLabWebhookBridgeSecrets, GitServerLocalConfig, GitServerLocalStoreOptions, PatMeta, PatValidationResult };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentrix/shared",
3
- "version": "2.10.0",
3
+ "version": "2.13.0",
4
4
  "description": "Shared types and schemas for Agentrix projects",
5
5
  "main": "./dist/index.cjs",
6
6
  "types": "./dist/index.d.cts",
@@ -12,6 +12,11 @@
12
12
  "./node": {
13
13
  "types": "./dist/node.d.cts",
14
14
  "default": "./dist/node.cjs"
15
+ },
16
+ "./gitlab-webhook": {
17
+ "types": "./dist/gitlabWebhook.d.cts",
18
+ "import": "./dist/gitlabWebhook.mjs",
19
+ "default": "./dist/gitlabWebhook.cjs"
15
20
  }
16
21
  },
17
22
  "scripts": {