@cogenta/channels 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.
Files changed (177) hide show
  1. package/dist/adapter.d.ts +111 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +2 -0
  4. package/dist/adapter.js.map +1 -0
  5. package/dist/approvals/commands.d.ts +28 -0
  6. package/dist/approvals/commands.d.ts.map +1 -0
  7. package/dist/approvals/commands.js +66 -0
  8. package/dist/approvals/commands.js.map +1 -0
  9. package/dist/approvals/dispatch.d.ts +22 -0
  10. package/dist/approvals/dispatch.d.ts.map +1 -0
  11. package/dist/approvals/dispatch.js +22 -0
  12. package/dist/approvals/dispatch.js.map +1 -0
  13. package/dist/approvals/message.d.ts +18 -0
  14. package/dist/approvals/message.d.ts.map +1 -0
  15. package/dist/approvals/message.js +40 -0
  16. package/dist/approvals/message.js.map +1 -0
  17. package/dist/approvals/signed-link.d.ts +19 -0
  18. package/dist/approvals/signed-link.d.ts.map +1 -0
  19. package/dist/approvals/signed-link.js +42 -0
  20. package/dist/approvals/signed-link.js.map +1 -0
  21. package/dist/approvals/store.d.ts +41 -0
  22. package/dist/approvals/store.d.ts.map +1 -0
  23. package/dist/approvals/store.js +41 -0
  24. package/dist/approvals/store.js.map +1 -0
  25. package/dist/approvals/token.d.ts +7 -0
  26. package/dist/approvals/token.d.ts.map +1 -0
  27. package/dist/approvals/token.js +40 -0
  28. package/dist/approvals/token.js.map +1 -0
  29. package/dist/formats/alert.d.ts +18 -0
  30. package/dist/formats/alert.d.ts.map +1 -0
  31. package/dist/formats/alert.js +49 -0
  32. package/dist/formats/alert.js.map +1 -0
  33. package/dist/formats/budget.d.ts +14 -0
  34. package/dist/formats/budget.d.ts.map +1 -0
  35. package/dist/formats/budget.js +13 -0
  36. package/dist/formats/budget.js.map +1 -0
  37. package/dist/formats/notification.d.ts +8 -0
  38. package/dist/formats/notification.d.ts.map +1 -0
  39. package/dist/formats/notification.js +26 -0
  40. package/dist/formats/notification.js.map +1 -0
  41. package/dist/formats/report.d.ts +18 -0
  42. package/dist/formats/report.d.ts.map +1 -0
  43. package/dist/formats/report.js +44 -0
  44. package/dist/formats/report.js.map +1 -0
  45. package/dist/inbound/authorize.d.ts +57 -0
  46. package/dist/inbound/authorize.d.ts.map +1 -0
  47. package/dist/inbound/authorize.js +32 -0
  48. package/dist/inbound/authorize.js.map +1 -0
  49. package/dist/inbound/router.d.ts +67 -0
  50. package/dist/inbound/router.d.ts.map +1 -0
  51. package/dist/inbound/router.js +69 -0
  52. package/dist/inbound/router.js.map +1 -0
  53. package/dist/index.d.ts +77 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +42 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/linking/codes.d.ts +6 -0
  58. package/dist/linking/codes.d.ts.map +1 -0
  59. package/dist/linking/codes.js +37 -0
  60. package/dist/linking/codes.js.map +1 -0
  61. package/dist/linking/store.d.ts +39 -0
  62. package/dist/linking/store.d.ts.map +1 -0
  63. package/dist/linking/store.js +77 -0
  64. package/dist/linking/store.js.map +1 -0
  65. package/dist/linking/tables.d.ts +14 -0
  66. package/dist/linking/tables.d.ts.map +1 -0
  67. package/dist/linking/tables.js +52 -0
  68. package/dist/linking/tables.js.map +1 -0
  69. package/dist/preferences/dispatcher.d.ts +37 -0
  70. package/dist/preferences/dispatcher.d.ts.map +1 -0
  71. package/dist/preferences/dispatcher.js +103 -0
  72. package/dist/preferences/dispatcher.js.map +1 -0
  73. package/dist/preferences/quiet-hours.d.ts +12 -0
  74. package/dist/preferences/quiet-hours.d.ts.map +1 -0
  75. package/dist/preferences/quiet-hours.js +22 -0
  76. package/dist/preferences/quiet-hours.js.map +1 -0
  77. package/dist/preferences/store.d.ts +8 -0
  78. package/dist/preferences/store.d.ts.map +1 -0
  79. package/dist/preferences/store.js +60 -0
  80. package/dist/preferences/store.js.map +1 -0
  81. package/dist/preferences/tables.d.ts +12 -0
  82. package/dist/preferences/tables.d.ts.map +1 -0
  83. package/dist/preferences/tables.js +55 -0
  84. package/dist/preferences/tables.js.map +1 -0
  85. package/dist/preferences/types.d.ts +37 -0
  86. package/dist/preferences/types.d.ts.map +1 -0
  87. package/dist/preferences/types.js +31 -0
  88. package/dist/preferences/types.js.map +1 -0
  89. package/dist/providers/discord/adapter.d.ts +38 -0
  90. package/dist/providers/discord/adapter.d.ts.map +1 -0
  91. package/dist/providers/discord/adapter.js +97 -0
  92. package/dist/providers/discord/adapter.js.map +1 -0
  93. package/dist/providers/discord/client.d.ts +67 -0
  94. package/dist/providers/discord/client.d.ts.map +1 -0
  95. package/dist/providers/discord/client.js +65 -0
  96. package/dist/providers/discord/client.js.map +1 -0
  97. package/dist/providers/discord/gateway.d.ts +55 -0
  98. package/dist/providers/discord/gateway.d.ts.map +1 -0
  99. package/dist/providers/discord/gateway.js +114 -0
  100. package/dist/providers/discord/gateway.js.map +1 -0
  101. package/dist/providers/discord/inbound.d.ts +22 -0
  102. package/dist/providers/discord/inbound.d.ts.map +1 -0
  103. package/dist/providers/discord/inbound.js +75 -0
  104. package/dist/providers/discord/inbound.js.map +1 -0
  105. package/dist/providers/discord/render.d.ts +9 -0
  106. package/dist/providers/discord/render.d.ts.map +1 -0
  107. package/dist/providers/discord/render.js +73 -0
  108. package/dist/providers/discord/render.js.map +1 -0
  109. package/dist/providers/email/adapter.d.ts +18 -0
  110. package/dist/providers/email/adapter.d.ts.map +1 -0
  111. package/dist/providers/email/adapter.js +41 -0
  112. package/dist/providers/email/adapter.js.map +1 -0
  113. package/dist/providers/email/file-transport.d.ts +14 -0
  114. package/dist/providers/email/file-transport.d.ts.map +1 -0
  115. package/dist/providers/email/file-transport.js +33 -0
  116. package/dist/providers/email/file-transport.js.map +1 -0
  117. package/dist/providers/email/render.d.ts +20 -0
  118. package/dist/providers/email/render.d.ts.map +1 -0
  119. package/dist/providers/email/render.js +89 -0
  120. package/dist/providers/email/render.js.map +1 -0
  121. package/dist/providers/email/transport.d.ts +25 -0
  122. package/dist/providers/email/transport.d.ts.map +1 -0
  123. package/dist/providers/email/transport.js +2 -0
  124. package/dist/providers/email/transport.js.map +1 -0
  125. package/dist/providers/slack/adapter.d.ts +35 -0
  126. package/dist/providers/slack/adapter.d.ts.map +1 -0
  127. package/dist/providers/slack/adapter.js +91 -0
  128. package/dist/providers/slack/adapter.js.map +1 -0
  129. package/dist/providers/slack/client.d.ts +45 -0
  130. package/dist/providers/slack/client.d.ts.map +1 -0
  131. package/dist/providers/slack/client.js +54 -0
  132. package/dist/providers/slack/client.js.map +1 -0
  133. package/dist/providers/slack/inbound.d.ts +23 -0
  134. package/dist/providers/slack/inbound.d.ts.map +1 -0
  135. package/dist/providers/slack/inbound.js +76 -0
  136. package/dist/providers/slack/inbound.js.map +1 -0
  137. package/dist/providers/slack/render.d.ts +8 -0
  138. package/dist/providers/slack/render.d.ts.map +1 -0
  139. package/dist/providers/slack/render.js +72 -0
  140. package/dist/providers/slack/render.js.map +1 -0
  141. package/dist/providers/slack/socket.d.ts +34 -0
  142. package/dist/providers/slack/socket.d.ts.map +1 -0
  143. package/dist/providers/slack/socket.js +70 -0
  144. package/dist/providers/slack/socket.js.map +1 -0
  145. package/dist/providers/telegram/adapter.d.ts +35 -0
  146. package/dist/providers/telegram/adapter.d.ts.map +1 -0
  147. package/dist/providers/telegram/adapter.js +109 -0
  148. package/dist/providers/telegram/adapter.js.map +1 -0
  149. package/dist/providers/telegram/client.d.ts +78 -0
  150. package/dist/providers/telegram/client.d.ts.map +1 -0
  151. package/dist/providers/telegram/client.js +53 -0
  152. package/dist/providers/telegram/client.js.map +1 -0
  153. package/dist/providers/telegram/inbound.d.ts +21 -0
  154. package/dist/providers/telegram/inbound.d.ts.map +1 -0
  155. package/dist/providers/telegram/inbound.js +72 -0
  156. package/dist/providers/telegram/inbound.js.map +1 -0
  157. package/dist/providers/telegram/render.d.ts +15 -0
  158. package/dist/providers/telegram/render.d.ts.map +1 -0
  159. package/dist/providers/telegram/render.js +87 -0
  160. package/dist/providers/telegram/render.js.map +1 -0
  161. package/dist/providers/webhook/adapter.d.ts +40 -0
  162. package/dist/providers/webhook/adapter.d.ts.map +1 -0
  163. package/dist/providers/webhook/adapter.js +71 -0
  164. package/dist/providers/webhook/adapter.js.map +1 -0
  165. package/dist/providers/webhook/render.d.ts +26 -0
  166. package/dist/providers/webhook/render.d.ts.map +1 -0
  167. package/dist/providers/webhook/render.js +41 -0
  168. package/dist/providers/webhook/render.js.map +1 -0
  169. package/dist/providers/webhook/signing.d.ts +59 -0
  170. package/dist/providers/webhook/signing.d.ts.map +1 -0
  171. package/dist/providers/webhook/signing.js +108 -0
  172. package/dist/providers/webhook/signing.js.map +1 -0
  173. package/dist/registry.d.ts +16 -0
  174. package/dist/registry.d.ts.map +1 -0
  175. package/dist/registry.js +39 -0
  176. package/dist/registry.js.map +1 -0
  177. package/package.json +42 -0
