@hasna/connectors 1.3.23 → 1.3.25

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 (36) hide show
  1. package/bin/index.js +492 -153
  2. package/bin/mcp.js +1078 -595
  3. package/bin/serve.js +774 -436
  4. package/connectors/connect-cloudflare/bun.lock +32 -0
  5. package/connectors/connect-gmail/bin/index.js +7027 -0
  6. package/connectors/connect-googledrive/bin/index.js +5958 -0
  7. package/connectors/connect-imessage/bin/.gitkeep +2 -0
  8. package/connectors/connect-imessage/bin/index.js +3016 -0
  9. package/connectors/connect-imessage/bun.lock +32 -0
  10. package/connectors/connect-imessage/package.json +46 -0
  11. package/connectors/connect-imessage/src/api/client.ts +139 -0
  12. package/connectors/connect-imessage/src/api/conversations.ts +49 -0
  13. package/connectors/connect-imessage/src/api/health.ts +16 -0
  14. package/connectors/connect-imessage/src/api/index.ts +62 -0
  15. package/connectors/connect-imessage/src/api/messages.ts +119 -0
  16. package/connectors/connect-imessage/src/cli/index.ts +258 -0
  17. package/connectors/connect-imessage/src/index.ts +26 -0
  18. package/connectors/connect-imessage/src/types/index.ts +134 -0
  19. package/connectors/connect-imessage/src/utils/config.ts +219 -0
  20. package/connectors/connect-imessage/src/utils/output.ts +121 -0
  21. package/connectors/connect-imessage/tsconfig.json +16 -0
  22. package/dist/core/builtins.test.d.ts +1 -0
  23. package/dist/core/connectors/googledrive.d.ts +6 -0
  24. package/dist/core/connectors/internal-operations.test.d.ts +1 -0
  25. package/dist/core/connectors/internal-runtime.test.d.ts +1 -0
  26. package/dist/core/errors.test.d.ts +1 -0
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +640 -86
  29. package/dist/lib/runner-auth.test.d.ts +1 -0
  30. package/dist/lib/runner.d.ts +31 -0
  31. package/dist/lib/test-endpoints.test.d.ts +1 -0
  32. package/dist/lib/workflow-runner.d.ts +7 -0
  33. package/dist/server/auth-exchange.test.d.ts +1 -0
  34. package/dist/server/auth-refresh.test.d.ts +1 -0
  35. package/dist/server/auth.d.ts +1 -0
  36. package/package.json +1 -1
