@commandable/integration-data 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/dist/credentials-index.d.ts.map +1 -1
  2. package/dist/credentials-index.js +130 -0
  3. package/dist/credentials-index.js.map +1 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/loader.d.ts +48 -0
  9. package/dist/loader.d.ts.map +1 -1
  10. package/dist/loader.js +46 -4
  11. package/dist/loader.js.map +1 -1
  12. package/integrations/__tests__/liveHarness.ts +16 -3
  13. package/integrations/airtable/.env.test +9 -0
  14. package/integrations/airtable/.env.test.example +11 -0
  15. package/integrations/airtable/README.md +27 -0
  16. package/integrations/airtable/__tests__/get_handlers.test.ts +43 -5
  17. package/integrations/airtable/credentials.json +2 -1
  18. package/integrations/confluence/.env.test +25 -0
  19. package/integrations/confluence/.env.test.example +36 -0
  20. package/integrations/confluence/README.md +28 -0
  21. package/integrations/confluence/__tests__/get_handlers.test.ts +121 -0
  22. package/integrations/confluence/__tests__/usage_parity.test.ts +14 -0
  23. package/integrations/confluence/__tests__/write_handlers.test.ts +131 -0
  24. package/integrations/confluence/credentials.json +39 -0
  25. package/integrations/confluence/credentials_hint.md +4 -0
  26. package/integrations/confluence/credentials_hint_api_token.md +9 -0
  27. package/integrations/confluence/credentials_hint_oauth_token.md +8 -0
  28. package/integrations/confluence/handlers/add_comment.js +19 -0
  29. package/integrations/confluence/handlers/add_label.js +16 -0
  30. package/integrations/confluence/handlers/create_page.js +22 -0
  31. package/integrations/confluence/handlers/delete_page.js +17 -0
  32. package/integrations/confluence/handlers/get_comments.js +33 -0
  33. package/integrations/confluence/handlers/get_page_children.js +30 -0
  34. package/integrations/confluence/handlers/get_space.js +22 -0
  35. package/integrations/confluence/handlers/list_spaces.js +39 -0
  36. package/integrations/confluence/handlers/read_page.js +49 -0
  37. package/integrations/confluence/handlers/search_pages.js +42 -0
  38. package/integrations/confluence/handlers/update_page.js +42 -0
  39. package/integrations/confluence/manifest.json +85 -0
  40. package/integrations/confluence/prompt.md +55 -0
  41. package/integrations/confluence/schemas/add_comment.json +22 -0
  42. package/integrations/confluence/schemas/add_label.json +19 -0
  43. package/integrations/confluence/schemas/create_page.json +33 -0
  44. package/integrations/confluence/schemas/delete_page.json +23 -0
  45. package/integrations/confluence/schemas/empty.json +6 -0
  46. package/integrations/confluence/schemas/get_comments.json +24 -0
  47. package/integrations/confluence/schemas/get_page_children.json +28 -0
  48. package/integrations/confluence/schemas/get_space.json +18 -0
  49. package/integrations/confluence/schemas/list_spaces.json +36 -0
  50. package/integrations/confluence/schemas/read_page.json +28 -0
  51. package/integrations/confluence/schemas/search_pages.json +26 -0
  52. package/integrations/confluence/schemas/update_page.json +31 -0
  53. package/integrations/github/.env.test +16 -0
  54. package/integrations/github/.env.test.example +17 -0
  55. package/integrations/github/README.md +75 -0
  56. package/integrations/github/__tests__/get_handlers.test.ts +5 -5
  57. package/integrations/github/__tests__/write_handlers.test.ts +176 -58
  58. package/integrations/github/credentials.json +4 -2
  59. package/integrations/github/handlers/create_file.js +46 -0
  60. package/integrations/github/handlers/delete_file.js +14 -1
  61. package/integrations/github/handlers/edit_file.js +52 -0
  62. package/integrations/github/handlers/edit_files.js +107 -0
  63. package/integrations/github/manifest.json +74 -47
  64. package/integrations/github/prompt.md +36 -0
  65. package/integrations/github/schemas/create_file.json +13 -0
  66. package/integrations/github/schemas/delete_file.json +2 -2
  67. package/integrations/github/schemas/edit_file.json +26 -0
  68. package/integrations/github/schemas/edit_files.json +39 -0
  69. package/integrations/google-calendar/.env.test.example +11 -0
  70. package/integrations/google-calendar/README.md +41 -0
  71. package/integrations/google-calendar/__tests__/write_and_admin_handlers.test.ts +7 -7
  72. package/integrations/google-calendar/credentials.json +4 -2
  73. package/integrations/google-calendar/credentials_hint.md +1 -1
  74. package/integrations/google-calendar/manifest.json +27 -17
  75. package/integrations/google-docs/README.md +30 -0
  76. package/integrations/google-docs/credentials_hint.md +1 -1
  77. package/integrations/google-drive/README.md +26 -0
  78. package/integrations/google-drive/credentials.json +4 -2
  79. package/integrations/google-gmail/.env.test.example +11 -0
  80. package/integrations/google-gmail/README.md +49 -0
  81. package/integrations/google-gmail/credentials.json +4 -2
  82. package/integrations/google-gmail/handlers/create_draft_email.js +1 -1
  83. package/integrations/google-gmail/handlers/read_email.js +1 -1
  84. package/integrations/google-gmail/handlers/send_email.js +1 -1
  85. package/integrations/google-gmail/manifest.json +36 -25
  86. package/integrations/google-sheet/README.md +27 -0
  87. package/integrations/google-sheet/credentials_hint.md +1 -1
  88. package/integrations/google-slides/README.md +28 -0
  89. package/integrations/google-slides/credentials_hint.md +1 -1
  90. package/integrations/hubspot/.env.test.example +20 -0
  91. package/integrations/hubspot/README.md +48 -0
  92. package/integrations/hubspot/__tests__/get_handlers.test.ts +151 -0
  93. package/integrations/hubspot/__tests__/usage_parity.test.ts +10 -0
  94. package/integrations/hubspot/__tests__/write_handlers.test.ts +244 -0
  95. package/integrations/hubspot/credentials.json +50 -0
  96. package/integrations/hubspot/credentials_hint.md +20 -0
  97. package/integrations/hubspot/credentials_hint_oauth_token.md +16 -0
  98. package/integrations/hubspot/handlers/archive_company.js +13 -0
  99. package/integrations/hubspot/handlers/archive_contact.js +13 -0
  100. package/integrations/hubspot/handlers/archive_deal.js +13 -0
  101. package/integrations/hubspot/handlers/archive_ticket.js +13 -0
  102. package/integrations/hubspot/handlers/create_association.js +18 -0
  103. package/integrations/hubspot/handlers/create_company.js +13 -0
  104. package/integrations/hubspot/handlers/create_contact.js +14 -0
  105. package/integrations/hubspot/handlers/create_deal.js +16 -0
  106. package/integrations/hubspot/handlers/create_note.js +44 -0
  107. package/integrations/hubspot/handlers/create_task.js +48 -0
  108. package/integrations/hubspot/handlers/create_ticket.js +15 -0
  109. package/integrations/hubspot/handlers/get_associations.js +14 -0
  110. package/integrations/hubspot/handlers/get_company.js +18 -0
  111. package/integrations/hubspot/handlers/get_contact.js +18 -0
  112. package/integrations/hubspot/handlers/get_deal.js +18 -0
  113. package/integrations/hubspot/handlers/get_ticket.js +20 -0
  114. package/integrations/hubspot/handlers/list_owners.js +12 -0
  115. package/integrations/hubspot/handlers/list_pipelines.js +5 -0
  116. package/integrations/hubspot/handlers/list_properties.js +11 -0
  117. package/integrations/hubspot/handlers/remove_association.js +22 -0
  118. package/integrations/hubspot/handlers/search_companies.js +43 -0
  119. package/integrations/hubspot/handlers/search_contacts.js +43 -0
  120. package/integrations/hubspot/handlers/search_deals.js +43 -0
  121. package/integrations/hubspot/handlers/search_notes.js +43 -0
  122. package/integrations/hubspot/handlers/search_tasks.js +43 -0
  123. package/integrations/hubspot/handlers/search_tickets.js +43 -0
  124. package/integrations/hubspot/handlers/update_company.js +13 -0
  125. package/integrations/hubspot/handlers/update_contact.js +14 -0
  126. package/integrations/hubspot/handlers/update_deal.js +16 -0
  127. package/integrations/hubspot/handlers/update_task.js +17 -0
  128. package/integrations/hubspot/handlers/update_ticket.js +15 -0
  129. package/integrations/hubspot/manifest.json +230 -0
  130. package/integrations/hubspot/prompt.md +69 -0
  131. package/integrations/hubspot/schemas/archive_company.json +13 -0
  132. package/integrations/hubspot/schemas/archive_contact.json +13 -0
  133. package/integrations/hubspot/schemas/archive_deal.json +9 -0
  134. package/integrations/hubspot/schemas/archive_ticket.json +9 -0
  135. package/integrations/hubspot/schemas/create_association.json +24 -0
  136. package/integrations/hubspot/schemas/create_company.json +14 -0
  137. package/integrations/hubspot/schemas/create_contact.json +15 -0
  138. package/integrations/hubspot/schemas/create_deal.json +20 -0
  139. package/integrations/hubspot/schemas/create_note.json +37 -0
  140. package/integrations/hubspot/schemas/create_task.json +51 -0
  141. package/integrations/hubspot/schemas/create_ticket.json +16 -0
  142. package/integrations/hubspot/schemas/empty.json +6 -0
  143. package/integrations/hubspot/schemas/get_associations.json +30 -0
  144. package/integrations/hubspot/schemas/get_company.json +27 -0
  145. package/integrations/hubspot/schemas/get_contact.json +27 -0
  146. package/integrations/hubspot/schemas/get_deal.json +20 -0
  147. package/integrations/hubspot/schemas/get_ticket.json +20 -0
  148. package/integrations/hubspot/schemas/list_owners.json +25 -0
  149. package/integrations/hubspot/schemas/list_pipelines.json +13 -0
  150. package/integrations/hubspot/schemas/list_properties.json +17 -0
  151. package/integrations/hubspot/schemas/remove_association.json +24 -0
  152. package/integrations/hubspot/schemas/search_companies.json +56 -0
  153. package/integrations/hubspot/schemas/search_contacts.json +56 -0
  154. package/integrations/hubspot/schemas/search_deals.json +43 -0
  155. package/integrations/hubspot/schemas/search_notes.json +43 -0
  156. package/integrations/hubspot/schemas/search_tasks.json +43 -0
  157. package/integrations/hubspot/schemas/search_tickets.json +43 -0
  158. package/integrations/hubspot/schemas/update_company.json +20 -0
  159. package/integrations/hubspot/schemas/update_contact.json +21 -0
  160. package/integrations/hubspot/schemas/update_deal.json +19 -0
  161. package/integrations/hubspot/schemas/update_task.json +31 -0
  162. package/integrations/hubspot/schemas/update_ticket.json +18 -0
  163. package/integrations/jira/.env.test +46 -0
  164. package/integrations/jira/.env.test.example +41 -0
  165. package/integrations/jira/README.md +46 -0
  166. package/integrations/jira/__tests__/get_handlers.test.ts +193 -0
  167. package/integrations/jira/__tests__/usage_parity.test.ts +14 -0
  168. package/integrations/jira/__tests__/write_handlers.test.ts +157 -0
  169. package/integrations/jira/credentials.json +39 -0
  170. package/integrations/jira/credentials_hint.md +4 -0
  171. package/integrations/jira/credentials_hint_api_token.md +6 -0
  172. package/integrations/jira/credentials_hint_oauth_token.md +6 -0
  173. package/integrations/jira/handlers/add_comment.js +9 -0
  174. package/integrations/jira/handlers/assign_issue.js +11 -0
  175. package/integrations/jira/handlers/create_issue.js +37 -0
  176. package/integrations/jira/handlers/create_sprint.js +19 -0
  177. package/integrations/jira/handlers/delete_issue.js +10 -0
  178. package/integrations/jira/handlers/get_backlog_issues.js +13 -0
  179. package/integrations/jira/handlers/get_board.js +6 -0
  180. package/integrations/jira/handlers/get_issue.js +63 -0
  181. package/integrations/jira/handlers/get_issue_comments.js +31 -0
  182. package/integrations/jira/handlers/get_myself.js +14 -0
  183. package/integrations/jira/handlers/get_project.js +28 -0
  184. package/integrations/jira/handlers/get_sprint.js +5 -0
  185. package/integrations/jira/handlers/get_sprint_issues.js +13 -0
  186. package/integrations/jira/handlers/get_transitions.js +23 -0
  187. package/integrations/jira/handlers/list_boards.js +34 -0
  188. package/integrations/jira/handlers/list_projects.js +29 -0
  189. package/integrations/jira/handlers/list_sprints.js +29 -0
  190. package/integrations/jira/handlers/move_issues_to_sprint.js +11 -0
  191. package/integrations/jira/handlers/search_issues.js +43 -0
  192. package/integrations/jira/handlers/search_users.js +21 -0
  193. package/integrations/jira/handlers/transition_issue.js +44 -0
  194. package/integrations/jira/handlers/update_issue.js +40 -0
  195. package/integrations/jira/handlers/update_sprint.js +20 -0
  196. package/integrations/jira/manifest.json +204 -0
  197. package/integrations/jira/prompt.md +80 -0
  198. package/integrations/jira/schemas/add_comment.json +16 -0
  199. package/integrations/jira/schemas/assign_issue.json +16 -0
  200. package/integrations/jira/schemas/create_issue.json +49 -0
  201. package/integrations/jira/schemas/create_sprint.json +29 -0
  202. package/integrations/jira/schemas/delete_issue.json +12 -0
  203. package/integrations/jira/schemas/empty.json +6 -0
  204. package/integrations/jira/schemas/get_backlog_issues.json +33 -0
  205. package/integrations/jira/schemas/get_board.json +13 -0
  206. package/integrations/jira/schemas/get_issue.json +23 -0
  207. package/integrations/jira/schemas/get_issue_comments.json +23 -0
  208. package/integrations/jira/schemas/get_project.json +17 -0
  209. package/integrations/jira/schemas/get_sprint.json +13 -0
  210. package/integrations/jira/schemas/get_sprint_issues.json +33 -0
  211. package/integrations/jira/schemas/get_transitions.json +12 -0
  212. package/integrations/jira/schemas/list_boards.json +27 -0
  213. package/integrations/jira/schemas/list_projects.json +22 -0
  214. package/integrations/jira/schemas/list_sprints.json +29 -0
  215. package/integrations/jira/schemas/move_issues_to_sprint.json +19 -0
  216. package/integrations/jira/schemas/search_issues.json +28 -0
  217. package/integrations/jira/schemas/search_users.json +18 -0
  218. package/integrations/jira/schemas/transition_issue.json +38 -0
  219. package/integrations/jira/schemas/update_issue.json +47 -0
  220. package/integrations/jira/schemas/update_sprint.json +33 -0
  221. package/integrations/new_integration_prompt.md +173 -2
  222. package/integrations/notion/.env.test +10 -0
  223. package/integrations/notion/.env.test.example +13 -0
  224. package/integrations/notion/README.md +42 -0
  225. package/integrations/notion/credentials.json +2 -1
  226. package/integrations/notion/manifest.json +64 -35
  227. package/integrations/trello/.env.test +6 -0
  228. package/integrations/trello/.env.test.example +9 -0
  229. package/integrations/trello/README.md +50 -0
  230. package/integrations/trello/credentials.json +2 -1
  231. package/package.json +7 -3
