@alfe.ai/integration-manifest 0.0.11 → 0.2.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 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,208 +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
- /** If true, this field is not shown in the dashboard UI (install wizard or configure modal) */
32
- hidden?: boolean;
33
- /** Only show this field if another field has a truthy value (string) or matches a specific value (object) */
34
- depends_on_field?: string | {
35
- key: string;
36
- value: string | number | boolean;
37
- };
38
- /** Only show this field if a specific integration is installed */
39
- depends_on_integration?: string;
40
- }
41
- interface McpServerDeclaration {
42
- /** Unique identifier within this integration */
43
- id: string;
44
- /** Command to spawn (e.g., 'npx', 'node', 'xero-mcp-proxy') */
45
- command: string;
46
- /** Arguments for the command */
47
- args?: string[];
48
- /** Environment variables — supports {{config.KEY}} interpolation from config + secrets */
49
- env?: Record<string, string>;
50
- /** Working directory (optional) */
51
- cwd?: string;
52
- /** If true, applier skips auto-application — a lifecycle hook manages this MCP server instead */
53
- hook_managed?: boolean;
54
- }
55
- interface CommandDeclaration {
56
- /** Dot-namespaced command name (e.g. "support.diagnostic") */
57
- name: string;
58
- /** Relative path to handler file within integration directory */
59
- handler: string;
60
- /** Exported function name (default: "handle") */
61
- method?: string;
62
- /** Timeout in milliseconds (default: 30000) */
63
- timeout_ms?: number;
64
- /** Human-readable description */
65
- description?: string;
66
- }
67
- interface SkillInstall {
68
- /** Relative path within the integration repo to the skill directory */
69
- path?: string;
70
- /** ClawHub skill slug to install from the registry */
71
- clawhub?: string;
72
- }
73
- interface PluginInstall {
74
- /** npm package name to install */
75
- package: string;
76
- }
77
- type AgentRuntime = 'openclaw' | 'nanoclaw' | (string & {});
78
- interface RuntimeInstall {
79
- plugins?: PluginInstall[];
80
- skills?: SkillInstall[];
81
- /** Deep-merged into the runtime's agent config on activation */
82
- config?: Record<string, unknown>;
83
- }
84
- interface InstallTargets {
85
- /** Universal skills — applied to all runtimes */
86
- skills?: SkillInstall[];
87
- /** Universal plugins — applied to all runtimes */
88
- plugins?: PluginInstall[];
89
- /** Per-runtime installs */
90
- runtimes?: Record<AgentRuntime, RuntimeInstall>;
91
- }
92
- interface IntegrationHooks {
93
- pre_install?: string;
94
- post_install?: string;
95
- post_activate?: string;
96
- pre_uninstall?: string;
97
- post_uninstall?: string;
98
- health_check?: string;
99
- }
100
- interface IntegrationPricingPlan {
101
- name: string;
102
- price: number;
103
- currency?: string;
104
- interval?: 'month' | 'year';
105
- }
106
- interface IntegrationPricing {
107
- type: 'free' | 'paid' | 'usage';
108
- /** Single price (shorthand for integrations with one plan) */
109
- price?: number;
110
- currency?: string;
111
- interval?: 'month' | 'year';
112
- /** Description of usage-based pricing (for type: 'usage') */
113
- description?: string;
114
- /** Multiple plans/tiers (e.g., starter, growth, scale) */
115
- plans?: Record<string, IntegrationPricingPlan>;
116
- }
117
- interface IntegrationAuthor {
118
- name: string;
119
- url?: string;
120
- }
121
- interface IntegrationManifest {
122
- id: string;
123
- name: string;
124
- version: string;
125
- description: string;
126
- /** Simple author string (legacy) or structured author object */
127
- author: string | IntegrationAuthor;
128
- license: string;
129
- depends_on: string[];
130
- min_gateway_version: string;
131
- installs: InstallTargets;
132
- config_schema: ConfigSchemaField[];
133
- capabilities: string[];
134
- hooks: IntegrationHooks;
135
- commands: CommandDeclaration[];
136
- /** MCP servers to configure in the agent runtime */
137
- mcp_servers: McpServerDeclaration[];
138
- /** Git repository URL (HTTPS) — not present in YAML, injected by the publish API */
139
- repository?: string;
140
- /**
141
- * Agent runtimes this integration supports.
142
- * If omitted or empty, the integration is considered universal (all runtimes).
143
- * Example: ['openclaw'] means this integration only works with OpenClaw.
144
- */
145
- supported_agents?: AgentRuntime[];
146
- /**
147
- * Scopes where this integration can be installed.
148
- * Default: ['agent'] (per-agent only).
149
- * 'org' means it can be installed at the org level and cascades to all agents.
150
- */
151
- supported_scopes?: ('agent' | 'org')[];
152
- /** Marketplace metadata */
153
- publisherId?: string;
154
- icon?: string;
155
- pricing?: IntegrationPricing;
156
- preview_images?: string[];
157
- /** Long-form features list for the detail view */
158
- features?: string[];
159
- }
160
- interface PublishedVersion {
161
- version: string;
162
- commit_hash: string;
163
- changelog?: string;
164
- published_at: string;
165
- }
166
- interface PublishedIntegration {
167
- manifest: IntegrationManifest;
168
- versions: PublishedVersion[];
169
- /** Resolved asset URLs baked at publish time */
170
- resolved_assets: {
171
- icon?: string;
172
- preview_images?: string[];
173
- };
174
- }
175
- type IntegrationStatus = 'installing' | 'installed' | 'configured' | 'active' | 'error';
176
- interface IntegrationStateEntry {
177
- status: IntegrationStatus;
178
- version: string;
179
- installedAt: string;
180
- config: Record<string, unknown>;
181
- error?: string;
182
- /** Number of consecutive auto-reinstall attempts. Reset on success or explicit reinstall. */
183
- reinstallAttempts?: number;
184
- }
185
- interface IntegrationsStateFile {
186
- version: number;
187
- integrations: Record<string, IntegrationStateEntry>;
188
- }
189
- interface HealthCheckResult {
190
- id: string;
191
- name?: string;
192
- healthy: boolean;
193
- status: IntegrationStatus;
194
- version?: string;
195
- message?: string;
196
- stdout?: string;
197
- stderr?: string;
198
- }
199
- interface HealthReport {
200
- overall: boolean;
201
- results: HealthCheckResult[];
202
- checkedAt: string;
203
- }
204
- //#endregion
205
- //#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 locked channel 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
+ }>;
206
52
  declare const ConfigFieldTypeSchema: z.ZodEnum<{
207
53
  string: "string";
208
54
  number: "number";
@@ -251,12 +97,49 @@ declare const ConfigSchemaFieldSchema: z.ZodObject<{
251
97
  }, z.core.$strip>]>>;
