digital-tools 2.1.1 → 2.3.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 (293) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -0
  3. package/dist/client.d.ts +109 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +69 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/define.d.ts +2 -2
  8. package/dist/define.d.ts.map +1 -1
  9. package/dist/define.js +22 -20
  10. package/dist/define.js.map +1 -1
  11. package/dist/function-ref.d.ts +229 -0
  12. package/dist/function-ref.d.ts.map +1 -0
  13. package/dist/function-ref.js +28 -0
  14. package/dist/function-ref.js.map +1 -0
  15. package/dist/function-sugar.d.ts +57 -0
  16. package/dist/function-sugar.d.ts.map +1 -0
  17. package/dist/function-sugar.js +79 -0
  18. package/dist/function-sugar.js.map +1 -0
  19. package/dist/index.d.ts +10 -3
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +24 -4
  22. package/dist/index.js.map +1 -1
  23. package/dist/providers/analytics/mixpanel.d.ts.map +1 -1
  24. package/dist/providers/analytics/mixpanel.js +21 -18
  25. package/dist/providers/analytics/mixpanel.js.map +1 -1
  26. package/dist/providers/calendar/cal-com.d.ts.map +1 -1
  27. package/dist/providers/calendar/cal-com.js +10 -10
  28. package/dist/providers/calendar/cal-com.js.map +1 -1
  29. package/dist/providers/calendar/google-calendar.d.ts.map +1 -1
  30. package/dist/providers/calendar/google-calendar.js +4 -4
  31. package/dist/providers/calendar/google-calendar.js.map +1 -1
  32. package/dist/providers/crm/hubspot.d.ts.map +1 -1
  33. package/dist/providers/crm/hubspot.js +107 -85
  34. package/dist/providers/crm/hubspot.js.map +1 -1
  35. package/dist/providers/development/github.d.ts.map +1 -1
  36. package/dist/providers/development/github.js +40 -43
  37. package/dist/providers/development/github.js.map +1 -1
  38. package/dist/providers/ecommerce/shopify.d.ts.map +1 -1
  39. package/dist/providers/ecommerce/shopify.js +79 -62
  40. package/dist/providers/ecommerce/shopify.js.map +1 -1
  41. package/dist/providers/email/resend.d.ts.map +1 -1
  42. package/dist/providers/email/resend.js +20 -16
  43. package/dist/providers/email/resend.js.map +1 -1
  44. package/dist/providers/email/sendgrid.d.ts.map +1 -1
  45. package/dist/providers/email/sendgrid.js +12 -9
  46. package/dist/providers/email/sendgrid.js.map +1 -1
  47. package/dist/providers/finance/stripe.d.ts.map +1 -1
  48. package/dist/providers/finance/stripe.js +44 -42
  49. package/dist/providers/finance/stripe.js.map +1 -1
  50. package/dist/providers/forms/typeform.d.ts.map +1 -1
  51. package/dist/providers/forms/typeform.js +68 -58
  52. package/dist/providers/forms/typeform.js.map +1 -1
  53. package/dist/providers/knowledge/notion.d.ts.map +1 -1
  54. package/dist/providers/knowledge/notion.js +75 -41
  55. package/dist/providers/knowledge/notion.js.map +1 -1
  56. package/dist/providers/marketing/mailchimp.d.ts.map +1 -1
  57. package/dist/providers/marketing/mailchimp.js +74 -61
  58. package/dist/providers/marketing/mailchimp.js.map +1 -1
  59. package/dist/providers/media/cloudinary.d.ts.map +1 -1
  60. package/dist/providers/media/cloudinary.js +30 -28
  61. package/dist/providers/media/cloudinary.js.map +1 -1
  62. package/dist/providers/messaging/slack.d.ts.map +1 -1
  63. package/dist/providers/messaging/slack.js +75 -58
  64. package/dist/providers/messaging/slack.js.map +1 -1
  65. package/dist/providers/messaging/twilio-sms.d.ts.map +1 -1
  66. package/dist/providers/messaging/twilio-sms.js +33 -15
  67. package/dist/providers/messaging/twilio-sms.js.map +1 -1
  68. package/dist/providers/project-management/linear.d.ts.map +1 -1
  69. package/dist/providers/project-management/linear.js +31 -27
  70. package/dist/providers/project-management/linear.js.map +1 -1
  71. package/dist/providers/spreadsheet/google-sheets.d.ts.map +1 -1
  72. package/dist/providers/spreadsheet/google-sheets.js +21 -18
  73. package/dist/providers/spreadsheet/google-sheets.js.map +1 -1
  74. package/dist/providers/spreadsheet/xlsx.d.ts.map +1 -1
  75. package/dist/providers/spreadsheet/xlsx.js +4 -4
  76. package/dist/providers/spreadsheet/xlsx.js.map +1 -1
  77. package/dist/providers/storage/index.js +1 -0
  78. package/dist/providers/storage/index.js.map +1 -1
  79. package/dist/providers/storage/s3.d.ts.map +1 -1
  80. package/dist/providers/storage/s3.js +36 -27
  81. package/dist/providers/storage/s3.js.map +1 -1
  82. package/dist/providers/support/zendesk.d.ts.map +1 -1
  83. package/dist/providers/support/zendesk.js +24 -25
  84. package/dist/providers/support/zendesk.js.map +1 -1
  85. package/dist/providers/tasks/todoist.d.ts.map +1 -1
  86. package/dist/providers/tasks/todoist.js +18 -18
  87. package/dist/providers/tasks/todoist.js.map +1 -1
  88. package/dist/providers/video-conferencing/google-meet.d.ts.map +1 -1
  89. package/dist/providers/video-conferencing/google-meet.js +11 -11
  90. package/dist/providers/video-conferencing/google-meet.js.map +1 -1
  91. package/dist/providers/video-conferencing/jitsi.js +14 -14
  92. package/dist/providers/video-conferencing/jitsi.js.map +1 -1
  93. package/dist/providers/video-conferencing/teams.d.ts.map +1 -1
  94. package/dist/providers/video-conferencing/teams.js +9 -7
  95. package/dist/providers/video-conferencing/teams.js.map +1 -1
  96. package/dist/providers/video-conferencing/zoom.d.ts.map +1 -1
  97. package/dist/providers/video-conferencing/zoom.js +26 -24
  98. package/dist/providers/video-conferencing/zoom.js.map +1 -1
  99. package/dist/tools/data.d.ts.map +1 -1
  100. package/dist/tools/data.js +5 -12
  101. package/dist/tools/data.js.map +1 -1
  102. package/dist/tools/index.d.ts +1 -0
  103. package/dist/tools/index.d.ts.map +1 -1
  104. package/dist/tools/index.js +1 -0
  105. package/dist/tools/index.js.map +1 -1
  106. package/dist/tools/system.d.ts +289 -0
  107. package/dist/tools/system.d.ts.map +1 -0
  108. package/dist/tools/system.js +752 -0
  109. package/dist/tools/system.js.map +1 -0
  110. package/dist/tools/web.d.ts.map +1 -1
  111. package/dist/tools/web.js +22 -10
  112. package/dist/tools/web.js.map +1 -1
  113. package/dist/track-record.d.ts +101 -0
  114. package/dist/track-record.d.ts.map +1 -0
  115. package/dist/track-record.js +17 -0
  116. package/dist/track-record.js.map +1 -0
  117. package/dist/types.d.ts +210 -9
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/verb-registration.d.ts +122 -0
  120. package/dist/verb-registration.d.ts.map +1 -0
  121. package/dist/verb-registration.js +176 -0
  122. package/dist/verb-registration.js.map +1 -0
  123. package/dist/worker.d.ts +93 -0
  124. package/dist/worker.d.ts.map +1 -0
  125. package/dist/worker.js +315 -0
  126. package/dist/worker.js.map +1 -0
  127. package/dist/wrap.d.ts +89 -0
  128. package/dist/wrap.d.ts.map +1 -0
  129. package/dist/wrap.js +225 -0
  130. package/dist/wrap.js.map +1 -0
  131. package/package.json +21 -4
  132. package/src/client.ts +136 -0
  133. package/src/define.ts +31 -37
  134. package/src/function-ref.ts +264 -0
  135. package/src/function-sugar.ts +134 -0
  136. package/src/index.ts +132 -10
  137. package/src/providers/analytics/mixpanel.ts +19 -18
  138. package/src/providers/calendar/cal-com.ts +29 -18
  139. package/src/providers/calendar/google-calendar.ts +20 -14
  140. package/src/providers/crm/hubspot.ts +225 -99
  141. package/src/providers/development/github.ts +206 -135
  142. package/src/providers/ecommerce/shopify.ts +250 -89
  143. package/src/providers/email/resend.ts +101 -28
  144. package/src/providers/email/sendgrid.ts +12 -9
  145. package/src/providers/finance/stripe.ts +128 -49
  146. package/src/providers/forms/typeform.ts +74 -58
  147. package/src/providers/knowledge/notion.ts +340 -88
  148. package/src/providers/marketing/mailchimp.ts +86 -70
  149. package/src/providers/media/cloudinary.ts +99 -41
  150. package/src/providers/messaging/slack.ts +283 -85
  151. package/src/providers/messaging/twilio-sms.ts +35 -15
  152. package/src/providers/project-management/linear.ts +143 -55
  153. package/src/providers/spreadsheet/google-sheets.ts +222 -56
  154. package/src/providers/spreadsheet/xlsx.ts +47 -16
  155. package/src/providers/storage/s3.ts +119 -47
  156. package/src/providers/support/zendesk.ts +196 -46
  157. package/src/providers/tasks/todoist.ts +20 -26
  158. package/src/providers/video-conferencing/google-meet.ts +17 -20
  159. package/src/providers/video-conferencing/jitsi.ts +14 -14
  160. package/src/providers/video-conferencing/teams.ts +14 -13
  161. package/src/providers/video-conferencing/zoom.ts +54 -49
  162. package/src/tools/data.ts +6 -16
  163. package/src/tools/index.ts +1 -0
  164. package/src/tools/system.ts +887 -0
  165. package/src/tools/web.ts +22 -10
  166. package/src/track-record.ts +106 -0
  167. package/src/types.ts +241 -13
  168. package/src/verb-registration.ts +197 -0
  169. package/src/worker.ts +370 -0
  170. package/src/wrap.ts +260 -0
  171. package/test/client.test.ts +146 -0
  172. package/test/communication-tools-extended.test.ts +734 -0
  173. package/test/data-tools-extended.test.ts +743 -0
  174. package/test/define-extended.test.ts +819 -0
  175. package/test/define.test.ts +150 -41
  176. package/test/entities.test.ts +623 -0
  177. package/test/extended-entities.test.ts +1228 -0
  178. package/test/provider-implementations.test.ts +725 -0
  179. package/test/provider-registry-extended.test.ts +583 -0
  180. package/test/providers/google-sheets.test.ts +851 -0
  181. package/test/providers/helpers.ts +554 -0
  182. package/test/providers/hubspot.test.ts +576 -0
  183. package/test/providers/slack.test.ts +932 -0
  184. package/test/providers/stripe.test.ts +701 -0
  185. package/test/providers.test.ts +578 -0
  186. package/test/system-tools-extended.test.ts +632 -0
  187. package/test/system.test.ts +673 -0
  188. package/test/tools.test.ts +15 -11
  189. package/test/types.test.ts +402 -0
  190. package/test/verb-registration.test.ts +395 -0
  191. package/test/web-tools.test.ts +553 -0
  192. package/test/worker-extended.test.ts +699 -0
  193. package/test/worker.test.ts +576 -0
  194. package/test/wrap.test.ts +366 -0
  195. package/tsconfig.json +3 -13
  196. package/vitest.config.ts +37 -0
  197. package/wrangler.jsonc +9 -0
  198. package/.turbo/turbo-build.log +0 -5
  199. package/dist/providers/voice/vapi.d.ts +0 -27
  200. package/dist/providers/voice/vapi.d.ts.map +0 -1
  201. package/dist/providers/voice/vapi.js +0 -440
  202. package/dist/providers/voice/vapi.js.map +0 -1
  203. package/src/define.js +0 -267
  204. package/src/entities/advertising.js +0 -999
  205. package/src/entities/ai.js +0 -756
  206. package/src/entities/analytics.js +0 -1588
  207. package/src/entities/automation.js +0 -601
  208. package/src/entities/communication.js +0 -1150
  209. package/src/entities/crm.js +0 -1386
  210. package/src/entities/design.js +0 -546
  211. package/src/entities/development.js +0 -2212
  212. package/src/entities/document.js +0 -874
  213. package/src/entities/ecommerce.js +0 -1429
  214. package/src/entities/experiment.js +0 -1039
  215. package/src/entities/finance.js +0 -3478
  216. package/src/entities/forms.js +0 -1892
  217. package/src/entities/hr.js +0 -661
  218. package/src/entities/identity.js +0 -997
  219. package/src/entities/index.js +0 -282
  220. package/src/entities/infrastructure.js +0 -1153
  221. package/src/entities/knowledge.js +0 -1438
  222. package/src/entities/marketing.js +0 -1610
  223. package/src/entities/media.js +0 -1634
  224. package/src/entities/notification.js +0 -1199
  225. package/src/entities/presentation.js +0 -1274
  226. package/src/entities/productivity.js +0 -1317
  227. package/src/entities/project-management.js +0 -1136
  228. package/src/entities/recruiting.js +0 -736
  229. package/src/entities/shipping.js +0 -509
  230. package/src/entities/signature.js +0 -1102
  231. package/src/entities/site.js +0 -222
  232. package/src/entities/spreadsheet.js +0 -1341
  233. package/src/entities/storage.js +0 -1198
  234. package/src/entities/support.js +0 -1166
  235. package/src/entities/video-conferencing.js +0 -1750
  236. package/src/entities/video.js +0 -950
  237. package/src/entities.js +0 -1663
  238. package/src/index.js +0 -74
  239. package/src/providers/analytics/index.js +0 -17
  240. package/src/providers/analytics/mixpanel.js +0 -255
  241. package/src/providers/calendar/cal-com.js +0 -303
  242. package/src/providers/calendar/google-calendar.js +0 -335
  243. package/src/providers/calendar/index.js +0 -20
  244. package/src/providers/crm/hubspot.js +0 -566
  245. package/src/providers/crm/index.js +0 -17
  246. package/src/providers/development/github.js +0 -472
  247. package/src/providers/development/index.js +0 -17
  248. package/src/providers/ecommerce/index.js +0 -17
  249. package/src/providers/ecommerce/shopify.js +0 -378
  250. package/src/providers/email/index.js +0 -20
  251. package/src/providers/email/resend.js +0 -258
  252. package/src/providers/email/sendgrid.js +0 -161
  253. package/src/providers/finance/index.js +0 -17
  254. package/src/providers/finance/stripe.js +0 -549
  255. package/src/providers/forms/index.js +0 -17
  256. package/src/providers/forms/typeform.js +0 -500
  257. package/src/providers/index.js +0 -123
  258. package/src/providers/knowledge/index.js +0 -17
  259. package/src/providers/knowledge/notion.js +0 -389
  260. package/src/providers/marketing/index.js +0 -17
  261. package/src/providers/marketing/mailchimp.js +0 -443
  262. package/src/providers/media/cloudinary.js +0 -318
  263. package/src/providers/media/index.js +0 -17
  264. package/src/providers/messaging/index.js +0 -20
  265. package/src/providers/messaging/slack.js +0 -393
  266. package/src/providers/messaging/twilio-sms.js +0 -249
  267. package/src/providers/project-management/index.js +0 -17
  268. package/src/providers/project-management/linear.js +0 -575
  269. package/src/providers/registry.js +0 -86
  270. package/src/providers/spreadsheet/google-sheets.js +0 -375
  271. package/src/providers/spreadsheet/index.js +0 -20
  272. package/src/providers/spreadsheet/xlsx.js +0 -423
  273. package/src/providers/storage/index.js +0 -24
  274. package/src/providers/storage/s3.js +0 -419
  275. package/src/providers/support/index.js +0 -17
  276. package/src/providers/support/zendesk.js +0 -373
  277. package/src/providers/tasks/index.js +0 -17
  278. package/src/providers/tasks/todoist.js +0 -286
  279. package/src/providers/types.js +0 -9
  280. package/src/providers/video-conferencing/google-meet.js +0 -286
  281. package/src/providers/video-conferencing/index.js +0 -31
  282. package/src/providers/video-conferencing/jitsi.js +0 -254
  283. package/src/providers/video-conferencing/teams.js +0 -270
  284. package/src/providers/video-conferencing/zoom.js +0 -332
  285. package/src/registry.js +0 -128
  286. package/src/tools/communication.js +0 -184
  287. package/src/tools/data.js +0 -205
  288. package/src/tools/index.js +0 -11
  289. package/src/tools/web.js +0 -137
  290. package/src/types.js +0 -10
  291. package/test/define.test.js +0 -306
  292. package/test/registry.test.js +0 -357
  293. package/test/tools.test.js +0 -363