@@ -0,0 +1,32 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "@hasna/connect-imessage",
7
+ "dependencies": {
8
+ "chalk": "^5.3.0",
9
+ "commander": "^12.1.0",
10
+ },
11
+ "devDependencies": {
12
+ "@types/bun": "latest",
13
+ "typescript": "^5",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="],
19
+
20
+ "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
21
+
22
+ "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
23
+
24
+ "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
25
+
26
+ "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
27
+
28
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
29
+
30
+ "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
31
+ }
32
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@hasna/connect-imessage",
3
+ "version": "0.0.1",
4
+ "description": "iMessage connector CLI - bridge-first iMessage transport for send, reply, and conversation management",
5
+ "type": "module",
6
+ "bin": {
7
+ "connect-imessage": "bin/index.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "scripts": {
18
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun && bun build ./src/cli/index.ts --outdir ./bin --target bun",
19
+ "dev": "bun run ./src/cli/index.ts",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepublishOnly": "bun run build"
22
+ },
23
+ "keywords": [
24
+ "imessage",
25
+ "apple",
26
+ "messages",
27
+ "connector",
28
+ "cli",
29
+ "typescript",
30
+ "bun",
31
+ "hasna"
32
+ ],
33
+ "author": "Hasna",
34
+ "license": "Apache-2.0",
35
+ "devDependencies": {
36
+ "@types/bun": "latest",
37
+ "typescript": "^5"
38
+ },
39
+ "dependencies": {
40
+ "commander": "^12.1.0",
41
+ "chalk": "^5.3.0"
42
+ },
43
+ "engines": {
44
+ "bun": ">=1.0.0"
45
+ }
46
+ }
@@ -0,0 +1,139 @@
1
+ import type { IMessageConfig, IMessageApiResponse } from '../types';
2
+ import { IMessageApiError } from '../types';
3
+
4
+ export interface ImessageRequestOptions {
5
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
6
+ params?: Record<string, string | number | boolean | undefined>;
7
+ body?: Record<string, unknown> | unknown[];
8
+ }
9
+
10
+ /**
11
+ * HTTP client for the iMessage bridge API
12
+ */
13
+ export class ImessageClient {
14
+ private readonly bridgeUrl: string;
15
+ private readonly apiKey?: string;
16
+ private readonly deviceId?: string;
17
+
18
+ constructor(config: IMessageConfig) {
19
+ if (!config.bridgeUrl) {
20
+ throw new Error('Bridge URL is required');
21
+ }
22
+ // Normalize URL - strip trailing slash
23
+ this.bridgeUrl = config.bridgeUrl.replace(/\/+$/, '');
24
+ this.apiKey = config.apiKey;
25
+ this.deviceId = config.deviceId;
26
+ }
27
+
28
+ private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
29
+ const url = new URL(this.bridgeUrl + path);
30
+ if (params) {
31
+ Object.entries(params).forEach(([key, value]) => {
32
+ if (value !== undefined && value !== null && value !== '') {
33
+ url.searchParams.append(key, String(value));
34
+ }
35
+ });
36
+ }
37
+ return url.toString();
38
+ }
39
+
40
+ private getHeaders(): Record<string, string> {
41
+ const headers: Record<string, string> = {
42
+ 'Accept': 'application/json',
43
+ };
44
+
45
+ if (this.apiKey) {
46
+ headers['Authorization'] = 'Bearer ' + this.apiKey;
47
+ }
48
+ if (this.deviceId) {
49
+ headers['X-API-Key'] = this.deviceId;
50
+ }
51
+
52
+ return headers;
53
+ }
54
+
55
+ async request<T>(
56
+ path: string,
57
+ options: ImessageRequestOptions = {}
58
+ ): Promise<T> {
59
+ const { method = 'GET', params, body } = options;
60
+
61
+ const url = this.buildUrl(path, method === 'GET' ? params : undefined);
62
+ const headers = this.getHeaders();
63
+
64
+ const fetchOptions: RequestInit = {
65
+ method,
66
+ headers,
67
+ };
68
+
69
+ if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
70
+ headers['Content-Type'] = 'application/json';
71
+ fetchOptions.body = JSON.stringify(body);
72
+ }
73
+
74
+ let response: Response;
75
+ try {
76
+ response = await fetch(url, fetchOptions);
77
+ } catch (error) {
78
+ if (error instanceof Error) {
79
+ throw new IMessageApiError('Failed to reach bridge: ' + error.message, 0);
80
+ }
81
+ throw new IMessageApiError('Failed to reach bridge', 0);
82
+ }
83
+
84
+ let data: unknown;
85
+ const contentType = response.headers.get('content-type') || '';
86
+ if (contentType.includes('application/json')) {
87
+ const text = await response.text();
88
+ if (text) {
89
+ try {
90
+ data = JSON.parse(text);
91
+ } catch {
92
+ data = text;
93
+ }
94
+ }
95
+ } else {
96
+ data = await response.text();
97
+ }
98
+
99
+ if (!response.ok) {
100
+ const errorData = data as IMessageApiResponse;
101
+ const message = errorData?.error || (typeof data === 'string' ? data : response.statusText);
102
+ throw new IMessageApiError(message || 'Bridge request failed', response.status, errorData?.code);
103
+ }
104
+
105
+ return (data as IMessageApiResponse<T>)?.data ?? (data as T);
106
+ }
107
+
108
+ async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
109
+ return this.request<T>(path, { method: 'GET', params });
110
+ }
111
+
112
+ async post<T>(path: string, body?: Record<string, unknown>): Promise<T> {
113
+ return this.request<T>(path, { method: 'POST', body });
114
+ }
115
+
116
+ async put<T>(path: string, body?: Record<string, unknown>): Promise<T> {
117
+ return this.request<T>(path, { method: 'PUT', body });
118
+ }
119
+
120
+ async patch<T>(path: string, body?: Record<string, unknown>): Promise<T> {
121
+ return this.request<T>(path, { method: 'PATCH', body });
122
+ }
123
+
124
+ async delete<T>(path: string): Promise<T> {
125
+ return this.request<T>(path, { method: 'DELETE' });
126
+ }
127
+
128
+ /**
129
+ * Get a preview of the bridge URL (for debugging)
130
+ */
131
+ getBridgeUrlPreview(): string {
132
+ try {
133
+ const url = new URL(this.bridgeUrl);
134
+ return url.host;
135
+ } catch {
136
+ return 'invalid-url';
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,49 @@
1
+ import type { ImessageClient } from './client';
2
+ import type { IMessageConversation } from '../types';
3
+
4
+ export interface ListConversationsOptions {
5
+ limit?: number;
6
+ offset?: number;
7
+ }
8
+
9
+ /**
10
+ * Conversation API module - list and retrieve iMessage conversations
11
+ */
12
+ export class ConversationsApi {
13
+ constructor(private readonly client: ImessageClient) {}
14
+
15
+ /**
16
+ * List all conversations
17
+ */
18
+ async list(options: ListConversationsOptions = {}): Promise<IMessageConversation[]> {
19
+ const params: Record<string, number | undefined> = {};
20
+ if (options.limit) params.limit = options.limit;
21
+ if (options.offset) params.offset = options.offset;
22
+ return this.client.get<IMessageConversation[]>('/conversations', params);
23
+ }
24
+
25
+ /**
26
+ * Iterate over all conversations (handles pagination)
27
+ */
28
+ async *listAll(options: ListConversationsOptions = {}): AsyncGenerator<IMessageConversation> {
29
+ let offset = options.offset || 0;
30
+ const limit = options.limit || 50;
31
+
32
+ while (true) {
33
+ const conversations = await this.list({ limit, offset });
34
+ if (conversations.length === 0) break;
35
+ for (const conversation of conversations) {
36
+ yield conversation;
37
+ }
38
+ if (conversations.length < limit) break;
39
+ offset += conversations.length;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Get a specific conversation by chat GUID
45
+ */
46
+ async get(chatGuid: string): Promise<IMessageConversation> {
47
+ return this.client.get<IMessageConversation>(`/conversations/${encodeURIComponent(chatGuid)}`);
48
+ }
49
+ }
@@ -0,0 +1,16 @@
1
+ import type { ImessageClient } from './client';
2
+ import type { IMessageHealth } from '../types';
3
+
4
+ /**
5
+ * Health API module - check bridge and iMessage service status
6
+ */
7
+ export class HealthApi {
8
+ constructor(private readonly client: ImessageClient) {}
9
+
10
+ /**
11
+ * Check bridge and iMessage health
12
+ */
13
+ async check(): Promise<IMessageHealth> {
14
+ return this.client.get<IMessageHealth>('/health');
15
+ }
16
+ }
@@ -0,0 +1,62 @@
1
+ import type { IMessageConfig } from '../types';
2
+ import { ImessageClient } from './client';
3
+ import { HealthApi } from './health';
4
+ import { ConversationsApi } from './conversations';
5
+ import { MessagesApi } from './messages';
6
+
7
+ /**
8
+ * Main iMessage Connector class
9
+ * Provides access to iMessage via a bridge API
10
+ */
11
+ export class IMessage {
12
+ private readonly client: ImessageClient;
13
+
14
+ // Service APIs
15
+ public readonly health: HealthApi;
16
+ public readonly conversations: ConversationsApi;
17
+ public readonly messages: MessagesApi;
18
+
19
+ constructor(config: IMessageConfig) {
20
+ this.client = new ImessageClient(config);
21
+ this.health = new HealthApi(this.client);
22
+ this.conversations = new ConversationsApi(this.client);
23
+ this.messages = new MessagesApi(this.client);
24
+ }
25
+
26
+ /**
27
+ * Create a client from environment variables
28
+ * Looks for IMESSAGE_BRIDGE_URL, IMESSAGE_API_KEY, IMESSAGE_DEVICE_ID
29
+ */
30
+ static fromEnv(): IMessage {
31
+ const bridgeUrl = process.env.IMESSAGE_BRIDGE_URL;
32
+
33
+ if (!bridgeUrl) {
34
+ throw new Error('IMESSAGE_BRIDGE_URL environment variable is required');
35
+ }
36
+
37
+ return new IMessage({
38
+ bridgeUrl,
39
+ apiKey: process.env.IMESSAGE_API_KEY,
40
+ deviceId: process.env.IMESSAGE_DEVICE_ID,
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Get the bridge URL preview
46
+ */
47
+ getBridgeUrlPreview(): string {
48
+ return this.client.getBridgeUrlPreview();
49
+ }
50
+
51
+ /**
52
+ * Get the underlying client for direct API access
53
+ */
54
+ getClient(): ImessageClient {
55
+ return this.client;
56
+ }
57
+ }
58
+
59
+ export { ImessageClient } from './client';
60
+ export { HealthApi } from './health';
61
+ export { ConversationsApi } from './conversations';
62
+ export { MessagesApi } from './messages';
@@ -0,0 +1,119 @@
1
+ import type { ImessageClient } from './client';
2
+ import type { IMessage, IMessageAttachment } from '../types';
3
+
4
+ export interface ListMessagesOptions {
5
+ chatGuid: string;
6
+ limit?: number;
7
+ offset?: number;
8
+ before?: string;
9
+ after?: string;
10
+ }
11
+
12
+ export interface SendMessageOptions {
13
+ chatGuid?: string;
14
+ recipient?: string;
15
+ text: string;
16
+ method?: string;
17
+ effectId?: string;
18
+ }
19
+
20
+ export interface ReplyMessageOptions {
21
+ text: string;
22
+ selectedMessageGuid: string;
23
+ }
24
+
25
+ /**
26
+ * Message API module - send, receive, and manage iMessages
27
+ */
28
+ export class MessagesApi {
29
+ constructor(private readonly client: ImessageClient) {}
30
+
31
+ /**
32
+ * List messages for a conversation
33
+ */
34
+ async list(options: ListMessagesOptions): Promise<IMessage[]> {
35
+ const params: Record<string, string | number | undefined> = {
36
+ chatGuid: options.chatGuid,
37
+ };
38
+ if (options.limit) params.limit = options.limit;
39
+ if (options.offset) params.offset = options.offset;
40
+ if (options.before) params.before = options.before;
41
+ if (options.after) params.after = options.after;
42
+ return this.client.get<IMessage[]>('/messages', params);
43
+ }
44
+
45
+ /**
46
+ * Iterate over all messages (handles pagination)
47
+ */
48
+ async *listAll(options: ListMessagesOptions): AsyncGenerator<IMessage> {
49
+ let offset = options.offset || 0;
50
+ const limit = options.limit || 50;
51
+
52
+ while (true) {
53
+ const messages = await this.list({ ...options, limit, offset });
54
+ if (messages.length === 0) break;
55
+ for (const message of messages) {
56
+ yield message;
57
+ }
58
+ if (messages.length < limit) break;
59
+ offset += messages.length;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Send a message to a conversation
65
+ */
66
+ async send(options: SendMessageOptions): Promise<IMessage> {
67
+ const body: Record<string, unknown> = {
68
+ text: options.text,
69
+ };
70
+ if (options.chatGuid) body.chatGuid = options.chatGuid;
71
+ if (options.recipient) body.recipient = options.recipient;
72
+ if (options.method) body.method = options.method;
73
+ if (options.effectId) body.effectId = options.effectId;
74
+ return this.client.post<IMessage>('/messages', body);
75
+ }
76
+
77
+ /**
78
+ * Send a reply to a specific message (tapback or quoted reply)
79
+ */
80
+ async reply(messageGuid: string, options: ReplyMessageOptions): Promise<IMessage> {
81
+ const body: Record<string, unknown> = {
82
+ text: options.text,
83
+ selectedMessageGuid: options.selectedMessageGuid,
84
+ };
85
+ return this.client.post<IMessage>(`/messages/${messageGuid}/reply`, body);
86
+ }
87
+
88
+ /**
89
+ * Get a specific message by GUID
90
+ */
91
+ async get(messageGuid: string): Promise<IMessage> {
92
+ return this.client.get<IMessage>(`/messages/${messageGuid}`);
93
+ }
94
+
95
+ /**
96
+ * Download a message attachment
97
+ */
98
+ async downloadAttachment(attachmentGuid: string): Promise<ArrayBuffer> {
99
+ return this.client.request<ArrayBuffer>(`/attachments/${attachmentGuid}`, {
100
+ method: 'GET',
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Send a tapback (reaction) to a message
106
+ */
107
+ async tapback(messageGuid: string, tapback: string): Promise<IMessage> {
108
+ return this.client.post<IMessage>(`/messages/${messageGuid}/tapback`, {
109
+ tapback,
110
+ } as unknown as Record<string, unknown>);
111
+ }
112
+
113
+ /**
114
+ * Mark messages as read in a conversation
115
+ */
116
+ async markRead(chatGuid: string): Promise<void> {
117
+ await this.client.post<void>(`/conversations/${encodeURIComponent(chatGuid)}/read`);
118
+ }
119
+ }