@framers/agentos-extensions-registry 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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @framers/agentos-extensions-registry
2
+
3
+ Curated extension registry bundle for AgentOS. Single import to register channels, tools, and integrations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @framers/agentos-extensions-registry
9
+ ```
10
+
11
+ Channel extensions are **optional dependencies** — only installed channels are loaded. Missing packages are silently skipped.
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { createCuratedManifest } from '@framers/agentos-extensions-registry';
17
+
18
+ // Load all available extensions
19
+ const manifest = await createCuratedManifest({ channels: 'all', tools: 'all' });
20
+
21
+ // Or selectively enable specific channels
22
+ const manifest = await createCuratedManifest({
23
+ channels: ['telegram', 'discord', 'slack'],
24
+ tools: 'all',
25
+ secrets: {
26
+ 'telegram.botToken': process.env.TELEGRAM_BOT_TOKEN,
27
+ 'discord.botToken': process.env.DISCORD_BOT_TOKEN,
28
+ 'slack.botToken': process.env.SLACK_BOT_TOKEN,
29
+ 'slack.signingSecret': process.env.SLACK_SIGNING_SECRET,
30
+ },
31
+ });
32
+
33
+ // Use with AgentOS
34
+ const agent = new AgentOS();
35
+ await agent.initialize({ extensionManifest: manifest });
36
+ ```
37
+
38
+ ## API
39
+
40
+ ### `createCuratedManifest(options?)`
41
+
42
+ Creates a pre-configured `ExtensionManifest` with all available curated extensions.
43
+
44
+ **Options:**
45
+
46
+ | Parameter | Type | Default | Description |
47
+ | -------------- | ----------------------------- | -------- | ------------------------------------ |
48
+ | `channels` | `string[] \| 'all' \| 'none'` | `'none'` | Which channel platforms to enable |
49
+ | `tools` | `string[] \| 'all' \| 'none'` | `'all'` | Which tool extensions to enable |
50
+ | `secrets` | `Record<string, string>` | `{}` | Secrets map (falls back to env vars) |
51
+ | `basePriority` | `number` | `50` | Base priority for all extensions |
52
+ | `overrides` | `Record<string, Override>` | `{}` | Per-extension overrides |
53
+
54
+ **Override shape:**
55
+
56
+ ```typescript
57
+ { enabled?: boolean; priority?: number; options?: any }
58
+ ```
59
+
60
+ ### `getAvailableExtensions()`
61
+
62
+ Returns a list of extensions whose optional dependencies are installed.
63
+
64
+ ### `getAvailableChannels()`
65
+
66
+ Returns a list of available channel adapters.
67
+
68
+ ## Available Channel Extensions
69
+
70
+ | Channel | Package | SDK | Secret |
71
+ | -------- | --------------------------------------- | ----------- | ---------------------------------------- |
72
+ | Telegram | `@framers/agentos-ext-channel-telegram` | grammY | `telegram.botToken` |
73
+ | WhatsApp | `@framers/agentos-ext-channel-whatsapp` | Baileys | `whatsapp.sessionData` |
74
+ | Discord | `@framers/agentos-ext-channel-discord` | discord.js | `discord.botToken` |
75
+ | Slack | `@framers/agentos-ext-channel-slack` | @slack/bolt | `slack.botToken` + `slack.signingSecret` |
76
+ | WebChat | `@framers/agentos-ext-channel-webchat` | (built-in) | none |
77
+
78
+ ## License
79
+
80
+ MIT
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@framers/agentos-extensions-registry",
3
+ "version": "0.1.0",
4
+ "description": "Curated extension registry bundle for AgentOS — single import to register channels, tools, and integrations",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "clean": "rm -rf dist",
19
+ "test": "vitest run"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/framersai/agentos-extensions-registry.git"
24
+ },
25
+ "keywords": [
26
+ "agentos",
27
+ "extensions",
28
+ "registry",
29
+ "channels",
30
+ "tools"
31
+ ],
32
+ "author": {
33
+ "name": "Framers AI",
34
+ "email": "team@frame.dev",
35
+ "url": "https://frame.dev"
36
+ },
37
+ "license": "MIT",
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "peerDependencies": {
42
+ "@framers/agentos": ">=0.1.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "@framers/agentos": {
46
+ "optional": false
47
+ }
48
+ },
49
+ "optionalDependencies": {
50
+ "@framers/agentos-ext-auth": "^1.0.0",
51
+ "@framers/agentos-ext-web-search": "^1.0.0",
52
+ "@framers/agentos-ext-web-browser": "^1.0.0",
53
+ "@framers/agentos-ext-telegram": "^1.0.0",
54
+ "@framers/agentos-ext-cli-executor": "^1.0.0",
55
+ "@framers/agentos-ext-giphy": "^1.0.0",
56
+ "@framers/agentos-ext-image-search": "^1.0.0",
57
+ "@framers/agentos-ext-voice-synthesis": "^1.0.0",
58
+ "@framers/agentos-ext-news-search": "^1.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@framers/agentos": "^0.1.0",
62
+ "typescript": "^5.4.5",
63
+ "vitest": "^1.0.0"
64
+ }
65
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @fileoverview Channel registry — metadata catalog for all supported
3
+ * messaging channel extensions.
4
+ *
5
+ * Each entry defines the channel's metadata, secret requirements, and
6
+ * default priority. The actual adapter implementations live in
7
+ * `@framers/agentos-extensions/registry/curated/channels/<platform>/`.
8
+ *
9
+ * @module @framers/agentos-extensions-registry/channel-registry
10
+ */
11
+
12
+ import type { ChannelRegistryEntry } from './types.js';
13
+
14
+ /**
15
+ * Full catalog of messaging channel extensions.
16
+ * Ordered by priority tier (P0 first, then P1, P2).
17
+ */
18
+ export const CHANNEL_CATALOG: ChannelRegistryEntry[] = [
19
+ // ── P0: Core Channels ──
20
+ {
21
+ packageName: '@framers/agentos-ext-channel-telegram',
22
+ name: 'channel-telegram',
23
+ category: 'channel',
24
+ platform: 'telegram',
25
+ displayName: 'Telegram',
26
+ description: 'Telegram Bot API via grammY — supports text, images, inline keyboards, groups.',
27
+ sdkPackage: 'grammy',
28
+ requiredSecrets: ['telegram.botToken'],
29
+ defaultPriority: 50,
30
+ available: false,
31
+ },
32
+ {
33
+ packageName: '@framers/agentos-ext-channel-whatsapp',
34
+ name: 'channel-whatsapp',
35
+ category: 'channel',
36
+ platform: 'whatsapp',
37
+ displayName: 'WhatsApp',
38
+ description: 'WhatsApp Web bridge via Baileys — supports text, images, documents, voice notes.',
39
+ sdkPackage: '@whiskeysockets/baileys',
40
+ requiredSecrets: ['whatsapp.sessionData'],
41
+ defaultPriority: 50,
42
+ available: false,
43
+ },
44
+ {
45
+ packageName: '@framers/agentos-ext-channel-discord',
46
+ name: 'channel-discord',
47
+ category: 'channel',
48
+ platform: 'discord',
49
+ displayName: 'Discord',
50
+ description: 'Discord bot via discord.js — supports text, embeds, threads, reactions, buttons.',
51
+ sdkPackage: 'discord.js',
52
+ requiredSecrets: ['discord.botToken'],
53
+ defaultPriority: 50,
54
+ available: false,
55
+ },
56
+ {
57
+ packageName: '@framers/agentos-ext-channel-slack',
58
+ name: 'channel-slack',
59
+ category: 'channel',
60
+ platform: 'slack',
61
+ displayName: 'Slack',
62
+ description:
63
+ 'Slack workspace integration via Bolt — supports text, blocks, threads, reactions.',
64
+ sdkPackage: '@slack/bolt',
65
+ requiredSecrets: ['slack.botToken', 'slack.appToken'],
66
+ defaultPriority: 50,
67
+ available: false,
68
+ },
69
+ {
70
+ packageName: '@framers/agentos-ext-channel-webchat',
71
+ name: 'channel-webchat',
72
+ category: 'channel',
73
+ platform: 'webchat',
74
+ displayName: 'WebChat',
75
+ description: 'Browser-based chat widget via Socket.IO — embeds in any website.',
76
+ sdkPackage: 'socket.io',
77
+ requiredSecrets: [],
78
+ defaultPriority: 50,
79
+ available: false,
80
+ },
81
+
82
+ // ── P1: Extended Channels ──
83
+ {
84
+ packageName: '@framers/agentos-ext-channel-signal',
85
+ name: 'channel-signal',
86
+ category: 'channel',
87
+ platform: 'signal',
88
+ displayName: 'Signal',
89
+ description: 'Signal messenger via signal-cli — end-to-end encrypted messaging.',
90
+ sdkPackage: 'signal-cli',
91
+ requiredSecrets: ['signal.phoneNumber'],
92
+ defaultPriority: 40,
93
+ available: false,
94
+ },
95
+ {
96
+ packageName: '@framers/agentos-ext-channel-imessage',
97
+ name: 'channel-imessage',
98
+ category: 'channel',
99
+ platform: 'imessage',
100
+ displayName: 'iMessage',
101
+ description: 'iMessage bridge via BlueBubbles — macOS server required.',
102
+ sdkPackage: 'bluebubbles-node',
103
+ requiredSecrets: ['imessage.serverUrl', 'imessage.password'],
104
+ defaultPriority: 40,
105
+ available: false,
106
+ },
107
+ {
108
+ packageName: '@framers/agentos-ext-channel-google-chat',
109
+ name: 'channel-google-chat',
110
+ category: 'channel',
111
+ platform: 'google-chat',
112
+ displayName: 'Google Chat',
113
+ description: 'Google Workspace Chat via Cloud API — supports cards and threads.',
114
+ sdkPackage: '@google-cloud/chat',
115
+ requiredSecrets: ['googlechat.serviceAccount'],
116
+ defaultPriority: 40,
117
+ available: false,
118
+ },
119
+ {
120
+ packageName: '@framers/agentos-ext-channel-teams',
121
+ name: 'channel-teams',
122
+ category: 'channel',
123
+ platform: 'teams',
124
+ displayName: 'Microsoft Teams',
125
+ description: 'Microsoft Teams bot via Teams AI library — supports adaptive cards.',
126
+ sdkPackage: '@microsoft/teams-ai',
127
+ requiredSecrets: ['teams.appId', 'teams.appPassword'],
128
+ defaultPriority: 40,
129
+ available: false,
130
+ },
131
+
132
+ // ── P2: Community Channels ──
133
+ {
134
+ packageName: '@framers/agentos-ext-channel-matrix',
135
+ name: 'channel-matrix',
136
+ category: 'channel',
137
+ platform: 'matrix',
138
+ displayName: 'Matrix',
139
+ description: 'Matrix/Element via matrix-js-sdk — decentralized, federated messaging.',
140
+ sdkPackage: 'matrix-js-sdk',
141
+ requiredSecrets: ['matrix.homeserverUrl', 'matrix.accessToken'],
142
+ defaultPriority: 30,
143
+ available: false,
144
+ },
145
+ {
146
+ packageName: '@framers/agentos-ext-channel-zalo',
147
+ name: 'channel-zalo',
148
+ category: 'channel',
149
+ platform: 'zalo',
150
+ displayName: 'Zalo',
151
+ description: 'Zalo Official Account API — popular in Vietnam.',
152
+ sdkPackage: 'zalo-api',
153
+ requiredSecrets: ['zalo.appId', 'zalo.secretKey'],
154
+ defaultPriority: 30,
155
+ available: false,
156
+ },
157
+ {
158
+ packageName: '@framers/agentos-ext-channel-email',
159
+ name: 'channel-email',
160
+ category: 'channel',
161
+ platform: 'email',
162
+ displayName: 'Email',
163
+ description: 'Email messaging via nodemailer (SMTP) + imap-simple (IMAP).',
164
+ sdkPackage: 'nodemailer',
165
+ requiredSecrets: ['email.smtpHost', 'email.smtpUser', 'email.smtpPassword'],
166
+ defaultPriority: 30,
167
+ available: false,
168
+ },
169
+ {
170
+ packageName: '@framers/agentos-ext-channel-sms',
171
+ name: 'channel-sms',
172
+ category: 'channel',
173
+ platform: 'sms',
174
+ displayName: 'SMS',
175
+ description: 'SMS messaging via Twilio — text-only, worldwide reach.',
176
+ sdkPackage: 'twilio',
177
+ requiredSecrets: ['twilio.accountSid', 'twilio.authToken', 'twilio.phoneNumber'],
178
+ defaultPriority: 30,
179
+ available: false,
180
+ },
181
+ ];
182
+
183
+ /**
184
+ * Get channel entries filtered by platform names.
185
+ */
186
+ export function getChannelEntries(platforms?: string[] | 'all' | 'none'): ChannelRegistryEntry[] {
187
+ if (platforms === 'none') return [];
188
+ if (!platforms || platforms === 'all') return [...CHANNEL_CATALOG];
189
+ return CHANNEL_CATALOG.filter((entry) => platforms.includes(entry.platform));
190
+ }
191
+
192
+ /**
193
+ * Get a single channel entry by platform.
194
+ */
195
+ export function getChannelEntry(platform: string): ChannelRegistryEntry | undefined {
196
+ return CHANNEL_CATALOG.find((entry) => entry.platform === platform);
197
+ }
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @fileoverview AgentOS Extensions Registry Bundle.
3
+ *
4
+ * Single-import package that discovers, configures, and registers all
5
+ * curated AgentOS extensions (channels, tools, integrations).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { createCuratedManifest } from '@framers/agentos-extensions-registry';
10
+ *
11
+ * const manifest = await createCuratedManifest({
12
+ * channels: ['telegram', 'whatsapp', 'discord'],
13
+ * tools: 'all',
14
+ * secrets: resolvedSecrets,
15
+ * });
16
+ *
17
+ * const agentOS = new AgentOS({ extensionManifest: manifest });
18
+ * ```
19
+ *
20
+ * @module @framers/agentos-extensions-registry
21
+ */
22
+
23
+ export {
24
+ createCuratedManifest,
25
+ getAvailableExtensions,
26
+ getAvailableChannels,
27
+ } from './manifest-builder.js';
28
+
29
+ export { CHANNEL_CATALOG, getChannelEntries, getChannelEntry } from './channel-registry.js';
30
+
31
+ export type {
32
+ RegistryOptions,
33
+ ExtensionOverrideConfig,
34
+ ExtensionInfo,
35
+ ChannelRegistryEntry,
36
+ } from './types.js';
@@ -0,0 +1,263 @@
1
+ /**
2
+ * @fileoverview Manifest builder — creates pre-configured ExtensionManifest
3
+ * instances from the curated extension catalog.
4
+ *
5
+ * This is the primary API of the registry package. Instead of manually wiring
6
+ * each extension pack in the backend, consumers call `createCuratedManifest()`
7
+ * with their desired configuration.
8
+ *
9
+ * @module @framers/agentos-extensions-registry/manifest-builder
10
+ */
11
+
12
+ import type { ExtensionManifest, ExtensionPackManifestEntry } from '@framers/agentos';
13
+ import type { RegistryOptions, ExtensionInfo, RegistryLogger } from './types.js';
14
+ import { CHANNEL_CATALOG, getChannelEntries } from './channel-registry.js';
15
+
16
+ /**
17
+ * Known tool extension packages and their metadata.
18
+ * Matches existing extensions in `agentos-extensions/registry/curated/`.
19
+ */
20
+ const TOOL_CATALOG: ExtensionInfo[] = [
21
+ {
22
+ packageName: '@framers/agentos-ext-auth',
23
+ name: 'auth',
24
+ category: 'tool',
25
+ displayName: 'Authentication',
26
+ description: 'User authentication and session management tools.',
27
+ requiredSecrets: [],
28
+ defaultPriority: 10,
29
+ available: false,
30
+ },
31
+ {
32
+ packageName: '@framers/agentos-ext-web-search',
33
+ name: 'web-search',
34
+ category: 'tool',
35
+ displayName: 'Web Search',
36
+ description: 'Web search via Serper.dev or similar providers.',
37
+ requiredSecrets: ['serper.apiKey'],
38
+ defaultPriority: 20,
39
+ available: false,
40
+ },
41
+ {
42
+ packageName: '@framers/agentos-ext-web-browser',
43
+ name: 'web-browser',
44
+ category: 'tool',
45
+ displayName: 'Web Browser',
46
+ description: 'Headless browser for page fetching and scraping.',
47
+ requiredSecrets: [],
48
+ defaultPriority: 20,
49
+ available: false,
50
+ },
51
+ {
52
+ packageName: '@framers/agentos-ext-telegram',
53
+ name: 'telegram',
54
+ category: 'integration',
55
+ displayName: 'Telegram (Legacy)',
56
+ description: 'Legacy Telegram bot integration (tool-based, pre-channel system).',
57
+ requiredSecrets: ['telegram.botToken'],
58
+ defaultPriority: 50,
59
+ available: false,
60
+ },
61
+ {
62
+ packageName: '@framers/agentos-ext-cli-executor',
63
+ name: 'cli-executor',
64
+ category: 'tool',
65
+ displayName: 'CLI Executor',
66
+ description: 'Execute shell commands in a sandboxed environment.',
67
+ requiredSecrets: [],
68
+ defaultPriority: 20,
69
+ available: false,
70
+ },
71
+ {
72
+ packageName: '@framers/agentos-ext-giphy',
73
+ name: 'giphy',
74
+ category: 'tool',
75
+ displayName: 'Giphy',
76
+ description: 'Search and share GIFs via the Giphy API.',
77
+ requiredSecrets: [],
78
+ defaultPriority: 30,
79
+ available: false,
80
+ },
81
+ {
82
+ packageName: '@framers/agentos-ext-image-search',
83
+ name: 'image-search',
84
+ category: 'tool',
85
+ displayName: 'Image Search',
86
+ description: 'Search for images via web APIs.',
87
+ requiredSecrets: [],
88
+ defaultPriority: 30,
89
+ available: false,
90
+ },
91
+ {
92
+ packageName: '@framers/agentos-ext-voice-synthesis',
93
+ name: 'voice-synthesis',
94
+ category: 'tool',
95
+ displayName: 'Voice Synthesis',
96
+ description: 'Text-to-speech synthesis via ElevenLabs or similar.',
97
+ requiredSecrets: [],
98
+ defaultPriority: 30,
99
+ available: false,
100
+ },
101
+ {
102
+ packageName: '@framers/agentos-ext-news-search',
103
+ name: 'news-search',
104
+ category: 'tool',
105
+ displayName: 'News Search',
106
+ description: 'Search recent news articles.',
107
+ requiredSecrets: [],
108
+ defaultPriority: 30,
109
+ available: false,
110
+ },
111
+ ];
112
+
113
+ /**
114
+ * Attempt to dynamically import a package. Returns the module if available,
115
+ * or `null` if the package is not installed.
116
+ */
117
+ async function tryImport(packageName: string): Promise<any | null> {
118
+ try {
119
+ return await import(packageName);
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Check which extensions from the catalog are actually installed
127
+ * and mark them as available.
128
+ */
129
+ export async function getAvailableExtensions(): Promise<ExtensionInfo[]> {
130
+ const allEntries: ExtensionInfo[] = [...TOOL_CATALOG, ...CHANNEL_CATALOG];
131
+ const results: ExtensionInfo[] = [];
132
+
133
+ for (const entry of allEntries) {
134
+ const mod = await tryImport(entry.packageName);
135
+ results.push({ ...entry, available: mod !== null });
136
+ }
137
+
138
+ return results;
139
+ }
140
+
141
+ /**
142
+ * Get available channel extensions.
143
+ */
144
+ export async function getAvailableChannels(): Promise<ExtensionInfo[]> {
145
+ const results: ExtensionInfo[] = [];
146
+ for (const entry of CHANNEL_CATALOG) {
147
+ const mod = await tryImport(entry.packageName);
148
+ results.push({ ...entry, available: mod !== null });
149
+ }
150
+ return results;
151
+ }
152
+
153
+ /**
154
+ * Creates a pre-configured `ExtensionManifest` with all available curated
155
+ * extensions. Missing optional dependencies are silently skipped.
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * import { createCuratedManifest } from '@framers/agentos-extensions-registry';
160
+ *
161
+ * // Enable all available extensions
162
+ * const manifest = await createCuratedManifest();
163
+ *
164
+ * // Selective: only Telegram + Discord channels, all tools
165
+ * const manifest = await createCuratedManifest({
166
+ * channels: ['telegram', 'discord'],
167
+ * tools: 'all',
168
+ * secrets: {
169
+ * 'telegram.botToken': process.env.TELEGRAM_BOT_TOKEN!,
170
+ * 'discord.botToken': process.env.DISCORD_BOT_TOKEN!,
171
+ * },
172
+ * });
173
+ *
174
+ * // Pass to AgentOS config
175
+ * const agentOS = new AgentOS({ extensionManifest: manifest });
176
+ * ```
177
+ */
178
+ export async function createCuratedManifest(options?: RegistryOptions): Promise<ExtensionManifest> {
179
+ const basePriority = options?.basePriority ?? 0;
180
+ const secrets = options?.secrets;
181
+ const logger: RegistryLogger | undefined = options?.logger ?? console;
182
+ const packs: ExtensionPackManifestEntry[] = [];
183
+
184
+ // ── Tool Extensions ──
185
+ const toolFilter = options?.tools ?? 'all';
186
+ const toolEntries =
187
+ toolFilter === 'none'
188
+ ? []
189
+ : toolFilter === 'all'
190
+ ? TOOL_CATALOG
191
+ : TOOL_CATALOG.filter((t) => toolFilter.includes(t.name));
192
+
193
+ for (const entry of toolEntries) {
194
+ const override = options?.overrides?.[entry.name];
195
+ if (override?.enabled === false) continue;
196
+
197
+ const mod = await tryImport(entry.packageName);
198
+ if (!mod) continue;
199
+
200
+ const factory = mod.createExtensionPack ?? mod.default?.createExtensionPack ?? mod.default;
201
+ if (typeof factory !== 'function') continue;
202
+
203
+ packs.push({
204
+ factory: () =>
205
+ factory({
206
+ options: {
207
+ ...override?.options,
208
+ secrets,
209
+ },
210
+ getSecret: (secretId: string) => secrets?.[secretId],
211
+ logger,
212
+ }),
213
+ priority: override?.priority ?? basePriority + entry.defaultPriority,
214
+ enabled: true,
215
+ identifier: `registry:${entry.name}`,
216
+ });
217
+ }
218
+
219
+ // ── Channel Extensions ──
220
+ const channelEntries = getChannelEntries(options?.channels);
221
+
222
+ for (const entry of channelEntries) {
223
+ const override = options?.overrides?.[entry.name];
224
+ if (override?.enabled === false) continue;
225
+
226
+ const mod = await tryImport(entry.packageName);
227
+ if (!mod) continue;
228
+
229
+ const factory = mod.createExtensionPack ?? mod.default?.createExtensionPack ?? mod.default;
230
+ if (typeof factory !== 'function') continue;
231
+
232
+ packs.push({
233
+ factory: () =>
234
+ factory({
235
+ options: {
236
+ ...override?.options,
237
+ secrets,
238
+ },
239
+ getSecret: (secretId: string) => secrets?.[secretId],
240
+ logger,
241
+ }),
242
+ priority: override?.priority ?? basePriority + entry.defaultPriority,
243
+ enabled: true,
244
+ identifier: `registry:${entry.name}`,
245
+ });
246
+ }
247
+
248
+ // ── Build Overrides ──
249
+ const manifestOverrides = options?.overrides
250
+ ? {
251
+ tools: Object.fromEntries(
252
+ Object.entries(options.overrides)
253
+ .filter(([, v]) => v.enabled !== undefined || v.priority !== undefined)
254
+ .map(([k, v]) => [k, { enabled: v.enabled, priority: v.priority }])
255
+ ),
256
+ }
257
+ : undefined;
258
+
259
+ return {
260
+ packs,
261
+ overrides: manifestOverrides,
262
+ };
263
+ }
package/src/types.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @fileoverview Configuration types for the extensions registry bundle.
3
+ * @module @framers/agentos-extensions-registry/types
4
+ */
5
+
6
+ import type { ChannelPlatform } from '@framers/agentos';
7
+
8
+ export interface RegistryLogger {
9
+ info?: (...args: any[]) => void;
10
+ warn?: (...args: any[]) => void;
11
+ error?: (...args: any[]) => void;
12
+ debug?: (...args: any[]) => void;
13
+ }
14
+
15
+ /**
16
+ * Options for building a curated extension manifest.
17
+ */
18
+ export interface RegistryOptions {
19
+ /**
20
+ * Which messaging channels to enable.
21
+ * - Array of platform names: selectively enable specific channels.
22
+ * - `'all'`: enable all available channels (default).
23
+ * - `'none'`: disable all channels.
24
+ */
25
+ channels?: ChannelPlatform[] | 'all' | 'none';
26
+
27
+ /**
28
+ * Which tool extensions to enable.
29
+ * - Array of tool pack names (e.g., 'web-search', 'giphy').
30
+ * - `'all'`: enable all available tools (default).
31
+ * - `'none'`: disable all tools.
32
+ */
33
+ tools?: string[] | 'all' | 'none';
34
+
35
+ /**
36
+ * Secrets map (API keys, tokens, credentials).
37
+ * Keys match secret IDs from `extension-secrets.json` (e.g., 'telegram.botToken').
38
+ * Falls back to environment variables if not provided here.
39
+ */
40
+ secrets?: Record<string, string>;
41
+
42
+ /**
43
+ * Optional logger passed to extension-pack factories. Defaults to `console`.
44
+ */
45
+ logger?: RegistryLogger;
46
+
47
+ /**
48
+ * Base priority for all extensions. Individual packs add their own
49
+ * offset on top of this. Default: 0.
50
+ */
51
+ basePriority?: number;
52
+
53
+ /**
54
+ * Per-extension overrides. Keys are pack names or descriptor IDs.
55
+ */
56
+ overrides?: Record<string, ExtensionOverrideConfig>;
57
+ }
58
+
59
+ /**
60
+ * Override configuration for a specific extension pack or descriptor.
61
+ */
62
+ export interface ExtensionOverrideConfig {
63
+ /** Force enable/disable. */
64
+ enabled?: boolean;
65
+ /** Override priority. */
66
+ priority?: number;
67
+ /** Additional options passed to the pack factory. */
68
+ options?: Record<string, unknown>;
69
+ }
70
+
71
+ /**
72
+ * Metadata about an extension available in the registry.
73
+ */
74
+ export interface ExtensionInfo {
75
+ /** Package name (e.g., '@framers/agentos-ext-telegram'). */
76
+ packageName: string;
77
+ /** Short name (e.g., 'telegram'). */
78
+ name: string;
79
+ /** Category (e.g., 'channel', 'tool', 'integration'). */
80
+ category: 'channel' | 'tool' | 'integration' | 'provenance';
81
+ /** Whether the package's dependencies are installed and importable. */
82
+ available: boolean;
83
+ /** Human-readable display name. */
84
+ displayName: string;
85
+ /** Description. */
86
+ description: string;
87
+ /** Required secret IDs. */
88
+ requiredSecrets: string[];
89
+ /** Default priority. */
90
+ defaultPriority: number;
91
+ }
92
+
93
+ /**
94
+ * Registry entry for a channel extension.
95
+ */
96
+ export interface ChannelRegistryEntry extends ExtensionInfo {
97
+ category: 'channel';
98
+ /** Platform identifier. */
99
+ platform: ChannelPlatform;
100
+ /** SDK package name (e.g., 'grammy', 'discord.js'). */
101
+ sdkPackage: string;
102
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Unit tests for createCuratedManifest and registry helpers.
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
6
+ import { createCuratedManifest, getAvailableExtensions, getAvailableChannels } from '../src/index';
7
+
8
+ describe('createCuratedManifest', () => {
9
+ it('should return a manifest object with packs array', async () => {
10
+ const manifest = await createCuratedManifest({ channels: 'none', tools: 'none' });
11
+ expect(manifest).toHaveProperty('packs');
12
+ expect(Array.isArray(manifest.packs)).toBe(true);
13
+ });
14
+
15
+ it('should return empty packs when channels and tools are "none"', async () => {
16
+ const manifest = await createCuratedManifest({ channels: 'none', tools: 'none' });
17
+ expect(manifest.packs).toHaveLength(0);
18
+ });
19
+
20
+ it('should attempt to load all channels when channels="all"', async () => {
21
+ // In test environment, no optional deps are installed, so packs will be empty
22
+ const manifest = await createCuratedManifest({ channels: 'all', tools: 'none' });
23
+ // tryImport fails for all → packs = []
24
+ expect(manifest.packs).toEqual([]);
25
+ });
26
+
27
+ it('should attempt to load all tools when tools="all"', async () => {
28
+ const manifest = await createCuratedManifest({ channels: 'none', tools: 'all' });
29
+ // tryImport fails for all → packs = []
30
+ expect(manifest.packs).toEqual([]);
31
+ });
32
+
33
+ it('should filter channels by platform name', async () => {
34
+ const manifest = await createCuratedManifest({
35
+ channels: ['telegram', 'discord'],
36
+ tools: 'none',
37
+ });
38
+ // Even though packages aren't installed, the filter should work
39
+ expect(manifest.packs).toEqual([]);
40
+ });
41
+
42
+ it('should apply priority overrides', async () => {
43
+ const manifest = await createCuratedManifest({
44
+ channels: ['telegram'],
45
+ tools: 'none',
46
+ overrides: { 'channel-telegram': { priority: 999 } },
47
+ });
48
+ // Package not available, but override would be applied if it were
49
+ expect(manifest.packs).toEqual([]);
50
+ });
51
+
52
+ it('should skip disabled extensions via overrides', async () => {
53
+ const manifest = await createCuratedManifest({
54
+ channels: 'all',
55
+ tools: 'all',
56
+ overrides: {
57
+ 'channel-telegram': { enabled: false },
58
+ auth: { enabled: false },
59
+ },
60
+ });
61
+ expect(manifest.packs).toEqual([]);
62
+ });
63
+
64
+ it('should pass secrets to extension factories', async () => {
65
+ const manifest = await createCuratedManifest({
66
+ channels: ['telegram'],
67
+ tools: 'none',
68
+ secrets: { 'telegram.botToken': 'test-token-123' },
69
+ });
70
+ // Can't test factory call since package not installed, but structure is correct
71
+ expect(manifest).toHaveProperty('packs');
72
+ });
73
+
74
+ it('should handle basePriority offset', async () => {
75
+ const manifest = await createCuratedManifest({
76
+ channels: 'none',
77
+ tools: 'none',
78
+ basePriority: 100,
79
+ });
80
+ expect(manifest.packs).toEqual([]);
81
+ });
82
+ });
83
+
84
+ describe('getAvailableExtensions', () => {
85
+ it('should return an array', async () => {
86
+ const extensions = await getAvailableExtensions();
87
+ expect(Array.isArray(extensions)).toBe(true);
88
+ });
89
+ });
90
+
91
+ describe('getAvailableChannels', () => {
92
+ it('should return an array', async () => {
93
+ const channels = await getAvailableChannels();
94
+ expect(Array.isArray(channels)).toBe(true);
95
+ });
96
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": ["src/**/*.ts"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }