@gdrl/kronos-lib 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/dist/index.js ADDED
@@ -0,0 +1,748 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ KronosBadRequestError: () => KronosBadRequestError,
24
+ KronosClient: () => KronosClient,
25
+ KronosError: () => KronosError,
26
+ KronosForbiddenError: () => KronosForbiddenError,
27
+ KronosNotFoundError: () => KronosNotFoundError,
28
+ KronosServerError: () => KronosServerError,
29
+ KronosUnauthorizedError: () => KronosUnauthorizedError,
30
+ generateIdempotencyKey: () => generateIdempotencyKey,
31
+ verifyWebhookSecret: () => verifyWebhookSecret
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/errors.ts
36
+ var KronosError = class _KronosError extends Error {
37
+ constructor(message, status, code, body) {
38
+ super(message);
39
+ this.status = status;
40
+ this.code = code;
41
+ this.body = body;
42
+ this.name = "KronosError";
43
+ Object.setPrototypeOf(this, _KronosError.prototype);
44
+ }
45
+ };
46
+ var KronosBadRequestError = class _KronosBadRequestError extends KronosError {
47
+ constructor(message, body) {
48
+ super(message, 400, "bad_request", body);
49
+ this.name = "KronosBadRequestError";
50
+ Object.setPrototypeOf(this, _KronosBadRequestError.prototype);
51
+ }
52
+ };
53
+ var KronosUnauthorizedError = class _KronosUnauthorizedError extends KronosError {
54
+ constructor(message, body) {
55
+ super(message, 401, "unauthorized", body);
56
+ this.name = "KronosUnauthorizedError";
57
+ Object.setPrototypeOf(this, _KronosUnauthorizedError.prototype);
58
+ }
59
+ };
60
+ var KronosForbiddenError = class _KronosForbiddenError extends KronosError {
61
+ constructor(message, body) {
62
+ super(message, 403, "forbidden", body);
63
+ this.name = "KronosForbiddenError";
64
+ Object.setPrototypeOf(this, _KronosForbiddenError.prototype);
65
+ }
66
+ };
67
+ var KronosNotFoundError = class _KronosNotFoundError extends KronosError {
68
+ constructor(message, body) {
69
+ super(message, 404, "not_found", body);
70
+ this.name = "KronosNotFoundError";
71
+ Object.setPrototypeOf(this, _KronosNotFoundError.prototype);
72
+ }
73
+ };
74
+ var KronosServerError = class _KronosServerError extends KronosError {
75
+ constructor(message, status, body) {
76
+ super(message, status, "server_error", body);
77
+ this.name = "KronosServerError";
78
+ Object.setPrototypeOf(this, _KronosServerError.prototype);
79
+ }
80
+ };
81
+
82
+ // src/utils.ts
83
+ function generateIdempotencyKey() {
84
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
85
+ return crypto.randomUUID();
86
+ }
87
+ return `idem_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
88
+ }
89
+
90
+ // src/client.ts
91
+ var WORKER_URL = "https://api.kronos.ai";
92
+ var MASTRA_URL = "https://mastra.kronos.ai";
93
+ var INTERNAL_CONTEXT_GRAPH_SKILL_NAME = "__internal_context_graph__";
94
+ var KronosClient = class {
95
+ constructor(config) {
96
+ if (!config.apiKey) {
97
+ throw new Error("KronosClient: apiKey is required in constructor");
98
+ }
99
+ if (!config.appId) {
100
+ throw new Error("KronosClient: appId is required in constructor");
101
+ }
102
+ this.workerUrl = WORKER_URL.replace(/\/$/, "");
103
+ this.mastraUrl = MASTRA_URL.replace(/\/$/, "");
104
+ this.apiKey = config.apiKey;
105
+ this.appId = config.appId;
106
+ console.log("[KronosClient] \u2705 KronosClient initialized:", {
107
+ appId: this.appId,
108
+ workerUrl: this.workerUrl,
109
+ mastraUrl: this.mastraUrl,
110
+ hasApiKey: !!this.apiKey
111
+ });
112
+ this.healthCheck().catch(() => {
113
+ });
114
+ }
115
+ /**
116
+ * Perform a health check to verify the client can connect to the Kronos worker
117
+ * This is called automatically during initialization
118
+ */
119
+ async healthCheck() {
120
+ try {
121
+ const response = await fetch(`${this.workerUrl}/health`, {
122
+ method: "GET",
123
+ headers: {
124
+ "Content-Type": "application/json"
125
+ }
126
+ });
127
+ if (response.ok) {
128
+ const data = await response.json();
129
+ if (data.status === "ok") {
130
+ console.log("[KronosClient] \u2705 Health check passed - Client is connected and working");
131
+ } else {
132
+ console.warn("[KronosClient] \u26A0\uFE0F Health check returned unexpected status:", data);
133
+ }
134
+ } else {
135
+ console.warn(`[KronosClient] \u26A0\uFE0F Health check failed with status ${response.status}. Client may not be working correctly.`);
136
+ }
137
+ } catch (error) {
138
+ console.warn("[KronosClient] \u26A0\uFE0F Health check failed - Unable to connect to Kronos worker:", error.message);
139
+ console.warn("[KronosClient] Please verify that:");
140
+ console.warn("[KronosClient] 1. The worker URL is correct:", this.workerUrl);
141
+ console.warn("[KronosClient] 2. The worker is running and accessible");
142
+ console.warn("[KronosClient] 3. Network connectivity is available");
143
+ }
144
+ }
145
+ // ---------------------------------------------------------------------------
146
+ // Worker request helper (uses Bearer auth)
147
+ // ---------------------------------------------------------------------------
148
+ async workerFetch(path, options = {}) {
149
+ const { json, idempotencyKey, method } = options;
150
+ const headers = {
151
+ "Content-Type": "application/json",
152
+ Authorization: `Bearer ${this.apiKey}`
153
+ };
154
+ if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey;
155
+ const res = await fetch(`${this.workerUrl}${path}`, {
156
+ method: method ?? (json ? "POST" : "GET"),
157
+ headers,
158
+ body: json ? JSON.stringify(json) : void 0
159
+ });
160
+ return this.handleResponse(res);
161
+ }
162
+ // ---------------------------------------------------------------------------
163
+ // Mastra request helper (no Bearer auth per current API)
164
+ // ---------------------------------------------------------------------------
165
+ async mastraFetch(path, options = {}) {
166
+ const { json, method } = options;
167
+ const headers = {
168
+ "Content-Type": "application/json"
169
+ };
170
+ const res = await fetch(`${this.mastraUrl}${path}`, {
171
+ method: method ?? (json ? "POST" : "GET"),
172
+ headers,
173
+ body: json ? JSON.stringify(json) : void 0
174
+ });
175
+ return this.handleResponse(res);
176
+ }
177
+ async handleResponse(res) {
178
+ const text = await res.text();
179
+ let body = null;
180
+ try {
181
+ body = text ? JSON.parse(text) : null;
182
+ } catch {
183
+ body = text;
184
+ }
185
+ if (!res.ok) {
186
+ const errBody = body;
187
+ const message = errBody && typeof errBody.error === "string" ? errBody.error : `Request failed with status ${res.status}`;
188
+ switch (res.status) {
189
+ case 400:
190
+ throw new KronosBadRequestError(message, body);
191
+ case 401:
192
+ throw new KronosUnauthorizedError(message, body);
193
+ case 403:
194
+ throw new KronosForbiddenError(message, body);
195
+ case 404:
196
+ throw new KronosNotFoundError(message, body);
197
+ default:
198
+ if (res.status >= 500) {
199
+ throw new KronosServerError(message, res.status, body);
200
+ }
201
+ throw new KronosError(message, res.status, void 0, body);
202
+ }
203
+ }
204
+ return body ?? null;
205
+ }
206
+ // ---------------------------------------------------------------------------
207
+ // Ingest API
208
+ // ---------------------------------------------------------------------------
209
+ /**
210
+ * Submit a document for ingestion.
211
+ * @param params - source_type, content, optional metadata, addToMemory, and idempotencyKey
212
+ * @returns ingest_id
213
+ */
214
+ async ingest(params) {
215
+ const idempotencyKey = params.idempotencyKey ?? generateIdempotencyKey();
216
+ const body = {
217
+ app_id: this.appId,
218
+ tenant_user_id: params.tenant_user_id,
219
+ source_type: params.source_type,
220
+ priority: params.priority,
221
+ addToMemory: params.addToMemory,
222
+ content: params.content,
223
+ metadata: params.metadata
224
+ };
225
+ return this.workerFetch("/v1/ingest", {
226
+ method: "POST",
227
+ json: body,
228
+ idempotencyKey
229
+ });
230
+ }
231
+ /**
232
+ * Get the status of an ingest.
233
+ */
234
+ async getIngestStatus(ingestId) {
235
+ return this.workerFetch(`/v1/ingest/${ingestId}`, {
236
+ method: "GET"
237
+ });
238
+ }
239
+ // ---------------------------------------------------------------------------
240
+ // MCP Server API
241
+ // ---------------------------------------------------------------------------
242
+ /**
243
+ * Create an MCP server scoped to the given scopes.
244
+ * Use connectMCPServer() to get a short-lived access token for MCP auth.
245
+ */
246
+ async createMCPServer(params) {
247
+ const body = {
248
+ app_id: this.appId,
249
+ tenant_user_id: params.tenant_user_id,
250
+ scope_ids: params.scope_ids ?? [],
251
+ name: params.name,
252
+ description: params.description,
253
+ options: {
254
+ exposeSkillsTool: params.options?.exposeSkillsTool ?? true,
255
+ exposeContextGraphTool: params.options?.exposeContextGraphTool ?? false
256
+ }
257
+ };
258
+ const res = await this.workerFetch("/v1/mcp-servers", { method: "POST", json: body });
259
+ return {
260
+ mcp_server_id: res.mcp_server_id,
261
+ url: res.url.startsWith("http") ? res.url : `${this.workerUrl}${res.url}`,
262
+ status: res.status,
263
+ name: res.name,
264
+ description: res.description,
265
+ scope_ids: res.scope_ids,
266
+ tool_capabilities: res.tool_capabilities ?? {
267
+ skills: true,
268
+ context_graph: false
269
+ }
270
+ };
271
+ }
272
+ /**
273
+ * List MCP servers for a user. Returns metadata only (no token).
274
+ */
275
+ async listMCPServers(params) {
276
+ const path = `/v1/mcp-servers?${new URLSearchParams({
277
+ app_id: this.appId,
278
+ tenant_user_id: params.tenant_user_id
279
+ }).toString()}`;
280
+ return this.workerFetch(path, { method: "GET" });
281
+ }
282
+ /**
283
+ * Get MCP server details for a user by server ID. Returns metadata only (no token).
284
+ */
285
+ async getMCPServer(params) {
286
+ const path = `/v1/mcp-servers/${params.serverId}?${new URLSearchParams({
287
+ app_id: this.appId,
288
+ tenant_user_id: params.tenant_user_id
289
+ }).toString()}`;
290
+ return this.workerFetch(path, { method: "GET" });
291
+ }
292
+ async updateMCPServer(params) {
293
+ const body = {
294
+ app_id: this.appId,
295
+ tenant_user_id: params.tenant_user_id,
296
+ tool_capabilities: params.tool_capabilities
297
+ };
298
+ return this.workerFetch(`/v1/mcp-servers/${params.serverId}`, {
299
+ method: "PUT",
300
+ json: body
301
+ });
302
+ }
303
+ /**
304
+ * Connect to an MCP server by ID and receive a short-lived access token.
305
+ * Use the returned access_token as Bearer token when calling the MCP endpoint.
306
+ */
307
+ async connectMCPServer(params) {
308
+ const body = {
309
+ app_id: this.appId,
310
+ tenant_user_id: params.tenant_user_id
311
+ };
312
+ const res = await this.workerFetch(
313
+ `/v1/mcp-servers/${params.serverId}/connect`,
314
+ { method: "POST", json: body }
315
+ );
316
+ return {
317
+ ...res,
318
+ url: res.url.startsWith("http") ? res.url : `${this.workerUrl}${res.url}`
319
+ };
320
+ }
321
+ /**
322
+ * Rotate the token for an MCP server. The new token is returned only on rotate.
323
+ */
324
+ async rotateMCPServerToken(params) {
325
+ return this.workerFetch(
326
+ `/v1/mcp-servers/${params.serverId}/rotate-token`,
327
+ {
328
+ method: "POST",
329
+ json: { app_id: this.appId, tenant_user_id: params.tenant_user_id }
330
+ }
331
+ );
332
+ }
333
+ /**
334
+ * Get memory stats for a user (total claim count across entire memory graph).
335
+ * Worker caches the response for 5 minutes per user.
336
+ */
337
+ async getMemoryStats(params) {
338
+ const path = `/v1/memory-stats?${new URLSearchParams({
339
+ app_id: this.appId,
340
+ tenant_user_id: params.tenant_user_id
341
+ }).toString()}`;
342
+ return this.workerFetch(path, { method: "GET" });
343
+ }
344
+ /**
345
+ * Read the current scratchsheet document for a user.
346
+ */
347
+ async getScratchsheet(params) {
348
+ const path = `/v1/scratchsheet?${new URLSearchParams({
349
+ app_id: this.appId,
350
+ tenant_user_id: params.tenant_user_id
351
+ }).toString()}`;
352
+ return this.workerFetch(path, { method: "GET" });
353
+ }
354
+ /**
355
+ * List scopes for a user. Returns full scope details. Cached 5 min per user.
356
+ */
357
+ async listScopes(params) {
358
+ const path = `/v1/scopes?${new URLSearchParams({
359
+ app_id: this.appId,
360
+ tenant_user_id: params.tenant_user_id
361
+ }).toString()}`;
362
+ return this.workerFetch(path, { method: "GET" });
363
+ }
364
+ /**
365
+ * Get a single scope by ID. Returns full scope details.
366
+ */
367
+ async getScope(params) {
368
+ const path = `/v1/scopes/${encodeURIComponent(params.scope_id)}?${new URLSearchParams({
369
+ app_id: this.appId,
370
+ tenant_user_id: params.tenant_user_id
371
+ }).toString()}`;
372
+ return this.workerFetch(path, { method: "GET" });
373
+ }
374
+ // ---------------------------------------------------------------------------
375
+ // Scope API (Mastra)
376
+ // ---------------------------------------------------------------------------
377
+ /**
378
+ * Create a scope. A backfill job runs asynchronously to populate it.
379
+ * If spec.allowed_source_types is omitted, it defaults to ['all'].
380
+ * Uses the Worker so the scopes list cache is invalidated after create.
381
+ */
382
+ async createScope(params) {
383
+ const spec = {
384
+ ...params.spec,
385
+ allowed_source_types: params.spec.allowed_source_types ?? ["all"]
386
+ };
387
+ const body = {
388
+ app_id: this.appId,
389
+ tenant_user_id: params.tenant_user_id,
390
+ spec,
391
+ created_by: params.created_by,
392
+ description: params.description
393
+ };
394
+ return this.workerFetch("/v1/scopes", {
395
+ method: "POST",
396
+ json: body
397
+ });
398
+ }
399
+ /**
400
+ * Update a scope. Only provided fields are updated.
401
+ * If include_query, exclude_query, match_mode, time_range, or allowed_source_types change,
402
+ * a backfill job runs to recompute membership (response includes backfill_job_id).
403
+ * Otherwise only DB and Neo4j metadata are updated.
404
+ */
405
+ async updateScope(params) {
406
+ const { scope_id, tenant_user_id, ...patch } = params;
407
+ const body = {};
408
+ if (patch.name !== void 0) body.name = patch.name;
409
+ if (patch.description !== void 0) body.description = patch.description;
410
+ if (patch.include_query !== void 0) body.include_query = patch.include_query;
411
+ if (patch.exclude_query !== void 0) body.exclude_query = patch.exclude_query;
412
+ if (patch.match_mode !== void 0) body.match_mode = patch.match_mode;
413
+ if (patch.time_range !== void 0) body.time_range = patch.time_range;
414
+ if (patch.allowed_source_types !== void 0) body.allowed_source_types = patch.allowed_source_types;
415
+ const path = `/v1/scopes/${encodeURIComponent(scope_id)}?${new URLSearchParams({
416
+ app_id: this.appId,
417
+ tenant_user_id
418
+ }).toString()}`;
419
+ return this.workerFetch(path, {
420
+ method: "PATCH",
421
+ json: body
422
+ });
423
+ }
424
+ /**
425
+ * Soft-delete a scope. Invalidates the scopes list cache for that user.
426
+ */
427
+ async deleteScope(params) {
428
+ const path = `/v1/scopes/${encodeURIComponent(params.scope_id)}?${new URLSearchParams({
429
+ app_id: this.appId,
430
+ tenant_user_id: params.tenant_user_id
431
+ }).toString()}`;
432
+ return this.workerFetch(path, { method: "DELETE" });
433
+ }
434
+ // ---------------------------------------------------------------------------
435
+ // Webhook Verification API (Worker)
436
+ // ---------------------------------------------------------------------------
437
+ /**
438
+ * Verify a webhook secret for a trigger.
439
+ * This method calls the Kronos worker API to verify the webhook secret.
440
+ *
441
+ * @param params - app_id, tenant_user_id, trigger_id and received_secret to verify
442
+ * @returns true if secret is valid, false otherwise
443
+ */
444
+ async verifyWebhookSecret(params) {
445
+ const body = {
446
+ trigger_id: params.trigger_id,
447
+ app_id: this.appId,
448
+ tenant_user_id: params.tenant_user_id,
449
+ received_secret: params.received_secret
450
+ };
451
+ try {
452
+ const response = await this.workerFetch("/v1/triggers/verify-webhook", {
453
+ method: "POST",
454
+ json: body
455
+ });
456
+ return response.valid;
457
+ } catch (error) {
458
+ console.error("[KronosClient] Webhook verification error:", error);
459
+ return false;
460
+ }
461
+ }
462
+ // ---------------------------------------------------------------------------
463
+ // Trigger Management API (Worker)
464
+ // ---------------------------------------------------------------------------
465
+ /**
466
+ * List triggers for a tenant user.
467
+ */
468
+ async listTriggers(params) {
469
+ const query = new URLSearchParams({
470
+ app_id: this.appId,
471
+ tenant_user_id: params.tenant_user_id
472
+ });
473
+ return this.workerFetch(`/v1/triggers?${query.toString()}`, {
474
+ method: "GET"
475
+ });
476
+ }
477
+ /**
478
+ * Create a trigger.
479
+ * Supports NL creation (trigger_description) or manual creation (trigger_type/trigger_spec).
480
+ *
481
+ * @returns CreateTriggerResponse (accepted for NL, trigger_id for manual)
482
+ */
483
+ async createTrigger(params) {
484
+ const body = {
485
+ app_id: this.appId,
486
+ tenant_user_id: params.tenant_user_id,
487
+ webhook_url: params.webhook_url,
488
+ webhook_secret: params.webhook_secret,
489
+ title: params.title,
490
+ action_description: params.action_description,
491
+ skill_id: params.skill_id,
492
+ skill_version_id: params.skill_version_id,
493
+ trigger_description: params.trigger_description,
494
+ trigger_type: params.trigger_type,
495
+ trigger_spec: params.trigger_spec,
496
+ next_run_at: params.next_run_at,
497
+ on_triggered: params.on_triggered,
498
+ metadata: params.metadata
499
+ };
500
+ return this.workerFetch("/v1/triggers", {
501
+ method: "POST",
502
+ json: body,
503
+ idempotencyKey: params.idempotencyKey
504
+ });
505
+ }
506
+ /**
507
+ * Get details for a specific trigger.
508
+ * Uses listTriggers and filters by trigger_id.
509
+ */
510
+ async getTriggerDetails(params) {
511
+ const response = await this.listTriggers({ tenant_user_id: params.tenant_user_id });
512
+ const trigger = response.triggers.find((t) => t.trigger_id === params.trigger_id);
513
+ if (!trigger) {
514
+ throw new KronosNotFoundError(`Trigger not found: ${params.trigger_id}`);
515
+ }
516
+ return trigger;
517
+ }
518
+ /**
519
+ * Update a trigger.
520
+ * Updates the webhook_url, action_description, status, or metadata of an existing trigger.
521
+ *
522
+ * @param params - app_id, tenant_user_id, trigger_id and fields to update
523
+ * @returns UpdateTriggerResponse with success status
524
+ */
525
+ async updateTrigger(params) {
526
+ const body = {
527
+ app_id: this.appId,
528
+ tenant_user_id: params.tenant_user_id,
529
+ webhook_url: params.webhook_url,
530
+ title: params.title,
531
+ trigger_type: params.trigger_type,
532
+ trigger_spec: params.trigger_spec,
533
+ next_run_at: params.next_run_at,
534
+ action_description: params.action_description,
535
+ skill_id: params.skill_id,
536
+ skill_version_id: params.skill_version_id,
537
+ on_triggered: params.on_triggered,
538
+ status: params.status,
539
+ metadata: params.metadata
540
+ };
541
+ return this.workerFetch(`/v1/triggers/${params.trigger_id}`, {
542
+ method: "PUT",
543
+ json: body
544
+ });
545
+ }
546
+ /**
547
+ * Delete a trigger.
548
+ */
549
+ async deleteTrigger(params) {
550
+ const query = new URLSearchParams({
551
+ app_id: this.appId,
552
+ tenant_user_id: params.tenant_user_id
553
+ });
554
+ return this.workerFetch(
555
+ `/v1/triggers/${encodeURIComponent(params.trigger_id)}?${query.toString()}`,
556
+ { method: "DELETE" }
557
+ );
558
+ }
559
+ // ---------------------------------------------------------------------------
560
+ // Frontend Tokens (Server-side only)
561
+ // ---------------------------------------------------------------------------
562
+ /**
563
+ * Issue a short-lived, user-scoped frontend token.
564
+ * The browser can use this token directly against the Kronos worker for
565
+ * allowed operations (e.g. trigger CRUD) without needing the app API key.
566
+ *
567
+ * This method must be called from a trusted server environment.
568
+ */
569
+ async createFrontendToken(params) {
570
+ const res = await this.workerFetch(
571
+ "/v1/frontend-tokens",
572
+ {
573
+ method: "POST",
574
+ json: {
575
+ app_id: this.appId,
576
+ tenant_user_id: params.tenantUserId,
577
+ ttl_seconds: params.ttlSeconds,
578
+ ops: params.ops
579
+ }
580
+ }
581
+ );
582
+ return { token: res.token, expiresAt: res.expires_at };
583
+ }
584
+ // ---------------------------------------------------------------------------
585
+ // Skills API (Worker)
586
+ // ---------------------------------------------------------------------------
587
+ /**
588
+ * Canonical skills API with operation-based contract.
589
+ */
590
+ async manageSkill(params) {
591
+ const idempotencyKey = params.operation === "create" || params.operation === "update" ? params.idempotencyKey : void 0;
592
+ return this.workerFetch("/v1/skills/manage", {
593
+ method: "POST",
594
+ json: {
595
+ ...params,
596
+ app_id: this.appId
597
+ },
598
+ idempotencyKey
599
+ });
600
+ }
601
+ async resolveContextGraphSkillId(tenantUserId) {
602
+ const listed = await this.manageSkill({
603
+ operation: "list",
604
+ tenant_user_id: tenantUserId,
605
+ include_internal: true
606
+ });
607
+ const match = (listed.skills ?? []).find(
608
+ (skill) => skill.name === INTERNAL_CONTEXT_GRAPH_SKILL_NAME
609
+ );
610
+ return match?.skill_id ?? null;
611
+ }
612
+ parseContextGraphProcedures(files) {
613
+ return files.filter((file) => file.path.startsWith("procedures/") && file.path.endsWith(".md")).map((file) => {
614
+ const content = file.content ?? "";
615
+ const normalized = content.replace(/\r\n/g, "\n");
616
+ if (normalized.startsWith("---\n")) {
617
+ const endIndex = normalized.indexOf("\n---\n", 4);
618
+ if (endIndex !== -1) {
619
+ const frontmatter = normalized.slice(4, endIndex);
620
+ const metadata = {};
621
+ for (const line of frontmatter.split("\n")) {
622
+ const separatorIndex = line.indexOf(":");
623
+ if (separatorIndex === -1) continue;
624
+ const key = line.slice(0, separatorIndex).trim();
625
+ const value = line.slice(separatorIndex + 1).trim();
626
+ if (key && value) {
627
+ metadata[key] = value;
628
+ }
629
+ }
630
+ return {
631
+ title: metadata.title || (file.path.split("/").pop() ?? file.path),
632
+ description: metadata.description || "",
633
+ path: file.path
634
+ };
635
+ }
636
+ }
637
+ const lines = content.split(/\r?\n/);
638
+ const titleLine = lines.find((line) => line.startsWith("# "));
639
+ const title = titleLine ? titleLine.replace(/^#\s+/, "").trim() : file.path.split("/").pop() ?? file.path;
640
+ const description = lines.slice(1).map((line) => line.trim()).find((line) => line.length > 0 && !line.startsWith("#")) ?? "";
641
+ return {
642
+ title,
643
+ description,
644
+ path: file.path
645
+ };
646
+ }).sort((a, b) => a.path.localeCompare(b.path));
647
+ }
648
+ async getContextGraph(params) {
649
+ const skillId = await this.resolveContextGraphSkillId(params.tenant_user_id);
650
+ if (!skillId) {
651
+ return {
652
+ skill_id: null,
653
+ procedures: []
654
+ };
655
+ }
656
+ const treeResult = await this.manageSkill({
657
+ operation: "read",
658
+ tenant_user_id: params.tenant_user_id,
659
+ skill_id: skillId,
660
+ mode: "tree"
661
+ });
662
+ const procedurePaths = (treeResult.tree ?? []).filter((node) => node.kind === "file" && node.path.startsWith("procedures/") && node.path.endsWith(".md")).map((node) => node.path);
663
+ if (procedurePaths.length === 0) {
664
+ return {
665
+ skill_id: skillId,
666
+ procedures: []
667
+ };
668
+ }
669
+ const filesResult = await this.manageSkill({
670
+ operation: "read",
671
+ tenant_user_id: params.tenant_user_id,
672
+ skill_id: skillId,
673
+ mode: "files",
674
+ files: procedurePaths,
675
+ fileContent: "full"
676
+ });
677
+ return {
678
+ skill_id: skillId,
679
+ procedures: this.parseContextGraphProcedures(filesResult.files ?? [])
680
+ };
681
+ }
682
+ async readContextGraph(params) {
683
+ const skillId = await this.resolveContextGraphSkillId(params.tenant_user_id);
684
+ const requestedPath = params.path?.trim() || "SKILL.md";
685
+ if (!skillId) {
686
+ return {
687
+ skill_id: null,
688
+ path: requestedPath,
689
+ content: null
690
+ };
691
+ }
692
+ if (requestedPath === "SKILL.md") {
693
+ const result2 = await this.manageSkill({
694
+ operation: "read",
695
+ tenant_user_id: params.tenant_user_id,
696
+ skill_id: skillId,
697
+ mode: "instructions"
698
+ });
699
+ return {
700
+ skill_id: skillId,
701
+ path: "SKILL.md",
702
+ content: result2.instructions ?? null
703
+ };
704
+ }
705
+ const result = await this.manageSkill({
706
+ operation: "read",
707
+ tenant_user_id: params.tenant_user_id,
708
+ skill_id: skillId,
709
+ mode: "files",
710
+ files: [requestedPath],
711
+ fileContent: "full"
712
+ });
713
+ const file = (result.files ?? []).find((entry) => entry.path === requestedPath);
714
+ return {
715
+ skill_id: skillId,
716
+ path: requestedPath,
717
+ content: file?.content ?? null
718
+ };
719
+ }
720
+ };
721
+
722
+ // src/webhook-verification.ts
723
+ function verifyWebhookSecret(receivedSecret, storedSecret) {
724
+ if (!receivedSecret || !storedSecret) {
725
+ return false;
726
+ }
727
+ if (receivedSecret.length !== storedSecret.length) {
728
+ return false;
729
+ }
730
+ let result = 0;
731
+ for (let i = 0; i < receivedSecret.length; i++) {
732
+ result |= receivedSecret.charCodeAt(i) ^ storedSecret.charCodeAt(i);
733
+ }
734
+ return result === 0;
735
+ }
736
+ // Annotate the CommonJS export names for ESM import in node:
737
+ 0 && (module.exports = {
738
+ KronosBadRequestError,
739
+ KronosClient,
740
+ KronosError,
741
+ KronosForbiddenError,
742
+ KronosNotFoundError,
743
+ KronosServerError,
744
+ KronosUnauthorizedError,
745
+ generateIdempotencyKey,
746
+ verifyWebhookSecret
747
+ });
748
+ //# sourceMappingURL=index.js.map