@alfe.ai/integration-manifest 0.1.0 → 0.2.1

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 CHANGED
@@ -61,6 +61,46 @@ hooks:
61
61
 
62
62
  Only `id`, `name`, `version`, `description`, and `author` are required — everything else has sensible defaults.
63
63
 
64
+ ## Custom Connections — `expected_credentials`
65
+
66
+ For manifests consumed by [Custom Connections](../../services/connect/DEVELOPING.md#custom-connections-pr-8a-of-channels-and-credential-driven-integrations) (`requires_connection: ["custom"]`), the manifest declares the credential fields it needs the tenant operator to fill in via an `expected_credentials` block:
67
+
68
+ ```yaml
69
+ requires_connection: ["custom"]
70
+ expected_credentials:
71
+ - key: api_key
72
+ type: secret
73
+ label: "API Key"
74
+ description: "From the upstream dashboard"
75
+ required: true
76
+ - key: base_url
77
+ type: url
78
+ label: "Base URL"
79
+ placeholder: "https://api.example.com"
80
+ required: false
81
+ - key: workspace_id
82
+ type: string
83
+ label: "Workspace ID"
84
+ pattern: "^[a-zA-Z0-9_-]+$"
85
+ required: true
86
+ ```
87
+
88
+ Supported field `type`s — same vocabulary as the dashboard form renderer:
89
+
90
+ | Type | Stored as | Notes |
91
+ |-----------|------------------------------------|-----------------------------------------|
92
+ | `secret` | KMS-encrypted (`encryptedAccessToken`) | Joined into a JSON bundle by services/connect at write time |
93
+ | `string` | `providerMetadata.{key}` | Plain string |
94
+ | `url` | `providerMetadata.{key}` | Plain string; URL validation client-side |
95
+ | `number` | `providerMetadata.{key}` | Stored as JS number |
96
+ | `boolean` | `providerMetadata.{key}` | Stored as JS boolean |
97
+
98
+ When adding a new credential field type:
99
+
100
+ 1. Extend `CredentialFieldTypeSchema` in `src/schema.ts`.
101
+ 2. Extend `CredentialFieldType` consumers — the dashboard form renderer in PR 8b and the storage projection in `services/connect/src/providers/custom.ts`'s `buildCredentialsResponse` (if the storage shape differs).
102
+ 3. Document the new type here.
103
+
64
104
  ## Usage
65
105
 
66
106
  ```typescript
package/dist/index.d.ts CHANGED
@@ -1,240 +1,54 @@
1
1
  import { z } from "zod";
2
2
 
3
- //#region src/types.d.ts
3
+ //#region src/schema.d.ts
4
4
 
5
5
  /**
6
- * TypeScript types for the Alfe integration manifest (`alfe-integration.yaml`).
6
+ * Connect provider ids that a manifest's `requires_connection` field may
7
+ * reference. The source of truth lives in `services/connect/src/providers/`
8
+ * (one `ProviderDefinition` per id) — this list mirrors that registry so
9
+ * the manifest schema can validate references statically.
7
10
  *
8
- * These are the canonical types used by all packages that interact with
9
- * integration manifests -- registry, lifecycle manager, CLI, etc.
11
+ * **Keep in sync** with `services/connect/src/providers/index.ts`. Adding a
12
+ * new connect provider requires updating both: add the provider definition
13
+ * there, then add its id here so manifests can declare `requires_connection`
14
+ * against it. Drift in the other direction (id here without provider) is
15
+ * caught at runtime when the integrations service tries to resolve the
16
+ * provider and gets nothing back.
10
17
  */
11
- type ConfigFieldType = 'secret' | 'string' | 'number' | 'boolean' | 'enum' | 'select' | 'multi_select' | 'oauth_connect';
12
- interface SelectOption {
13
- value: string;
14
- label: string;
15
- }
16
- interface ConfigSchemaField {
17
- key: string;
18
- type: ConfigFieldType;
19
- label: string;
20
- description?: string;
21
- required: boolean;
22
- default?: string | number | boolean;
23
- /** Who can mutate this field at runtime. Default: 'admin' */
24
- editable: 'admin' | 'agent';
25
- /** Only used when type === 'enum' */
26
- options?: string[];
27
- /** Structured options for select/multi_select fields */
28
- select_options?: SelectOption[];
29
- /** OAuth provider identifier for oauth_connect fields */
30
- oauth_provider?: string;
31
- /** Force specific scope groups for OAuth (e.g. ["chat"]) hides other scopes in the dashboard */
32
- oauth_scopes?: string[];
33
- /** Integration ID to patch on OAuth callback (when using a shared OAuth provider) */
34
- oauth_integration_id?: string;
35
- /** If true, this field is not shown in the dashboard UI (install wizard or configure modal) */
36
- hidden?: boolean;
37
- /** Only show this field if another field has a truthy value (string) or matches a specific value (object) */
38
- depends_on_field?: string | {
39
- key: string;
40
- value: string | number | boolean;
41
- };
42
- /** Only show this field if a specific integration is installed */
43
- depends_on_integration?: string;
44
- }
45
- interface McpServerDeclaration {
46
- /** Unique identifier within this integration */
47
- id: string;
48
- /** Command to spawn (e.g., 'npx', 'node', 'xero-mcp-proxy') */
49
- command: string;
50
- /** Arguments for the command */
51
- args?: string[];
52
- /**
53
- * Environment variables. Two interpolation syntaxes are supported:
54
- *
55
- * - `{{config.KEY}}` — resolved from the integration's user-supplied
56
- * config + secrets via `resolveConfig()`. Existing behavior.
57
- * - `{{credentials.<provider>.<field>}}` — resolved by the
58
- * `mcp-applier` via `AgentApiClient.get<Provider>Credentials()`.
59
- * `<provider>` is the OAuth provider key (`atlassian`, `github`,
60
- * `xero`, `notion`, `myob`, …); `<field>` is a top-level key on the
61
- * credentials response (`accessToken`, `clientId`, `cloudId`, …).
62
- * Used in conjunction with `requires_credentials` to signal the
63
- * precondition.
64
- */
65
- env?: Record<string, string>;
66
- /** Working directory (optional) */
67
- cwd?: string;
68
- /**
69
- * OAuth provider whose credentials this server requires. When set and
70
- * the credentials are missing or 404, the applier logs and skips
71
- * registration silently — same graceful no-op the `post_activate.mjs`
72
- * hooks do today. Pair with `{{credentials.<provider>.<field>}}` env
73
- * references; the explicit field is preferred over inferring from the
74
- * presence of the interpolation pattern for clarity.
75
- */
76
- requires_credentials?: string;
77
- /**
78
- * @deprecated As of 2026-05-18 the `mcp-applier` handles every case
79
- * end-to-end (config + credentials interpolation, requires_credentials
80
- * skip-silently, manager.addServer with owner attribution). The
81
- * legacy `OpenClawApplier.applyMcpServers` path has been deleted so
82
- * the field is now accepted but completely ignored on parse. Drop
83
- * from new manifests.
84
- */
85
- hook_managed?: boolean;
86
- }
87
- interface CommandDeclaration {
88
- /** Dot-namespaced command name (e.g. "support.diagnostic") */
89
- name: string;
90
- /** Relative path to handler file within integration directory */
91
- handler: string;
92
- /** Exported function name (default: "handle") */
93
- method?: string;
94
- /** Timeout in milliseconds (default: 30000) */
95
- timeout_ms?: number;
96
- /** Human-readable description */
97
- description?: string;
98
- }
99
- interface SkillInstall {
100
- /** Relative path within the integration repo to the skill directory */
101
- path?: string;
102
- /** ClawHub skill slug to install from the registry */
103
- clawhub?: string;
104
- }
105
- interface PluginInstall {
106
- /** npm package name to install */
107
- package: string;
108
- }
109
- type AgentRuntime = 'openclaw' | 'nanoclaw' | (string & {});
110
- interface RuntimeInstall {
111
- plugins?: PluginInstall[];
112
- skills?: SkillInstall[];
113
- /** Deep-merged into the runtime's agent config on activation */
114
- config?: Record<string, unknown>;
115
- }
116
- interface InstallTargets {
117
- /** Universal skills — applied to all runtimes */
118
- skills?: SkillInstall[];
119
- /** Universal plugins — applied to all runtimes */
120
- plugins?: PluginInstall[];
121
- /** Per-runtime installs */
122
- runtimes?: Record<AgentRuntime, RuntimeInstall>;
123
- }
124
- interface IntegrationHooks {
125
- pre_install?: string;
126
- post_install?: string;
127
- post_activate?: string;
128
- pre_uninstall?: string;
129
- post_uninstall?: string;
130
- health_check?: string;
131
- }
132
- interface IntegrationPricingPlan {
133
- name: string;
134
- price: number;
135
- currency?: string;
136
- interval?: 'month' | 'year';
137
- }
138
- interface IntegrationPricing {
139
- type: 'free' | 'paid' | 'usage';
140
- /** Single price (shorthand for integrations with one plan) */
141
- price?: number;
142
- currency?: string;
143
- interval?: 'month' | 'year';
144
- /** Description of usage-based pricing (for type: 'usage') */
145
- description?: string;
146
- /** Multiple plans/tiers (e.g., starter, growth, scale) */
147
- plans?: Record<string, IntegrationPricingPlan>;
148
- }
149
- interface IntegrationAuthor {
150
- name: string;
151
- url?: string;
152
- }
153
- interface IntegrationManifest {
154
- id: string;
155
- name: string;
156
- version: string;
157
- description: string;
158
- /** Simple author string (legacy) or structured author object */
159
- author: string | IntegrationAuthor;
160
- license: string;
161
- depends_on: string[];
162
- min_gateway_version: string;
163
- installs: InstallTargets;
164
- config_schema: ConfigSchemaField[];
165
- capabilities: string[];
166
- hooks: IntegrationHooks;
167
- commands: CommandDeclaration[];
168
- /** MCP servers to configure in the agent runtime */
169
- mcp_servers: McpServerDeclaration[];
170
- /** Git repository URL (HTTPS) — not present in YAML, injected by the publish API */
171
- repository?: string;
172
- /**
173
- * Agent runtimes this integration supports.
174
- * If omitted or empty, the integration is considered universal (all runtimes).
175
- * Example: ['openclaw'] means this integration only works with OpenClaw.
176
- */
177
- supported_agents?: AgentRuntime[];
178
- /**
179
- * Scopes where this integration can be installed.
180
- * Default: ['agent'] (per-agent only).
181
- * 'org' means it can be installed at the org level and cascades to all agents.
182
- */
183
- supported_scopes?: ('agent' | 'org')[];
184
- /** Marketplace metadata */
185
- publisherId?: string;
186
- icon?: string;
187
- pricing?: IntegrationPricing;
188
- preview_images?: string[];
189
- /** Long-form features list for the detail view */
190
- features?: string[];
191
- }
192
- interface PublishedVersion {
193
- version: string;
194
- commit_hash: string;
195
- changelog?: string;
196
- published_at: string;
197
- }
198
- interface PublishedIntegration {
199
- manifest: IntegrationManifest;
200
- versions: PublishedVersion[];
201
- /** Resolved asset URLs baked at publish time */
202
- resolved_assets: {
203
- icon?: string;
204
- preview_images?: string[];
205
- };
206
- }
207
- type IntegrationStatus = 'installing' | 'installed' | 'configured' | 'active' | 'error';
208
- interface IntegrationStateEntry {
209
- status: IntegrationStatus;
210
- version: string;
211
- installedAt: string;
212
- config: Record<string, unknown>;
213
- error?: string;
214
- /** Number of consecutive auto-reinstall attempts. Reset on success or explicit reinstall. */
215
- reinstallAttempts?: number;
216
- }
217
- interface IntegrationsStateFile {
218
- version: number;
219
- integrations: Record<string, IntegrationStateEntry>;
220
- }
221
- interface HealthCheckResult {
222
- id: string;
223
- name?: string;
224
- healthy: boolean;
225
- status: IntegrationStatus;
226
- version?: string;
227
- message?: string;
228
- stdout?: string;
229
- stderr?: string;
230
- }
231
- interface HealthReport {
232
- overall: boolean;
233
- results: HealthCheckResult[];
234
- checkedAt: string;
235
- }
236
- //#endregion
237
- //#region src/schema.d.ts
18
+ declare const KNOWN_CONNECT_PROVIDER_IDS: readonly ["atlassian", "custom", "discord", "github", "google", "microsoft", "mobile", "myob", "notion", "slack", "xero"];
19
+ type KnownConnectProviderId = (typeof KNOWN_CONNECT_PROVIDER_IDS)[number];
20
+ /**
21
+ * `custom` is a special provider id: it doesn't correspond to a fixed
22
+ * external service. Instead, every Custom Connection carries its own
23
+ * manifest URL (a GitHub repo path) on its `providerMetadata`, and the
24
+ * integration framework resolves the manifest at install time.
25
+ *
26
+ * Custom manifests therefore declare `requires_connection: ['custom']`
27
+ * and ship an `expected_credentials` schema (below) describing the
28
+ * credential fields the user must fill out when creating the Connection.
29
+ */
30
+ /**
31
+ * Channel types are the inbound-message-route taxonomy used by
32
+ * `ChannelEntity` (added in PR 2 of the channels-and-credential-driven-
33
+ * integrations plan). A manifest under the submodule's `channels/<id>/`
34
+ * folder declares which channel type it maps to via `channel_type`, and
35
+ * `getDesiredState` activates the manifest when at least one Channel of
36
+ * that type exists in the agent's resolved view.
37
+ *
38
+ * The enum is lockedchannel adapters and the dashboard's compatibility
39
+ * matrix both depend on this exact list.
40
+ */
41
+ declare const LOCKED_CHANNEL_TYPES: readonly ["whatsapp", "sms", "voice", "google_chat", "slack", "discord", "teams"];
42
+ type ChannelType = (typeof LOCKED_CHANNEL_TYPES)[number];
43
+ declare const ChannelTypeSchema: z.ZodEnum<{
44
+ discord: "discord";
45
+ slack: "slack";
46
+ whatsapp: "whatsapp";
47
+ sms: "sms";
48
+ voice: "voice";
49
+ google_chat: "google_chat";
50
+ teams: "teams";
51
+ }>;
238
52
  declare const ConfigFieldTypeSchema: z.ZodEnum<{
239
53
  string: "string";
240
54
  number: "number";
@@ -283,6 +97,42 @@ declare const ConfigSchemaFieldSchema: z.ZodObject<{
283
97
  }, z.core.$strip>]>>;
284
98
  depends_on_integration: z.ZodOptional<z.ZodString>;
285
99
  }, z.core.$strip>;
100
+ /**
101
+ * A field a manifest's owner expects the user to fill in when creating
102
+ * a Custom Connection. Used ONLY by manifests authored against the
103
+ * `custom` connect provider — built-in OAuth-based integrations don't
104
+ * declare these (their credential shape comes from the OAuth flow).
105
+ *
106
+ * The dashboard renders an `<AddCustomConnectionModal>` form whose
107
+ * fields are derived from this array; the backend's
108
+ * `services/connect/api/connections/custom/post.ts` (PR 8b) validates
109
+ * the submitted credential map against this schema before encrypting
110
+ * and storing it on the Connection.
111
+ */
112
+ declare const CredentialFieldTypeSchema: z.ZodEnum<{
113
+ string: "string";
114
+ number: "number";
115
+ boolean: "boolean";
116
+ secret: "secret";
117
+ url: "url";
118
+ }>;
119
+ declare const CredentialFieldSpecSchema: z.ZodObject<{
120
+ key: z.ZodString;
121
+ type: z.ZodEnum<{
122
+ string: "string";
123
+ number: "number";
124
+ boolean: "boolean";
125
+ secret: "secret";
126
+ url: "url";
127
+ }>;
128
+ label: z.ZodString;
129
+ description: z.ZodOptional<z.ZodString>;
130
+ required: z.ZodDefault<z.ZodBoolean>;
131
+ placeholder: z.ZodOptional<z.ZodString>;
132
+ pattern: z.ZodOptional<z.ZodString>;
133
+ }, z.core.$strip>;
134
+ type CredentialFieldSpec = z.infer<typeof CredentialFieldSpecSchema>;
135
+ type CredentialFieldType = z.infer<typeof CredentialFieldTypeSchema>;
286
136
  declare const McpServerDeclarationSchema: z.ZodObject<{
287
137
  id: z.ZodString;
288
138
  command: z.ZodString;
@@ -433,6 +283,43 @@ declare const IntegrationManifestSchema: z.ZodObject<{
433
283
  requires_credentials: z.ZodOptional<z.ZodString>;
434
284
  hook_managed: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
435
285
  }, z.core.$strip>>>>;
286
+ requires_connection: z.ZodOptional<z.ZodArray<z.ZodEnum<{
287
+ atlassian: "atlassian";
288
+ custom: "custom";
289
+ discord: "discord";
290
+ github: "github";
291
+ google: "google";
292
+ microsoft: "microsoft";
293
+ mobile: "mobile";
294
+ myob: "myob";
295
+ notion: "notion";
296
+ slack: "slack";
297
+ xero: "xero";
298
+ }>>>;
299
+ channel_type: z.ZodOptional<z.ZodEnum<{
300
+ discord: "discord";
301
+ slack: "slack";
302
+ whatsapp: "whatsapp";
303
+ sms: "sms";
304
+ voice: "voice";
305
+ google_chat: "google_chat";
306
+ teams: "teams";
307
+ }>>;
308
+ expected_credentials: z.ZodOptional<z.ZodArray<z.ZodObject<{
309
+ key: z.ZodString;
310
+ type: z.ZodEnum<{
311
+ string: "string";
312
+ number: "number";
313
+ boolean: "boolean";
314
+ secret: "secret";
315
+ url: "url";
316
+ }>;
317
+ label: z.ZodString;
318
+ description: z.ZodOptional<z.ZodString>;
319
+ required: z.ZodDefault<z.ZodBoolean>;
320
+ placeholder: z.ZodOptional<z.ZodString>;
321
+ pattern: z.ZodOptional<z.ZodString>;
322
+ }, z.core.$strip>>>;
436
323
  repository: z.ZodOptional<z.ZodURL>;
437
324
  supported_agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
438
325
  supported_scopes: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -476,6 +363,262 @@ declare const IntegrationManifestSchema: z.ZodObject<{
476
363
  declare function buildConfigValidationSchema(configSchema: z.infer<typeof ConfigSchemaFieldSchema>[]): z.ZodObject<Record<string, z.ZodType>>;
477
364
  type ManifestSchemaType = z.infer<typeof IntegrationManifestSchema>;
478
365
  //#endregion
366
+ //#region src/types.d.ts
367
+ type ConfigFieldType = 'secret' | 'string' | 'number' | 'boolean' | 'enum' | 'select' | 'multi_select' | 'oauth_connect';
368
+ interface SelectOption {
369
+ value: string;
370
+ label: string;
371
+ }
372
+ interface ConfigSchemaField {
373
+ key: string;
374
+ type: ConfigFieldType;
375
+ label: string;
376
+ description?: string;
377
+ required: boolean;
378
+ default?: string | number | boolean;
379
+ /** Who can mutate this field at runtime. Default: 'admin' */
380
+ editable: 'admin' | 'agent';
381
+ /** Only used when type === 'enum' */
382
+ options?: string[];
383
+ /** Structured options for select/multi_select fields */
384
+ select_options?: SelectOption[];
385
+ /** OAuth provider identifier for oauth_connect fields */
386
+ oauth_provider?: string;
387
+ /** Force specific scope groups for OAuth (e.g. ["chat"]) — hides other scopes in the dashboard */
388
+ oauth_scopes?: string[];
389
+ /** Integration ID to patch on OAuth callback (when using a shared OAuth provider) */
390
+ oauth_integration_id?: string;
391
+ /** If true, this field is not shown in the dashboard UI (install wizard or configure modal) */
392
+ hidden?: boolean;
393
+ /** Only show this field if another field has a truthy value (string) or matches a specific value (object) */
394
+ depends_on_field?: string | {
395
+ key: string;
396
+ value: string | number | boolean;
397
+ };
398
+ /** Only show this field if a specific integration is installed */
399
+ depends_on_integration?: string;
400
+ }
401
+ interface McpServerDeclaration {
402
+ /** Unique identifier within this integration */
403
+ id: string;
404
+ /** Command to spawn (e.g., 'npx', 'node', 'xero-mcp-proxy') */
405
+ command: string;
406
+ /** Arguments for the command */
407
+ args?: string[];
408
+ /**
409
+ * Environment variables. Two interpolation syntaxes are supported:
410
+ *
411
+ * - `{{config.KEY}}` — resolved from the integration's user-supplied
412
+ * config + secrets via `resolveConfig()`. Existing behavior.
413
+ * - `{{credentials.<provider>.<field>}}` — resolved by the
414
+ * `mcp-applier` via `AgentApiClient.get<Provider>Credentials()`.
415
+ * `<provider>` is the OAuth provider key (`atlassian`, `github`,
416
+ * `xero`, `notion`, `myob`, …); `<field>` is a top-level key on the
417
+ * credentials response (`accessToken`, `clientId`, `cloudId`, …).
418
+ * Used in conjunction with `requires_credentials` to signal the
419
+ * precondition.
420
+ */
421
+ env?: Record<string, string>;
422
+ /** Working directory (optional) */
423
+ cwd?: string;
424
+ /**
425
+ * OAuth provider whose credentials this server requires. When set and
426
+ * the credentials are missing or 404, the applier logs and skips
427
+ * registration silently — same graceful no-op the `post_activate.mjs`
428
+ * hooks do today. Pair with `{{credentials.<provider>.<field>}}` env
429
+ * references; the explicit field is preferred over inferring from the
430
+ * presence of the interpolation pattern for clarity.
431
+ */
432
+ requires_credentials?: string;
433
+ /**
434
+ * @deprecated As of 2026-05-18 the `mcp-applier` handles every case
435
+ * end-to-end (config + credentials interpolation, requires_credentials
436
+ * skip-silently, manager.addServer with owner attribution). The
437
+ * legacy `OpenClawApplier.applyMcpServers` path has been deleted so
438
+ * the field is now accepted but completely ignored on parse. Drop
439
+ * from new manifests.
440
+ */
441
+ hook_managed?: boolean;
442
+ }
443
+ interface CommandDeclaration {
444
+ /** Dot-namespaced command name (e.g. "support.diagnostic") */
445
+ name: string;
446
+ /** Relative path to handler file within integration directory */
447
+ handler: string;
448
+ /** Exported function name (default: "handle") */
449
+ method?: string;
450
+ /** Timeout in milliseconds (default: 30000) */
451
+ timeout_ms?: number;
452
+ /** Human-readable description */
453
+ description?: string;
454
+ }
455
+ interface SkillInstall {
456
+ /** Relative path within the integration repo to the skill directory */
457
+ path?: string;
458
+ /** ClawHub skill slug to install from the registry */
459
+ clawhub?: string;
460
+ }
461
+ interface PluginInstall {
462
+ /** npm package name to install */
463
+ package: string;
464
+ }
465
+ type AgentRuntime = 'openclaw' | 'nanoclaw' | (string & {});
466
+ interface RuntimeInstall {
467
+ plugins?: PluginInstall[];
468
+ skills?: SkillInstall[];
469
+ /** Deep-merged into the runtime's agent config on activation */
470
+ config?: Record<string, unknown>;
471
+ }
472
+ interface InstallTargets {
473
+ /** Universal skills — applied to all runtimes */
474
+ skills?: SkillInstall[];
475
+ /** Universal plugins — applied to all runtimes */
476
+ plugins?: PluginInstall[];
477
+ /** Per-runtime installs */
478
+ runtimes?: Record<AgentRuntime, RuntimeInstall>;
479
+ }
480
+ interface IntegrationHooks {
481
+ pre_install?: string;
482
+ post_install?: string;
483
+ post_activate?: string;
484
+ pre_uninstall?: string;
485
+ post_uninstall?: string;
486
+ health_check?: string;
487
+ }
488
+ interface IntegrationPricingPlan {
489
+ name: string;
490
+ price: number;
491
+ currency?: string;
492
+ interval?: 'month' | 'year';
493
+ }
494
+ interface IntegrationPricing {
495
+ type: 'free' | 'paid' | 'usage';
496
+ /** Single price (shorthand for integrations with one plan) */
497
+ price?: number;
498
+ currency?: string;
499
+ interval?: 'month' | 'year';
500
+ /** Description of usage-based pricing (for type: 'usage') */
501
+ description?: string;
502
+ /** Multiple plans/tiers (e.g., starter, growth, scale) */
503
+ plans?: Record<string, IntegrationPricingPlan>;
504
+ }
505
+ interface IntegrationAuthor {
506
+ name: string;
507
+ url?: string;
508
+ }
509
+ interface IntegrationManifest {
510
+ id: string;
511
+ name: string;
512
+ version: string;
513
+ description: string;
514
+ /** Simple author string (legacy) or structured author object */
515
+ author: string | IntegrationAuthor;
516
+ license: string;
517
+ depends_on: string[];
518
+ min_gateway_version: string;
519
+ installs: InstallTargets;
520
+ config_schema: ConfigSchemaField[];
521
+ capabilities: string[];
522
+ hooks: IntegrationHooks;
523
+ commands: CommandDeclaration[];
524
+ /** MCP servers to configure in the agent runtime */
525
+ mcp_servers: McpServerDeclaration[];
526
+ /** Git repository URL (HTTPS) — not present in YAML, injected by the publish API */
527
+ repository?: string;
528
+ /**
529
+ * Agent runtimes this integration supports.
530
+ * If omitted or empty, the integration is considered universal (all runtimes).
531
+ * Example: ['openclaw'] means this integration only works with OpenClaw.
532
+ */
533
+ supported_agents?: AgentRuntime[];
534
+ /**
535
+ * Scopes where this integration can be installed.
536
+ * Default: ['agent'] (per-agent only).
537
+ * 'org' means it can be installed at the org level and cascades to all agents.
538
+ */
539
+ supported_scopes?: ('agent' | 'org')[];
540
+ /**
541
+ * Connect provider ids whose credentials this integration resolves at
542
+ * runtime. OR-semantics: any one in the list satisfies activation. See
543
+ * the `requires_connection` field in `schema.ts` for full semantics.
544
+ * Typed as `KnownConnectProviderId[]` so consumers get compile-time
545
+ * narrowing matching the schema's runtime validation.
546
+ */
547
+ requires_connection?: KnownConnectProviderId[];
548
+ /**
549
+ * Channel type this integration adapts. Populated on integrations under
550
+ * the submodule's `channels/<id>/` folder that map to a single channel
551
+ * type. See `channel_type` in `schema.ts` for full semantics.
552
+ */
553
+ channel_type?: ChannelType;
554
+ /**
555
+ * Credential fields the manifest expects a Custom Connection's owner
556
+ * to fill in at create time. Used ONLY by manifests authored against
557
+ * the `custom` connect provider. See `expected_credentials` in
558
+ * `schema.ts` for full semantics.
559
+ */
560
+ expected_credentials?: CredentialFieldSpec[];
561
+ /** Marketplace metadata */
562
+ publisherId?: string;
563
+ icon?: string;
564
+ pricing?: IntegrationPricing;
565
+ preview_images?: string[];
566
+ /** Long-form features list for the detail view */
567
+ features?: string[];
568
+ }
569
+ interface PublishedVersion {
570
+ version: string;
571
+ commit_hash: string;
572
+ changelog?: string;
573
+ published_at: string;
574
+ }
575
+ interface PublishedIntegration {
576
+ manifest: IntegrationManifest;
577
+ versions: PublishedVersion[];
578
+ /** Resolved asset URLs baked at publish time */
579
+ resolved_assets: {
580
+ icon?: string;
581
+ preview_images?: string[];
582
+ };
583
+ }
584
+ type IntegrationStatus = 'installing' | 'installed' | 'configured' | 'active' | 'error';
585
+ interface IntegrationStateEntry {
586
+ status: IntegrationStatus;
587
+ version: string;
588
+ installedAt: string;
589
+ config: Record<string, unknown>;
590
+ error?: string;
591
+ /** Number of consecutive auto-reinstall attempts. Reset on success or explicit reinstall. */
592
+ reinstallAttempts?: number;
593
+ /**
594
+ * For Custom Connection-driven installs: the backing custom
595
+ * Connection's id. Persisted at install time so `activate()` can
596
+ * scope credential resolution to that specific Connection
597
+ * (`{{credentials.custom.*}}` resolves against this connectionId).
598
+ * Absent for registry/channel/connection-driven integrations.
599
+ */
600
+ customConnectionId?: string;
601
+ }
602
+ interface IntegrationsStateFile {
603
+ version: number;
604
+ integrations: Record<string, IntegrationStateEntry>;
605
+ }
606
+ interface HealthCheckResult {
607
+ id: string;
608
+ name?: string;
609
+ healthy: boolean;
610
+ status: IntegrationStatus;
611
+ version?: string;
612
+ message?: string;
613
+ stdout?: string;
614
+ stderr?: string;
615
+ }
616
+ interface HealthReport {
617
+ overall: boolean;
618
+ results: HealthCheckResult[];
619
+ checkedAt: string;
620
+ }
621
+ //#endregion
479
622
  //#region src/parser.d.ts
480
623
  /**
481
624
  * Pluggable warn-sink for parse-time advisories. Defaults to the
@@ -611,4 +754,4 @@ declare function migrateConfig(currentConfig: Record<string, unknown>, diff: Con
611
754
  warnings: string[];
612
755
  };
613
756
  //#endregion
614
- export { type AgentRuntime, type CommandContext, type CommandDeclaration, CommandDeclarationSchema, type CommandHandlerFn, type CommandResult, type ConfigFieldType, ConfigFieldTypeSchema, type ConfigSchemaDiff, type ConfigSchemaField, ConfigSchemaFieldSchema, type HealthCheckResult, type HealthReport, type InstallTargets, InstallTargetsSchema, type IntegrationHooks, IntegrationHooksSchema, type IntegrationManifest, IntegrationManifestSchema, type IntegrationPricing, type IntegrationPricingPlan, type IntegrationStateEntry, type IntegrationStatus, type IntegrationsStateFile, type InterpolationContext, ManifestParseError, type ManifestSchemaType, type McpServerDeclaration, McpServerDeclarationSchema, type ParseWarner, type PluginInstall, PluginInstallSchema, type PublishedIntegration, type PublishedVersion, type RuntimeInstall, RuntimeInstallSchema, type SelectOption, SelectOptionSchema, type SkillInstall, SkillInstallSchema, buildConfigValidationSchema, diffConfigSchemas, extractTemplateReferences, interpolateConfig, isRelativePath, migrateConfig, parseManifestFile, parseManifestString, resolveAssetUrl, setParseWarner };
757
+ export { type AgentRuntime, type ChannelType, ChannelTypeSchema, type CommandContext, type CommandDeclaration, CommandDeclarationSchema, type CommandHandlerFn, type CommandResult, type ConfigFieldType, ConfigFieldTypeSchema, type ConfigSchemaDiff, type ConfigSchemaField, ConfigSchemaFieldSchema, type CredentialFieldSpec, CredentialFieldSpecSchema, type CredentialFieldType, CredentialFieldTypeSchema, type HealthCheckResult, type HealthReport, type InstallTargets, InstallTargetsSchema, type IntegrationHooks, IntegrationHooksSchema, type IntegrationManifest, IntegrationManifestSchema, type IntegrationPricing, type IntegrationPricingPlan, type IntegrationStateEntry, type IntegrationStatus, type IntegrationsStateFile, type InterpolationContext, KNOWN_CONNECT_PROVIDER_IDS, type KnownConnectProviderId, LOCKED_CHANNEL_TYPES, ManifestParseError, type ManifestSchemaType, type McpServerDeclaration, McpServerDeclarationSchema, type ParseWarner, type PluginInstall, PluginInstallSchema, type PublishedIntegration, type PublishedVersion, type RuntimeInstall, RuntimeInstallSchema, type SelectOption, SelectOptionSchema, type SkillInstall, SkillInstallSchema, buildConfigValidationSchema, diffConfigSchemas, extractTemplateReferences, interpolateConfig, isRelativePath, migrateConfig, parseManifestFile, parseManifestString, resolveAssetUrl, setParseWarner };
package/dist/index.js CHANGED
@@ -9,6 +9,63 @@ import { parse } from "yaml";
9
9
  * Used by the parser and by the lifecycle manager to ensure manifest
10
10
  * correctness before installation.
11
11
  */
12
+ /**
13
+ * Connect provider ids that a manifest's `requires_connection` field may
14
+ * reference. The source of truth lives in `services/connect/src/providers/`
15
+ * (one `ProviderDefinition` per id) — this list mirrors that registry so
16
+ * the manifest schema can validate references statically.
17
+ *
18
+ * **Keep in sync** with `services/connect/src/providers/index.ts`. Adding a
19
+ * new connect provider requires updating both: add the provider definition
20
+ * there, then add its id here so manifests can declare `requires_connection`
21
+ * against it. Drift in the other direction (id here without provider) is
22
+ * caught at runtime when the integrations service tries to resolve the
23
+ * provider and gets nothing back.
24
+ */
25
+ const KNOWN_CONNECT_PROVIDER_IDS = [
26
+ "atlassian",
27
+ "custom",
28
+ "discord",
29
+ "github",
30
+ "google",
31
+ "microsoft",
32
+ "mobile",
33
+ "myob",
34
+ "notion",
35
+ "slack",
36
+ "xero"
37
+ ];
38
+ /**
39
+ * `custom` is a special provider id: it doesn't correspond to a fixed
40
+ * external service. Instead, every Custom Connection carries its own
41
+ * manifest URL (a GitHub repo path) on its `providerMetadata`, and the
42
+ * integration framework resolves the manifest at install time.
43
+ *
44
+ * Custom manifests therefore declare `requires_connection: ['custom']`
45
+ * and ship an `expected_credentials` schema (below) describing the
46
+ * credential fields the user must fill out when creating the Connection.
47
+ */
48
+ /**
49
+ * Channel types are the inbound-message-route taxonomy used by
50
+ * `ChannelEntity` (added in PR 2 of the channels-and-credential-driven-
51
+ * integrations plan). A manifest under the submodule's `channels/<id>/`
52
+ * folder declares which channel type it maps to via `channel_type`, and
53
+ * `getDesiredState` activates the manifest when at least one Channel of
54
+ * that type exists in the agent's resolved view.
55
+ *
56
+ * The enum is locked — channel adapters and the dashboard's compatibility
57
+ * matrix both depend on this exact list.
58
+ */
59
+ const LOCKED_CHANNEL_TYPES = [
60
+ "whatsapp",
61
+ "sms",
62
+ "voice",
63
+ "google_chat",
64
+ "slack",
65
+ "discord",
66
+ "teams"
67
+ ];
68
+ const ChannelTypeSchema = z.enum(LOCKED_CHANNEL_TYPES);
12
69
  const ConfigFieldTypeSchema = z.enum([
13
70
  "secret",
14
71
  "string",
@@ -59,6 +116,34 @@ const ConfigSchemaFieldSchema = z.object({
59
116
  if (field.type === "oauth_connect") return field.oauth_provider !== void 0 && field.oauth_provider.length > 0;
60
117
  return true;
61
118
  }, { message: "oauth_connect fields must specify an oauth_provider" });
119
+ /**
120
+ * A field a manifest's owner expects the user to fill in when creating
121
+ * a Custom Connection. Used ONLY by manifests authored against the
122
+ * `custom` connect provider — built-in OAuth-based integrations don't
123
+ * declare these (their credential shape comes from the OAuth flow).
124
+ *
125
+ * The dashboard renders an `<AddCustomConnectionModal>` form whose
126
+ * fields are derived from this array; the backend's
127
+ * `services/connect/api/connections/custom/post.ts` (PR 8b) validates
128
+ * the submitted credential map against this schema before encrypting
129
+ * and storing it on the Connection.
130
+ */
131
+ const CredentialFieldTypeSchema = z.enum([
132
+ "string",
133
+ "secret",
134
+ "url",
135
+ "number",
136
+ "boolean"
137
+ ]);
138
+ const CredentialFieldSpecSchema = z.object({
139
+ key: z.string().min(1, "Credential field key must not be empty").regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/, "Credential field key must be a valid identifier (letters, digits, underscores; not starting with a digit)"),
140
+ type: CredentialFieldTypeSchema,
141
+ label: z.string().min(1, "Credential field label must not be empty"),
142
+ description: z.string().optional(),
143
+ required: z.boolean().default(true),
144
+ placeholder: z.string().optional(),
145
+ pattern: z.string().max(256).optional()
146
+ });
62
147
  const McpServerDeclarationSchema = z.object({
63
148
  id: z.string().min(1, "MCP server id must not be empty").regex(/^[a-z0-9][a-z0-9-]*$/, "MCP server id must be lowercase alphanumeric with hyphens"),
64
149
  command: z.string().min(1, "MCP server command must not be empty"),
@@ -149,6 +234,9 @@ const IntegrationManifestSchema = z.object({
149
234
  hooks: IntegrationHooksSchema.optional().default({}),
150
235
  commands: z.array(CommandDeclarationSchema).optional().default([]),
151
236
  mcp_servers: z.array(McpServerDeclarationSchema).optional().default([]),
237
+ requires_connection: z.array(z.enum(KNOWN_CONNECT_PROVIDER_IDS)).optional(),
238
+ channel_type: ChannelTypeSchema.optional(),
239
+ expected_credentials: z.array(CredentialFieldSpecSchema).optional(),
152
240
  repository: z.url().optional(),
153
241
  supported_agents: z.array(RuntimeKeySchema).optional(),
154
242
  supported_scopes: z.array(z.enum([
@@ -421,4 +509,4 @@ function migrateConfig(currentConfig, diff) {
421
509
  };
422
510
  }
423
511
  //#endregion
424
- export { CommandDeclarationSchema, ConfigFieldTypeSchema, ConfigSchemaFieldSchema, InstallTargetsSchema, IntegrationHooksSchema, IntegrationManifestSchema, ManifestParseError, McpServerDeclarationSchema, PluginInstallSchema, RuntimeInstallSchema, SelectOptionSchema, SkillInstallSchema, buildConfigValidationSchema, diffConfigSchemas, extractTemplateReferences, interpolateConfig, isRelativePath, migrateConfig, parseManifestFile, parseManifestString, resolveAssetUrl, setParseWarner };
512
+ export { ChannelTypeSchema, CommandDeclarationSchema, ConfigFieldTypeSchema, ConfigSchemaFieldSchema, CredentialFieldSpecSchema, CredentialFieldTypeSchema, InstallTargetsSchema, IntegrationHooksSchema, IntegrationManifestSchema, KNOWN_CONNECT_PROVIDER_IDS, LOCKED_CHANNEL_TYPES, ManifestParseError, McpServerDeclarationSchema, PluginInstallSchema, RuntimeInstallSchema, SelectOptionSchema, SkillInstallSchema, buildConfigValidationSchema, diffConfigSchemas, extractTemplateReferences, interpolateConfig, isRelativePath, migrateConfig, parseManifestFile, parseManifestString, resolveAssetUrl, setParseWarner };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/integration-manifest",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Integration manifest schema, types, and parser for Alfe integration platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",