@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,193 @@
1
+ import { beforeAll, describe, expect, it } from 'vitest'
2
+ import { createCredentialStore, createIntegrationNode, createProxy, createToolbox, hasEnv } from '../../__tests__/liveHarness.js'
3
+
4
+ // LIVE Jira read tests using credentials
5
+ //
6
+ // Variant: api_token
7
+ // - JIRA_DOMAIN
8
+ // - JIRA_EMAIL
9
+ // - JIRA_API_TOKEN
10
+ //
11
+ // Optional (improves coverage):
12
+ // - JIRA_TEST_PROJECT_KEY
13
+
14
+ const env = process.env as Record<string, string | undefined>
15
+
16
+ const suiteOrSkip = hasEnv('JIRA_DOMAIN', 'JIRA_EMAIL', 'JIRA_API_TOKEN') ? describe : describe.skip
17
+
18
+ suiteOrSkip('jira read handlers (live)', () => {
19
+ describe('variant: api_token', () => {
20
+ const ctx: {
21
+ projectKey?: string
22
+ issueKey?: string
23
+ boardId?: number
24
+ sprintId?: number
25
+ } = {}
26
+
27
+ let jira: ReturnType<typeof createToolbox>
28
+
29
+ beforeAll(async () => {
30
+ const credentialStore = createCredentialStore(async () => ({
31
+ domain: env.JIRA_DOMAIN!,
32
+ email: env.JIRA_EMAIL!,
33
+ apiToken: env.JIRA_API_TOKEN!,
34
+ }))
35
+ const proxy = createProxy(credentialStore)
36
+ jira = createToolbox('jira', proxy, createIntegrationNode('jira', { label: 'Jira', credentialId: 'jira-creds', credentialVariant: 'api_token' }), 'api_token')
37
+
38
+ try {
39
+ const list_projects = jira.read('list_projects')
40
+ const projectsResp = await list_projects({ maxResults: 5 })
41
+ const first = projectsResp?.projects?.[0]
42
+ ctx.projectKey = env.JIRA_TEST_PROJECT_KEY || first?.key
43
+ }
44
+ catch {}
45
+
46
+ try {
47
+ if (ctx.projectKey) {
48
+ const search_issues = jira.read('search_issues')
49
+ const issuesResp = await search_issues({
50
+ jql: `project = ${ctx.projectKey} ORDER BY updated DESC`,
51
+ fields: ['summary', 'updated'],
52
+ maxResults: 1,
53
+ })
54
+ const firstIssue = issuesResp?.issues?.[0]
55
+ ctx.issueKey = firstIssue?.key
56
+ }
57
+ }
58
+ catch {}
59
+
60
+ try {
61
+ const list_boards = jira.read('list_boards')
62
+ const boardsResp = await list_boards({ maxResults: 5 })
63
+ const firstBoard = boardsResp?.boards?.[0]
64
+ ctx.boardId = typeof firstBoard?.id === 'number' ? firstBoard.id : undefined
65
+ }
66
+ catch {}
67
+
68
+ try {
69
+ if (ctx.boardId) {
70
+ const list_sprints = jira.read('list_sprints')
71
+ const sprintsResp = await list_sprints({ boardId: ctx.boardId, maxResults: 5 })
72
+ const firstSprint = sprintsResp?.sprints?.[0]
73
+ ctx.sprintId = typeof firstSprint?.id === 'number' ? firstSprint.id : undefined
74
+ }
75
+ }
76
+ catch {}
77
+ }, 60000)
78
+
79
+ it('get_myself returns authenticated user', async () => {
80
+ const get_myself = jira.read('get_myself')
81
+ const me = await get_myself({})
82
+ expect(me?.accountId).toBeTruthy()
83
+ }, 30000)
84
+
85
+ it('list_projects returns projects', async () => {
86
+ const list_projects = jira.read('list_projects')
87
+ const res = await list_projects({ maxResults: 5 })
88
+ expect(res?.projects).toBeTruthy()
89
+ expect(Array.isArray(res.projects)).toBe(true)
90
+ }, 30000)
91
+
92
+ it('get_project returns issueTypes when available', async () => {
93
+ if (!ctx.projectKey)
94
+ return expect(true).toBe(true)
95
+ const get_project = jira.read('get_project')
96
+ const project = await get_project({ projectIdOrKey: ctx.projectKey, expandIssueTypes: true })
97
+ expect(project?.key?.toUpperCase?.()).toBe(ctx.projectKey.toUpperCase())
98
+ expect(Array.isArray(project?.issueTypes)).toBe(true)
99
+ }, 30000)
100
+
101
+ it('search_issues returns issue list (best effort)', async () => {
102
+ if (!ctx.projectKey)
103
+ return expect(true).toBe(true)
104
+ const search_issues = jira.read('search_issues')
105
+ const res = await search_issues({ jql: `project = ${ctx.projectKey} ORDER BY updated DESC`, maxResults: 5 })
106
+ expect(res?.issues).toBeTruthy()
107
+ expect(Array.isArray(res.issues)).toBe(true)
108
+ }, 30000)
109
+
110
+ it('get_issue returns issue details (if any issue key discovered)', async () => {
111
+ if (!ctx.issueKey)
112
+ return expect(true).toBe(true)
113
+ const get_issue = jira.read('get_issue')
114
+ const issue = await get_issue({ issueIdOrKey: ctx.issueKey })
115
+ expect(issue?.key).toBe(ctx.issueKey)
116
+ }, 30000)
117
+
118
+ it('get_issue_comments returns comments (if any issue key discovered)', async () => {
119
+ if (!ctx.issueKey)
120
+ return expect(true).toBe(true)
121
+ const get_issue_comments = jira.read('get_issue_comments')
122
+ const comments = await get_issue_comments({ issueIdOrKey: ctx.issueKey, maxResults: 5 })
123
+ expect(comments?.comments).toBeTruthy()
124
+ expect(Array.isArray(comments.comments)).toBe(true)
125
+ }, 30000)
126
+
127
+ it('get_transitions returns transitions (if any issue key discovered)', async () => {
128
+ if (!ctx.issueKey)
129
+ return expect(true).toBe(true)
130
+ const get_transitions = jira.read('get_transitions')
131
+ const res = await get_transitions({ issueIdOrKey: ctx.issueKey })
132
+ expect(Array.isArray(res?.transitions)).toBe(true)
133
+ }, 30000)
134
+
135
+ it('search_users returns users (best effort)', async () => {
136
+ const get_myself = jira.read('get_myself')
137
+ const me = await get_myself({})
138
+ const query = me?.displayName || 'a'
139
+ const search_users = jira.read('search_users')
140
+ const res = await search_users({ query, maxResults: 5 })
141
+ expect(Array.isArray(res?.users)).toBe(true)
142
+ }, 30000)
143
+
144
+ it('boards: list_boards works (best effort)', async () => {
145
+ const list_boards = jira.read('list_boards')
146
+ const res = await list_boards({ maxResults: 5 })
147
+ expect(res?.boards).toBeTruthy()
148
+ expect(Array.isArray(res.boards)).toBe(true)
149
+ }, 30000)
150
+
151
+ it('boards: get_board works when boardId is available', async () => {
152
+ if (!ctx.boardId)
153
+ return expect(true).toBe(true)
154
+ const get_board = jira.read('get_board')
155
+ const board = await get_board({ boardId: ctx.boardId })
156
+ expect(board?.id).toBe(ctx.boardId)
157
+ }, 30000)
158
+
159
+ it('boards: list_sprints works when boardId is available', async () => {
160
+ if (!ctx.boardId)
161
+ return expect(true).toBe(true)
162
+ const list_sprints = jira.read('list_sprints')
163
+ const res = await list_sprints({ boardId: ctx.boardId, maxResults: 5 })
164
+ expect(res?.sprints).toBeTruthy()
165
+ expect(Array.isArray(res.sprints)).toBe(true)
166
+ }, 30000)
167
+
168
+ it('boards: get_sprint works when sprintId is available', async () => {
169
+ if (!ctx.sprintId)
170
+ return expect(true).toBe(true)
171
+ const get_sprint = jira.read('get_sprint')
172
+ const sprint = await get_sprint({ sprintId: ctx.sprintId })
173
+ expect(sprint?.id).toBe(ctx.sprintId)
174
+ }, 30000)
175
+
176
+ it('boards: get_sprint_issues works when sprintId is available', async () => {
177
+ if (!ctx.sprintId)
178
+ return expect(true).toBe(true)
179
+ const get_sprint_issues = jira.read('get_sprint_issues')
180
+ const res = await get_sprint_issues({ sprintId: ctx.sprintId, maxResults: 5 })
181
+ expect(res).toBeTruthy()
182
+ }, 30000)
183
+
184
+ it('boards: get_backlog_issues works when boardId is available', async () => {
185
+ if (!ctx.boardId)
186
+ return expect(true).toBe(true)
187
+ const get_backlog_issues = jira.read('get_backlog_issues')
188
+ const res = await get_backlog_issues({ boardId: ctx.boardId, maxResults: 5 })
189
+ expect(res).toBeTruthy()
190
+ }, 30000)
191
+ })
192
+ })
193
+
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getMissingToolUsages } from '../../__tests__/usageParity.js'
3
+
4
+ describe('jira static usage parity', () => {
5
+ it('every api_token tool is referenced in tests', () => {
6
+ const missing = getMissingToolUsages({
7
+ integrationName: 'jira',
8
+ importMetaUrl: import.meta.url,
9
+ credentialVariant: 'api_token',
10
+ })
11
+ expect(missing, `Missing handler usages in tests: ${missing.join(', ')}`).toEqual([])
12
+ })
13
+ })
14
+
@@ -0,0 +1,157 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest'
2
+ import { createCredentialStore, createIntegrationNode, createProxy, createToolbox, hasEnv, safeCleanup } from '../../__tests__/liveHarness.js'
3
+
4
+ // LIVE Jira write tests using credentials
5
+ //
6
+ // Required for write tests:
7
+ // - JIRA_TEST_PROJECT_KEY
8
+ //
9
+ // Variant: api_token
10
+ // - JIRA_DOMAIN
11
+ // - JIRA_EMAIL
12
+ // - JIRA_API_TOKEN
13
+ //
14
+ // Optional:
15
+ // - JIRA_TEST_TRANSITION_NAME (enables transition_issue)
16
+ // - JIRA_TEST_BOARD_ID (enables sprint roundtrip: create_sprint -> update_sprint -> move_issues_to_sprint -> update_sprint)
17
+
18
+ const env = process.env as Record<string, string | undefined>
19
+
20
+ const suiteOrSkip = (hasEnv('JIRA_DOMAIN', 'JIRA_EMAIL', 'JIRA_API_TOKEN') && hasEnv('JIRA_TEST_PROJECT_KEY'))
21
+ ? describe
22
+ : describe.skip
23
+
24
+ suiteOrSkip('jira write handlers (live)', () => {
25
+ describe('variant: api_token', () => {
26
+ const ctx: {
27
+ createdIssueKey?: string
28
+ createdSprintId?: number
29
+ } = {}
30
+
31
+ let jira: ReturnType<typeof createToolbox>
32
+
33
+ beforeAll(async () => {
34
+ const credentialStore = createCredentialStore(async () => ({
35
+ domain: env.JIRA_DOMAIN!,
36
+ email: env.JIRA_EMAIL!,
37
+ apiToken: env.JIRA_API_TOKEN!,
38
+ }))
39
+ const proxy = createProxy(credentialStore)
40
+ jira = createToolbox('jira', proxy, createIntegrationNode('jira', { label: 'Jira', credentialId: 'jira-creds', credentialVariant: 'api_token' }), 'api_token')
41
+ }, 60000)
42
+
43
+ afterAll(async () => {
44
+ await safeCleanup(async () => {
45
+ if (!ctx.createdSprintId)
46
+ return
47
+ const update_sprint = jira.write('update_sprint')
48
+ await update_sprint({ sprintId: ctx.createdSprintId, state: 'closed' })
49
+ })
50
+ await safeCleanup(async () => {
51
+ if (!ctx.createdIssueKey)
52
+ return
53
+ const delete_issue = jira.write('delete_issue')
54
+ await delete_issue({ issueIdOrKey: ctx.createdIssueKey })
55
+ })
56
+ }, 60000)
57
+
58
+ it('create_issue -> get_issue -> update_issue -> add_comment -> get_issue_comments (and optional assign/transition) roundtrip', async () => {
59
+ const get_myself = jira.read('get_myself')
60
+ const me = await get_myself({})
61
+ expect(me?.accountId).toBeTruthy()
62
+
63
+ const get_project = jira.read('get_project')
64
+ const project = await get_project({ projectIdOrKey: env.JIRA_TEST_PROJECT_KEY, expandIssueTypes: true })
65
+ const issueTypes = Array.isArray(project?.issueTypes) ? project.issueTypes : []
66
+ const picked = issueTypes.find((t: any) => t && t.subtask === false) || issueTypes[0]
67
+ if (!picked?.id && !picked?.name)
68
+ return expect(true).toBe(true)
69
+
70
+ const create_issue = jira.write('create_issue')
71
+ const created = await create_issue({
72
+ projectKey: env.JIRA_TEST_PROJECT_KEY,
73
+ issueTypeId: picked?.id || undefined,
74
+ issueTypeName: picked?.id ? undefined : (picked?.name || undefined),
75
+ summary: `CmdTest ${Date.now()}`,
76
+ descriptionText: 'Created by integration tests.',
77
+ assigneeAccountId: me.accountId,
78
+ labels: ['cmdtest'],
79
+ })
80
+ const issueKey = created?.key || created?.id
81
+ expect(issueKey).toBeTruthy()
82
+ ctx.createdIssueKey = created?.key || created?.id
83
+
84
+ const get_issue = jira.read('get_issue')
85
+ const got = await get_issue({ issueIdOrKey: issueKey })
86
+ expect(got?.key).toBeTruthy()
87
+
88
+ const update_issue = jira.write('update_issue')
89
+ const updated = await update_issue({
90
+ issueIdOrKey: issueKey,
91
+ summary: `CmdTest Updated ${Date.now()}`,
92
+ descriptionText: 'Updated by integration tests.',
93
+ })
94
+ expect(updated?.success === true || updated).toBeTruthy()
95
+
96
+ const add_comment = jira.write('add_comment')
97
+ const commentText = `Test comment ${Date.now()}`
98
+ const comment = await add_comment({ issueIdOrKey: issueKey, bodyText: commentText })
99
+ expect(comment?.id).toBeTruthy()
100
+
101
+ const get_issue_comments = jira.read('get_issue_comments')
102
+ const comments = await get_issue_comments({ issueIdOrKey: issueKey, maxResults: 20 })
103
+ const found = (comments?.comments || []).some((c: any) =>
104
+ (c?.bodyMarkdown && String(c.bodyMarkdown).includes('Test comment'))
105
+ || (c?.bodyText && String(c.bodyText).includes('Test comment')),
106
+ )
107
+ expect(found).toBe(true)
108
+
109
+ const assign_issue = jira.write('assign_issue')
110
+ const assigned = await assign_issue({ issueIdOrKey: issueKey, accountId: me.accountId })
111
+ expect(assigned?.success === true || assigned).toBeTruthy()
112
+ const unassigned = await assign_issue({ issueIdOrKey: issueKey, accountId: null })
113
+ expect(unassigned?.success === true || unassigned).toBeTruthy()
114
+
115
+ if (env.JIRA_TEST_TRANSITION_NAME) {
116
+ const transition_issue = jira.write('transition_issue')
117
+ const transitioned = await transition_issue({
118
+ issueIdOrKey: issueKey,
119
+ transitionName: env.JIRA_TEST_TRANSITION_NAME,
120
+ commentText: 'Transitioned by integration tests.',
121
+ })
122
+ expect(transitioned?.success === true || transitioned).toBeTruthy()
123
+ }
124
+ }, 120000)
125
+
126
+ it('sprint roundtrip: create_sprint -> update_sprint (start) -> move_issues_to_sprint -> update_sprint (close) (optional)', async () => {
127
+ if (!env.JIRA_TEST_BOARD_ID || !ctx.createdIssueKey)
128
+ return expect(true).toBe(true)
129
+ const boardId = Number(env.JIRA_TEST_BOARD_ID)
130
+ if (!Number.isFinite(boardId))
131
+ return expect(true).toBe(true)
132
+
133
+ const create_sprint = jira.write('create_sprint')
134
+ const name = `CmdTest Sprint ${Date.now()}`
135
+ const sprint = await create_sprint({ boardId, name })
136
+ const sprintId = sprint?.id
137
+ expect(typeof sprintId).toBe('number')
138
+ ctx.createdSprintId = sprintId
139
+
140
+ const update_sprint = jira.write('update_sprint')
141
+ const today = new Date().toISOString().slice(0, 10)
142
+ const twoWeeks = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)
143
+ const started = await update_sprint({ sprintId, state: 'active', startDate: today, endDate: twoWeeks })
144
+ expect(started?.state === 'active' || started?.id === sprintId || started).toBeTruthy()
145
+
146
+ const move_issues_to_sprint = jira.write('move_issues_to_sprint')
147
+ const moved = await move_issues_to_sprint({ sprintId, issueKeys: [ctx.createdIssueKey] })
148
+ expect(moved?.success === true || moved).toBeTruthy()
149
+
150
+ const closed = await update_sprint({ sprintId, state: 'closed' })
151
+ expect(closed?.state === 'closed' || closed?.id === sprintId || closed).toBeTruthy()
152
+ ctx.createdSprintId = undefined
153
+ }, 120000)
154
+
155
+ })
156
+ })
157
+
@@ -0,0 +1,39 @@
1
+ {
2
+ "variants": {
3
+ "api_token": {
4
+ "label": "API Token (Email + Token)",
5
+ "schema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "domain": {
9
+ "type": "string",
10
+ "title": "Jira site domain",
11
+ "description": "The subdomain of your Jira Cloud site. Example: for https://mycompany.atlassian.net, enter 'mycompany'."
12
+ },
13
+ "email": {
14
+ "type": "string",
15
+ "title": "Atlassian account email",
16
+ "description": "Email address of the Atlassian account that owns the API token."
17
+ },
18
+ "apiToken": {
19
+ "type": "string",
20
+ "title": "Atlassian API token",
21
+ "description": "Atlassian API token for Jira Cloud. Combined with your email to form the Basic auth header."
22
+ }
23
+ },
24
+ "required": ["domain", "email", "apiToken"],
25
+ "additionalProperties": false
26
+ },
27
+ "baseUrlTemplate": "https://{{domain}}.atlassian.net",
28
+ "injection": {
29
+ "headers": {
30
+ "Authorization": "Basic {{base64(email + \":\" + apiToken)}}",
31
+ "Accept": "application/json"
32
+ }
33
+ },
34
+ "healthCheck": { "path": "/rest/api/3/myself" }
35
+ }
36
+ },
37
+ "default": "api_token"
38
+ }
39
+
@@ -0,0 +1,4 @@
1
+ Recommended: use **API Token (Email + Token)** unless you specifically need OAuth.
2
+
3
+ If you have multiple credential variants available, select one and follow its setup instructions.
4
+
@@ -0,0 +1,6 @@
1
+ 1. Open your Jira Cloud site in the browser (it looks like `https://YOUR_DOMAIN.atlassian.net`).
2
+ 2. Copy `YOUR_DOMAIN` (the subdomain) and use it as `domain`.
3
+ 3. Create an Atlassian API token at `https://id.atlassian.com/manage-profile/security/api-tokens`.
4
+ 4. Use the same Atlassian account email as `email` and paste the token as `apiToken`.
5
+ 5. Ensure the Atlassian account has access to the Jira projects you want to work with.
6
+
@@ -0,0 +1,6 @@
1
+ 1. Create an OAuth 2.0 (3LO) app in the Atlassian developer console.
2
+ 2. Add Jira scopes (typical minimum): `read:jira-work`, `write:jira-work`, `read:jira-user` (and `offline_access` if you want refresh tokens).
3
+ 3. Complete the OAuth flow to obtain an access token.
4
+ 4. Discover your `cloudId` by calling `GET https://api.atlassian.com/oauth/token/accessible-resources` with `Authorization: Bearer <access_token>` and using the returned `id`.
5
+ 5. Paste `cloudId` and the current OAuth `token` here.
6
+
@@ -0,0 +1,9 @@
1
+ async (input) => {
2
+ const res = await integration.fetch(`/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}/comment`, {
3
+ method: 'POST',
4
+ body: { body: utils.adf?.fromMarkdown(input.bodyText) },
5
+ })
6
+
7
+ return await res.json()
8
+ }
9
+
@@ -0,0 +1,11 @@
1
+ async (input) => {
2
+ const res = await integration.fetch(`/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}/assignee`, {
3
+ method: 'PUT',
4
+ body: { accountId: input.accountId },
5
+ })
6
+
7
+ if (res.status === 204)
8
+ return { success: true }
9
+ return await res.json()
10
+ }
11
+
@@ -0,0 +1,37 @@
1
+ async (input) => {
2
+ const fields = {
3
+ project: { key: input.projectKey },
4
+ summary: input.summary,
5
+ }
6
+
7
+ if (input.descriptionText)
8
+ fields.description = utils.adf?.fromMarkdown(input.descriptionText)
9
+
10
+ if (input.issueTypeId) {
11
+ fields.issuetype = { id: String(input.issueTypeId) }
12
+ }
13
+ else if (input.issueTypeName) {
14
+ fields.issuetype = { name: String(input.issueTypeName) }
15
+ }
16
+ else {
17
+ throw new Error(`Missing issue type. Provide issueTypeId or issueTypeName (call get_project to discover available issue types).`)
18
+ }
19
+
20
+ if (input.priorityId)
21
+ fields.priority = { id: input.priorityId }
22
+ else if (input.priorityName)
23
+ fields.priority = { name: input.priorityName }
24
+
25
+ if (Array.isArray(input.labels))
26
+ fields.labels = input.labels
27
+
28
+ if (input.assigneeAccountId)
29
+ fields.assignee = { accountId: input.assigneeAccountId }
30
+
31
+ const res = await integration.fetch('/rest/api/3/issue', {
32
+ method: 'POST',
33
+ body: { fields },
34
+ })
35
+
36
+ return await res.json()
37
+ }
@@ -0,0 +1,19 @@
1
+ async (input) => {
2
+ const body = {
3
+ originBoardId: input.boardId,
4
+ name: input.name,
5
+ }
6
+ if (input.startDate)
7
+ body.startDate = input.startDate
8
+ if (input.endDate)
9
+ body.endDate = input.endDate
10
+ if (input.goal)
11
+ body.goal = input.goal
12
+
13
+ const res = await integration.fetch('/rest/agile/1.0/sprint', {
14
+ method: 'POST',
15
+ body,
16
+ })
17
+ return await res.json()
18
+ }
19
+
@@ -0,0 +1,10 @@
1
+ async (input) => {
2
+ const res = await integration.fetch(`/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}`, {
3
+ method: 'DELETE',
4
+ })
5
+
6
+ if (res.status === 204)
7
+ return { success: true }
8
+ return await res.json()
9
+ }
10
+
@@ -0,0 +1,13 @@
1
+ async (input) => {
2
+ const params = new URLSearchParams()
3
+ if (input.jql)
4
+ params.set('jql', input.jql)
5
+ if (Array.isArray(input.fields) && input.fields.length)
6
+ params.set('fields', input.fields.join(','))
7
+ params.set('startAt', String(input.startAt ?? 0))
8
+ params.set('maxResults', String(input.maxResults ?? 50))
9
+
10
+ const res = await integration.fetch(`/rest/agile/1.0/board/${encodeURIComponent(String(input.boardId))}/backlog?${params.toString()}`)
11
+ return await res.json()
12
+ }
13
+
@@ -0,0 +1,6 @@
1
+ async (input) => {
2
+ const res = await integration.fetch(`/rest/agile/1.0/board/${encodeURIComponent(String(input.boardId))}`)
3
+ const data = await res.json()
4
+ return data
5
+ }
6
+
@@ -0,0 +1,63 @@
1
+ async (input) => {
2
+ const defaultFields = [
3
+ 'summary',
4
+ 'status',
5
+ 'assignee',
6
+ 'priority',
7
+ 'issuetype',
8
+ 'project',
9
+ 'description',
10
+ 'created',
11
+ 'updated',
12
+ 'labels',
13
+ ]
14
+
15
+ const fields = Array.isArray(input.fields) && input.fields.length ? input.fields : defaultFields
16
+ const params = new URLSearchParams()
17
+ if (fields?.length)
18
+ params.set('fields', fields.join(','))
19
+ if (Array.isArray(input.expand) && input.expand.length)
20
+ params.set('expand', input.expand.join(','))
21
+
22
+ const path = `/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}${params.toString() ? `?${params.toString()}` : ''}`
23
+ const res = await integration.fetch(path)
24
+ const data = await res.json()
25
+
26
+ const descAdf = data?.fields?.description
27
+ const descMarkdown = utils.adf?.toMarkdown(descAdf) || ''
28
+ const descText = descMarkdown ? '' : (utils.adf?.toPlainText(descAdf) || '')
29
+
30
+ return {
31
+ id: data.id ?? null,
32
+ key: data.key ?? null,
33
+ self: data.self ?? null,
34
+ summary: data.fields?.summary ?? null,
35
+ status: data.fields?.status
36
+ ? {
37
+ id: data.fields.status.id ?? null,
38
+ name: data.fields.status.name ?? null,
39
+ category: data.fields.status.statusCategory
40
+ ? {
41
+ key: data.fields.status.statusCategory.key ?? null,
42
+ name: data.fields.status.statusCategory.name ?? null,
43
+ }
44
+ : null,
45
+ }
46
+ : null,
47
+ assignee: data.fields?.assignee
48
+ ? {
49
+ accountId: data.fields.assignee.accountId ?? null,
50
+ displayName: data.fields.assignee.displayName ?? null,
51
+ }
52
+ : null,
53
+ priority: data.fields?.priority ? { id: data.fields.priority.id ?? null, name: data.fields.priority.name ?? null } : null,
54
+ issueType: data.fields?.issuetype ? { id: data.fields.issuetype.id ?? null, name: data.fields.issuetype.name ?? null } : null,
55
+ project: data.fields?.project ? { id: data.fields.project.id ?? null, key: data.fields.project.key ?? null, name: data.fields.project.name ?? null } : null,
56
+ labels: Array.isArray(data.fields?.labels) ? data.fields.labels : [],
57
+ descriptionMarkdown: descMarkdown || null,
58
+ descriptionText: descMarkdown ? null : (descText || null),
59
+ created: data.fields?.created ?? null,
60
+ updated: data.fields?.updated ?? null,
61
+ }
62
+ }
63
+
@@ -0,0 +1,31 @@
1
+ async (input) => {
2
+ const params = new URLSearchParams()
3
+ params.set('startAt', String(input.startAt ?? 0))
4
+ params.set('maxResults', String(input.maxResults ?? 50))
5
+
6
+ const path = `/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}/comment?${params.toString()}`
7
+ const res = await integration.fetch(path)
8
+ const data = await res.json()
9
+ const comments = Array.isArray(data.comments) ? data.comments : []
10
+
11
+ return {
12
+ startAt: data.startAt ?? (input.startAt ?? 0),
13
+ maxResults: data.maxResults ?? (input.maxResults ?? 50),
14
+ total: data.total ?? comments.length,
15
+ comments: comments.map((c) => {
16
+ const md = utils.adf?.toMarkdown(c.body) || ''
17
+ const text = md ? '' : (utils.adf?.toPlainText(c.body) || '')
18
+ return {
19
+ id: c.id ?? null,
20
+ created: c.created ?? null,
21
+ updated: c.updated ?? null,
22
+ author: c.author
23
+ ? { accountId: c.author.accountId ?? null, displayName: c.author.displayName ?? null }
24
+ : null,
25
+ bodyMarkdown: md || null,
26
+ bodyText: md ? null : (text || null),
27
+ }
28
+ }),
29
+ }
30
+ }
31
+
@@ -0,0 +1,14 @@
1
+ async (_input) => {
2
+ const res = await integration.fetch('/rest/api/3/myself')
3
+ const data = await res.json()
4
+
5
+ return {
6
+ accountId: data.accountId ?? null,
7
+ displayName: data.displayName ?? null,
8
+ active: data.active ?? null,
9
+ timeZone: data.timeZone ?? null,
10
+ locale: data.locale ?? null,
11
+ emailAddress: data.emailAddress ?? null,
12
+ }
13
+ }
14
+