@hasna/connectors 1.3.45 → 1.3.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/bin/index.js +792 -130
- package/bin/mcp.js +438 -52
- package/bin/serve.js +438 -52
- package/connectors/cloudflare-workers/.env.example +5 -0
- package/connectors/cloudflare-workers/AGENTS.md +11 -0
- package/connectors/cloudflare-workers/CLAUDE.md +11 -0
- package/connectors/cloudflare-workers/README.md +45 -0
- package/connectors/cloudflare-workers/package.json +47 -0
- package/connectors/cloudflare-workers/src/api/client.test.ts +144 -0
- package/connectors/cloudflare-workers/src/api/client.ts +386 -0
- package/connectors/cloudflare-workers/src/api/index.ts +1 -0
- package/connectors/cloudflare-workers/src/cli/index.ts +206 -0
- package/connectors/cloudflare-workers/src/index.ts +16 -0
- package/connectors/cloudflare-workers/src/types/index.ts +53 -0
- package/connectors/cloudflare-workers/src/utils/config.ts +61 -0
- package/connectors/cloudflare-workers/src/utils/output.ts +26 -0
- package/connectors/cloudflare-workers/tsconfig.build.json +4 -0
- package/connectors/cloudflare-workers/tsconfig.json +14 -0
- package/connectors/tidio/.env.example +3 -2
- package/connectors/tidio/CLAUDE.md +21 -96
- package/connectors/tidio/README.md +22 -18
- package/connectors/tidio/package.json +4 -2
- package/connectors/tidio/src/api/client.ts +26 -15
- package/connectors/tidio/src/api/index.ts +185 -128
- package/connectors/tidio/src/api/tidio.test.ts +175 -44
- package/connectors/tidio/src/cli/index.ts +154 -388
- package/connectors/tidio/src/index.ts +5 -3
- package/connectors/tidio/src/types/index.ts +135 -82
- package/connectors/tidio/src/utils/config.ts +90 -44
- package/connectors/wandb/.env.example +7 -0
- package/connectors/wandb/AGENTS.md +43 -0
- package/connectors/wandb/CLAUDE.md +67 -0
- package/connectors/wandb/README.md +65 -0
- package/connectors/wandb/package.json +52 -0
- package/connectors/wandb/src/api/client.test.ts +116 -0
- package/connectors/wandb/src/api/client.ts +83 -0
- package/connectors/wandb/src/api/graphql.ts +9 -0
- package/connectors/wandb/src/api/index.ts +36 -0
- package/connectors/wandb/src/api/projects.ts +36 -0
- package/connectors/wandb/src/api/viewer.ts +20 -0
- package/connectors/wandb/src/cli/index.ts +265 -0
- package/connectors/wandb/src/index.ts +22 -0
- package/connectors/wandb/src/types/index.ts +75 -0
- package/connectors/wandb/src/utils/config.ts +164 -0
- package/connectors/wandb/src/utils/output.ts +119 -0
- package/connectors/wandb/tsconfig.json +16 -0
- package/connectors/weights-biases/.env.example +7 -0
- package/connectors/weights-biases/CLAUDE.md +65 -0
- package/connectors/weights-biases/README.md +62 -0
- package/connectors/weights-biases/package.json +53 -0
- package/connectors/weights-biases/src/api/client.test.ts +46 -0
- package/connectors/weights-biases/src/api/client.ts +119 -0
- package/connectors/weights-biases/src/api/events.ts +18 -0
- package/connectors/weights-biases/src/api/index.ts +56 -0
- package/connectors/weights-biases/src/api/runs.ts +28 -0
- package/connectors/weights-biases/src/api/search.ts +10 -0
- package/connectors/weights-biases/src/cli/index.ts +289 -0
- package/connectors/weights-biases/src/index.ts +19 -0
- package/connectors/weights-biases/src/types/index.ts +92 -0
- package/connectors/weights-biases/src/utils/config.ts +168 -0
- package/connectors/weights-biases/src/utils/output.ts +116 -0
- package/connectors/weights-biases/tsconfig.json +16 -0
- package/connectors/zoominfo/.env.example +4 -0
- package/connectors/zoominfo/AGENTS.md +26 -0
- package/connectors/zoominfo/CLAUDE.md +20 -0
- package/connectors/zoominfo/README.md +69 -0
- package/connectors/zoominfo/package.json +52 -0
- package/connectors/zoominfo/src/api/client.test.ts +140 -0
- package/connectors/zoominfo/src/api/client.ts +228 -0
- package/connectors/zoominfo/src/api/index.ts +1 -0
- package/connectors/zoominfo/src/cli/index.ts +179 -0
- package/connectors/zoominfo/src/index.ts +3 -0
- package/connectors/zoominfo/src/types/index.ts +39 -0
- package/connectors/zoominfo/src/utils/config.ts +61 -0
- package/connectors/zoominfo/src/utils/output.ts +27 -0
- package/connectors/zoominfo/tsconfig.json +20 -0
- package/connectors/zotero/.env.example +7 -0
- package/connectors/zotero/.npmrc.example +2 -0
- package/connectors/zotero/AGENTS.md +40 -0
- package/connectors/zotero/CLAUDE.md +69 -0
- package/connectors/zotero/README.md +91 -0
- package/connectors/zotero/package.json +43 -0
- package/connectors/zotero/src/api/attachments.ts +130 -0
- package/connectors/zotero/src/api/client.test.ts +166 -0
- package/connectors/zotero/src/api/client.ts +169 -0
- package/connectors/zotero/src/api/collections.ts +26 -0
- package/connectors/zotero/src/api/index.ts +45 -0
- package/connectors/zotero/src/api/items.ts +64 -0
- package/connectors/zotero/src/cli/index.ts +386 -0
- package/connectors/zotero/src/index.ts +28 -0
- package/connectors/zotero/src/types/index.ts +129 -0
- package/connectors/zotero/src/utils/config.ts +245 -0
- package/connectors/zotero/src/utils/output.ts +119 -0
- package/connectors/zotero/tsconfig.json +16 -0
- package/connectors/zymbly/.env.example +2 -0
- package/connectors/zymbly/AGENTS.md +11 -0
- package/connectors/zymbly/CLAUDE.md +5 -0
- package/connectors/zymbly/README.md +35 -0
- package/connectors/zymbly/package.json +52 -0
- package/connectors/zymbly/src/api/client.test.ts +110 -0
- package/connectors/zymbly/src/api/client.ts +166 -0
- package/connectors/zymbly/src/api/index.ts +1 -0
- package/connectors/zymbly/src/cli/index.ts +156 -0
- package/connectors/zymbly/src/index.ts +12 -0
- package/connectors/zymbly/src/types/index.ts +34 -0
- package/connectors/zymbly/src/utils/config.ts +52 -0
- package/connectors/zymbly/src/utils/output.ts +17 -0
- package/connectors/zymbly/tsconfig.json +13 -0
- package/dist/index.js +45 -2
- package/dist/lib/compact-output.d.ts +40 -0
- package/dist/mcp/tools/jobs.d.ts +32 -0
- package/dist/mcp/tools/jobs.test.d.ts +1 -0
- package/package.json +3 -2
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect,
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
+
import { mkdtempSync, statSync } from 'fs';
|
|
3
|
+
import { rm } from 'fs/promises';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
2
6
|
import { Tidio, TidioClient } from './index';
|
|
7
|
+
import {
|
|
8
|
+
createProfile,
|
|
9
|
+
getProfilesDir,
|
|
10
|
+
loadProfile,
|
|
11
|
+
saveProfile,
|
|
12
|
+
setClientCredentials,
|
|
13
|
+
setProfileOverride,
|
|
14
|
+
validateProfileName,
|
|
15
|
+
} from '../utils/config';
|
|
3
16
|
|
|
4
17
|
const originalFetch = globalThis.fetch;
|
|
18
|
+
const originalConfigHome = process.env.TIDIO_CONFIG_HOME;
|
|
5
19
|
|
|
6
20
|
type CapturedRequest = {
|
|
7
21
|
url: URL;
|
|
@@ -18,95 +32,212 @@ function parseBody(body: BodyInit | null | undefined): unknown {
|
|
|
18
32
|
function installFetchMock(response?: Response) {
|
|
19
33
|
const captured: CapturedRequest[] = [];
|
|
20
34
|
globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => {
|
|
21
|
-
const url = new URL(
|
|
22
|
-
typeof input === 'string'
|
|
23
|
-
? input
|
|
24
|
-
: input instanceof URL
|
|
25
|
-
? input.toString()
|
|
26
|
-
: input.url,
|
|
27
|
-
);
|
|
35
|
+
const url = new URL(typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url);
|
|
28
36
|
captured.push({
|
|
29
37
|
url,
|
|
30
38
|
method: init?.method ?? 'GET',
|
|
31
39
|
headers: new Headers(init?.headers),
|
|
32
40
|
body: parseBody(init?.body),
|
|
33
41
|
});
|
|
34
|
-
|
|
35
|
-
return Response.json({ id: 'test-id' });
|
|
42
|
+
return response?.clone() ?? Response.json({ id: 'test-id' });
|
|
36
43
|
}) as typeof fetch;
|
|
37
44
|
return captured;
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
beforeEach(() => {
|
|
41
48
|
globalThis.fetch = originalFetch;
|
|
49
|
+
delete process.env.TIDIO_CLIENT_ID;
|
|
50
|
+
delete process.env.TIDIO_CLIENT_SECRET;
|
|
51
|
+
delete process.env.TIDIO_CONFIG_HOME;
|
|
52
|
+
setProfileOverride(undefined);
|
|
42
53
|
});
|
|
43
54
|
|
|
44
|
-
afterEach(() => {
|
|
55
|
+
afterEach(async () => {
|
|
45
56
|
globalThis.fetch = originalFetch;
|
|
57
|
+
if (originalConfigHome) {
|
|
58
|
+
process.env.TIDIO_CONFIG_HOME = originalConfigHome;
|
|
59
|
+
} else {
|
|
60
|
+
delete process.env.TIDIO_CONFIG_HOME;
|
|
61
|
+
}
|
|
62
|
+
setProfileOverride(undefined);
|
|
46
63
|
});
|
|
47
64
|
|
|
48
65
|
describe('TidioClient', () => {
|
|
49
|
-
test('requires
|
|
50
|
-
expect(() => new TidioClient({
|
|
66
|
+
test('requires client id and client secret', () => {
|
|
67
|
+
expect(() => new TidioClient({ clientId: '', clientSecret: 'cs_test' })).toThrow('Client ID is required');
|
|
68
|
+
expect(() => new TidioClient({ clientId: 'ci_test', clientSecret: '' })).toThrow('Client secret is required');
|
|
51
69
|
});
|
|
52
70
|
|
|
53
|
-
test('
|
|
71
|
+
test('sends current Tidio base URL, versioned Accept header, auth headers, and query params', async () => {
|
|
54
72
|
const captured = installFetchMock();
|
|
55
|
-
const client = new TidioClient({
|
|
73
|
+
const client = new TidioClient({ clientId: 'ci_test', clientSecret: 'cs_test' });
|
|
56
74
|
await client.get('/contacts', { limit: 25, updated_after: '2026-01-01T00:00:00Z' });
|
|
57
75
|
|
|
58
76
|
const req = captured[0]!;
|
|
59
|
-
expect(req.url.origin).toBe('https://api.tidio.
|
|
60
|
-
expect(req.url.pathname).toBe('/
|
|
77
|
+
expect(req.url.origin).toBe('https://api.tidio.com');
|
|
78
|
+
expect(req.url.pathname).toBe('/contacts');
|
|
61
79
|
expect(req.method).toBe('GET');
|
|
62
|
-
expect(req.headers.get('
|
|
63
|
-
expect(req.headers.get('
|
|
80
|
+
expect(req.headers.get('Accept')).toBe('application/json; version=1');
|
|
81
|
+
expect(req.headers.get('X-Tidio-Openapi-Client-Id')).toBe('ci_test');
|
|
82
|
+
expect(req.headers.get('X-Tidio-Openapi-Client-Secret')).toBe('cs_test');
|
|
64
83
|
expect(Object.fromEntries(req.url.searchParams.entries())).toEqual({
|
|
65
84
|
limit: '25',
|
|
66
85
|
updated_after: '2026-01-01T00:00:00Z',
|
|
67
86
|
});
|
|
68
87
|
});
|
|
69
88
|
|
|
70
|
-
test('
|
|
89
|
+
test('surfaces nested Tidio API error messages', async () => {
|
|
90
|
+
installFetchMock(Response.json({ errors: [{ message: 'invalid credentials' }] }, { status: 401 }));
|
|
91
|
+
const tidio = new Tidio({ clientId: 'ci_test', clientSecret: 'cs_test' });
|
|
92
|
+
await expect(tidio.getProject()).rejects.toThrow('invalid credentials');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Tidio documented API facade', () => {
|
|
97
|
+
test('createContact uses documented contact payload shape', async () => {
|
|
71
98
|
const captured = installFetchMock();
|
|
72
|
-
const tidio = new Tidio({
|
|
73
|
-
await tidio.
|
|
99
|
+
const tidio = new Tidio({ clientId: 'ci_test', clientSecret: 'cs_test' });
|
|
100
|
+
await tidio.createContact({
|
|
101
|
+
email: 'ada@example.com',
|
|
102
|
+
firstName: 'Ada',
|
|
103
|
+
lastName: 'Lovelace',
|
|
104
|
+
distinctId: 'crm-123',
|
|
105
|
+
properties: [{ name: 'plan', value: 'pro' }],
|
|
106
|
+
emailConsent: 'subscribed',
|
|
107
|
+
});
|
|
74
108
|
|
|
75
109
|
const req = captured[0]!;
|
|
76
|
-
expect(req.url.pathname).toBe('/
|
|
110
|
+
expect(req.url.pathname).toBe('/contacts');
|
|
77
111
|
expect(req.method).toBe('POST');
|
|
78
112
|
expect(req.headers.get('Content-Type')).toBe('application/json');
|
|
79
|
-
expect(req.body).toEqual({
|
|
113
|
+
expect(req.body).toEqual({
|
|
114
|
+
email: 'ada@example.com',
|
|
115
|
+
first_name: 'Ada',
|
|
116
|
+
last_name: 'Lovelace',
|
|
117
|
+
distinct_id: 'crm-123',
|
|
118
|
+
properties: [{ name: 'plan', value: 'pro' }],
|
|
119
|
+
email_consent: 'subscribed',
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('sendContactMessage uses /contacts/{contactId}/messages with message body', async () => {
|
|
124
|
+
const captured = installFetchMock(Response.json({ status: 'accepted' }, { status: 202 }));
|
|
125
|
+
const tidio = new Tidio({ clientId: 'ci_test', clientSecret: 'cs_test' });
|
|
126
|
+
await tidio.sendContactMessage('a1b4ca4c-1108-4432-b256-1e4cf2bf6f9e', { message: 'hello' });
|
|
127
|
+
|
|
128
|
+
const req = captured[0]!;
|
|
129
|
+
expect(req.url.pathname).toBe('/contacts/a1b4ca4c-1108-4432-b256-1e4cf2bf6f9e/messages');
|
|
130
|
+
expect(req.method).toBe('POST');
|
|
131
|
+
expect(req.body).toEqual({ message: 'hello' });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('updateContact treats documented 204 response as no content', async () => {
|
|
135
|
+
const captured = installFetchMock(new Response(null, { status: 204 }));
|
|
136
|
+
const tidio = new Tidio({ clientId: 'ci_test', clientSecret: 'cs_test' });
|
|
137
|
+
await expect(tidio.updateContact('a1b4ca4c-1108-4432-b256-1e4cf2bf6f9e', { firstName: 'Ada' })).resolves.toBeUndefined();
|
|
138
|
+
|
|
139
|
+
const req = captured[0]!;
|
|
140
|
+
expect(req.url.pathname).toBe('/contacts/a1b4ca4c-1108-4432-b256-1e4cf2bf6f9e');
|
|
141
|
+
expect(req.method).toBe('PATCH');
|
|
142
|
+
expect(req.body).toEqual({ first_name: 'Ada' });
|
|
80
143
|
});
|
|
81
144
|
|
|
82
|
-
test('
|
|
145
|
+
test('listContactMessages uses documented cursor pagination only', async () => {
|
|
83
146
|
const captured = installFetchMock();
|
|
84
|
-
const tidio = new Tidio({
|
|
85
|
-
await tidio.
|
|
86
|
-
url: 'https://example.com/hook',
|
|
87
|
-
events: ['contact.created', 'message.created'],
|
|
88
|
-
});
|
|
147
|
+
const tidio = new Tidio({ clientId: 'ci_test', clientSecret: 'cs_test' });
|
|
148
|
+
await tidio.listContactMessages('a1b4ca4c-1108-4432-b256-1e4cf2bf6f9e', { cursor: 'next' });
|
|
89
149
|
|
|
90
150
|
const req = captured[0]!;
|
|
91
|
-
expect(req.url.pathname).toBe('/
|
|
92
|
-
expect(req.
|
|
93
|
-
url: 'https://example.com/hook',
|
|
94
|
-
events: ['contact.created', 'message.created'],
|
|
95
|
-
});
|
|
151
|
+
expect(req.url.pathname).toBe('/contacts/a1b4ca4c-1108-4432-b256-1e4cf2bf6f9e/messages');
|
|
152
|
+
expect(Object.fromEntries(req.url.searchParams.entries())).toEqual({ cursor: 'next' });
|
|
96
153
|
});
|
|
97
154
|
|
|
98
|
-
test('
|
|
99
|
-
|
|
100
|
-
const tidio = new Tidio({
|
|
101
|
-
await
|
|
155
|
+
test('project, tickets, products, and Lyro helpers use documented paths', async () => {
|
|
156
|
+
const captured = installFetchMock();
|
|
157
|
+
const tidio = new Tidio({ clientId: 'ci_test', clientSecret: 'cs_test' });
|
|
158
|
+
await tidio.getProject();
|
|
159
|
+
await tidio.getTicket('ticket-1');
|
|
160
|
+
await tidio.upsertProducts([
|
|
161
|
+
{
|
|
162
|
+
id: 1,
|
|
163
|
+
title: 'Red shirt',
|
|
164
|
+
url: 'https://example.com/red-shirt',
|
|
165
|
+
default_currency: 'USD',
|
|
166
|
+
updated_at: '2026-01-01T00:00:00+00:00',
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
await tidio.listLyroDataSources({ kind: 'qa', cursor: 'next' });
|
|
170
|
+
await tidio.askLyroToAnswerTicket({
|
|
171
|
+
ticketId: '01H6XZJ88T5PMRRQC3C54WY1EJ',
|
|
172
|
+
subject: 'Question about delivery',
|
|
173
|
+
contactEmail: 'user@example.com',
|
|
174
|
+
contactName: 'Ada Lovelace',
|
|
175
|
+
recipientEmail: 'support@example.com',
|
|
176
|
+
messages: [
|
|
177
|
+
{
|
|
178
|
+
created_at: '2026-01-01T00:00:00+00:00',
|
|
179
|
+
message_id: '01JVRXTTZ4ACV3JZTQS8ZVKASK',
|
|
180
|
+
author_type: 'contact',
|
|
181
|
+
message_type: 'public',
|
|
182
|
+
message_content: 'Can you ship to the USA?',
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(captured.map(req => `${req.method} ${req.url.pathname}`)).toEqual([
|
|
188
|
+
'GET /project',
|
|
189
|
+
'GET /tickets/ticket-1',
|
|
190
|
+
'PUT /products/batch',
|
|
191
|
+
'GET /lyro/data-sources',
|
|
192
|
+
'POST /lyro/tickets',
|
|
193
|
+
]);
|
|
194
|
+
expect(captured[3]!.url.searchParams.get('kind')).toBe('qa');
|
|
195
|
+
expect(captured[3]!.url.searchParams.get('cursor')).toBe('next');
|
|
196
|
+
expect(captured[4]!.body).toEqual({
|
|
197
|
+
ticket_id: '01H6XZJ88T5PMRRQC3C54WY1EJ',
|
|
198
|
+
subject: 'Question about delivery',
|
|
199
|
+
contact_email: 'user@example.com',
|
|
200
|
+
contact_name: 'Ada Lovelace',
|
|
201
|
+
recipient_email: 'support@example.com',
|
|
202
|
+
messages: [
|
|
203
|
+
{
|
|
204
|
+
created_at: '2026-01-01T00:00:00+00:00',
|
|
205
|
+
message_id: '01JVRXTTZ4ACV3JZTQS8ZVKASK',
|
|
206
|
+
author_type: 'contact',
|
|
207
|
+
message_type: 'public',
|
|
208
|
+
message_content: 'Can you ship to the USA?',
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
});
|
|
102
212
|
});
|
|
103
213
|
});
|
|
104
214
|
|
|
105
215
|
describe('Tidio.fromEnv', () => {
|
|
106
|
-
test('requires
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
216
|
+
test('requires both TIDIO_CLIENT_ID and TIDIO_CLIENT_SECRET', () => {
|
|
217
|
+
process.env.TIDIO_CLIENT_ID = 'ci_test';
|
|
218
|
+
expect(() => Tidio.fromEnv()).toThrow('TIDIO_CLIENT_ID and TIDIO_CLIENT_SECRET environment variables are required');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('secure profile storage', () => {
|
|
223
|
+
test('validates profile names before path joins', () => {
|
|
224
|
+
expect(() => validateProfileName('../escape')).toThrow('Profile name can only contain');
|
|
225
|
+
expect(() => createProfile('../escape')).toThrow('Profile name can only contain');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('writes config directories as 0700 and profile files as 0600', async () => {
|
|
229
|
+
const home = mkdtempSync(join(tmpdir(), 'tidio-config-'));
|
|
230
|
+
process.env.TIDIO_CONFIG_HOME = home;
|
|
231
|
+
createProfile('default');
|
|
232
|
+
setClientCredentials('ci_test', 'cs_test');
|
|
233
|
+
saveProfile({ clientId: 'ci_other', clientSecret: 'cs_other' }, 'work');
|
|
234
|
+
|
|
235
|
+
expect((statSync(home).mode & 0o777).toString(8)).toBe('700');
|
|
236
|
+
expect((statSync(getProfilesDir()).mode & 0o777).toString(8)).toBe('700');
|
|
237
|
+
expect((statSync(join(getProfilesDir(), 'default.json')).mode & 0o777).toString(8)).toBe('600');
|
|
238
|
+
expect((statSync(join(getProfilesDir(), 'work.json')).mode & 0o777).toString(8)).toBe('600');
|
|
239
|
+
expect(loadProfile('work')).toEqual({ clientId: 'ci_other', clientSecret: 'cs_other' });
|
|
240
|
+
|
|
241
|
+
await rm(home, { recursive: true, force: true });
|
|
111
242
|
});
|
|
112
243
|
});
|