@@ -0,0 +1,108 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ import { CogentaError } from '@cogenta/core';
3
+ /**
4
+ * "Les webhooks entrants sont une surface d'attaque. Vérification de
5
+ * signature obligatoire, fenêtre temporelle, protection contre le rejeu. Un
6
+ * webhook non signé est un endpoint public arbitraire." (L6, "## Pièges
7
+ * connus"). This module is the whole reason L6 task 11 exists as its own
8
+ * deliverable — three checks, all mandatory: signature authenticity,
9
+ * timestamp freshness, and replay rejection.
10
+ *
11
+ * Same HMAC-SHA256 + constant-time-comparison construction as
12
+ * `approvals/signed-link.ts` (itself mirroring `StorageDriver.signedUrl`) —
13
+ * signs `timestamp + '.' + rawBody` rather than the body alone, so a replayed
14
+ * request with an old, otherwise-valid signature still fails freshness.
15
+ */
16
+ const DEFAULT_FRESHNESS_WINDOW_SECONDS = 5 * 60;
17
+ /** The two headers an outbound signed webhook carries; a receiver names them however it configures its route. */
18
+ export const WEBHOOK_TIMESTAMP_HEADER = 'X-Cogenta-Timestamp';
19
+ export const WEBHOOK_SIGNATURE_HEADER = 'X-Cogenta-Signature';
20
+ function signPayload(secret, timestampSeconds, rawBody) {
21
+ return createHmac('sha256', secret).update(`${timestampSeconds}.${rawBody}`).digest('hex');
22
+ }
23
+ /** The outbound half: what `createWebhookAdapter`'s `send()` attaches to every request. */
24
+ export function signOutgoingWebhook(secret, rawBody, nowSeconds = Math.floor(Date.now() / 1000)) {
25
+ const timestamp = String(nowSeconds);
26
+ return { timestamp, signature: signPayload(secret, nowSeconds, rawBody) };
27
+ }
28
+ /**
29
+ * Bounded, in-memory replay guard — proportionate to the freshness window a
30
+ * verifier already enforces (an entry older than the window can never pass
31
+ * freshness anyway, so nothing needs to be remembered past it). Not a
32
+ * general-purpose dedup store; one instance per verifying process/secret is
33
+ * the expected usage, matching how a route handler would own it.
34
+ */
35
+ export class WebhookReplayGuard {
36
+ #seenAtBySignature = new Map();
37
+ /** Returns `true` if `signature` was already recorded within a still-valid window. */
38
+ hasSeen(signature, nowSeconds, freshnessWindowSeconds) {
39
+ this.#purgeExpired(nowSeconds, freshnessWindowSeconds);
40
+ return this.#seenAtBySignature.has(signature);
41
+ }
42
+ remember(signature, nowSeconds) {
43
+ this.#seenAtBySignature.set(signature, nowSeconds);
44
+ }
45
+ #purgeExpired(nowSeconds, freshnessWindowSeconds) {
46
+ for (const [signature, seenAt] of this.#seenAtBySignature) {
47
+ if (nowSeconds - seenAt > freshnessWindowSeconds)
48
+ this.#seenAtBySignature.delete(signature);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * Three independent checks, all mandatory, in the order that fails cheapest
54
+ * first: a malformed/non-numeric timestamp or a stale one is rejected before
55
+ * ever computing an HMAC over it, and only a request that is both fresh and
56
+ * authentic is checked for replay (recording a signature for a request that
57
+ * never even had a valid signature would let an attacker exhaust the replay
58
+ * guard with garbage).
59
+ *
60
+ * Unlike `linking/codes.ts`'s deliberately uniform `CHANNEL_LINK_CODE_INVALID`
61
+ * (chosen to deny an attacker an enumeration oracle over *which accounts
62
+ * exist*), this returns a *distinguishable* reason. There is no identity to
63
+ * enumerate here — a webhook secret is either configured correctly or it
64
+ * isn't, and telling an operator debugging a misconfigured integration
65
+ * "your signature doesn't match" versus "your timestamp is stale" versus
66
+ * "this exact request was already processed" is a legitimate operational
67
+ * need, not an attack surface: none of the three reasons reveal anything an
68
+ * attacker couldn't already infer from a generic 401.
69
+ */
70
+ export function verifyIncomingWebhook(input) {
71
+ const now = input.now ?? Math.floor(Date.now() / 1000);
72
+ const freshnessWindowSeconds = input.freshnessWindowSeconds ?? DEFAULT_FRESHNESS_WINDOW_SECONDS;
73
+ const timestampSeconds = Number(input.headers.timestamp);
74
+ if (!Number.isFinite(timestampSeconds))
75
+ return { ok: false, reason: 'invalid_signature' };
76
+ if (Math.abs(now - timestampSeconds) > freshnessWindowSeconds) {
77
+ return { ok: false, reason: 'expired' };
78
+ }
79
+ const expected = Buffer.from(signPayload(input.secret, timestampSeconds, input.rawBody), 'utf8');
80
+ const received = Buffer.from(input.headers.signature, 'utf8');
81
+ if (expected.length !== received.length || !timingSafeEqual(expected, received)) {
82
+ return { ok: false, reason: 'invalid_signature' };
83
+ }
84
+ if (input.replayGuard !== undefined) {
85
+ if (input.replayGuard.hasSeen(input.headers.signature, now, freshnessWindowSeconds)) {
86
+ return { ok: false, reason: 'replayed' };
87
+ }
88
+ input.replayGuard.remember(input.headers.signature, now);
89
+ }
90
+ return { ok: true };
91
+ }
92
+ const REASON_TO_ERROR_CODE = {
93
+ invalid_signature: 'CHANNEL_WEBHOOK_SIGNATURE_INVALID',
94
+ expired: 'CHANNEL_WEBHOOK_EXPIRED',
95
+ replayed: 'CHANNEL_WEBHOOK_REPLAY_DETECTED',
96
+ };
97
+ /** Throws the typed `CogentaError` matching a failed `verifyIncomingWebhook` result; a no-op on success. */
98
+ export function assertValidIncomingWebhook(result) {
99
+ if (result.ok)
100
+ return;
101
+ const code = REASON_TO_ERROR_CODE[result.reason];
102
+ throw new CogentaError({
103
+ code,
104
+ message: `Incoming webhook rejected: ${result.reason}.`,
105
+ hint: 'Check the shared secret, clock skew between sender and receiver, and that this exact request has not already been delivered.',
106
+ });
107
+ }
108
+ //# sourceMappingURL=signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.js","sourceRoot":"","sources":["../../../src/providers/webhook/signing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ChannelAdapter } from './adapter.js';
2
+ export interface ChannelRegistry {
3
+ readonly has: (name: string) => boolean;
4
+ readonly get: (name: string) => ChannelAdapter;
5
+ /** Every configured adapter, in the order given to `createChannelRegistry`. */
6
+ readonly list: () => readonly ChannelAdapter[];
7
+ }
8
+ /**
9
+ * A site with zero channels configured must work fine — nothing here fails
10
+ * to construct on an empty list, mirroring how `createProviderRegistry`
11
+ * (`@cogenta/agents`) treats zero LLM providers as a normal, supported
12
+ * state rather than an error (R2's spirit applied to channels: no channel
13
+ * configured means no notifications are sent, never a broken CMS).
14
+ */
15
+ export declare function createChannelRegistry(adapters: readonly ChannelAdapter[]): ChannelRegistry;
16
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IACvC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,cAAc,CAAA;IAC9C,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,cAAc,EAAE,CAAA;CAC/C;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,eAAe,CA8B1F"}
@@ -0,0 +1,39 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ /**
3
+ * A site with zero channels configured must work fine — nothing here fails
4
+ * to construct on an empty list, mirroring how `createProviderRegistry`
5
+ * (`@cogenta/agents`) treats zero LLM providers as a normal, supported
6
+ * state rather than an error (R2's spirit applied to channels: no channel
7
+ * configured means no notifications are sent, never a broken CMS).
8
+ */
9
+ export function createChannelRegistry(adapters) {
10
+ const byName = new Map();
11
+ for (const adapter of adapters) {
12
+ if (byName.has(adapter.name)) {
13
+ throw new CogentaError({
14
+ code: 'CHANNEL_DUPLICATE',
15
+ message: `Two channel adapters are both named "${adapter.name}".`,
16
+ hint: 'Every ChannelAdapter passed to createChannelRegistry must have a unique name.',
17
+ details: { name: adapter.name },
18
+ });
19
+ }
20
+ byName.set(adapter.name, adapter);
21
+ }
22
+ return {
23
+ has: (name) => byName.has(name),
24
+ get(name) {
25
+ const adapter = byName.get(name);
26
+ if (adapter === undefined) {
27
+ throw new CogentaError({
28
+ code: 'CHANNEL_UNKNOWN',
29
+ message: `No channel adapter named "${name}" is configured for this site.`,
30
+ hint: 'Configure the channel, or send through one that is already configured.',
31
+ details: { name },
32
+ });
33
+ }
34
+ return adapter;
35
+ },
36
+ list: () => [...byName.values()],
37
+ };
38
+ }
39
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAU5C;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAmC;IACvE,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAA;IAChD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,YAAY,CAAC;gBACrB,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EAAE,wCAAwC,OAAO,CAAC,IAAI,IAAI;gBACjE,IAAI,EAAE,+EAA+E;gBACrF,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE;aAChC,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACnC,CAAC;IAED,OAAO;QACL,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QAC/B,GAAG,CAAC,IAAI;YACN,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YAChC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,IAAI,YAAY,CAAC;oBACrB,IAAI,EAAE,iBAAiB;oBACvB,OAAO,EAAE,6BAA6B,IAAI,gCAAgC;oBAC1E,IAAI,EAAE,wEAAwE;oBAC9E,OAAO,EAAE,EAAE,IAAI,EAAE;iBAClB,CAAC,CAAA;YACJ,CAAC;YACD,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;KACjC,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@cogenta/channels",
3
+ "version": "0.1.0",
4
+ "description": "Channel adapter interface and registry — Telegram, Slack, Discord, email, webhooks.",
5
+ "license": "MPL-2.0",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22.11.0"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public",
22
+ "provenance": true
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/cogenta-cms/cogenta.git",
27
+ "directory": "packages/channels"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "typecheck": "tsc -p tsconfig.typecheck.json",
32
+ "test": "vitest run --passWithNoTests"
33
+ },
34
+ "dependencies": {
35
+ "@cogenta/agents": "workspace:*",
36
+ "@cogenta/core": "workspace:*"
37
+ },
38
+ "devDependencies": {
39
+ "typescript": "catalog:",
40
+ "vitest": "catalog:"
41
+ }
42
+ }