@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.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/components/base-activation-dialog.d.ts +41 -0
- package/dist/components/base-activation-dialog.js +41 -0
- package/dist/components/base-activation-dialog.js.map +1 -0
- package/dist/components/base-activation-form.d.ts +36 -0
- package/dist/components/base-activation-form.js +21 -0
- package/dist/components/base-activation-form.js.map +1 -0
- package/dist/components/base-marketplace-tile.d.ts +34 -0
- package/dist/components/base-marketplace-tile.js +40 -0
- package/dist/components/base-marketplace-tile.js.map +1 -0
- package/dist/components/current-return-path.d.ts +19 -0
- package/dist/components/current-return-path.js +26 -0
- package/dist/components/current-return-path.js.map +1 -0
- package/dist/components/index.d.ts +40 -0
- package/dist/components/index.js +38 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/oauth-activation-form.d.ts +50 -0
- package/dist/components/oauth-activation-form.js +119 -0
- package/dist/components/oauth-activation-form.js.map +1 -0
- package/dist/components/widgets/ai-prompt-chip.d.ts +31 -0
- package/dist/components/widgets/ai-prompt-chip.js +38 -0
- package/dist/components/widgets/ai-prompt-chip.js.map +1 -0
- package/dist/components/widgets/ask-icon.d.ts +3 -0
- package/dist/components/widgets/ask-icon.js +5 -0
- package/dist/components/widgets/ask-icon.js.map +1 -0
- package/dist/components/widgets/use-animated-height.d.ts +12 -0
- package/dist/components/widgets/use-animated-height.js +39 -0
- package/dist/components/widgets/use-animated-height.js.map +1 -0
- package/dist/components/widgets/widget-ask-bar.d.ts +18 -0
- package/dist/components/widgets/widget-ask-bar.js +62 -0
- package/dist/components/widgets/widget-ask-bar.js.map +1 -0
- package/dist/components/widgets/widget-context.d.ts +38 -0
- package/dist/components/widgets/widget-context.js +4 -0
- package/dist/components/widgets/widget-context.js.map +1 -0
- package/dist/components/widgets/widget-state-components.d.ts +5 -0
- package/dist/components/widgets/widget-state-components.js +19 -0
- package/dist/components/widgets/widget-state-components.js.map +1 -0
- package/dist/components/widgets/widget.d.ts +91 -0
- package/dist/components/widgets/widget.js +205 -0
- package/dist/components/widgets/widget.js.map +1 -0
- package/dist/context/egress.d.ts +30 -0
- package/dist/context/egress.js +81 -0
- package/dist/context/egress.js.map +1 -0
- package/dist/context/errors.d.ts +57 -0
- package/dist/context/errors.js +78 -0
- package/dist/context/errors.js.map +1 -0
- package/dist/context/index.d.ts +19 -0
- package/dist/context/index.js +20 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/require-context.d.ts +26 -0
- package/dist/context/require-context.js +36 -0
- package/dist/context/require-context.js.map +1 -0
- package/dist/context/storage-validation.d.ts +71 -0
- package/dist/context/storage-validation.js +132 -0
- package/dist/context/storage-validation.js.map +1 -0
- package/dist/context/types.d.ts +11 -0
- package/dist/context/types.js +2 -0
- package/dist/context/types.js.map +1 -0
- package/dist/hooks/activation-actions-context.d.ts +80 -0
- package/dist/hooks/activation-actions-context.js +31 -0
- package/dist/hooks/activation-actions-context.js.map +1 -0
- package/dist/hooks/ask-assistant-context.d.ts +32 -0
- package/dist/hooks/ask-assistant-context.js +56 -0
- package/dist/hooks/ask-assistant-context.js.map +1 -0
- package/dist/hooks/fetch-integration-storage.d.ts +48 -0
- package/dist/hooks/fetch-integration-storage.js +68 -0
- package/dist/hooks/fetch-integration-storage.js.map +1 -0
- package/dist/hooks/index.d.ts +28 -0
- package/dist/hooks/index.js +32 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use-activate-integration.d.ts +15 -0
- package/dist/hooks/use-activate-integration.js +61 -0
- package/dist/hooks/use-activate-integration.js.map +1 -0
- package/dist/hooks/use-oauth-connection-status.d.ts +360 -0
- package/dist/hooks/use-oauth-connection-status.js +108 -0
- package/dist/hooks/use-oauth-connection-status.js.map +1 -0
- package/dist/hooks/use-widget-events.d.ts +20 -0
- package/dist/hooks/use-widget-events.js +51 -0
- package/dist/hooks/use-widget-events.js.map +1 -0
- package/dist/hooks/widget-event-context.d.ts +22 -0
- package/dist/hooks/widget-event-context.js +75 -0
- package/dist/hooks/widget-event-context.js.map +1 -0
- package/dist/hooks/widget-event-types.d.ts +55 -0
- package/dist/hooks/widget-event-types.js +9 -0
- package/dist/hooks/widget-event-types.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/cron.d.ts +29 -0
- package/dist/integration/cron.js +102 -0
- package/dist/integration/cron.js.map +1 -0
- package/dist/integration/define-integration.d.ts +28 -0
- package/dist/integration/define-integration.js +41 -0
- package/dist/integration/define-integration.js.map +1 -0
- package/dist/integration/event-surfaces.d.ts +15 -0
- package/dist/integration/event-surfaces.js +45 -0
- package/dist/integration/event-surfaces.js.map +1 -0
- package/dist/integration/index.d.ts +20 -0
- package/dist/integration/index.js +23 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/types.d.ts +7 -0
- package/dist/integration/types.js +2 -0
- package/dist/integration/types.js.map +1 -0
- package/dist/mcp/guards.d.ts +40 -0
- package/dist/mcp/guards.js +99 -0
- package/dist/mcp/guards.js.map +1 -0
- package/dist/mcp/index.d.ts +22 -0
- package/dist/mcp/index.js +22 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ownership.d.ts +74 -0
- package/dist/mcp/ownership.js +83 -0
- package/dist/mcp/ownership.js.map +1 -0
- package/dist/mcp/types.d.ts +145 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.js +19 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/invoke.d.ts +77 -0
- package/dist/testing/invoke.js +83 -0
- package/dist/testing/invoke.js.map +1 -0
- package/dist/testing/mock-context.d.ts +79 -0
- package/dist/testing/mock-context.js +206 -0
- package/dist/testing/mock-context.js.map +1 -0
- package/dist/types/icon.d.ts +12 -0
- package/dist/types/icon.js +2 -0
- package/dist/types/icon.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/integration.d.ts +258 -0
- package/dist/types/integration.js +2 -0
- package/dist/types/integration.js.map +1 -0
- package/dist/types/widget-ask-context.d.ts +36 -0
- package/dist/types/widget-ask-context.js +2 -0
- package/dist/types/widget-ask-context.js.map +1 -0
- package/dist/types/workspace-target.d.ts +33 -0
- package/dist/types/workspace-target.js +2 -0
- package/dist/types/workspace-target.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|