252
98
  depends_on_integration: z.ZodOptional<z.ZodString>;
253
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>;
254
136
  declare const McpServerDeclarationSchema: z.ZodObject<{
255
137
  id: z.ZodString;
256
138
  command: z.ZodString;
257
139
  args: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
258
140
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
259
141
  cwd: z.ZodOptional<z.ZodString>;
142
+ requires_credentials: z.ZodOptional<z.ZodString>;
260
143
  hook_managed: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
261
144
  }, z.core.$strip>;
262
145
  declare const CommandDeclarationSchema: z.ZodObject<{
@@ -397,8 +280,46 @@ declare const IntegrationManifestSchema: z.ZodObject<{
397
280
  args: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
398
281
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
399
282
  cwd: z.ZodOptional<z.ZodString>;
283
+ requires_credentials: z.ZodOptional<z.ZodString>;
400
284
  hook_managed: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
401
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>>>;
402
323
  repository: z.ZodOptional<z.ZodURL>;
403
324
  supported_agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
404
325
  supported_scopes: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEnum<{
@@ -442,7 +363,263 @@ declare const IntegrationManifestSchema: z.ZodObject<{
442
363
  declare function buildConfigValidationSchema(configSchema: z.infer<typeof ConfigSchemaFieldSchema>[]): z.ZodObject<Record<string, z.ZodType>>;
443
364
  type ManifestSchemaType = z.infer<typeof IntegrationManifestSchema>;
444
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
+ interface IntegrationsStateFile {
595
+ version: number;
596
+ integrations: Record<string, IntegrationStateEntry>;
597
+ }
598
+ interface HealthCheckResult {
599
+ id: string;
600
+ name?: string;
601
+ healthy: boolean;
602
+ status: IntegrationStatus;
603
+ version?: string;
604
+ message?: string;
605
+ stdout?: string;
606
+ stderr?: string;
607
+ }
608
+ interface HealthReport {
609
+ overall: boolean;
610
+ results: HealthCheckResult[];
611
+ checkedAt: string;
612
+ }
613
+ //#endregion
445
614
  //#region src/parser.d.ts
615
+ /**
616
+ * Pluggable warn-sink for parse-time advisories. Defaults to the
617
+ * Node `console.warn` so the message lands in the right place
618
+ * regardless of which logger the caller uses; callers that want
619
+ * structured logs can override.
620
+ */
621
+ type ParseWarner = (message: string, meta?: Record<string, unknown>) => void;
622
+ declare function setParseWarner(warner: ParseWarner): void;
446
623
  declare class ManifestParseError extends Error {
447
624
  readonly issues?: {
448
625
  path: string;
@@ -569,4 +746,4 @@ declare function migrateConfig(currentConfig: Record<string, unknown>, diff: Con
569
746
  warnings: string[];
570
747
  };
571
748
  //#endregion
572
- 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 PluginInstall, PluginInstallSchema, type PublishedIntegration, type PublishedVersion, type RuntimeInstall, RuntimeInstallSchema, type SelectOption, SelectOptionSchema, type SkillInstall, SkillInstallSchema, buildConfigValidationSchema, diffConfigSchemas, extractTemplateReferences, interpolateConfig, isRelativePath, migrateConfig, parseManifestFile, parseManifestString, resolveAssetUrl };
749
+ 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,12 +116,41 @@ 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"),
65
150
  args: z.array(z.string()).optional().default([]),
66
151
  env: z.record(z.string(), z.string()).optional(),
67
152
  cwd: z.string().optional(),
153
+ requires_credentials: z.string().min(1).optional(),
68
154
  hook_managed: z.boolean().optional().default(false)
69
155
  });
70
156
  const CommandDeclarationSchema = z.object({
@@ -148,6 +234,9 @@ const IntegrationManifestSchema = z.object({
148
234
  hooks: IntegrationHooksSchema.optional().default({}),
149
235
  commands: z.array(CommandDeclarationSchema).optional().default([]),
150
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(),
151
240
  repository: z.url().optional(),
152
241
  supported_agents: z.array(RuntimeKeySchema).optional(),
153
242
  supported_scopes: z.array(z.enum([
@@ -217,6 +306,13 @@ function buildConfigValidationSchema(configSchema) {
217
306
  * and validates against the Zod schema. Returns a fully typed
218
307
  * IntegrationManifest or throws a descriptive error.
219
308
  */
309
+ let parseWarner = (msg, meta) => {
310
+ if (meta && Object.keys(meta).length > 0) console.warn(`[integration-manifest] ${msg}`, meta);
311
+ else console.warn(`[integration-manifest] ${msg}`);
312
+ };
313
+ function setParseWarner(warner) {
314
+ parseWarner = warner;
315
+ }
220
316
  var ManifestParseError = class extends Error {
221
317
  constructor(message, issues) {
222
318
  super(message);
@@ -248,7 +344,22 @@ function parseManifestString(yaml, overrides) {
248
344
  }));
249
345
  throw new ManifestParseError(`Invalid integration manifest:\n${issues.map((i) => ` ${i.path ? `${i.path}: ` : ""}${i.message}`).join("\n")}`, issues);
250
346
  }
251
- return result.data;
347
+ const manifest = result.data;
348
+ warnDeprecatedFields(manifest);
349
+ return manifest;
350
+ }
351
+ /**
352
+ * Emit one-time-per-parse advisories for fields that pass schema
353
+ * validation but are deprecated. The flag is accepted-but-ignored,
354
+ * not rejected — the warn is the only signal manifest authors get
355
+ * since the `@deprecated` JSDoc only surfaces in TS callers' IDEs.
356
+ */
357
+ function warnDeprecatedFields(manifest) {
358
+ const hookManagedServers = manifest.mcp_servers.filter((s) => s.hook_managed === true).map((s) => s.id);
359
+ if (hookManagedServers.length > 0) parseWarner(`manifest "${manifest.id}" declares mcp_servers with deprecated hook_managed:true — drop the field, the mcp-applier handles every case end-to-end now`, {
360
+ integration: manifest.id,
361
+ servers: hookManagedServers
362
+ });
252
363
  }
253
364
  /**
254
365
  * Read and parse an alfe-integration.yaml file from disk.
@@ -398,4 +509,4 @@ function migrateConfig(currentConfig, diff) {
398
509
  };
399
510
  }
400
511
  //#endregion
401
- export { CommandDeclarationSchema, ConfigFieldTypeSchema, ConfigSchemaFieldSchema, InstallTargetsSchema, IntegrationHooksSchema, IntegrationManifestSchema, ManifestParseError, McpServerDeclarationSchema, PluginInstallSchema, RuntimeInstallSchema, SelectOptionSchema, SkillInstallSchema, buildConfigValidationSchema, diffConfigSchemas, extractTemplateReferences, interpolateConfig, isRelativePath, migrateConfig, parseManifestFile, parseManifestString, resolveAssetUrl };
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.0.11",
3
+ "version": "0.2.0",
4
4
  "description": "Integration manifest schema, types, and parser for Alfe integration platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",