@@ -0,0 +1,553 @@
1
+ /**
2
+ * Comprehensive Tests for Web Tools
3
+ *
4
+ * Tests fetchUrl, parseHtml, and readUrl tools with real network calls
5
+ * where available, and comprehensive edge case testing.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach } from 'vitest'
9
+ import {
10
+ fetchUrl,
11
+ parseHtml,
12
+ readUrl,
13
+ webTools,
14
+ registry,
15
+ registerBuiltinTools,
16
+ } from '../src/index.js'
17
+
18
+ // Check if we have network access for integration tests
19
+ const hasNetwork = true // We'll test with real fetch
20
+
21
+ describe('Web Tools - fetchUrl', () => {
22
+ describe('metadata', () => {
23
+ it('has correct id', () => {
24
+ expect(fetchUrl.id).toBe('web.fetch')
25
+ })
26
+
27
+ it('has correct name', () => {
28
+ expect(fetchUrl.name).toBe('Fetch URL')
29
+ })
30
+
31
+ it('has correct category', () => {
32
+ expect(fetchUrl.category).toBe('web')
33
+ })
34
+
35
+ it('has correct subcategory', () => {
36
+ expect(fetchUrl.subcategory).toBe('fetch')
37
+ })
38
+
39
+ it('has description', () => {
40
+ expect(fetchUrl.description).toBe('Fetch content from a URL using HTTP')
41
+ })
42
+
43
+ it('is for both audiences', () => {
44
+ expect(fetchUrl.audience).toBe('both')
45
+ })
46
+
47
+ it('has http tag', () => {
48
+ expect(fetchUrl.tags).toContain('http')
49
+ })
50
+
51
+ it('has network tag', () => {
52
+ expect(fetchUrl.tags).toContain('network')
53
+ })
54
+
55
+ it('has api tag', () => {
56
+ expect(fetchUrl.tags).toContain('api')
57
+ })
58
+ })
59
+
60
+ describe('parameters', () => {
61
+ it('has url parameter', () => {
62
+ const urlParam = fetchUrl.parameters.find((p) => p.name === 'url')
63
+ expect(urlParam).toBeDefined()
64
+ })
65
+
66
+ it('url parameter is required', () => {
67
+ const urlParam = fetchUrl.parameters.find((p) => p.name === 'url')
68
+ expect(urlParam?.required).toBe(true)
69
+ })
70
+
71
+ it('has method parameter', () => {
72
+ const methodParam = fetchUrl.parameters.find((p) => p.name === 'method')
73
+ expect(methodParam).toBeDefined()
74
+ })
75
+
76
+ it('method parameter is optional', () => {
77
+ const methodParam = fetchUrl.parameters.find((p) => p.name === 'method')
78
+ expect(methodParam?.required).toBe(false)
79
+ })
80
+
81
+ it('has headers parameter', () => {
82
+ const headersParam = fetchUrl.parameters.find((p) => p.name === 'headers')
83
+ expect(headersParam).toBeDefined()
84
+ })
85
+
86
+ it('has body parameter', () => {
87
+ const bodyParam = fetchUrl.parameters.find((p) => p.name === 'body')
88
+ expect(bodyParam).toBeDefined()
89
+ })
90
+
91
+ it('has 4 parameters total', () => {
92
+ expect(fetchUrl.parameters).toHaveLength(4)
93
+ })
94
+ })
95
+
96
+ describe('handler - real network tests', () => {
97
+ it('fetches a real URL with GET', async () => {
98
+ const result = await fetchUrl.handler({
99
+ url: 'https://httpbin.org/get',
100
+ })
101
+
102
+ expect(result.status).toBe(200)
103
+ expect(result.body).toBeDefined()
104
+ expect(typeof result.body).toBe('string')
105
+ expect(result.headers).toBeDefined()
106
+ })
107
+
108
+ it('fetches with custom headers', async () => {
109
+ const result = await fetchUrl.handler({
110
+ url: 'https://httpbin.org/headers',
111
+ headers: {
112
+ 'X-Custom-Header': 'test-value',
113
+ },
114
+ })
115
+
116
+ expect(result.status).toBe(200)
117
+ const body = JSON.parse(result.body)
118
+ expect(body.headers['X-Custom-Header']).toBe('test-value')
119
+ })
120
+
121
+ it('performs POST request', async () => {
122
+ const result = await fetchUrl.handler({
123
+ url: 'https://httpbin.org/post',
124
+ method: 'POST',
125
+ headers: {
126
+ 'Content-Type': 'application/json',
127
+ },
128
+ body: JSON.stringify({ test: 'data' }),
129
+ })
130
+
131
+ expect(result.status).toBe(200)
132
+ const body = JSON.parse(result.body)
133
+ expect(body.json).toEqual({ test: 'data' })
134
+ })
135
+
136
+ it('handles 404 response', async () => {
137
+ const result = await fetchUrl.handler({
138
+ url: 'https://httpbin.org/status/404',
139
+ })
140
+
141
+ expect(result.status).toBe(404)
142
+ })
143
+
144
+ it('returns headers as object', async () => {
145
+ const result = await fetchUrl.handler({
146
+ url: 'https://httpbin.org/get',
147
+ })
148
+
149
+ expect(typeof result.headers).toBe('object')
150
+ expect(result.headers['content-type']).toBeDefined()
151
+ })
152
+ })
153
+ })
154
+
155
+ describe('Web Tools - parseHtml', () => {
156
+ describe('metadata', () => {
157
+ it('has correct id', () => {
158
+ expect(parseHtml.id).toBe('web.parse-html')
159
+ })
160
+
161
+ it('has correct name', () => {
162
+ expect(parseHtml.name).toBe('Parse HTML')
163
+ })
164
+
165
+ it('has correct category', () => {
166
+ expect(parseHtml.category).toBe('web')
167
+ })
168
+
169
+ it('has correct subcategory', () => {
170
+ expect(parseHtml.subcategory).toBe('scrape')
171
+ })
172
+
173
+ it('has html tag', () => {
174
+ expect(parseHtml.tags).toContain('html')
175
+ })
176
+
177
+ it('has parse tag', () => {
178
+ expect(parseHtml.tags).toContain('parse')
179
+ })
180
+
181
+ it('has extract tag', () => {
182
+ expect(parseHtml.tags).toContain('extract')
183
+ })
184
+ })
185
+
186
+ describe('parameters', () => {
187
+ it('has html parameter', () => {
188
+ const htmlParam = parseHtml.parameters.find((p) => p.name === 'html')
189
+ expect(htmlParam).toBeDefined()
190
+ expect(htmlParam?.required).toBe(true)
191
+ })
192
+
193
+ it('has selector parameter', () => {
194
+ const selectorParam = parseHtml.parameters.find((p) => p.name === 'selector')
195
+ expect(selectorParam).toBeDefined()
196
+ expect(selectorParam?.required).toBe(false)
197
+ })
198
+ })
199
+
200
+ describe('handler - text extraction', () => {
201
+ it('extracts text from simple HTML', async () => {
202
+ const result = await parseHtml.handler({
203
+ html: '<p>Hello World</p>',
204
+ })
205
+
206
+ expect(result.text).toBe('Hello World')
207
+ })
208
+
209
+ it('extracts text from nested elements', async () => {
210
+ const result = await parseHtml.handler({
211
+ html: '<div><p>First</p><span>Second</span></div>',
212
+ })
213
+
214
+ expect(result.text).toContain('First')
215
+ expect(result.text).toContain('Second')
216
+ })
217
+
218
+ it('handles multiple whitespace', async () => {
219
+ const result = await parseHtml.handler({
220
+ html: '<p>Hello World</p>',
221
+ })
222
+
223
+ expect(result.text).toBe('Hello World')
224
+ })
225
+
226
+ it('handles newlines in HTML', async () => {
227
+ const result = await parseHtml.handler({
228
+ html: '<p>Line 1</p>\n<p>Line 2</p>',
229
+ })
230
+
231
+ expect(result.text).toContain('Line 1')
232
+ expect(result.text).toContain('Line 2')
233
+ })
234
+
235
+ it('strips all HTML tags', async () => {
236
+ const result = await parseHtml.handler({
237
+ html: '<b>Bold</b> and <i>italic</i>',
238
+ })
239
+
240
+ expect(result.text).toBe('Bold and italic')
241
+ })
242
+ })
243
+
244
+ describe('handler - link extraction', () => {
245
+ it('extracts links from href attributes', async () => {
246
+ const result = await parseHtml.handler({
247
+ html: '<a href="https://example.com">Link</a>',
248
+ })
249
+
250
+ expect(result.links).toContain('https://example.com')
251
+ })
252
+
253
+ it('extracts multiple links', async () => {
254
+ const result = await parseHtml.handler({
255
+ html: '<a href="https://a.com">A</a><a href="https://b.com">B</a>',
256
+ })
257
+
258
+ expect(result.links).toHaveLength(2)
259
+ expect(result.links).toContain('https://a.com')
260
+ expect(result.links).toContain('https://b.com')
261
+ })
262
+
263
+ it('extracts relative links', async () => {
264
+ const result = await parseHtml.handler({
265
+ html: '<a href="/path/to/page">Page</a>',
266
+ })
267
+
268
+ expect(result.links).toContain('/path/to/page')
269
+ })
270
+
271
+ it('returns empty array when no links', async () => {
272
+ const result = await parseHtml.handler({
273
+ html: '<p>No links here</p>',
274
+ })
275
+
276
+ expect(result.links).toEqual([])
277
+ })
278
+ })
279
+
280
+ describe('handler - image extraction', () => {
281
+ it('extracts jpg images', async () => {
282
+ const result = await parseHtml.handler({
283
+ html: '<img src="image.jpg" />',
284
+ })
285
+
286
+ expect(result.images).toContain('image.jpg')
287
+ })
288
+
289
+ it('extracts png images', async () => {
290
+ const result = await parseHtml.handler({
291
+ html: '<img src="photo.png" />',
292
+ })
293
+
294
+ expect(result.images).toContain('photo.png')
295
+ })
296
+
297
+ it('extracts gif images', async () => {
298
+ const result = await parseHtml.handler({
299
+ html: '<img src="animation.gif" />',
300
+ })
301
+
302
+ expect(result.images).toContain('animation.gif')
303
+ })
304
+
305
+ it('extracts webp images', async () => {
306
+ const result = await parseHtml.handler({
307
+ html: '<img src="modern.webp" />',
308
+ })
309
+
310
+ expect(result.images).toContain('modern.webp')
311
+ })
312
+
313
+ it('extracts svg images', async () => {
314
+ const result = await parseHtml.handler({
315
+ html: '<img src="icon.svg" />',
316
+ })
317
+
318
+ expect(result.images).toContain('icon.svg')
319
+ })
320
+
321
+ it('extracts jpeg images', async () => {
322
+ const result = await parseHtml.handler({
323
+ html: '<img src="picture.jpeg" />',
324
+ })
325
+
326
+ expect(result.images).toContain('picture.jpeg')
327
+ })
328
+
329
+ it('extracts multiple images', async () => {
330
+ const result = await parseHtml.handler({
331
+ html: '<img src="a.jpg" /><img src="b.png" />',
332
+ })
333
+
334
+ expect(result.images).toHaveLength(2)
335
+ })
336
+
337
+ it('does not extract non-image src attributes', async () => {
338
+ const result = await parseHtml.handler({
339
+ html: '<script src="app.js"></script>',
340
+ })
341
+
342
+ expect(result.images).not.toContain('app.js')
343
+ })
344
+
345
+ it('returns empty array when no images', async () => {
346
+ const result = await parseHtml.handler({
347
+ html: '<p>No images here</p>',
348
+ })
349
+
350
+ expect(result.images).toEqual([])
351
+ })
352
+ })
353
+
354
+ describe('handler - complex HTML', () => {
355
+ it('handles complete HTML document', async () => {
356
+ const html = `
357
+ <!DOCTYPE html>
358
+ <html>
359
+ <head><title>Test Page</title></head>
360
+ <body>
361
+ <h1>Welcome</h1>
362
+ <p>This is content.</p>
363
+ <a href="https://example.com">Link</a>
364
+ <img src="photo.jpg" />
365
+ </body>
366
+ </html>
367
+ `
368
+
369
+ const result = await parseHtml.handler({ html })
370
+
371
+ expect(result.text).toContain('Welcome')
372
+ expect(result.text).toContain('This is content')
373
+ expect(result.links).toContain('https://example.com')
374
+ expect(result.images).toContain('photo.jpg')
375
+ })
376
+
377
+ it('handles empty HTML', async () => {
378
+ const result = await parseHtml.handler({ html: '' })
379
+
380
+ expect(result.text).toBe('')
381
+ expect(result.links).toEqual([])
382
+ expect(result.images).toEqual([])
383
+ })
384
+ })
385
+ })
386
+
387
+ describe('Web Tools - readUrl', () => {
388
+ describe('metadata', () => {
389
+ it('has correct id', () => {
390
+ expect(readUrl.id).toBe('web.read')
391
+ })
392
+
393
+ it('has correct name', () => {
394
+ expect(readUrl.name).toBe('Read URL')
395
+ })
396
+
397
+ it('has correct category', () => {
398
+ expect(readUrl.category).toBe('web')
399
+ })
400
+
401
+ it('has correct subcategory', () => {
402
+ expect(readUrl.subcategory).toBe('scrape')
403
+ })
404
+
405
+ it('is idempotent', () => {
406
+ expect(readUrl.idempotent).toBe(true)
407
+ })
408
+
409
+ it('has read tag', () => {
410
+ expect(readUrl.tags).toContain('read')
411
+ })
412
+
413
+ it('has scrape tag', () => {
414
+ expect(readUrl.tags).toContain('scrape')
415
+ })
416
+
417
+ it('has extract tag', () => {
418
+ expect(readUrl.tags).toContain('extract')
419
+ })
420
+ })
421
+
422
+ describe('parameters', () => {
423
+ it('has url parameter', () => {
424
+ const urlParam = readUrl.parameters.find((p) => p.name === 'url')
425
+ expect(urlParam).toBeDefined()
426
+ expect(urlParam?.required).toBe(true)
427
+ })
428
+
429
+ it('has only 1 parameter', () => {
430
+ expect(readUrl.parameters).toHaveLength(1)
431
+ })
432
+ })
433
+
434
+ describe('handler - real network tests', () => {
435
+ it('reads a real webpage', async () => {
436
+ const result = await readUrl.handler({
437
+ url: 'https://example.com',
438
+ })
439
+
440
+ expect(result.title).toBeDefined()
441
+ expect(result.text).toBeDefined()
442
+ expect(result.links).toBeDefined()
443
+ })
444
+
445
+ it('extracts title from page', async () => {
446
+ const result = await readUrl.handler({
447
+ url: 'https://example.com',
448
+ })
449
+
450
+ expect(typeof result.title).toBe('string')
451
+ expect(result.title.length).toBeGreaterThan(0)
452
+ })
453
+
454
+ it('extracts text from page body', async () => {
455
+ const result = await readUrl.handler({
456
+ url: 'https://example.com',
457
+ })
458
+
459
+ expect(typeof result.text).toBe('string')
460
+ expect(result.text.length).toBeGreaterThan(0)
461
+ })
462
+
463
+ it('extracts unique links', async () => {
464
+ const result = await readUrl.handler({
465
+ url: 'https://example.com',
466
+ })
467
+
468
+ expect(Array.isArray(result.links)).toBe(true)
469
+ // Check for uniqueness
470
+ const uniqueLinks = [...new Set(result.links)]
471
+ expect(uniqueLinks.length).toBe(result.links.length)
472
+ })
473
+
474
+ it('limits text to 10000 characters', async () => {
475
+ const result = await readUrl.handler({
476
+ url: 'https://example.com',
477
+ })
478
+
479
+ expect(result.text.length).toBeLessThanOrEqual(10000)
480
+ })
481
+ })
482
+ })
483
+
484
+ describe('Web Tools Array', () => {
485
+ it('contains fetchUrl', () => {
486
+ expect(webTools.map((t) => t.id)).toContain('web.fetch')
487
+ })
488
+
489
+ it('contains parseHtml', () => {
490
+ expect(webTools.map((t) => t.id)).toContain('web.parse-html')
491
+ })
492
+
493
+ it('contains readUrl', () => {
494
+ expect(webTools.map((t) => t.id)).toContain('web.read')
495
+ })
496
+
497
+ it('has exactly 3 tools', () => {
498
+ expect(webTools).toHaveLength(3)
499
+ })
500
+
501
+ it('all tools have web category', () => {
502
+ expect(webTools.every((t) => t.category === 'web')).toBe(true)
503
+ })
504
+
505
+ it('all tools have handlers', () => {
506
+ expect(webTools.every((t) => typeof t.handler === 'function')).toBe(true)
507
+ })
508
+
509
+ it('all tools have parameters', () => {
510
+ expect(webTools.every((t) => Array.isArray(t.parameters))).toBe(true)
511
+ })
512
+ })
513
+
514
+ describe('Web Tools Registry Integration', () => {
515
+ beforeEach(() => {
516
+ registry.clear()
517
+ })
518
+
519
+ it('can register web tools', () => {
520
+ for (const tool of webTools) {
521
+ registry.register(tool)
522
+ }
523
+
524
+ expect(registry.has('web.fetch')).toBe(true)
525
+ expect(registry.has('web.parse-html')).toBe(true)
526
+ expect(registry.has('web.read')).toBe(true)
527
+ })
528
+
529
+ it('can query web tools by category', () => {
530
+ registerBuiltinTools()
531
+
532
+ const tools = registry.byCategory('web')
533
+ expect(tools.length).toBeGreaterThanOrEqual(3)
534
+ })
535
+
536
+ it('can find tools by subcategory', () => {
537
+ registerBuiltinTools()
538
+
539
+ const fetchTools = registry.query({ subcategory: 'fetch' })
540
+ expect(fetchTools.some((t) => t.id === 'web.fetch')).toBe(true)
541
+
542
+ const scrapeTools = registry.query({ subcategory: 'scrape' })
543
+ expect(scrapeTools.some((t) => t.id === 'web.parse-html')).toBe(true)
544
+ expect(scrapeTools.some((t) => t.id === 'web.read')).toBe(true)
545
+ })
546
+
547
+ it('can search for tools by text', () => {
548
+ registerBuiltinTools()
549
+
550
+ const results = registry.query({ search: 'fetch' })
551
+ expect(results.some((t) => t.id === 'web.fetch')).toBe(true)
552
+ })
553
+ })