@commandable/integration-data 0.0.6 → 0.0.7
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/dist/credentials-index.d.ts.map +1 -1
- package/dist/credentials-index.js +119 -0
- package/dist/credentials-index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +33 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +36 -4
- package/dist/loader.js.map +1 -1
- package/integrations/__tests__/liveHarness.ts +16 -3
- package/integrations/airtable/.env.test +9 -0
- package/integrations/airtable/.env.test.example +11 -0
- package/integrations/airtable/README.md +27 -0
- package/integrations/airtable/__tests__/get_handlers.test.ts +43 -5
- package/integrations/confluence/.env.test +25 -0
- package/integrations/confluence/.env.test.example +36 -0
- package/integrations/confluence/README.md +28 -0
- package/integrations/confluence/__tests__/get_handlers.test.ts +121 -0
- package/integrations/confluence/__tests__/usage_parity.test.ts +14 -0
- package/integrations/confluence/__tests__/write_handlers.test.ts +131 -0
- package/integrations/confluence/credentials.json +39 -0
- package/integrations/confluence/credentials_hint.md +4 -0
- package/integrations/confluence/credentials_hint_api_token.md +9 -0
- package/integrations/confluence/credentials_hint_oauth_token.md +8 -0
- package/integrations/confluence/handlers/add_comment.js +19 -0
- package/integrations/confluence/handlers/add_label.js +16 -0
- package/integrations/confluence/handlers/create_page.js +22 -0
- package/integrations/confluence/handlers/delete_page.js +17 -0
- package/integrations/confluence/handlers/get_comments.js +33 -0
- package/integrations/confluence/handlers/get_page_children.js +30 -0
- package/integrations/confluence/handlers/get_space.js +22 -0
- package/integrations/confluence/handlers/list_spaces.js +39 -0
- package/integrations/confluence/handlers/read_page.js +49 -0
- package/integrations/confluence/handlers/search_pages.js +42 -0
- package/integrations/confluence/handlers/update_page.js +42 -0
- package/integrations/confluence/manifest.json +85 -0
- package/integrations/confluence/prompt.md +55 -0
- package/integrations/confluence/schemas/add_comment.json +22 -0
- package/integrations/confluence/schemas/add_label.json +19 -0
- package/integrations/confluence/schemas/create_page.json +33 -0
- package/integrations/confluence/schemas/delete_page.json +23 -0
- package/integrations/confluence/schemas/empty.json +6 -0
- package/integrations/confluence/schemas/get_comments.json +24 -0
- package/integrations/confluence/schemas/get_page_children.json +28 -0
- package/integrations/confluence/schemas/get_space.json +18 -0
- package/integrations/confluence/schemas/list_spaces.json +36 -0
- package/integrations/confluence/schemas/read_page.json +28 -0
- package/integrations/confluence/schemas/search_pages.json +26 -0
- package/integrations/confluence/schemas/update_page.json +31 -0
- package/integrations/github/.env.test +16 -0
- package/integrations/github/.env.test.example +17 -0
- package/integrations/github/README.md +75 -0
- package/integrations/github/__tests__/get_handlers.test.ts +5 -5
- package/integrations/github/__tests__/write_handlers.test.ts +176 -58
- package/integrations/github/handlers/create_file.js +46 -0
- package/integrations/github/handlers/delete_file.js +14 -1
- package/integrations/github/handlers/edit_file.js +52 -0
- package/integrations/github/handlers/edit_files.js +107 -0
- package/integrations/github/manifest.json +74 -47
- package/integrations/github/prompt.md +36 -0
- package/integrations/github/schemas/create_file.json +13 -0
- package/integrations/github/schemas/delete_file.json +2 -2
- package/integrations/github/schemas/edit_file.json +26 -0
- package/integrations/github/schemas/edit_files.json +39 -0
- package/integrations/google-calendar/.env.test.example +11 -0
- package/integrations/google-calendar/README.md +41 -0
- package/integrations/google-calendar/__tests__/write_and_admin_handlers.test.ts +7 -7
- package/integrations/google-calendar/manifest.json +27 -17
- package/integrations/google-docs/README.md +30 -0
- package/integrations/google-drive/README.md +26 -0
- package/integrations/google-gmail/.env.test.example +11 -0
- package/integrations/google-gmail/README.md +49 -0
- package/integrations/google-gmail/handlers/create_draft_email.js +1 -1
- package/integrations/google-gmail/handlers/read_email.js +1 -1
- package/integrations/google-gmail/handlers/send_email.js +1 -1
- package/integrations/google-gmail/manifest.json +36 -25
- package/integrations/google-sheet/README.md +27 -0
- package/integrations/google-slides/README.md +28 -0
- package/integrations/hubspot/.env.test.example +20 -0
- package/integrations/hubspot/README.md +48 -0
- package/integrations/hubspot/__tests__/get_handlers.test.ts +151 -0
- package/integrations/hubspot/__tests__/usage_parity.test.ts +10 -0
- package/integrations/hubspot/__tests__/write_handlers.test.ts +244 -0
- package/integrations/hubspot/credentials.json +48 -0
- package/integrations/hubspot/credentials_hint.md +20 -0
- package/integrations/hubspot/credentials_hint_oauth_token.md +16 -0
- package/integrations/hubspot/handlers/archive_company.js +13 -0
- package/integrations/hubspot/handlers/archive_contact.js +13 -0
- package/integrations/hubspot/handlers/archive_deal.js +13 -0
- package/integrations/hubspot/handlers/archive_ticket.js +13 -0
- package/integrations/hubspot/handlers/create_association.js +18 -0
- package/integrations/hubspot/handlers/create_company.js +13 -0
- package/integrations/hubspot/handlers/create_contact.js +14 -0
- package/integrations/hubspot/handlers/create_deal.js +16 -0
- package/integrations/hubspot/handlers/create_note.js +44 -0
- package/integrations/hubspot/handlers/create_task.js +48 -0
- package/integrations/hubspot/handlers/create_ticket.js +15 -0
- package/integrations/hubspot/handlers/get_associations.js +14 -0
- package/integrations/hubspot/handlers/get_company.js +18 -0
- package/integrations/hubspot/handlers/get_contact.js +18 -0
- package/integrations/hubspot/handlers/get_deal.js +18 -0
- package/integrations/hubspot/handlers/get_ticket.js +20 -0
- package/integrations/hubspot/handlers/list_owners.js +12 -0
- package/integrations/hubspot/handlers/list_pipelines.js +5 -0
- package/integrations/hubspot/handlers/list_properties.js +11 -0
- package/integrations/hubspot/handlers/remove_association.js +22 -0
- package/integrations/hubspot/handlers/search_companies.js +43 -0
- package/integrations/hubspot/handlers/search_contacts.js +43 -0
- package/integrations/hubspot/handlers/search_deals.js +43 -0
- package/integrations/hubspot/handlers/search_notes.js +43 -0
- package/integrations/hubspot/handlers/search_tasks.js +43 -0
- package/integrations/hubspot/handlers/search_tickets.js +43 -0
- package/integrations/hubspot/handlers/update_company.js +13 -0
- package/integrations/hubspot/handlers/update_contact.js +14 -0
- package/integrations/hubspot/handlers/update_deal.js +16 -0
- package/integrations/hubspot/handlers/update_task.js +17 -0
- package/integrations/hubspot/handlers/update_ticket.js +15 -0
- package/integrations/hubspot/manifest.json +230 -0
- package/integrations/hubspot/prompt.md +69 -0
- package/integrations/hubspot/schemas/archive_company.json +13 -0
- package/integrations/hubspot/schemas/archive_contact.json +13 -0
- package/integrations/hubspot/schemas/archive_deal.json +9 -0
- package/integrations/hubspot/schemas/archive_ticket.json +9 -0
- package/integrations/hubspot/schemas/create_association.json +24 -0
- package/integrations/hubspot/schemas/create_company.json +14 -0
- package/integrations/hubspot/schemas/create_contact.json +15 -0
- package/integrations/hubspot/schemas/create_deal.json +20 -0
- package/integrations/hubspot/schemas/create_note.json +37 -0
- package/integrations/hubspot/schemas/create_task.json +51 -0
- package/integrations/hubspot/schemas/create_ticket.json +16 -0
- package/integrations/hubspot/schemas/empty.json +6 -0
- package/integrations/hubspot/schemas/get_associations.json +30 -0
- package/integrations/hubspot/schemas/get_company.json +27 -0
- package/integrations/hubspot/schemas/get_contact.json +27 -0
- package/integrations/hubspot/schemas/get_deal.json +20 -0
- package/integrations/hubspot/schemas/get_ticket.json +20 -0
- package/integrations/hubspot/schemas/list_owners.json +25 -0
- package/integrations/hubspot/schemas/list_pipelines.json +13 -0
- package/integrations/hubspot/schemas/list_properties.json +17 -0
- package/integrations/hubspot/schemas/remove_association.json +24 -0
- package/integrations/hubspot/schemas/search_companies.json +56 -0
- package/integrations/hubspot/schemas/search_contacts.json +56 -0
- package/integrations/hubspot/schemas/search_deals.json +43 -0
- package/integrations/hubspot/schemas/search_notes.json +43 -0
- package/integrations/hubspot/schemas/search_tasks.json +43 -0
- package/integrations/hubspot/schemas/search_tickets.json +43 -0
- package/integrations/hubspot/schemas/update_company.json +20 -0
- package/integrations/hubspot/schemas/update_contact.json +21 -0
- package/integrations/hubspot/schemas/update_deal.json +19 -0
- package/integrations/hubspot/schemas/update_task.json +31 -0
- package/integrations/hubspot/schemas/update_ticket.json +18 -0
- package/integrations/jira/.env.test +46 -0
- package/integrations/jira/.env.test.example +41 -0
- package/integrations/jira/README.md +46 -0
- package/integrations/jira/__tests__/get_handlers.test.ts +193 -0
- package/integrations/jira/__tests__/usage_parity.test.ts +14 -0
- package/integrations/jira/__tests__/write_handlers.test.ts +157 -0
- package/integrations/jira/credentials.json +39 -0
- package/integrations/jira/credentials_hint.md +4 -0
- package/integrations/jira/credentials_hint_api_token.md +6 -0
- package/integrations/jira/credentials_hint_oauth_token.md +6 -0
- package/integrations/jira/handlers/add_comment.js +9 -0
- package/integrations/jira/handlers/assign_issue.js +11 -0
- package/integrations/jira/handlers/create_issue.js +37 -0
- package/integrations/jira/handlers/create_sprint.js +19 -0
- package/integrations/jira/handlers/delete_issue.js +10 -0
- package/integrations/jira/handlers/get_backlog_issues.js +13 -0
- package/integrations/jira/handlers/get_board.js +6 -0
- package/integrations/jira/handlers/get_issue.js +63 -0
- package/integrations/jira/handlers/get_issue_comments.js +31 -0
- package/integrations/jira/handlers/get_myself.js +14 -0
- package/integrations/jira/handlers/get_project.js +28 -0
- package/integrations/jira/handlers/get_sprint.js +5 -0
- package/integrations/jira/handlers/get_sprint_issues.js +13 -0
- package/integrations/jira/handlers/get_transitions.js +23 -0
- package/integrations/jira/handlers/list_boards.js +34 -0
- package/integrations/jira/handlers/list_projects.js +29 -0
- package/integrations/jira/handlers/list_sprints.js +29 -0
- package/integrations/jira/handlers/move_issues_to_sprint.js +11 -0
- package/integrations/jira/handlers/search_issues.js +43 -0
- package/integrations/jira/handlers/search_users.js +21 -0
- package/integrations/jira/handlers/transition_issue.js +44 -0
- package/integrations/jira/handlers/update_issue.js +40 -0
- package/integrations/jira/handlers/update_sprint.js +20 -0
- package/integrations/jira/manifest.json +204 -0
- package/integrations/jira/prompt.md +80 -0
- package/integrations/jira/schemas/add_comment.json +16 -0
- package/integrations/jira/schemas/assign_issue.json +16 -0
- package/integrations/jira/schemas/create_issue.json +49 -0
- package/integrations/jira/schemas/create_sprint.json +29 -0
- package/integrations/jira/schemas/delete_issue.json +12 -0
- package/integrations/jira/schemas/empty.json +6 -0
- package/integrations/jira/schemas/get_backlog_issues.json +33 -0
- package/integrations/jira/schemas/get_board.json +13 -0
- package/integrations/jira/schemas/get_issue.json +23 -0
- package/integrations/jira/schemas/get_issue_comments.json +23 -0
- package/integrations/jira/schemas/get_project.json +17 -0
- package/integrations/jira/schemas/get_sprint.json +13 -0
- package/integrations/jira/schemas/get_sprint_issues.json +33 -0
- package/integrations/jira/schemas/get_transitions.json +12 -0
- package/integrations/jira/schemas/list_boards.json +27 -0
- package/integrations/jira/schemas/list_projects.json +22 -0
- package/integrations/jira/schemas/list_sprints.json +29 -0
- package/integrations/jira/schemas/move_issues_to_sprint.json +19 -0
- package/integrations/jira/schemas/search_issues.json +28 -0
- package/integrations/jira/schemas/search_users.json +18 -0
- package/integrations/jira/schemas/transition_issue.json +38 -0
- package/integrations/jira/schemas/update_issue.json +47 -0
- package/integrations/jira/schemas/update_sprint.json +33 -0
- package/integrations/new_integration_prompt.md +173 -2
- package/integrations/notion/.env.test +10 -0
- package/integrations/notion/.env.test.example +13 -0
- package/integrations/notion/README.md +42 -0
- package/integrations/notion/manifest.json +64 -35
- package/integrations/trello/.env.test +6 -0
- package/integrations/trello/.env.test.example +9 -0
- package/integrations/trello/README.md +50 -0
- package/package.json +7 -3
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
createCredentialStore,
|
|
4
|
+
createIntegrationNode,
|
|
5
|
+
createProxy,
|
|
6
|
+
createToolbox,
|
|
7
|
+
hasEnv,
|
|
8
|
+
safeCleanup,
|
|
9
|
+
} from '../../__tests__/liveHarness.js'
|
|
10
|
+
|
|
11
|
+
// LIVE HubSpot write tests using credentials
|
|
12
|
+
// Required env vars:
|
|
13
|
+
// - HUBSPOT_TOKEN
|
|
14
|
+
//
|
|
15
|
+
// Optional env vars (pin pipeline/stage IDs):
|
|
16
|
+
// - HUBSPOT_TEST_DEAL_PIPELINE_ID
|
|
17
|
+
// - HUBSPOT_TEST_DEAL_STAGE_ID
|
|
18
|
+
// - HUBSPOT_TEST_TICKET_PIPELINE_ID
|
|
19
|
+
// - HUBSPOT_TEST_TICKET_STAGE_ID
|
|
20
|
+
|
|
21
|
+
const env = process.env as Record<string, string | undefined>
|
|
22
|
+
const suite = hasEnv('HUBSPOT_TOKEN') ? describe : describe.skip
|
|
23
|
+
|
|
24
|
+
function pickFirstPipelineAndStage(resp: any): { pipelineId?: string, stageId?: string } {
|
|
25
|
+
const pipelines = resp?.results || resp?.pipelines || resp
|
|
26
|
+
if (!Array.isArray(pipelines))
|
|
27
|
+
return {}
|
|
28
|
+
|
|
29
|
+
for (const p of pipelines) {
|
|
30
|
+
const pipelineId = p?.id
|
|
31
|
+
const stages = p?.stages
|
|
32
|
+
if (!pipelineId || !Array.isArray(stages) || stages.length === 0)
|
|
33
|
+
continue
|
|
34
|
+
const stageId = stages.find((s: any) => s?.id)?.id
|
|
35
|
+
if (stageId)
|
|
36
|
+
return { pipelineId: String(pipelineId), stageId: String(stageId) }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
suite('hubspot write handlers (live)', () => {
|
|
43
|
+
const ctx: {
|
|
44
|
+
contactId?: string
|
|
45
|
+
companyId?: string
|
|
46
|
+
taskId?: string
|
|
47
|
+
dealId?: string
|
|
48
|
+
ticketId?: string
|
|
49
|
+
} = {}
|
|
50
|
+
|
|
51
|
+
let read: Record<string, (input: any) => Promise<any>>
|
|
52
|
+
let write: Record<string, (input: any) => Promise<any>>
|
|
53
|
+
|
|
54
|
+
beforeAll(async () => {
|
|
55
|
+
const credentialStore = createCredentialStore(async () => ({ token: env.HUBSPOT_TOKEN || '' }))
|
|
56
|
+
const proxy = createProxy(credentialStore)
|
|
57
|
+
const node = createIntegrationNode('hubspot')
|
|
58
|
+
const toolbox = createToolbox('hubspot', proxy, node)
|
|
59
|
+
|
|
60
|
+
// IMPORTANT: These literal tool-name strings are intentionally present so usage-parity can verify coverage.
|
|
61
|
+
read = {
|
|
62
|
+
list_pipelines: toolbox.read('list_pipelines'),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
write = {
|
|
66
|
+
create_contact: toolbox.write('create_contact'),
|
|
67
|
+
update_contact: toolbox.write('update_contact'),
|
|
68
|
+
archive_contact: toolbox.write('archive_contact'),
|
|
69
|
+
|
|
70
|
+
create_company: toolbox.write('create_company'),
|
|
71
|
+
update_company: toolbox.write('update_company'),
|
|
72
|
+
archive_company: toolbox.write('archive_company'),
|
|
73
|
+
|
|
74
|
+
create_association: toolbox.write('create_association'),
|
|
75
|
+
remove_association: toolbox.write('remove_association'),
|
|
76
|
+
|
|
77
|
+
create_note: toolbox.write('create_note'),
|
|
78
|
+
|
|
79
|
+
create_task: toolbox.write('create_task'),
|
|
80
|
+
update_task: toolbox.write('update_task'),
|
|
81
|
+
|
|
82
|
+
create_deal: toolbox.write('create_deal'),
|
|
83
|
+
update_deal: toolbox.write('update_deal'),
|
|
84
|
+
archive_deal: toolbox.write('archive_deal'),
|
|
85
|
+
|
|
86
|
+
create_ticket: toolbox.write('create_ticket'),
|
|
87
|
+
update_ticket: toolbox.write('update_ticket'),
|
|
88
|
+
archive_ticket: toolbox.write('archive_ticket'),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
expect(read).toBeTruthy()
|
|
92
|
+
expect(write).toBeTruthy()
|
|
93
|
+
}, 60000)
|
|
94
|
+
|
|
95
|
+
it('contact/company/engagements roundtrip', async () => {
|
|
96
|
+
const uniq = Date.now()
|
|
97
|
+
|
|
98
|
+
// Create contact
|
|
99
|
+
const createdContact = await write.create_contact({
|
|
100
|
+
firstname: 'CmdTest',
|
|
101
|
+
lastname: `HubSpot ${uniq}`,
|
|
102
|
+
email: `cmdtest+hubspot-${uniq}@example.com`,
|
|
103
|
+
})
|
|
104
|
+
ctx.contactId = createdContact?.id
|
|
105
|
+
expect(ctx.contactId).toBeTruthy()
|
|
106
|
+
|
|
107
|
+
// Update contact
|
|
108
|
+
await write.update_contact({
|
|
109
|
+
id: ctx.contactId,
|
|
110
|
+
firstname: 'CmdTestUpdated',
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Create company
|
|
114
|
+
const createdCompany = await write.create_company({
|
|
115
|
+
name: `CmdTest HubSpot Company ${uniq}`,
|
|
116
|
+
domain: `cmdtest-${uniq}.example.com`,
|
|
117
|
+
})
|
|
118
|
+
ctx.companyId = createdCompany?.id
|
|
119
|
+
expect(ctx.companyId).toBeTruthy()
|
|
120
|
+
|
|
121
|
+
// Associate contact <-> company
|
|
122
|
+
await write.create_association({
|
|
123
|
+
fromObjectType: 'contacts',
|
|
124
|
+
fromObjectId: ctx.contactId,
|
|
125
|
+
toObjectType: 'companies',
|
|
126
|
+
toObjectId: ctx.companyId,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Remove association
|
|
130
|
+
await write.remove_association({
|
|
131
|
+
fromObjectType: 'contacts',
|
|
132
|
+
fromObjectId: ctx.contactId,
|
|
133
|
+
toObjectType: 'companies',
|
|
134
|
+
toObjectId: ctx.companyId,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Create note associated to contact
|
|
138
|
+
await write.create_note({
|
|
139
|
+
body: `CmdTest note ${uniq}`,
|
|
140
|
+
associateWith: [{ objectType: 'contacts', objectId: ctx.contactId }],
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Create task associated to contact
|
|
144
|
+
const createdTask = await write.create_task({
|
|
145
|
+
subject: `CmdTest task ${uniq}`,
|
|
146
|
+
body: 'Created by integration test',
|
|
147
|
+
status: 'NOT_STARTED',
|
|
148
|
+
priority: 'MEDIUM',
|
|
149
|
+
dueTimestamp: Date.now() + 60 * 60 * 1000,
|
|
150
|
+
associateWith: [{ objectType: 'contacts', objectId: ctx.contactId }],
|
|
151
|
+
})
|
|
152
|
+
ctx.taskId = createdTask?.task?.id || createdTask?.id
|
|
153
|
+
expect(ctx.taskId).toBeTruthy()
|
|
154
|
+
|
|
155
|
+
// Mark task completed
|
|
156
|
+
await write.update_task({
|
|
157
|
+
id: ctx.taskId,
|
|
158
|
+
status: 'COMPLETED',
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Cleanup (best-effort)
|
|
162
|
+
await safeCleanup(async () => {
|
|
163
|
+
if (ctx.companyId)
|
|
164
|
+
await write.archive_company({ id: ctx.companyId })
|
|
165
|
+
})
|
|
166
|
+
await safeCleanup(async () => {
|
|
167
|
+
if (ctx.contactId)
|
|
168
|
+
await write.archive_contact({ id: ctx.contactId })
|
|
169
|
+
})
|
|
170
|
+
}, 120000)
|
|
171
|
+
|
|
172
|
+
it('deal and ticket roundtrip when pipeline + stage IDs are available', async () => {
|
|
173
|
+
const uniq = Date.now()
|
|
174
|
+
|
|
175
|
+
// Deals
|
|
176
|
+
let dealPipelineId = env.HUBSPOT_TEST_DEAL_PIPELINE_ID
|
|
177
|
+
let dealStageId = env.HUBSPOT_TEST_DEAL_STAGE_ID
|
|
178
|
+
if (!dealPipelineId || !dealStageId) {
|
|
179
|
+
try {
|
|
180
|
+
const pipelinesResp = await read.list_pipelines({ objectType: 'deals' })
|
|
181
|
+
const picked = pickFirstPipelineAndStage(pipelinesResp)
|
|
182
|
+
dealPipelineId = dealPipelineId || picked.pipelineId
|
|
183
|
+
dealStageId = dealStageId || picked.stageId
|
|
184
|
+
} catch {
|
|
185
|
+
// If scopes don't allow pipelines, skip deal tests.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (dealPipelineId && dealStageId) {
|
|
190
|
+
const createdDeal = await write.create_deal({
|
|
191
|
+
dealname: `CmdTest Deal ${uniq}`,
|
|
192
|
+
amount: 123,
|
|
193
|
+
pipeline: dealPipelineId,
|
|
194
|
+
dealstage: dealStageId,
|
|
195
|
+
})
|
|
196
|
+
ctx.dealId = createdDeal?.id
|
|
197
|
+
expect(ctx.dealId).toBeTruthy()
|
|
198
|
+
|
|
199
|
+
await write.update_deal({ id: ctx.dealId, amount: 456 })
|
|
200
|
+
|
|
201
|
+
await safeCleanup(async () => {
|
|
202
|
+
if (ctx.dealId)
|
|
203
|
+
await write.archive_deal({ id: ctx.dealId })
|
|
204
|
+
})
|
|
205
|
+
} else {
|
|
206
|
+
expect(true).toBe(true)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Tickets
|
|
210
|
+
let ticketPipelineId = env.HUBSPOT_TEST_TICKET_PIPELINE_ID
|
|
211
|
+
let ticketStageId = env.HUBSPOT_TEST_TICKET_STAGE_ID
|
|
212
|
+
if (!ticketPipelineId || !ticketStageId) {
|
|
213
|
+
try {
|
|
214
|
+
const pipelinesResp = await read.list_pipelines({ objectType: 'tickets' })
|
|
215
|
+
const picked = pickFirstPipelineAndStage(pipelinesResp)
|
|
216
|
+
ticketPipelineId = ticketPipelineId || picked.pipelineId
|
|
217
|
+
ticketStageId = ticketStageId || picked.stageId
|
|
218
|
+
} catch {
|
|
219
|
+
// If scopes don't allow pipelines, skip ticket tests.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (ticketPipelineId && ticketStageId) {
|
|
224
|
+
const createdTicket = await write.create_ticket({
|
|
225
|
+
subject: `CmdTest Ticket ${uniq}`,
|
|
226
|
+
content: 'Created by integration test',
|
|
227
|
+
hs_pipeline: ticketPipelineId,
|
|
228
|
+
hs_pipeline_stage: ticketStageId,
|
|
229
|
+
})
|
|
230
|
+
ctx.ticketId = createdTicket?.id
|
|
231
|
+
expect(ctx.ticketId).toBeTruthy()
|
|
232
|
+
|
|
233
|
+
await write.update_ticket({ id: ctx.ticketId, subject: `CmdTest Ticket Updated ${uniq}` })
|
|
234
|
+
|
|
235
|
+
await safeCleanup(async () => {
|
|
236
|
+
if (ctx.ticketId)
|
|
237
|
+
await write.archive_ticket({ id: ctx.ticketId })
|
|
238
|
+
})
|
|
239
|
+
} else {
|
|
240
|
+
expect(true).toBe(true)
|
|
241
|
+
}
|
|
242
|
+
}, 180000)
|
|
243
|
+
})
|
|
244
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"variants": {
|
|
3
|
+
"private_app_token": {
|
|
4
|
+
"label": "Private App Token",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"token": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "Private app access token",
|
|
11
|
+
"description": "HubSpot private app access token. This is sent as a Bearer token in the Authorization header."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": ["token"],
|
|
15
|
+
"additionalProperties": false
|
|
16
|
+
},
|
|
17
|
+
"injection": {
|
|
18
|
+
"headers": {
|
|
19
|
+
"Authorization": "Bearer {{token}}",
|
|
20
|
+
"Accept": "application/json"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"oauth_token": {
|
|
25
|
+
"label": "OAuth Access Token",
|
|
26
|
+
"schema": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"token": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"title": "OAuth access token",
|
|
32
|
+
"description": "OAuth 2.0 access token for HubSpot (Bearer token)."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"required": ["token"],
|
|
36
|
+
"additionalProperties": false
|
|
37
|
+
},
|
|
38
|
+
"injection": {
|
|
39
|
+
"headers": {
|
|
40
|
+
"Authorization": "Bearer {{token}}",
|
|
41
|
+
"Accept": "application/json"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"default": "private_app_token"
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## HubSpot credentials (Private App Token)
|
|
2
|
+
|
|
3
|
+
1. In HubSpot, open **Settings**.
|
|
4
|
+
2. Go to **Integrations** → **Private Apps**.
|
|
5
|
+
3. Click **Create private app**.
|
|
6
|
+
4. Name the app (e.g. "Commandable MCP") and save.
|
|
7
|
+
5. In **Scopes**, enable at least:
|
|
8
|
+
- `crm.objects.contacts.read` / `crm.objects.contacts.write`
|
|
9
|
+
- `crm.objects.companies.read` / `crm.objects.companies.write`
|
|
10
|
+
- `crm.objects.deals.read` / `crm.objects.deals.write`
|
|
11
|
+
- `tickets` (for tickets read/write)
|
|
12
|
+
- `crm.objects.owners.read`
|
|
13
|
+
- `crm.schemas.contacts.read`, `crm.schemas.companies.read`, `crm.schemas.deals.read`, `crm.schemas.tickets.read` (for listing properties)
|
|
14
|
+
6. Create the app and copy the generated **Access token**.
|
|
15
|
+
7. Paste the token into this integration's `token` credential field.
|
|
16
|
+
|
|
17
|
+
Notes:
|
|
18
|
+
- Keep this token secret. Anyone with the token can access your HubSpot account within the app's scopes.
|
|
19
|
+
- If you get 401/403 errors, double-check the app scopes and that the token is still active.
|
|
20
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## HubSpot credentials (OAuth access token)
|
|
2
|
+
|
|
3
|
+
1. Create (or use an existing) HubSpot public app that can issue OAuth 2.0 access tokens.
|
|
4
|
+
2. Ensure the app requests the scopes you need (for example):
|
|
5
|
+
- `crm.objects.contacts.read` / `crm.objects.contacts.write`
|
|
6
|
+
- `crm.objects.companies.read` / `crm.objects.companies.write`
|
|
7
|
+
- `crm.objects.deals.read` / `crm.objects.deals.write`
|
|
8
|
+
- `tickets`
|
|
9
|
+
- `crm.objects.owners.read`
|
|
10
|
+
- relevant `crm.schemas.*.read` scopes if you want to list properties
|
|
11
|
+
3. Complete the OAuth authorization code flow to obtain an **access token**.
|
|
12
|
+
4. Paste the access token into this integration's `token` credential field.
|
|
13
|
+
|
|
14
|
+
Notes:
|
|
15
|
+
- HubSpot OAuth access tokens are short-lived. If calls start failing with 401, refresh and provide a new access token.
|
|
16
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(`/crm/v3/objects/companies/${encodeURIComponent(input.id)}`, {
|
|
3
|
+
method: 'DELETE',
|
|
4
|
+
})
|
|
5
|
+
const text = await res.text()
|
|
6
|
+
if (!text) return { ok: res.ok, status: res.status }
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text)
|
|
9
|
+
} catch {
|
|
10
|
+
return { ok: res.ok, status: res.status, body: text }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(`/crm/v3/objects/contacts/${encodeURIComponent(input.id)}`, {
|
|
3
|
+
method: 'DELETE',
|
|
4
|
+
})
|
|
5
|
+
const text = await res.text()
|
|
6
|
+
if (!text) return { ok: res.ok, status: res.status }
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text)
|
|
9
|
+
} catch {
|
|
10
|
+
return { ok: res.ok, status: res.status, body: text }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(`/crm/v3/objects/deals/${encodeURIComponent(input.id)}`, {
|
|
3
|
+
method: 'DELETE',
|
|
4
|
+
})
|
|
5
|
+
const text = await res.text()
|
|
6
|
+
if (!text) return { ok: res.ok, status: res.status }
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text)
|
|
9
|
+
} catch {
|
|
10
|
+
return { ok: res.ok, status: res.status, body: text }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(`/crm/v3/objects/tickets/${encodeURIComponent(input.id)}`, {
|
|
3
|
+
method: 'DELETE',
|
|
4
|
+
})
|
|
5
|
+
const text = await res.text()
|
|
6
|
+
if (!text) return { ok: res.ok, status: res.status }
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text)
|
|
9
|
+
} catch {
|
|
10
|
+
return { ok: res.ok, status: res.status, body: text }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(
|
|
3
|
+
`/crm/v4/objects/${encodeURIComponent(input.fromObjectType)}/${encodeURIComponent(
|
|
4
|
+
input.fromObjectId
|
|
5
|
+
)}/associations/default/${encodeURIComponent(input.toObjectType)}/${encodeURIComponent(
|
|
6
|
+
input.toObjectId
|
|
7
|
+
)}`,
|
|
8
|
+
{ method: 'PUT' }
|
|
9
|
+
)
|
|
10
|
+
const text = await res.text()
|
|
11
|
+
if (!text) return { ok: res.ok, status: res.status }
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(text)
|
|
14
|
+
} catch {
|
|
15
|
+
return { ok: res.ok, status: res.status, body: text }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const props = { ...(input.properties || {}) }
|
|
3
|
+
if (input.name !== undefined) props.name = input.name
|
|
4
|
+
if (input.domain !== undefined) props.domain = input.domain
|
|
5
|
+
|
|
6
|
+
const body = { properties: props }
|
|
7
|
+
const res = await integration.fetch(`/crm/v3/objects/companies`, {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
body,
|
|
10
|
+
})
|
|
11
|
+
return await res.json()
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const props = { ...(input.properties || {}) }
|
|
3
|
+
if (input.firstname !== undefined) props.firstname = input.firstname
|
|
4
|
+
if (input.lastname !== undefined) props.lastname = input.lastname
|
|
5
|
+
if (input.email !== undefined) props.email = input.email
|
|
6
|
+
|
|
7
|
+
const body = { properties: props }
|
|
8
|
+
const res = await integration.fetch(`/crm/v3/objects/contacts`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
body,
|
|
11
|
+
})
|
|
12
|
+
return await res.json()
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const props = { ...(input.properties || {}) }
|
|
3
|
+
if (input.dealname !== undefined) props.dealname = input.dealname
|
|
4
|
+
if (input.amount !== undefined) props.amount = String(input.amount)
|
|
5
|
+
if (input.pipeline !== undefined) props.pipeline = input.pipeline
|
|
6
|
+
if (input.dealstage !== undefined) props.dealstage = input.dealstage
|
|
7
|
+
if (input.closedate !== undefined) props.closedate = String(input.closedate)
|
|
8
|
+
|
|
9
|
+
const body = { properties: props }
|
|
10
|
+
const res = await integration.fetch(`/crm/v3/objects/deals`, {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
body,
|
|
13
|
+
})
|
|
14
|
+
return await res.json()
|
|
15
|
+
}
|
|
16
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const nowMs = Date.now()
|
|
3
|
+
|
|
4
|
+
const props = {
|
|
5
|
+
hs_note_body: input.body,
|
|
6
|
+
hs_timestamp: input.timestamp !== undefined ? String(input.timestamp) : String(nowMs),
|
|
7
|
+
...(input.properties || {}),
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (input.hubspot_owner_id !== undefined) {
|
|
11
|
+
props.hubspot_owner_id = String(input.hubspot_owner_id)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const createRes = await integration.fetch(`/crm/v3/objects/notes`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
body: { properties: props },
|
|
17
|
+
})
|
|
18
|
+
const note = await createRes.json()
|
|
19
|
+
|
|
20
|
+
const associationResults = []
|
|
21
|
+
if (Array.isArray(input.associateWith) && input.associateWith.length > 0 && note?.id) {
|
|
22
|
+
for (const a of input.associateWith) {
|
|
23
|
+
const res = await integration.fetch(
|
|
24
|
+
`/crm/v4/objects/notes/${encodeURIComponent(String(note.id))}/associations/default/${encodeURIComponent(
|
|
25
|
+
a.objectType
|
|
26
|
+
)}/${encodeURIComponent(a.objectId)}`,
|
|
27
|
+
{ method: 'PUT' }
|
|
28
|
+
)
|
|
29
|
+
const text = await res.text()
|
|
30
|
+
if (!text) {
|
|
31
|
+
associationResults.push({ ok: res.ok, status: res.status })
|
|
32
|
+
} else {
|
|
33
|
+
try {
|
|
34
|
+
associationResults.push(JSON.parse(text))
|
|
35
|
+
} catch {
|
|
36
|
+
associationResults.push({ ok: res.ok, status: res.status, body: text })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { note, associationResults }
|
|
43
|
+
}
|
|
44
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const nowMs = Date.now()
|
|
3
|
+
|
|
4
|
+
const props = {
|
|
5
|
+
hs_task_subject: input.subject,
|
|
6
|
+
hs_task_body: input.body,
|
|
7
|
+
hs_task_status: input.status,
|
|
8
|
+
hs_task_priority: input.priority,
|
|
9
|
+
hs_timestamp:
|
|
10
|
+
input.dueTimestamp !== undefined ? String(input.dueTimestamp) : String(nowMs),
|
|
11
|
+
...(input.properties || {}),
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (input.hubspot_owner_id !== undefined) {
|
|
15
|
+
props.hubspot_owner_id = String(input.hubspot_owner_id)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const createRes = await integration.fetch(`/crm/v3/objects/tasks`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
body: { properties: props },
|
|
21
|
+
})
|
|
22
|
+
const task = await createRes.json()
|
|
23
|
+
|
|
24
|
+
const associationResults = []
|
|
25
|
+
if (Array.isArray(input.associateWith) && input.associateWith.length > 0 && task?.id) {
|
|
26
|
+
for (const a of input.associateWith) {
|
|
27
|
+
const res = await integration.fetch(
|
|
28
|
+
`/crm/v4/objects/tasks/${encodeURIComponent(String(task.id))}/associations/default/${encodeURIComponent(
|
|
29
|
+
a.objectType
|
|
30
|
+
)}/${encodeURIComponent(a.objectId)}`,
|
|
31
|
+
{ method: 'PUT' }
|
|
32
|
+
)
|
|
33
|
+
const text = await res.text()
|
|
34
|
+
if (!text) {
|
|
35
|
+
associationResults.push({ ok: res.ok, status: res.status })
|
|
36
|
+
} else {
|
|
37
|
+
try {
|
|
38
|
+
associationResults.push(JSON.parse(text))
|
|
39
|
+
} catch {
|
|
40
|
+
associationResults.push({ ok: res.ok, status: res.status, body: text })
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { task, associationResults }
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const props = { ...(input.properties || {}) }
|
|
3
|
+
if (input.subject !== undefined) props.subject = input.subject
|
|
4
|
+
if (input.content !== undefined) props.content = input.content
|
|
5
|
+
if (input.hs_pipeline !== undefined) props.hs_pipeline = input.hs_pipeline
|
|
6
|
+
if (input.hs_pipeline_stage !== undefined) props.hs_pipeline_stage = input.hs_pipeline_stage
|
|
7
|
+
|
|
8
|
+
const body = { properties: props }
|
|
9
|
+
const res = await integration.fetch(`/crm/v3/objects/tickets`, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
body,
|
|
12
|
+
})
|
|
13
|
+
return await res.json()
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input?.after) params.set('after', String(input.after))
|
|
4
|
+
if (input?.limit) params.set('limit', String(input.limit))
|
|
5
|
+
|
|
6
|
+
const suffix = params.toString() ? `?${params.toString()}` : ''
|
|
7
|
+
const res = await integration.fetch(
|
|
8
|
+
`/crm/v4/objects/${encodeURIComponent(input.fromObjectType)}/${encodeURIComponent(
|
|
9
|
+
input.fromObjectId
|
|
10
|
+
)}/associations/${encodeURIComponent(input.toObjectType)}${suffix}`
|
|
11
|
+
)
|
|
12
|
+
return await res.json()
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
|
|
4
|
+
if (Array.isArray(input.properties)) {
|
|
5
|
+
for (const p of input.properties) params.append('properties', p)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (Array.isArray(input.associations)) {
|
|
9
|
+
for (const a of input.associations) params.append('associations', a)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof input.archived === 'boolean') params.set('archived', String(input.archived))
|
|
13
|
+
|
|
14
|
+
const suffix = params.toString() ? `?${params.toString()}` : ''
|
|
15
|
+
const res = await integration.fetch(`/crm/v3/objects/companies/${encodeURIComponent(input.id)}${suffix}`)
|
|
16
|
+
return await res.json()
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
|
|
4
|
+
if (Array.isArray(input.properties)) {
|
|
5
|
+
for (const p of input.properties) params.append('properties', p)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (Array.isArray(input.associations)) {
|
|
9
|
+
for (const a of input.associations) params.append('associations', a)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof input.archived === 'boolean') params.set('archived', String(input.archived))
|
|
13
|
+
|
|
14
|
+
const suffix = params.toString() ? `?${params.toString()}` : ''
|
|
15
|
+
const res = await integration.fetch(`/crm/v3/objects/contacts/${encodeURIComponent(input.id)}${suffix}`)
|
|
16
|
+
return await res.json()
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
|
|
4
|
+
if (Array.isArray(input.properties)) {
|
|
5
|
+
for (const p of input.properties) params.append('properties', p)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (Array.isArray(input.associations)) {
|
|
9
|
+
for (const a of input.associations) params.append('associations', a)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof input.archived === 'boolean') params.set('archived', String(input.archived))
|
|
13
|
+
|
|
14
|
+
const suffix = params.toString() ? `?${params.toString()}` : ''
|
|
15
|
+
const res = await integration.fetch(`/crm/v3/objects/deals/${encodeURIComponent(input.id)}${suffix}`)
|
|
16
|
+
return await res.json()
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
|
|
4
|
+
if (Array.isArray(input.properties)) {
|
|
5
|
+
for (const p of input.properties) params.append('properties', p)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (Array.isArray(input.associations)) {
|
|
9
|
+
for (const a of input.associations) params.append('associations', a)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (typeof input.archived === 'boolean') params.set('archived', String(input.archived))
|
|
13
|
+
|
|
14
|
+
const suffix = params.toString() ? `?${params.toString()}` : ''
|
|
15
|
+
const res = await integration.fetch(
|
|
16
|
+
`/crm/v3/objects/tickets/${encodeURIComponent(input.id)}${suffix}`
|
|
17
|
+
)
|
|
18
|
+
return await res.json()
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input?.email) params.set('email', input.email)
|
|
4
|
+
if (input?.after) params.set('after', String(input.after))
|
|
5
|
+
if (input?.limit) params.set('limit', String(input.limit))
|
|
6
|
+
if (typeof input?.archived === 'boolean') params.set('archived', String(input.archived))
|
|
7
|
+
|
|
8
|
+
const suffix = params.toString() ? `?${params.toString()}` : ''
|
|
9
|
+
const res = await integration.fetch(`/crm/v3/owners/${suffix}`)
|
|
10
|
+
return await res.json()
|
|
11
|
+
}
|
|
12
|
+
|