@@ -0,0 +1,151 @@
1
+ import { beforeAll, describe, expect, it } from 'vitest'
2
+ import {
3
+ createCredentialStore,
4
+ createIntegrationNode,
5
+ createProxy,
6
+ createToolbox,
7
+ hasEnv,
8
+ } from '../../__tests__/liveHarness.js'
9
+
10
+ // LIVE HubSpot read tests using credentials
11
+ // Required env vars:
12
+ // - HUBSPOT_TOKEN
13
+ //
14
+ // Optional env vars:
15
+ // - HUBSPOT_TEST_CONTACT_ID
16
+ // - HUBSPOT_TEST_COMPANY_ID
17
+ // - HUBSPOT_TEST_DEAL_ID
18
+ // - HUBSPOT_TEST_TICKET_ID
19
+
20
+ const env = process.env as Record<string, string | undefined>
21
+ const suite = hasEnv('HUBSPOT_TOKEN') ? describe : describe.skip
22
+
23
+ suite('hubspot read handlers (live)', () => {
24
+ const ctx: {
25
+ contactId?: string
26
+ companyId?: string
27
+ dealId?: string
28
+ ticketId?: string
29
+ } = {}
30
+
31
+ let read: Record<string, (input: any) => Promise<any>>
32
+
33
+ beforeAll(async () => {
34
+ ctx.contactId = env.HUBSPOT_TEST_CONTACT_ID
35
+ ctx.companyId = env.HUBSPOT_TEST_COMPANY_ID
36
+ ctx.dealId = env.HUBSPOT_TEST_DEAL_ID
37
+ ctx.ticketId = env.HUBSPOT_TEST_TICKET_ID
38
+
39
+ const credentialStore = createCredentialStore(async () => ({ token: env.HUBSPOT_TOKEN || '' }))
40
+ const proxy = createProxy(credentialStore)
41
+ const node = createIntegrationNode('hubspot')
42
+ const toolbox = createToolbox('hubspot', proxy, node)
43
+
44
+ // IMPORTANT: These literal tool-name strings are intentionally present so usage-parity can verify coverage.
45
+ read = {
46
+ search_contacts: toolbox.read('search_contacts'),
47
+ get_contact: toolbox.read('get_contact'),
48
+ search_companies: toolbox.read('search_companies'),
49
+ get_company: toolbox.read('get_company'),
50
+ list_owners: toolbox.read('list_owners'),
51
+ list_properties: toolbox.read('list_properties'),
52
+ list_pipelines: toolbox.read('list_pipelines'),
53
+ get_associations: toolbox.read('get_associations'),
54
+ search_deals: toolbox.read('search_deals'),
55
+ get_deal: toolbox.read('get_deal'),
56
+ search_tickets: toolbox.read('search_tickets'),
57
+ get_ticket: toolbox.read('get_ticket'),
58
+ search_notes: toolbox.read('search_notes'),
59
+ search_tasks: toolbox.read('search_tasks'),
60
+ // read tools in the manifest are limited to those above; remaining tools are write-scoped
61
+ }
62
+
63
+ expect(read).toBeTruthy()
64
+ }, 60000)
65
+
66
+ it('search_contacts returns results (or empty)', async () => {
67
+ const result = await read.search_contacts({ limit: 1 })
68
+ expect(result).toBeTruthy()
69
+ }, 30000)
70
+
71
+ it('search_companies returns results (or empty)', async () => {
72
+ const result = await read.search_companies({ limit: 1 })
73
+ expect(result).toBeTruthy()
74
+ }, 30000)
75
+
76
+ it('list_owners returns owners', async () => {
77
+ const result = await read.list_owners({ limit: 1 })
78
+ expect(result).toBeTruthy()
79
+ }, 30000)
80
+
81
+ it('list_properties(contacts) returns properties', async () => {
82
+ const result = await read.list_properties({ objectType: 'contacts' })
83
+ expect(result).toBeTruthy()
84
+ }, 30000)
85
+
86
+ it('list_pipelines(deals) returns pipelines', async () => {
87
+ const result = await read.list_pipelines({ objectType: 'deals' })
88
+ expect(result).toBeTruthy()
89
+ }, 30000)
90
+
91
+ it('get_contact returns a contact when HUBSPOT_TEST_CONTACT_ID is set', async () => {
92
+ if (!ctx.contactId)
93
+ return expect(true).toBe(true)
94
+ const result = await read.get_contact({ id: ctx.contactId })
95
+ expect(result?.id).toBeTruthy()
96
+ }, 30000)
97
+
98
+ it('get_company returns a company when HUBSPOT_TEST_COMPANY_ID is set', async () => {
99
+ if (!ctx.companyId)
100
+ return expect(true).toBe(true)
101
+ const result = await read.get_company({ id: ctx.companyId })
102
+ expect(result?.id).toBeTruthy()
103
+ }, 30000)
104
+
105
+ it('get_deal returns a deal when HUBSPOT_TEST_DEAL_ID is set', async () => {
106
+ if (!ctx.dealId)
107
+ return expect(true).toBe(true)
108
+ const result = await read.get_deal({ id: ctx.dealId })
109
+ expect(result?.id).toBeTruthy()
110
+ }, 30000)
111
+
112
+ it('get_ticket returns a ticket when HUBSPOT_TEST_TICKET_ID is set', async () => {
113
+ if (!ctx.ticketId)
114
+ return expect(true).toBe(true)
115
+ const result = await read.get_ticket({ id: ctx.ticketId })
116
+ expect(result?.id).toBeTruthy()
117
+ }, 30000)
118
+
119
+ it('search_deals returns results (or empty)', async () => {
120
+ const result = await read.search_deals({ limit: 1 })
121
+ expect(result).toBeTruthy()
122
+ }, 30000)
123
+
124
+ it('search_tickets returns results (or empty)', async () => {
125
+ const result = await read.search_tickets({ limit: 1 })
126
+ expect(result).toBeTruthy()
127
+ }, 30000)
128
+
129
+ it('search_notes returns results (or empty)', async () => {
130
+ const result = await read.search_notes({ limit: 1 })
131
+ expect(result).toBeTruthy()
132
+ }, 30000)
133
+
134
+ it('search_tasks returns results (or empty)', async () => {
135
+ const result = await read.search_tasks({ limit: 1 })
136
+ expect(result).toBeTruthy()
137
+ }, 30000)
138
+
139
+ it('get_associations returns associations when HUBSPOT_TEST_CONTACT_ID is set', async () => {
140
+ if (!ctx.contactId)
141
+ return expect(true).toBe(true)
142
+ const result = await read.get_associations({
143
+ fromObjectType: 'contacts',
144
+ fromObjectId: ctx.contactId,
145
+ toObjectType: 'companies',
146
+ limit: 1,
147
+ })
148
+ expect(result).toBeTruthy()
149
+ }, 30000)
150
+ })
151
+
@@ -0,0 +1,10 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getMissingToolUsages } from '../../__tests__/usageParity.js'
3
+
4
+ describe('hubspot static usage parity', () => {
5
+ it('every manifest tool is referenced in tests', () => {
6
+ const missing = getMissingToolUsages({ integrationName: 'hubspot', importMetaUrl: import.meta.url })
7
+ expect(missing, `Missing handler usages in tests: ${missing.join(', ')}`).toEqual([])
8
+ })
9
+ })
10
+
@@ -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,50 @@
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
+ "healthCheck": { "path": "/crm/v3/owners?limit=1" }
24
+ },
25
+ "oauth_token": {
26
+ "label": "OAuth Access Token",
27
+ "schema": {
28
+ "type": "object",
29
+ "properties": {
30
+ "token": {
31
+ "type": "string",
32
+ "title": "OAuth access token",
33
+ "description": "OAuth 2.0 access token for HubSpot (Bearer token)."
34
+ }
35
+ },
36
+ "required": ["token"],
37
+ "additionalProperties": false
38
+ },
39
+ "injection": {
40
+ "headers": {
41
+ "Authorization": "Bearer {{token}}",
42
+ "Accept": "application/json"
43
+ }
44
+ },
45
+ "healthCheck": { "path": "/crm/v3/owners?limit=1" }
46
+ }
47
+ },
48
+ "default": "private_app_token"
49
+ }
50
+
@@ -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
+