@ekanos/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/components/base-activation-dialog.d.ts +41 -0
  4. package/dist/components/base-activation-dialog.js +41 -0
  5. package/dist/components/base-activation-dialog.js.map +1 -0
  6. package/dist/components/base-activation-form.d.ts +36 -0
  7. package/dist/components/base-activation-form.js +21 -0
  8. package/dist/components/base-activation-form.js.map +1 -0
  9. package/dist/components/base-marketplace-tile.d.ts +34 -0
  10. package/dist/components/base-marketplace-tile.js +40 -0
  11. package/dist/components/base-marketplace-tile.js.map +1 -0
  12. package/dist/components/current-return-path.d.ts +19 -0
  13. package/dist/components/current-return-path.js +26 -0
  14. package/dist/components/current-return-path.js.map +1 -0
  15. package/dist/components/index.d.ts +40 -0
  16. package/dist/components/index.js +38 -0
  17. package/dist/components/index.js.map +1 -0
  18. package/dist/components/oauth-activation-form.d.ts +50 -0
  19. package/dist/components/oauth-activation-form.js +119 -0
  20. package/dist/components/oauth-activation-form.js.map +1 -0
  21. package/dist/components/widgets/ai-prompt-chip.d.ts +31 -0
  22. package/dist/components/widgets/ai-prompt-chip.js +38 -0
  23. package/dist/components/widgets/ai-prompt-chip.js.map +1 -0
  24. package/dist/components/widgets/ask-icon.d.ts +3 -0
  25. package/dist/components/widgets/ask-icon.js +5 -0
  26. package/dist/components/widgets/ask-icon.js.map +1 -0
  27. package/dist/components/widgets/use-animated-height.d.ts +12 -0
  28. package/dist/components/widgets/use-animated-height.js +39 -0
  29. package/dist/components/widgets/use-animated-height.js.map +1 -0
  30. package/dist/components/widgets/widget-ask-bar.d.ts +18 -0
  31. package/dist/components/widgets/widget-ask-bar.js +62 -0
  32. package/dist/components/widgets/widget-ask-bar.js.map +1 -0
  33. package/dist/components/widgets/widget-context.d.ts +38 -0
  34. package/dist/components/widgets/widget-context.js +4 -0
  35. package/dist/components/widgets/widget-context.js.map +1 -0
  36. package/dist/components/widgets/widget-state-components.d.ts +5 -0
  37. package/dist/components/widgets/widget-state-components.js +19 -0
  38. package/dist/components/widgets/widget-state-components.js.map +1 -0
  39. package/dist/components/widgets/widget.d.ts +91 -0
  40. package/dist/components/widgets/widget.js +205 -0
  41. package/dist/components/widgets/widget.js.map +1 -0
  42. package/dist/context/egress.d.ts +30 -0
  43. package/dist/context/egress.js +81 -0
  44. package/dist/context/egress.js.map +1 -0
  45. package/dist/context/errors.d.ts +57 -0
  46. package/dist/context/errors.js +78 -0
  47. package/dist/context/errors.js.map +1 -0
  48. package/dist/context/index.d.ts +19 -0
  49. package/dist/context/index.js +20 -0
  50. package/dist/context/index.js.map +1 -0
  51. package/dist/context/require-context.d.ts +26 -0
  52. package/dist/context/require-context.js +36 -0
  53. package/dist/context/require-context.js.map +1 -0
  54. package/dist/context/storage-validation.d.ts +71 -0
  55. package/dist/context/storage-validation.js +132 -0
  56. package/dist/context/storage-validation.js.map +1 -0
  57. package/dist/context/types.d.ts +11 -0
  58. package/dist/context/types.js +2 -0
  59. package/dist/context/types.js.map +1 -0
  60. package/dist/hooks/activation-actions-context.d.ts +80 -0
  61. package/dist/hooks/activation-actions-context.js +31 -0
  62. package/dist/hooks/activation-actions-context.js.map +1 -0
  63. package/dist/hooks/ask-assistant-context.d.ts +32 -0
  64. package/dist/hooks/ask-assistant-context.js +56 -0
  65. package/dist/hooks/ask-assistant-context.js.map +1 -0
  66. package/dist/hooks/fetch-integration-storage.d.ts +48 -0
  67. package/dist/hooks/fetch-integration-storage.js +68 -0
  68. package/dist/hooks/fetch-integration-storage.js.map +1 -0
  69. package/dist/hooks/index.d.ts +28 -0
  70. package/dist/hooks/index.js +32 -0
  71. package/dist/hooks/index.js.map +1 -0
  72. package/dist/hooks/use-activate-integration.d.ts +15 -0
  73. package/dist/hooks/use-activate-integration.js +61 -0
  74. package/dist/hooks/use-activate-integration.js.map +1 -0
  75. package/dist/hooks/use-oauth-connection-status.d.ts +360 -0
  76. package/dist/hooks/use-oauth-connection-status.js +108 -0
  77. package/dist/hooks/use-oauth-connection-status.js.map +1 -0
  78. package/dist/hooks/use-widget-events.d.ts +20 -0
  79. package/dist/hooks/use-widget-events.js +51 -0
  80. package/dist/hooks/use-widget-events.js.map +1 -0
  81. package/dist/hooks/widget-event-context.d.ts +22 -0
  82. package/dist/hooks/widget-event-context.js +75 -0
  83. package/dist/hooks/widget-event-context.js.map +1 -0
  84. package/dist/hooks/widget-event-types.d.ts +55 -0
  85. package/dist/hooks/widget-event-types.js +9 -0
  86. package/dist/hooks/widget-event-types.js.map +1 -0
  87. package/dist/index.d.ts +19 -0
  88. package/dist/index.js +19 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/integration/cron.d.ts +29 -0
  91. package/dist/integration/cron.js +102 -0
  92. package/dist/integration/cron.js.map +1 -0
  93. package/dist/integration/define-integration.d.ts +28 -0
  94. package/dist/integration/define-integration.js +41 -0
  95. package/dist/integration/define-integration.js.map +1 -0
  96. package/dist/integration/event-surfaces.d.ts +15 -0
  97. package/dist/integration/event-surfaces.js +45 -0
  98. package/dist/integration/event-surfaces.js.map +1 -0
  99. package/dist/integration/index.d.ts +20 -0
  100. package/dist/integration/index.js +23 -0
  101. package/dist/integration/index.js.map +1 -0
  102. package/dist/integration/types.d.ts +7 -0
  103. package/dist/integration/types.js +2 -0
  104. package/dist/integration/types.js.map +1 -0
  105. package/dist/mcp/guards.d.ts +40 -0
  106. package/dist/mcp/guards.js +99 -0
  107. package/dist/mcp/guards.js.map +1 -0
  108. package/dist/mcp/index.d.ts +22 -0
  109. package/dist/mcp/index.js +22 -0
  110. package/dist/mcp/index.js.map +1 -0
  111. package/dist/mcp/ownership.d.ts +74 -0
  112. package/dist/mcp/ownership.js +83 -0
  113. package/dist/mcp/ownership.js.map +1 -0
  114. package/dist/mcp/types.d.ts +145 -0
  115. package/dist/mcp/types.js +2 -0
  116. package/dist/mcp/types.js.map +1 -0
  117. package/dist/testing/index.d.ts +16 -0
  118. package/dist/testing/index.js +19 -0
  119. package/dist/testing/index.js.map +1 -0
  120. package/dist/testing/invoke.d.ts +77 -0
  121. package/dist/testing/invoke.js +83 -0
  122. package/dist/testing/invoke.js.map +1 -0
  123. package/dist/testing/mock-context.d.ts +79 -0
  124. package/dist/testing/mock-context.js +206 -0
  125. package/dist/testing/mock-context.js.map +1 -0
  126. package/dist/types/icon.d.ts +12 -0
  127. package/dist/types/icon.js +2 -0
  128. package/dist/types/icon.js.map +1 -0
  129. package/dist/types/index.d.ts +10 -0
  130. package/dist/types/index.js +8 -0
  131. package/dist/types/index.js.map +1 -0
  132. package/dist/types/integration.d.ts +258 -0
  133. package/dist/types/integration.js +2 -0
  134. package/dist/types/integration.js.map +1 -0
  135. package/dist/types/widget-ask-context.d.ts +36 -0
  136. package/dist/types/widget-ask-context.js +2 -0
  137. package/dist/types/widget-ask-context.js.map +1 -0
  138. package/dist/types/workspace-target.d.ts +33 -0
  139. package/dist/types/workspace-target.js +2 -0
  140. package/dist/types/workspace-target.js.map +1 -0
  141. package/package.json +116 -0
