@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.
- package/dist/adapter.d.ts +111 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +2 -0
- package/dist/adapter.js.map +1 -0
- package/dist/approvals/commands.d.ts +28 -0
- package/dist/approvals/commands.d.ts.map +1 -0
- package/dist/approvals/commands.js +66 -0
- package/dist/approvals/commands.js.map +1 -0
- package/dist/approvals/dispatch.d.ts +22 -0
- package/dist/approvals/dispatch.d.ts.map +1 -0
- package/dist/approvals/dispatch.js +22 -0
- package/dist/approvals/dispatch.js.map +1 -0
- package/dist/approvals/message.d.ts +18 -0
- package/dist/approvals/message.d.ts.map +1 -0
- package/dist/approvals/message.js +40 -0
- package/dist/approvals/message.js.map +1 -0
- package/dist/approvals/signed-link.d.ts +19 -0
- package/dist/approvals/signed-link.d.ts.map +1 -0
- package/dist/approvals/signed-link.js +42 -0
- package/dist/approvals/signed-link.js.map +1 -0
- package/dist/approvals/store.d.ts +41 -0
- package/dist/approvals/store.d.ts.map +1 -0
- package/dist/approvals/store.js +41 -0
- package/dist/approvals/store.js.map +1 -0
- package/dist/approvals/token.d.ts +7 -0
- package/dist/approvals/token.d.ts.map +1 -0
- package/dist/approvals/token.js +40 -0
- package/dist/approvals/token.js.map +1 -0
- package/dist/formats/alert.d.ts +18 -0
- package/dist/formats/alert.d.ts.map +1 -0
- package/dist/formats/alert.js +49 -0
- package/dist/formats/alert.js.map +1 -0
- package/dist/formats/budget.d.ts +14 -0
- package/dist/formats/budget.d.ts.map +1 -0
- package/dist/formats/budget.js +13 -0
- package/dist/formats/budget.js.map +1 -0
- package/dist/formats/notification.d.ts +8 -0
- package/dist/formats/notification.d.ts.map +1 -0
- package/dist/formats/notification.js +26 -0
- package/dist/formats/notification.js.map +1 -0
- package/dist/formats/report.d.ts +18 -0
- package/dist/formats/report.d.ts.map +1 -0
- package/dist/formats/report.js +44 -0
- package/dist/formats/report.js.map +1 -0
- package/dist/inbound/authorize.d.ts +57 -0
- package/dist/inbound/authorize.d.ts.map +1 -0
- package/dist/inbound/authorize.js +32 -0
- package/dist/inbound/authorize.js.map +1 -0
- package/dist/inbound/router.d.ts +67 -0
- package/dist/inbound/router.d.ts.map +1 -0
- package/dist/inbound/router.js +69 -0
- package/dist/inbound/router.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/linking/codes.d.ts +6 -0
- package/dist/linking/codes.d.ts.map +1 -0
- package/dist/linking/codes.js +37 -0
- package/dist/linking/codes.js.map +1 -0
- package/dist/linking/store.d.ts +39 -0
- package/dist/linking/store.d.ts.map +1 -0
- package/dist/linking/store.js +77 -0
- package/dist/linking/store.js.map +1 -0
- package/dist/linking/tables.d.ts +14 -0
- package/dist/linking/tables.d.ts.map +1 -0
- package/dist/linking/tables.js +52 -0
- package/dist/linking/tables.js.map +1 -0
- package/dist/preferences/dispatcher.d.ts +37 -0
- package/dist/preferences/dispatcher.d.ts.map +1 -0
- package/dist/preferences/dispatcher.js +103 -0
- package/dist/preferences/dispatcher.js.map +1 -0
- package/dist/preferences/quiet-hours.d.ts +12 -0
- package/dist/preferences/quiet-hours.d.ts.map +1 -0
- package/dist/preferences/quiet-hours.js +22 -0
- package/dist/preferences/quiet-hours.js.map +1 -0
- package/dist/preferences/store.d.ts +8 -0
- package/dist/preferences/store.d.ts.map +1 -0
- package/dist/preferences/store.js +60 -0
- package/dist/preferences/store.js.map +1 -0
- package/dist/preferences/tables.d.ts +12 -0
- package/dist/preferences/tables.d.ts.map +1 -0
- package/dist/preferences/tables.js +55 -0
- package/dist/preferences/tables.js.map +1 -0
- package/dist/preferences/types.d.ts +37 -0
- package/dist/preferences/types.d.ts.map +1 -0
- package/dist/preferences/types.js +31 -0
- package/dist/preferences/types.js.map +1 -0
- package/dist/providers/discord/adapter.d.ts +38 -0
- package/dist/providers/discord/adapter.d.ts.map +1 -0
- package/dist/providers/discord/adapter.js +97 -0
- package/dist/providers/discord/adapter.js.map +1 -0
- package/dist/providers/discord/client.d.ts +67 -0
- package/dist/providers/discord/client.d.ts.map +1 -0
- package/dist/providers/discord/client.js +65 -0
- package/dist/providers/discord/client.js.map +1 -0
- package/dist/providers/discord/gateway.d.ts +55 -0
- package/dist/providers/discord/gateway.d.ts.map +1 -0
- package/dist/providers/discord/gateway.js +114 -0
- package/dist/providers/discord/gateway.js.map +1 -0
- package/dist/providers/discord/inbound.d.ts +22 -0
- package/dist/providers/discord/inbound.d.ts.map +1 -0
- package/dist/providers/discord/inbound.js +75 -0
- package/dist/providers/discord/inbound.js.map +1 -0
- package/dist/providers/discord/render.d.ts +9 -0
- package/dist/providers/discord/render.d.ts.map +1 -0
- package/dist/providers/discord/render.js +73 -0
- package/dist/providers/discord/render.js.map +1 -0
- package/dist/providers/email/adapter.d.ts +18 -0
- package/dist/providers/email/adapter.d.ts.map +1 -0
- package/dist/providers/email/adapter.js +41 -0
- package/dist/providers/email/adapter.js.map +1 -0
- package/dist/providers/email/file-transport.d.ts +14 -0
- package/dist/providers/email/file-transport.d.ts.map +1 -0
- package/dist/providers/email/file-transport.js +33 -0
- package/dist/providers/email/file-transport.js.map +1 -0
- package/dist/providers/email/render.d.ts +20 -0
- package/dist/providers/email/render.d.ts.map +1 -0
- package/dist/providers/email/render.js +89 -0
- package/dist/providers/email/render.js.map +1 -0
- package/dist/providers/email/transport.d.ts +25 -0
- package/dist/providers/email/transport.d.ts.map +1 -0
- package/dist/providers/email/transport.js +2 -0
- package/dist/providers/email/transport.js.map +1 -0
- package/dist/providers/slack/adapter.d.ts +35 -0
- package/dist/providers/slack/adapter.d.ts.map +1 -0
- package/dist/providers/slack/adapter.js +91 -0
- package/dist/providers/slack/adapter.js.map +1 -0
- package/dist/providers/slack/client.d.ts +45 -0
- package/dist/providers/slack/client.d.ts.map +1 -0
- package/dist/providers/slack/client.js +54 -0
- package/dist/providers/slack/client.js.map +1 -0
- package/dist/providers/slack/inbound.d.ts +23 -0
- package/dist/providers/slack/inbound.d.ts.map +1 -0
- package/dist/providers/slack/inbound.js +76 -0
- package/dist/providers/slack/inbound.js.map +1 -0
- package/dist/providers/slack/render.d.ts +8 -0
- package/dist/providers/slack/render.d.ts.map +1 -0
- package/dist/providers/slack/render.js +72 -0
- package/dist/providers/slack/render.js.map +1 -0
- package/dist/providers/slack/socket.d.ts +34 -0
- package/dist/providers/slack/socket.d.ts.map +1 -0
- package/dist/providers/slack/socket.js +70 -0
- package/dist/providers/slack/socket.js.map +1 -0
- package/dist/providers/telegram/adapter.d.ts +35 -0
- package/dist/providers/telegram/adapter.d.ts.map +1 -0
- package/dist/providers/telegram/adapter.js +109 -0
- package/dist/providers/telegram/adapter.js.map +1 -0
- package/dist/providers/telegram/client.d.ts +78 -0
- package/dist/providers/telegram/client.d.ts.map +1 -0
- package/dist/providers/telegram/client.js +53 -0
- package/dist/providers/telegram/client.js.map +1 -0
- package/dist/providers/telegram/inbound.d.ts +21 -0
- package/dist/providers/telegram/inbound.d.ts.map +1 -0
- package/dist/providers/telegram/inbound.js +72 -0
- package/dist/providers/telegram/inbound.js.map +1 -0
- package/dist/providers/telegram/render.d.ts +15 -0
- package/dist/providers/telegram/render.d.ts.map +1 -0
- package/dist/providers/telegram/render.js +87 -0
- package/dist/providers/telegram/render.js.map +1 -0
- package/dist/providers/webhook/adapter.d.ts +40 -0
- package/dist/providers/webhook/adapter.d.ts.map +1 -0
- package/dist/providers/webhook/adapter.js +71 -0
- package/dist/providers/webhook/adapter.js.map +1 -0
- package/dist/providers/webhook/render.d.ts +26 -0
- package/dist/providers/webhook/render.d.ts.map +1 -0
- package/dist/providers/webhook/render.js +41 -0
- package/dist/providers/webhook/render.js.map +1 -0
- package/dist/providers/webhook/signing.d.ts +59 -0
- package/dist/providers/webhook/signing.d.ts.map +1 -0
- package/dist/providers/webhook/signing.js +108 -0
- package/dist/providers/webhook/signing.js.map +1 -0
- package/dist/registry.d.ts +16 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +39 -0
- package/dist/registry.js.map +1 -0
- 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"}
|
package/dist/registry.js
ADDED
|
@@ -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
|
+
}
|