@nuxtagentic/integrations 0.0.0-stage → 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.md +9 -0
- package/README.md +175 -2
- package/client.ts +124 -0
- package/module.ts +44 -0
- package/package.json +52 -3
- package/runtime/assets/logos/discord.svg +1 -0
- package/runtime/assets/logos/github.svg +1 -0
- package/runtime/assets/logos/gmail.svg +1 -0
- package/runtime/assets/logos/googleads.svg +1 -0
- package/runtime/assets/logos/googleanalytics.svg +1 -0
- package/runtime/assets/logos/googlecalendar.svg +1 -0
- package/runtime/assets/logos/googledrive.svg +1 -0
- package/runtime/assets/logos/googlesearchconsole.svg +1 -0
- package/runtime/assets/logos/hubspot.svg +1 -0
- package/runtime/assets/logos/intercom.svg +1 -0
- package/runtime/assets/logos/linear.svg +1 -0
- package/runtime/assets/logos/mcp.svg +1 -0
- package/runtime/assets/logos/meta.svg +1 -0
- package/runtime/assets/logos/notion.svg +1 -0
- package/runtime/assets/logos/reddit.svg +1 -0
- package/runtime/assets/logos/sentry.svg +1 -0
- package/runtime/assets/logos/shopify.svg +1 -0
- package/runtime/assets/logos/slack.svg +1 -0
- package/runtime/assets/logos/stripe.svg +1 -0
- package/runtime/assets/logos/tiktok-dark.svg +1 -0
- package/runtime/assets/logos/tiktok.svg +1 -0
- package/runtime/assets/logos/vercel.svg +1 -0
- package/runtime/assets/logos/x.svg +1 -0
- package/runtime/components/IntegrationCard.vue +82 -0
- package/runtime/components/IntegrationCatalog.vue +130 -0
- package/runtime/components/IntegrationConnectionPanel.vue +589 -0
- package/runtime/components/IntegrationLogo.vue +41 -0
- package/runtime/pages/integrations.vue +481 -0
- package/runtime/server/api/integrations/approvals/[id].post.ts +69 -0
- package/runtime/server/api/integrations/catalog/[provider]/action.post.ts +35 -0
- package/runtime/server/api/integrations/catalog/[provider]/connection.post.ts +242 -0
- package/runtime/server/api/integrations/catalog/[provider]/tools.get.ts +40 -0
- package/runtime/server/api/integrations/catalog/index.get.ts +48 -0
- package/runtime/server/api/integrations/catalog/tools.get.ts +27 -0
- package/runtime/server/plugins/connect-maintenance.ts +30 -0
- package/runtime/server/utils/approval-continuation.ts +37 -0
- package/runtime/server/utils/google-mcp.ts +296 -0
- package/runtime/server/utils/integration-adapters.ts +836 -0
- package/runtime/server/utils/integration-context.ts +510 -0
- package/runtime/server/utils/integration-crypto.ts +13 -0
- package/runtime/server/utils/request-body.ts +38 -0
- package/runtime/shared/agent.ts +14 -0
- package/runtime/shared/catalog.ts +566 -0
- package/runtime/shared/google-mcp.ts +8 -0
- package/runtime/workspace.ts +15 -0
- package/schema.ts +179 -0
package/schema.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasOrgRole,
|
|
3
|
+
model,
|
|
4
|
+
t,
|
|
5
|
+
type AccessResult,
|
|
6
|
+
type CloudConfigUser,
|
|
7
|
+
type WhereBuilder,
|
|
8
|
+
} from '@cloudconfig/core'
|
|
9
|
+
import { getIntegrationDefinition } from './runtime/shared/catalog'
|
|
10
|
+
|
|
11
|
+
/** An approval moves pending → approved | rejected, then approved → completed | failed. */
|
|
12
|
+
const approvalTransitions: Record<string, string[]> = {
|
|
13
|
+
pending: ['approved', 'rejected'],
|
|
14
|
+
approved: ['completed', 'failed'],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Your own connections, plus the workspace's shared ones while you are a
|
|
19
|
+
* member. Membership comes from the session on every request, so leaving an
|
|
20
|
+
* organization hides its connections at once.
|
|
21
|
+
*/
|
|
22
|
+
function visibleConnections(user: CloudConfigUser | null, where: WhereBuilder): AccessResult {
|
|
23
|
+
if (!user) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
const own = {
|
|
27
|
+
type: 'group' as const,
|
|
28
|
+
logic: 'and' as const,
|
|
29
|
+
children: [where('scope', 'eq', 'user'), where('ownerId', 'eq', user.id)],
|
|
30
|
+
}
|
|
31
|
+
const organization = user.organization?.id
|
|
32
|
+
if (typeof organization !== 'number') {
|
|
33
|
+
return own
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
type: 'group',
|
|
37
|
+
logic: 'or',
|
|
38
|
+
children: [
|
|
39
|
+
own,
|
|
40
|
+
{
|
|
41
|
+
type: 'group',
|
|
42
|
+
logic: 'and',
|
|
43
|
+
children: [where('scope', 'eq', 'workspace'), where('organizationId', 'eq', organization)],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Changing a shared connection is for organization owners and admins only. */
|
|
50
|
+
function manageableConnections(user: CloudConfigUser | null, where: WhereBuilder): AccessResult {
|
|
51
|
+
if (!user) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
if (hasOrgRole(user, 'owner', 'admin')) {
|
|
55
|
+
return visibleConnections(user, where)
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
type: 'group',
|
|
59
|
+
logic: 'and',
|
|
60
|
+
children: [where('scope', 'eq', 'user'), where('ownerId', 'eq', user.id)],
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const integrationsModels = {
|
|
65
|
+
/**
|
|
66
|
+
* One catalog integration bound to a CloudConfig Connect connection, with the
|
|
67
|
+
* actions agents may use through it. Tokens never live here: Connect seals
|
|
68
|
+
* them and checks who may use them on every call.
|
|
69
|
+
*/
|
|
70
|
+
integrationConnection: model(
|
|
71
|
+
{
|
|
72
|
+
provider: t.text().required().index(),
|
|
73
|
+
workspace: t.text().required().index(),
|
|
74
|
+
/** `user` acts as the person who connected; `workspace` is shared with the organization. */
|
|
75
|
+
scope: t.select(['user', 'workspace']).default('user'),
|
|
76
|
+
organizationId: t.number().index(),
|
|
77
|
+
/** The Connect connection. Zero for unconnected example records. */
|
|
78
|
+
connectionId: t.number().default(0).index(),
|
|
79
|
+
/** Connect's public uid, for the connection's event endpoint. */
|
|
80
|
+
connectionUid: t.text().default('').serverOnly(),
|
|
81
|
+
account: t.text().default(''),
|
|
82
|
+
label: t.text().default(''),
|
|
83
|
+
enabled: t.boolean().default(true),
|
|
84
|
+
/** Allowed action ids, or MCP tool names. */
|
|
85
|
+
permissions: t.json().default([]),
|
|
86
|
+
/** MCP tool names that need an owner's approval before they run. */
|
|
87
|
+
approvals: t.json().default([]),
|
|
88
|
+
/** Non-secret per-connection values (Google Ads customer, …). */
|
|
89
|
+
settings: t.json().default({}),
|
|
90
|
+
ownerId: t.number().required().serverOnly(),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
access: {
|
|
94
|
+
list: ({ user, where }) => visibleConnections(user, where),
|
|
95
|
+
read: ({ user, where }) => visibleConnections(user, where),
|
|
96
|
+
create: ({ user, input }) =>
|
|
97
|
+
!!user &&
|
|
98
|
+
input?.ownerId === user.id &&
|
|
99
|
+
(input.scope !== 'workspace' ||
|
|
100
|
+
(input.organizationId === user.organization?.id && hasOrgRole(user, 'owner', 'admin'))),
|
|
101
|
+
update: ({ user, where }) => manageableConnections(user, where),
|
|
102
|
+
delete: ({ user, where }) => manageableConnections(user, where),
|
|
103
|
+
},
|
|
104
|
+
validate: (input, existing) => {
|
|
105
|
+
const provider = getIntegrationDefinition(String(input.provider))
|
|
106
|
+
if (!provider || !input.workspace) {
|
|
107
|
+
return [{ path: 'provider', message: 'Choose an integration and workspace.' }]
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
existing &&
|
|
111
|
+
(input.provider !== existing.provider ||
|
|
112
|
+
input.workspace !== existing.workspace ||
|
|
113
|
+
input.scope !== existing.scope ||
|
|
114
|
+
input.connectionId !== existing.connectionId)
|
|
115
|
+
) {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
path: 'provider',
|
|
119
|
+
message: 'Connections cannot be moved to another account or workspace.',
|
|
120
|
+
},
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
if (input.scope === 'workspace' && typeof input.organizationId !== 'number') {
|
|
124
|
+
return [{ path: 'scope', message: 'Shared connections belong to an organization.' }]
|
|
125
|
+
}
|
|
126
|
+
const permissions = input.permissions
|
|
127
|
+
if (!Array.isArray(permissions) || permissions.some((id) => typeof id !== 'string')) {
|
|
128
|
+
return [{ path: 'permissions', message: 'Choose the actions agents may use.' }]
|
|
129
|
+
}
|
|
130
|
+
// MCP servers define their own tools; catalog integrations a fixed set.
|
|
131
|
+
if (
|
|
132
|
+
!provider.auth.mcp &&
|
|
133
|
+
permissions.some((id) => !provider.actions.some((action) => action.id === id))
|
|
134
|
+
) {
|
|
135
|
+
return [
|
|
136
|
+
{ path: 'permissions', message: 'Choose permissions supported by this integration.' },
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
return []
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
),
|
|
143
|
+
integrationApproval: model(
|
|
144
|
+
{
|
|
145
|
+
connectionId: t.number().required(),
|
|
146
|
+
action: t.text().required(),
|
|
147
|
+
input: t.json().required(),
|
|
148
|
+
status: t
|
|
149
|
+
.select(['pending', 'approved', 'rejected', 'completed', 'failed'])
|
|
150
|
+
.default('pending'),
|
|
151
|
+
result: t.json(),
|
|
152
|
+
continuation: t.json().serverOnly(),
|
|
153
|
+
ownerId: t.number().required().serverOnly(),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
access: {
|
|
157
|
+
list: ({ user, where }) => (user ? where('ownerId', 'eq', user.id) : false),
|
|
158
|
+
read: ({ user, where }) => (user ? where('ownerId', 'eq', user.id) : false),
|
|
159
|
+
create: ({ user, input }) => !!user && input?.ownerId === user.id,
|
|
160
|
+
update: ({ user, input, where }) =>
|
|
161
|
+
user && input?.ownerId === user.id ? where('ownerId', 'eq', user.id) : false,
|
|
162
|
+
},
|
|
163
|
+
validate: (input, existing) => {
|
|
164
|
+
if (!existing) {
|
|
165
|
+
return []
|
|
166
|
+
}
|
|
167
|
+
if (JSON.stringify(input.continuation) !== JSON.stringify(existing.continuation)) {
|
|
168
|
+
return [{ path: 'continuation', message: 'Keep the original run continuation.' }]
|
|
169
|
+
}
|
|
170
|
+
const allowed = approvalTransitions[String(existing.status)] ?? []
|
|
171
|
+
return allowed.includes(String(input.status))
|
|
172
|
+
? []
|
|
173
|
+
: [{ path: 'status', message: 'This approval has already been handled.' }]
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type IntegrationsConfig = { models: typeof integrationsModels }
|