@@ -0,0 +1,258 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { IconRenderer } from './icon.js';
3
+ export interface IntegrationCapability {
4
+ label: string;
5
+ description: string;
6
+ icon?: IconRenderer;
7
+ }
8
+ export interface IntegrationPermission {
9
+ label: string;
10
+ detail: string;
11
+ type: 'read' | 'write';
12
+ }
13
+ export type HealthStatus = 'healthy' | 'degraded' | 'unhealthy' | 'unknown';
14
+ /**
15
+ * Result returned by `BaseIntegration.healthCheck(accountId)`. Routed through
16
+ * `classifyHealth()` (server-side) before writing to
17
+ * `integration_health_status`.
18
+ */
19
+ export interface HealthCheckResult {
20
+ status: HealthStatus;
21
+ errorCode?: string;
22
+ errorMessage?: string;
23
+ /** Rolling 24h error rate from `account_product_usage_events`, 0..100. */
24
+ errorRatePct?: number;
25
+ /** Sample size used to compute errorRatePct; if below threshold, treated as unknown. */
26
+ sampleSize?: number;
27
+ /** Whether the most recent observed call succeeded. */
28
+ lastObservationSucceeded?: boolean;
29
+ /** Token/credential expiry, if known. Drives token-expiry warnings. */
30
+ tokenExpiresAt?: string;
31
+ /** Free-form per-integration data carried to the dashboard. */
32
+ metadata?: Record<string, unknown>;
33
+ }
34
+ export interface ActivationResult {
35
+ success: boolean;
36
+ configData?: Record<string, unknown>;
37
+ externalId?: string;
38
+ errorMessage?: string;
39
+ metadata?: Record<string, unknown>;
40
+ }
41
+ interface BaseIntegrationComponentProps {
42
+ accountId: string;
43
+ sourceId?: string;
44
+ integrationConfig?: Record<string, unknown>;
45
+ }
46
+ type ProductReference = {
47
+ productId: string;
48
+ productSlug?: never;
49
+ } | {
50
+ productSlug: string;
51
+ productId?: never;
52
+ };
53
+ export type IntegrationComponentProps = BaseIntegrationComponentProps & ProductReference;
54
+ export type MarketplaceTileProps = IntegrationComponentProps & {
55
+ onActivate?: () => void;
56
+ isActivated?: boolean;
57
+ /** Navigation URL for the integration detail page */
58
+ href?: string;
59
+ };
60
+ export type ActivationFormProps = IntegrationComponentProps & {
61
+ onSuccess?: (result: ActivationResult) => void;
62
+ onCancel?: () => void;
63
+ isLoading?: boolean;
64
+ /** Render inline instead of in a dialog */
65
+ inline?: boolean;
66
+ /**
67
+ * The TEAM account context the user is activating within — separate from
68
+ * `accountId`, which for `scope: 'user'` integrations is collapsed to the
69
+ * personal account for activation storage/token purposes. Only consumed by
70
+ * workspace materialization (`materializeWorkspaceOnActivation`), never for
71
+ * activation/token storage itself (plan 009 Part A2). Optional: undefined
72
+ * for team-scoped integrations (their `accountId` already IS the team) and
73
+ * for any caller that hasn't threaded it yet — the server action falls back
74
+ * to `accountId` when absent.
75
+ */
76
+ teamAccountId?: string;
77
+ };
78
+ export interface IntegrationMetadata {
79
+ productId: string;
80
+ productSlug: string;
81
+ integrationName: string;
82
+ description: string;
83
+ category: string;
84
+ version: string;
85
+ icon?: string;
86
+ /** Bundled logo URL — overrides product.logo_path on the marketplace */
87
+ logoPath?: string;
88
+ /** When 'user', activation data is stored under the user's personal account (user.id) instead of the team account */
89
+ scope?: 'user' | 'team';
90
+ /**
91
+ * Set FALSE to assert this integration has NO self-serve activation surface —
92
+ * the marketplace detail page then renders no activation form at all.
93
+ *
94
+ * This is deliberately distinct from "has no CUSTOM activation form": an
95
+ * integration that omits `getActivationForm()` still gets the generic one, and
96
+ * six integrations depend on that. Use this flag only when submitting ANY
97
+ * activation form would be a dead end (e.g. Workflows, which is turned on by
98
+ * the `source_configs.enable_workflows` feature flag and whose `activate()`
99
+ * throws 'not implemented' — showing a form there hands the user a button that
100
+ * cannot work). Undefined/true = normal self-serve activation.
101
+ */
102
+ selfServeActivation?: boolean;
103
+ /**
104
+ * How this integration's MCP tools resolve credentials — the integration's
105
+ * SELF-DECLARATION of machine-executability, consumed dynamically by the
106
+ * workflow engine's fail-closed tool allowlist (isGatewayExposableTool).
107
+ *
108
+ * 'account' — credentials/identity resolve from team- or source-level
109
+ * config via the ToolContext (`getAccountId(ctx)` + `ctx.supabase`).
110
+ * Safe for durable machine-principal execution (workflows): eligible
111
+ * tools (read + public/internal sensitivity) become workflow-callable.
112
+ * 'user' — credentials or identity are personal (ctx.user.id, personal
113
+ * OAuth, session reads like getSupabaseServerClient()/requireUser()).
114
+ * NEVER machine-executable: a workflow would silently ride one
115
+ * employee's personal grant (N8N_INTEGRATION_PLAN adversarial #4).
116
+ * 'source' — platform-level credentials (diagnostics-class integrations).
117
+ * Not exposed to account workflows.
118
+ *
119
+ * UNDECLARED = fail closed (treated as not machine-executable). Declaring
120
+ * 'account' is a security assertion: the integration's MCP run() handlers
121
+ * must use ctx-based resolution ONLY — no session clients, no personal
122
+ * grants. A guard test in @kit/integrations-workflows enforces the obvious
123
+ * violations (session-client reads in mcp/ code of 'account' declarers).
124
+ */
125
+ credentialModel?: 'account' | 'user' | 'source';
126
+ /**
127
+ * What KIND of thing this integration is — the platform's presentation and
128
+ * governance discriminant. Undefined means `'integration'`, which is what
129
+ * every third-party connector is; the field only ever needs setting to opt
130
+ * INTO feature treatment.
131
+ *
132
+ * 'integration' — connects Fusion to a third party. Has credentials, an
133
+ * activation flow, and a marketplace presence. Its widgets live on
134
+ * workspace dashboards and it declares its workspaces via
135
+ * `getWorkspaceTargets()`.
136
+ * 'feature' — a Fusion-native capability with a bespoke experience: no
137
+ * third-party credentials, its own route tree under
138
+ * `/home/[account]/…`, and its own sidebar section declared via
139
+ * `getSidebarNav()` (optionally rendered by its own
140
+ * `getSidebarComponent()`).
141
+ *
142
+ * The two are mutually exclusive on the nav question. A `'feature'` MUST
143
+ * declare `getSidebarNav()` and MUST NOT declare `getWorkspaceTargets()` —
144
+ * its widgets are placed onto whatever workspace an admin chooses in the
145
+ * composer (`/admin/sources/[id]/layout`) rather than onto a workspace
146
+ * named after itself. `apps/web/lib/workspaces/__tests__/sidebar-nav-declarations.test.ts`
147
+ * fails the build on either violation.
148
+ */
149
+ kind?: 'integration' | 'feature';
150
+ }
151
+ export interface WidgetConfig {
152
+ id: string;
153
+ name: string;
154
+ component: ComponentType<IntegrationComponentProps>;
155
+ gridSize?: {
156
+ cols: number;
157
+ rows: number;
158
+ } | {
159
+ cols: number;
160
+ rows: number;
161
+ }[];
162
+ gridPosition?: {
163
+ col: number;
164
+ row: number;
165
+ };
166
+ widgetState: 'active' | 'inactive' | 'disabled';
167
+ layouts?: {
168
+ lg?: {
169
+ x: number;
170
+ y: number;
171
+ w: number;
172
+ h: number;
173
+ maxHeight?: number;
174
+ };
175
+ md?: {
176
+ x: number;
177
+ y: number;
178
+ w: number;
179
+ h: number;
180
+ maxHeight?: number;
181
+ };
182
+ sm?: {
183
+ x: number;
184
+ y: number;
185
+ w: number;
186
+ h: number;
187
+ maxHeight?: number;
188
+ };
189
+ };
190
+ integrationMetadata?: IntegrationMetadata;
191
+ category?: {
192
+ id: string;
193
+ name: string;
194
+ slug: string;
195
+ icon: string | null;
196
+ };
197
+ productId?: string;
198
+ isCollapsible?: boolean;
199
+ collapsed?: boolean;
200
+ widgetConfigId?: string;
201
+ workspaceId?: string | null;
202
+ isPinnable?: boolean;
203
+ isPinned?: boolean;
204
+ aiFooterEnabled?: boolean;
205
+ health?: {
206
+ status: HealthStatus;
207
+ lastSuccessAt?: string | null;
208
+ errorMessage?: string | null;
209
+ };
210
+ }
211
+ export interface IntegrationConfig {
212
+ id: string;
213
+ account_id: string | null;
214
+ product_id: string;
215
+ config_type: 'api_credentials' | 'activation_data' | 'settings';
216
+ config_data: Record<string, unknown>;
217
+ status: 'active' | 'inactive' | 'pending' | 'error' | 'suspended';
218
+ is_active: boolean;
219
+ created_at: string;
220
+ updated_at: string;
221
+ created_by: string | null;
222
+ updated_by: string | null;
223
+ }
224
+ /**
225
+ * The PARTNER-VISIBLE `data_type` values on `account_product_data` — a strict
226
+ * SUBSET of `@kit/integrations-core`'s union.
227
+ *
228
+ * `'secret'` is deliberately absent. Secret rows are host-managed (one row per
229
+ * secret name, value in Vault, `vault_secret_id` on the row), written only by
230
+ * the capability context's `persistSecret` and read only through
231
+ * `ctx.secrets`. Naming the kind here would advertise a row shape partner code
232
+ * must never address: `ctx.storage` rejects it, and core's generic accessors
233
+ * reject it too (`config-utils.ts`, `assertNotHostOnlyDataType`) precisely
234
+ * because a write there would land the plaintext in the `data` column.
235
+ *
236
+ * When you add an integration-specific kind, add it in BOTH places (core owns
237
+ * the full union) and keep this one partner-appropriate. Prefer `dataSubtype`
238
+ * for orthogonal slicing within a kind (e.g. `data_type='cache'`,
239
+ * `data_subtype='profile'`); reserve a new top-level kind for data that has a
240
+ * distinct lifecycle or is queried independently.
241
+ */
242
+ export type AccountProductDataType = 'activation' | 'settings' | 'metrics_summary' | 'sync_state' | 'cache' | 'cash_forecast' | 'tax_config';
243
+ export interface AccountProductData {
244
+ id: string;
245
+ account_id: string;
246
+ product_id: string;
247
+ data_type: AccountProductDataType;
248
+ data_subtype?: string;
249
+ data: Record<string, unknown>;
250
+ expires_at?: string;
251
+ integration_config_id?: string;
252
+ external_id?: string;
253
+ created_at: string;
254
+ updated_at: string;
255
+ created_by: string | null;
256
+ updated_by: string | null;
257
+ }
258
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.js","sourceRoot":"","sources":["../../src/types/integration.ts"],"names":[],"mappings":"","sourcesContent":["import type { ComponentType } from 'react';\n\nimport type { IconRenderer } from './icon';\n\n// Capability shown on the integration detail page (numbered grid)\nexport interface IntegrationCapability {\n label: string;\n description: string;\n icon?: IconRenderer;\n}\n\n// Permission scope shown on the integration detail page\nexport interface IntegrationPermission {\n label: string;\n detail: string;\n type: 'read' | 'write';\n}\n\n// ---- Integration Health Monitoring (issue #474) -----------------------------\n\n// Higher = healthier. Used internally for \"least healthy wins\" merging.\nexport type HealthStatus = 'healthy' | 'degraded' | 'unhealthy' | 'unknown';\n\n/**\n * Result returned by `BaseIntegration.healthCheck(accountId)`. Routed through\n * `classifyHealth()` (server-side) before writing to\n * `integration_health_status`.\n */\nexport interface HealthCheckResult {\n status: HealthStatus;\n errorCode?: string;\n errorMessage?: string;\n /** Rolling 24h error rate from `account_product_usage_events`, 0..100. */\n errorRatePct?: number;\n /** Sample size used to compute errorRatePct; if below threshold, treated as unknown. */\n sampleSize?: number;\n /** Whether the most recent observed call succeeded. */\n lastObservationSucceeded?: boolean;\n /** Token/credential expiry, if known. Drives token-expiry warnings. */\n tokenExpiresAt?: string;\n /** Free-form per-integration data carried to the dashboard. */\n metadata?: Record<string, unknown>;\n}\n\n// Activation result from integration\nexport interface ActivationResult {\n success: boolean;\n configData?: Record<string, unknown>;\n externalId?: string;\n errorMessage?: string;\n metadata?: Record<string, unknown>;\n}\n\n// Base props that all integration components need\ninterface BaseIntegrationComponentProps {\n accountId: string;\n sourceId?: string;\n integrationConfig?: Record<string, unknown>;\n}\n\n// Discriminated union for product reference - only one allowed\ntype ProductReference =\n | { productId: string; productSlug?: never }\n | { productSlug: string; productId?: never };\n\n// New base type using discriminated union\nexport type IntegrationComponentProps = BaseIntegrationComponentProps &\n ProductReference;\n\n// Marketplace tile props\nexport type MarketplaceTileProps = IntegrationComponentProps & {\n onActivate?: () => void;\n isActivated?: boolean;\n /** Navigation URL for the integration detail page */\n href?: string;\n};\n\n// Activation form props\nexport type ActivationFormProps = IntegrationComponentProps & {\n onSuccess?: (result: ActivationResult) => void;\n onCancel?: () => void;\n isLoading?: boolean;\n /** Render inline instead of in a dialog */\n inline?: boolean;\n /**\n * The TEAM account context the user is activating within — separate from\n * `accountId`, which for `scope: 'user'` integrations is collapsed to the\n * personal account for activation storage/token purposes. Only consumed by\n * workspace materialization (`materializeWorkspaceOnActivation`), never for\n * activation/token storage itself (plan 009 Part A2). Optional: undefined\n * for team-scoped integrations (their `accountId` already IS the team) and\n * for any caller that hasn't threaded it yet — the server action falls back\n * to `accountId` when absent.\n */\n teamAccountId?: string;\n};\n\n// Integration metadata\nexport interface IntegrationMetadata {\n productId: string;\n productSlug: string;\n integrationName: string;\n description: string;\n category: string;\n version: string;\n icon?: string;\n /** Bundled logo URL — overrides product.logo_path on the marketplace */\n logoPath?: string;\n /** When 'user', activation data is stored under the user's personal account (user.id) instead of the team account */\n scope?: 'user' | 'team';\n /**\n * Set FALSE to assert this integration has NO self-serve activation surface —\n * the marketplace detail page then renders no activation form at all.\n *\n * This is deliberately distinct from \"has no CUSTOM activation form\": an\n * integration that omits `getActivationForm()` still gets the generic one, and\n * six integrations depend on that. Use this flag only when submitting ANY\n * activation form would be a dead end (e.g. Workflows, which is turned on by\n * the `source_configs.enable_workflows` feature flag and whose `activate()`\n * throws 'not implemented' — showing a form there hands the user a button that\n * cannot work). Undefined/true = normal self-serve activation.\n */\n selfServeActivation?: boolean;\n /**\n * How this integration's MCP tools resolve credentials — the integration's\n * SELF-DECLARATION of machine-executability, consumed dynamically by the\n * workflow engine's fail-closed tool allowlist (isGatewayExposableTool).\n *\n * 'account' — credentials/identity resolve from team- or source-level\n * config via the ToolContext (`getAccountId(ctx)` + `ctx.supabase`).\n * Safe for durable machine-principal execution (workflows): eligible\n * tools (read + public/internal sensitivity) become workflow-callable.\n * 'user' — credentials or identity are personal (ctx.user.id, personal\n * OAuth, session reads like getSupabaseServerClient()/requireUser()).\n * NEVER machine-executable: a workflow would silently ride one\n * employee's personal grant (N8N_INTEGRATION_PLAN adversarial #4).\n * 'source' — platform-level credentials (diagnostics-class integrations).\n * Not exposed to account workflows.\n *\n * UNDECLARED = fail closed (treated as not machine-executable). Declaring\n * 'account' is a security assertion: the integration's MCP run() handlers\n * must use ctx-based resolution ONLY — no session clients, no personal\n * grants. A guard test in @kit/integrations-workflows enforces the obvious\n * violations (session-client reads in mcp/ code of 'account' declarers).\n */\n credentialModel?: 'account' | 'user' | 'source';\n /**\n * What KIND of thing this integration is — the platform's presentation and\n * governance discriminant. Undefined means `'integration'`, which is what\n * every third-party connector is; the field only ever needs setting to opt\n * INTO feature treatment.\n *\n * 'integration' — connects Fusion to a third party. Has credentials, an\n * activation flow, and a marketplace presence. Its widgets live on\n * workspace dashboards and it declares its workspaces via\n * `getWorkspaceTargets()`.\n * 'feature' — a Fusion-native capability with a bespoke experience: no\n * third-party credentials, its own route tree under\n * `/home/[account]/…`, and its own sidebar section declared via\n * `getSidebarNav()` (optionally rendered by its own\n * `getSidebarComponent()`).\n *\n * The two are mutually exclusive on the nav question. A `'feature'` MUST\n * declare `getSidebarNav()` and MUST NOT declare `getWorkspaceTargets()` —\n * its widgets are placed onto whatever workspace an admin chooses in the\n * composer (`/admin/sources/[id]/layout`) rather than onto a workspace\n * named after itself. `apps/web/lib/workspaces/__tests__/sidebar-nav-declarations.test.ts`\n * fails the build on either violation.\n */\n kind?: 'integration' | 'feature';\n}\n\n// Widget configuration\nexport interface WidgetConfig {\n id: string;\n name: string;\n component: ComponentType<IntegrationComponentProps>;\n gridSize?: { cols: number; rows: number } | { cols: number; rows: number }[];\n gridPosition?: { col: number; row: number };\n widgetState: 'active' | 'inactive' | 'disabled';\n // React-grid-layout format\n layouts?: {\n lg?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n md?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n sm?: { x: number; y: number; w: number; h: number; maxHeight?: number };\n };\n // Integration metadata for widgets\n integrationMetadata?: IntegrationMetadata;\n category?: { id: string; name: string; slug: string; icon: string | null };\n // Database product ID (UUID) from widget_config table\n productId?: string;\n isCollapsible?: boolean;\n // For collapsed state persistence\n collapsed?: boolean;\n widgetConfigId?: string;\n // Resolved workspace placement (explicit widget_config.workspace_id\n // override, else the product's inheriting attachment). Populated by the\n // dashboard widgets loader; null = no workspace (unscoped dashboard).\n workspaceId?: string | null;\n isPinnable?: boolean;\n isPinned?: boolean;\n // When true, the dashboard renders the \"Ask about this\" AI footer bar for\n // this widget. The widget may still pass `askContext` to the wrapper for a\n // rich snapshot; otherwise a minimal default is used.\n aiFooterEnabled?: boolean;\n // Integration health for this widget's product. `unhealthy` downgrades the\n // widget to `disabled`; `degraded` surfaces a stale-data footer.\n health?: {\n status: HealthStatus;\n lastSuccessAt?: string | null;\n errorMessage?: string | null;\n };\n}\n\n// Database types for integration tables.\n//\n// PARTNER-VISIBLE SUBSET: these row types deliberately OMIT `vault_secret_id`.\n// A vault id is a host handle to a decryptable secret; the partner surface\n// never exposes one (docs/devex/sdk-export-map.md, \"Vault access\" —\n// `ctx.secrets` fences it off). `@kit/integrations-core` keeps the full row\n// (host code needs the handle); `types/__tests__/core-parity.test-d.ts` pins\n// the SDK type as core's row minus exactly the host-only fields, so a new\n// core column cannot drift in here unnoticed.\nexport interface IntegrationConfig {\n id: string;\n account_id: string | null;\n product_id: string;\n config_type: 'api_credentials' | 'activation_data' | 'settings';\n config_data: Record<string, unknown>;\n status: 'active' | 'inactive' | 'pending' | 'error' | 'suspended';\n is_active: boolean;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n}\n\n/**\n * The PARTNER-VISIBLE `data_type` values on `account_product_data` — a strict\n * SUBSET of `@kit/integrations-core`'s union.\n *\n * `'secret'` is deliberately absent. Secret rows are host-managed (one row per\n * secret name, value in Vault, `vault_secret_id` on the row), written only by\n * the capability context's `persistSecret` and read only through\n * `ctx.secrets`. Naming the kind here would advertise a row shape partner code\n * must never address: `ctx.storage` rejects it, and core's generic accessors\n * reject it too (`config-utils.ts`, `assertNotHostOnlyDataType`) precisely\n * because a write there would land the plaintext in the `data` column.\n *\n * When you add an integration-specific kind, add it in BOTH places (core owns\n * the full union) and keep this one partner-appropriate. Prefer `dataSubtype`\n * for orthogonal slicing within a kind (e.g. `data_type='cache'`,\n * `data_subtype='profile'`); reserve a new top-level kind for data that has a\n * distinct lifecycle or is queried independently.\n */\nexport type AccountProductDataType =\n | 'activation'\n | 'settings'\n | 'metrics_summary'\n | 'sync_state'\n | 'cache'\n // Integration-specific kinds — extend here when needed.\n | 'cash_forecast' // quickbooks — user-supplied manual cash-forecast estimates\n | 'tax_config'; // quickbooks — per-user federal/state tax rate config\n\nexport interface AccountProductData {\n id: string;\n account_id: string;\n product_id: string;\n data_type: AccountProductDataType;\n data_subtype?: string;\n data: Record<string, unknown>;\n expires_at?: string;\n integration_config_id?: string;\n external_id?: string;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n}\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A widget snapshot is a JSON-serializable view of what the user currently
3
+ * sees in the widget. It MUST NOT contain React nodes, functions, class
4
+ * instances, credentials, tokens, or internal-only IDs. The chat API route
5
+ * validates the shape before injecting it into the system prompt.
6
+ */
7
+ export type WidgetSnapshot = Record<string, unknown> | null;
8
+ /**
9
+ * A pre-canned prompt rendered as a pill inside the expanded "Ask about this"
10
+ * footer. Clicking a suggestion dispatches the prompt to the assistant dock
11
+ * along with the widget's snapshot.
12
+ */
13
+ export interface WidgetSuggestedPrompt {
14
+ label: string;
15
+ prompt: string;
16
+ }
17
+ /**
18
+ * Context a widget passes to the AI assistant when its "Ask about this"
19
+ * bar is submitted. The bridge in apps/web attaches `snapshot` to the user
20
+ * message as AI SDK message metadata (NOT as visible chat text). The chat
21
+ * API route injects the snapshot into the system instruction so the model
22
+ * has authoritative widget state; the dock UI renders a small attachment
23
+ * chip above the user bubble using `title` as the label.
24
+ */
25
+ export interface WidgetAskContext {
26
+ widgetId: string;
27
+ title: string;
28
+ snapshot: WidgetSnapshot;
29
+ suggestedPrompt: string;
30
+ /**
31
+ * Optional pre-canned prompts shown as pills above the input in the
32
+ * expanded "Ask about this" footer. Use these to surface the same
33
+ * contextual prompts that previously sat on the widget body.
34
+ */
35
+ suggestedPrompts?: WidgetSuggestedPrompt[];
36
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=widget-ask-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-ask-context.js","sourceRoot":"","sources":["../../src/types/widget-ask-context.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * A widget snapshot is a JSON-serializable view of what the user currently\n * sees in the widget. It MUST NOT contain React nodes, functions, class\n * instances, credentials, tokens, or internal-only IDs. The chat API route\n * validates the shape before injecting it into the system prompt.\n */\nexport type WidgetSnapshot = Record<string, unknown> | null;\n\n/**\n * A pre-canned prompt rendered as a pill inside the expanded \"Ask about this\"\n * footer. Clicking a suggestion dispatches the prompt to the assistant dock\n * along with the widget's snapshot.\n */\nexport interface WidgetSuggestedPrompt {\n label: string;\n prompt: string;\n}\n\n/**\n * Context a widget passes to the AI assistant when its \"Ask about this\"\n * bar is submitted. The bridge in apps/web attaches `snapshot` to the user\n * message as AI SDK message metadata (NOT as visible chat text). The chat\n * API route injects the snapshot into the system instruction so the model\n * has authoritative widget state; the dock UI renders a small attachment\n * chip above the user bubble using `title` as the label.\n */\nexport interface WidgetAskContext {\n widgetId: string;\n title: string;\n snapshot: WidgetSnapshot;\n suggestedPrompt: string;\n /**\n * Optional pre-canned prompts shown as pills above the input in the\n * expanded \"Ask about this\" footer. Use these to surface the same\n * contextual prompts that previously sat on the widget body.\n */\n suggestedPrompts?: WidgetSuggestedPrompt[];\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The contract an integration uses to declare the workspaces it ships with
3
+ * (`BaseIntegration.getWorkspaceTargets()`).
4
+ *
5
+ * The host's canonical definition is `z.input<typeof WorkspaceTargetSchema>`
6
+ * (`@kit/integrations-core`, `schemas/workspace-target.schema.ts`) — the
7
+ * schema validates declarations in CI and at materialization time, including
8
+ * cross-field rules a type cannot carry (icon-name validity, slug format,
9
+ * `inheritsWidgets`/`includedWidgetIds` exclusivity, one inheriting target
10
+ * per set). This structural twin exists so the SDK's root entrypoint owns its
11
+ * types without importing the host schema; the parity test beside these types
12
+ * keeps the two shapes mutually assignable.
13
+ */
14
+ export interface WorkspaceTargetDefinition {
15
+ /** Stable URL segment — `/workspace/<slug>` routes reference it. Lowercase
16
+ * alphanumeric with single hyphens between segments. */
17
+ slug: string;
18
+ name: string;
19
+ /** A Font Awesome class value, or a legacy lucide-react name the shared
20
+ * lucide→FA map can translate. Validated by the host at gate time. */
21
+ icon?: string;
22
+ description?: string;
23
+ /** Defaults to `'masonry'` when omitted. */
24
+ layout?: 'masonry' | 'two-column';
25
+ /**
26
+ * Whether this workspace receives the product's otherwise-unplaced
27
+ * widgets. Exactly one target per product must set it; mutually exclusive
28
+ * with `includedWidgetIds`.
29
+ */
30
+ inheritsWidgets?: boolean;
31
+ /** Limit this workspace to specific widget ids. */
32
+ includedWidgetIds?: string[];
33
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workspace-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-target.js","sourceRoot":"","sources":["../../src/types/workspace-target.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * The contract an integration uses to declare the workspaces it ships with\n * (`BaseIntegration.getWorkspaceTargets()`).\n *\n * The host's canonical definition is `z.input<typeof WorkspaceTargetSchema>`\n * (`@kit/integrations-core`, `schemas/workspace-target.schema.ts`) — the\n * schema validates declarations in CI and at materialization time, including\n * cross-field rules a type cannot carry (icon-name validity, slug format,\n * `inheritsWidgets`/`includedWidgetIds` exclusivity, one inheriting target\n * per set). This structural twin exists so the SDK's root entrypoint owns its\n * types without importing the host schema; the parity test beside these types\n * keeps the two shapes mutually assignable.\n */\nexport interface WorkspaceTargetDefinition {\n /** Stable URL segment — `/workspace/<slug>` routes reference it. Lowercase\n * alphanumeric with single hyphens between segments. */\n slug: string;\n name: string;\n /** A Font Awesome class value, or a legacy lucide-react name the shared\n * lucide→FA map can translate. Validated by the host at gate time. */\n icon?: string;\n description?: string;\n /** Defaults to `'masonry'` when omitted. */\n layout?: 'masonry' | 'two-column';\n /**\n * Whether this workspace receives the product's otherwise-unplaced\n * widgets. Exactly one target per product must set it; mutually exclusive\n * with `includedWidgetIds`.\n */\n inheritsWidgets?: boolean;\n /** Limit this workspace to specific widget ids. */\n includedWidgetIds?: string[];\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "name": "@ekanos/sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "The official SDK for building Ekanos integrations.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/companydotcom/fusion.git",
10
+ "directory": "packages/integration-sdk"
11
+ },
12
+ "homepage": "https://github.com/companydotcom/fusion/tree/dev/packages/integration-sdk#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/companydotcom/fusion/issues"
15
+ },
16
+ "sideEffects": [
17
+ "**/mcp/**"
18
+ ],
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./components": {
30
+ "types": "./dist/components/index.d.ts",
31
+ "default": "./dist/components/index.js"
32
+ },
33
+ "./hooks": {
34
+ "types": "./dist/hooks/index.d.ts",
35
+ "default": "./dist/hooks/index.js"
36
+ },
37
+ "./mcp": {
38
+ "types": "./dist/mcp/index.d.ts",
39
+ "default": "./dist/mcp/index.js"
40
+ },
41
+ "./context": {
42
+ "types": "./dist/context/index.d.ts",
43
+ "default": "./dist/context/index.js"
44
+ },
45
+ "./testing": {
46
+ "types": "./dist/testing/index.d.ts",
47
+ "default": "./dist/testing/index.js"
48
+ },
49
+ "./integration": {
50
+ "types": "./dist/integration/index.d.ts",
51
+ "default": "./dist/integration/index.js"
52
+ }
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "dependencies": {
58
+ "@supabase/supabase-js": "2.87.1",
59
+ "server-only": "^0.0.1",
60
+ "@ekanos/integration-schema": "0.1.0",
61
+ "@ekanos/ui": "0.1.0"
62
+ },
63
+ "peerDependencies": {
64
+ "@hookform/resolvers": "^5.2.2",
65
+ "@tanstack/react-query": "^5.101.4",
66
+ "next": "^16.0.0",
67
+ "react": "^19.2.8",
68
+ "react-hook-form": "^7.68.0",
69
+ "zod": "^3.25.76"
70
+ },
71
+ "devDependencies": {
72
+ "@hookform/resolvers": "^5.2.2",
73
+ "@tanstack/react-query": "5.101.4",
74
+ "@types/react": "19.2.18",
75
+ "next": "16.3.1",
76
+ "react": "19.2.8",
77
+ "react-hook-form": "^7.68.0",
78
+ "typescript": "^5.9.3",
79
+ "vitest": "4.1.10",
80
+ "zod": "^3.25.74",
81
+ "@kit/tsconfig": "0.1.0",
82
+ "@kit/eslint-config": "0.2.0",
83
+ "@kit/prettier-config": "0.1.0"
84
+ },
85
+ "prettier": "@kit/prettier-config",
86
+ "typesVersions": {
87
+ "*": {
88
+ "context": [
89
+ "dist/context/index.d.ts"
90
+ ],
91
+ "testing": [
92
+ "dist/testing/index.d.ts"
93
+ ],
94
+ "integration": [
95
+ "dist/integration/index.d.ts"
96
+ ],
97
+ "*": [
98
+ "dist/*"
99
+ ]
100
+ }
101
+ },
102
+ "scripts": {
103
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/rewrite-esm-specifiers.mjs",
104
+ "clean": "git clean -xdf .turbo node_modules dist",
105
+ "format": "prettier --check \"**/*.{ts,tsx}\"",
106
+ "lint": "eslint .",
107
+ "typecheck": "tsc --noEmit",
108
+ "test": "vitest run --config vitest.config.ts",
109
+ "api-report": "node scripts/api-report.mjs",
110
+ "api-report:check": "node scripts/api-report.mjs --check",
111
+ "pack:test": "node scripts/pack-test.mjs"
112
+ },
113
+ "main": "./dist/index.js",
114
+ "module": "./dist/index.js",
115
+ "types": "./dist/index.d.ts"
116
+ }