@hasna/connectors 1.3.32 → 1.3.33
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/bin/index.js +19345 -804
- package/bin/mcp.js +35033 -33318
- package/bin/serve.js +26861 -7773
- package/dist/cli/auth-no-browser.test.d.ts +1 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/core/builtins.test.d.ts +1 -0
- package/dist/core/connectors/gmail.d.ts +5 -0
- package/dist/core/connectors/googledrive.d.ts +6 -0
- package/dist/core/connectors/internal-operations.test.d.ts +1 -0
- package/dist/core/connectors/internal-runtime.test.d.ts +1 -0
- package/dist/core/errors.test.d.ts +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1399 -167
- package/dist/lib/connector-resolver.d.ts +37 -0
- package/dist/lib/connector-resolver.test.d.ts +1 -0
- package/dist/lib/lock.d.ts +1 -1
- package/dist/lib/manifest.d.ts +50 -0
- package/dist/lib/manifest.test.d.ts +1 -0
- package/dist/lib/runner-auth.test.d.ts +1 -0
- package/dist/lib/runner.d.ts +31 -0
- package/dist/lib/test-endpoints.test.d.ts +1 -0
- package/dist/lib/workflow-runner.d.ts +7 -0
- package/dist/mcp/http.d.ts +14 -0
- package/dist/mcp/http.test.d.ts +1 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/server.d.ts +3 -0
- package/dist/server/auth-exchange.test.d.ts +1 -0
- package/dist/server/auth-refresh.test.d.ts +1 -0
- package/dist/server/auth.d.ts +4 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,8 +15,8 @@ var __export = (target, all) => {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// src/lib/registry.ts
|
|
18
|
-
import { existsSync as
|
|
19
|
-
import { join as
|
|
18
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
|
|
19
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
20
20
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
21
21
|
|
|
22
22
|
// src/core/errors.ts
|
|
@@ -42,7 +42,7 @@ class ConnectorOperationNotFoundError extends Error {
|
|
|
42
42
|
|
|
43
43
|
// src/core/connector.ts
|
|
44
44
|
var CONNECTOR_NAME_RE = /^[a-z0-9-]+$/;
|
|
45
|
-
var OPERATION_NAME_RE = /^[
|
|
45
|
+
var OPERATION_NAME_RE = /^[A-Za-z0-9:._-]+$/;
|
|
46
46
|
function defineConnector(definition) {
|
|
47
47
|
const { meta, auth, operations } = definition;
|
|
48
48
|
if (!CONNECTOR_NAME_RE.test(meta.name)) {
|
|
@@ -4113,10 +4113,10 @@ var NEVER = INVALID;
|
|
|
4113
4113
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
4114
4114
|
function resolveGithubConnectorDir() {
|
|
4115
4115
|
const candidates = [
|
|
4116
|
-
join(__dirname2, "..", "..", "..", "connectors", "
|
|
4117
|
-
join(__dirname2, "..", "..", "connectors", "
|
|
4118
|
-
join(__dirname2, "..", "connectors", "
|
|
4119
|
-
join(process.cwd(), "connectors", "
|
|
4116
|
+
join(__dirname2, "..", "..", "..", "connectors", "github"),
|
|
4117
|
+
join(__dirname2, "..", "..", "connectors", "github"),
|
|
4118
|
+
join(__dirname2, "..", "connectors", "github"),
|
|
4119
|
+
join(process.cwd(), "connectors", "github")
|
|
4120
4120
|
];
|
|
4121
4121
|
for (const candidate of candidates) {
|
|
4122
4122
|
if (existsSync(candidate)) {
|
|
@@ -4966,10 +4966,805 @@ var githubConnector = defineConnector({
|
|
|
4966
4966
|
}
|
|
4967
4967
|
}
|
|
4968
4968
|
});
|
|
4969
|
+
|
|
4970
|
+
// src/core/connectors/gmail.ts
|
|
4971
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
4972
|
+
import { homedir } from "os";
|
|
4973
|
+
import { basename, join as join2 } from "path";
|
|
4974
|
+
var GMAIL_API_BASE = "https://gmail.googleapis.com/gmail/v1";
|
|
4975
|
+
var TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
4976
|
+
var REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
4977
|
+
var MAX_GMAIL_RETRIES = 5;
|
|
4978
|
+
var listMessagesSchema = exports_external.object({
|
|
4979
|
+
max: exports_external.coerce.number().int().positive().max(500).optional(),
|
|
4980
|
+
maxResults: exports_external.coerce.number().int().positive().max(500).optional(),
|
|
4981
|
+
pageToken: exports_external.string().optional(),
|
|
4982
|
+
query: exports_external.string().optional(),
|
|
4983
|
+
q: exports_external.string().optional(),
|
|
4984
|
+
label: exports_external.string().optional(),
|
|
4985
|
+
labelIds: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
4986
|
+
includeSpamTrash: exports_external.boolean().optional()
|
|
4987
|
+
});
|
|
4988
|
+
var messageIdSchema = exports_external.object({
|
|
4989
|
+
args: exports_external.array(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional(),
|
|
4990
|
+
messageId: exports_external.string().optional()
|
|
4991
|
+
});
|
|
4992
|
+
var readMessageSchema = messageIdSchema.extend({
|
|
4993
|
+
body: exports_external.boolean().optional(),
|
|
4994
|
+
html: exports_external.boolean().optional(),
|
|
4995
|
+
format: exports_external.enum(["full", "metadata", "minimal", "raw"]).optional()
|
|
4996
|
+
});
|
|
4997
|
+
var attachmentListSchema = messageIdSchema;
|
|
4998
|
+
var attachmentDownloadSchema = messageIdSchema.extend({
|
|
4999
|
+
attachmentId: exports_external.string().optional(),
|
|
5000
|
+
filename: exports_external.string().optional(),
|
|
5001
|
+
mimeType: exports_external.string().optional(),
|
|
5002
|
+
dir: exports_external.string().optional(),
|
|
5003
|
+
outputDir: exports_external.string().optional()
|
|
5004
|
+
});
|
|
5005
|
+
var historyListSchema = exports_external.object({
|
|
5006
|
+
startHistoryId: exports_external.string(),
|
|
5007
|
+
historyTypes: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
5008
|
+
labelId: exports_external.string().optional(),
|
|
5009
|
+
maxResults: exports_external.coerce.number().int().positive().max(500).optional(),
|
|
5010
|
+
pageToken: exports_external.string().optional()
|
|
5011
|
+
});
|
|
5012
|
+
var replySchema = messageIdSchema.extend({
|
|
5013
|
+
body: exports_external.string(),
|
|
5014
|
+
html: exports_external.boolean().optional(),
|
|
5015
|
+
isHtml: exports_external.boolean().optional(),
|
|
5016
|
+
cc: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional(),
|
|
5017
|
+
bcc: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional()
|
|
5018
|
+
});
|
|
5019
|
+
var gmailConnector = defineConnector({
|
|
5020
|
+
meta: {
|
|
5021
|
+
name: "gmail",
|
|
5022
|
+
displayName: "Gmail",
|
|
5023
|
+
description: "Profile-aware Gmail mailbox operations for sync, labels, attachments, history, and replies.",
|
|
5024
|
+
category: "communication",
|
|
5025
|
+
tags: ["google", "gmail", "email", "mailbox"]
|
|
5026
|
+
},
|
|
5027
|
+
auth: {
|
|
5028
|
+
type: "oauth2",
|
|
5029
|
+
supportsProfiles: true,
|
|
5030
|
+
fields: [
|
|
5031
|
+
{ key: "clientId", env: "GMAIL_CLIENT_ID", label: "OAuth client ID" },
|
|
5032
|
+
{ key: "clientSecret", env: "GMAIL_CLIENT_SECRET", label: "OAuth client secret", secret: true }
|
|
5033
|
+
]
|
|
5034
|
+
},
|
|
5035
|
+
createContext: ({ profile }) => ({ profile: profile || "default" }),
|
|
5036
|
+
operations: {
|
|
5037
|
+
"profiles.list": {
|
|
5038
|
+
summary: "List configured Gmail profiles.",
|
|
5039
|
+
execute: () => ({ profiles: listProfiles() })
|
|
5040
|
+
},
|
|
5041
|
+
"profile.get": {
|
|
5042
|
+
summary: "Get the authenticated Gmail profile.",
|
|
5043
|
+
execute: async ({ context }) => requestJson(context.profile, "/users/me/profile", {})
|
|
5044
|
+
},
|
|
5045
|
+
"messages.list": {
|
|
5046
|
+
summary: "List Gmail messages.",
|
|
5047
|
+
inputSchema: listMessagesSchema,
|
|
5048
|
+
execute: async ({ context }, input) => {
|
|
5049
|
+
const labelIds = normalizeStringArray(input.labelIds ?? input.label);
|
|
5050
|
+
return requestJson(context.profile, "/users/me/messages", {
|
|
5051
|
+
maxResults: input.maxResults ?? input.max ?? 50,
|
|
5052
|
+
pageToken: input.pageToken,
|
|
5053
|
+
q: input.q ?? input.query,
|
|
5054
|
+
labelIds: labelIds.length > 0 ? labelIds.join(",") : undefined,
|
|
5055
|
+
includeSpamTrash: input.includeSpamTrash
|
|
5056
|
+
});
|
|
5057
|
+
}
|
|
5058
|
+
},
|
|
5059
|
+
"messages.read": {
|
|
5060
|
+
summary: "Read a Gmail message with optional extracted body.",
|
|
5061
|
+
inputSchema: readMessageSchema,
|
|
5062
|
+
execute: async ({ context }, input) => {
|
|
5063
|
+
const messageId = getMessageId(input);
|
|
5064
|
+
const message = await requestJson(context.profile, `/users/me/messages/${encodeURIComponent(messageId)}`, { format: input.format ?? "full" });
|
|
5065
|
+
const headers = headersToObject(message.payload?.headers ?? []);
|
|
5066
|
+
return {
|
|
5067
|
+
...message,
|
|
5068
|
+
from: headers.From ?? headers.from ?? "",
|
|
5069
|
+
to: headers.To ?? headers.to ?? "",
|
|
5070
|
+
cc: headers.Cc ?? headers.cc ?? "",
|
|
5071
|
+
subject: headers.Subject ?? headers.subject ?? "",
|
|
5072
|
+
date: headers.Date ?? headers.date ?? "",
|
|
5073
|
+
body: input.body ? extractBody(message, Boolean(input.html)) : undefined,
|
|
5074
|
+
size: message.sizeEstimate
|
|
5075
|
+
};
|
|
5076
|
+
}
|
|
5077
|
+
},
|
|
5078
|
+
"messages.getRaw": {
|
|
5079
|
+
summary: "Read raw base64url Gmail message content.",
|
|
5080
|
+
inputSchema: messageIdSchema,
|
|
5081
|
+
execute: async ({ context }, input) => {
|
|
5082
|
+
const messageId = getMessageId(input);
|
|
5083
|
+
return requestJson(context.profile, `/users/me/messages/${encodeURIComponent(messageId)}`, { format: "raw" });
|
|
5084
|
+
}
|
|
5085
|
+
},
|
|
5086
|
+
"messages.mark-read": {
|
|
5087
|
+
summary: "Mark a message as read.",
|
|
5088
|
+
inputSchema: messageIdSchema,
|
|
5089
|
+
execute: async ({ context }, input) => modifyMessage(context.profile, getMessageId(input), undefined, ["UNREAD"])
|
|
5090
|
+
},
|
|
5091
|
+
"messages.mark-unread": {
|
|
5092
|
+
summary: "Mark a message as unread.",
|
|
5093
|
+
inputSchema: messageIdSchema,
|
|
5094
|
+
execute: async ({ context }, input) => modifyMessage(context.profile, getMessageId(input), ["UNREAD"], undefined)
|
|
5095
|
+
},
|
|
5096
|
+
"messages.archive": {
|
|
5097
|
+
summary: "Archive a message by removing the INBOX label.",
|
|
5098
|
+
inputSchema: messageIdSchema,
|
|
5099
|
+
execute: async ({ context }, input) => modifyMessage(context.profile, getMessageId(input), undefined, ["INBOX"])
|
|
5100
|
+
},
|
|
5101
|
+
"messages.star": {
|
|
5102
|
+
summary: "Star a message.",
|
|
5103
|
+
inputSchema: messageIdSchema,
|
|
5104
|
+
execute: async ({ context }, input) => modifyMessage(context.profile, getMessageId(input), ["STARRED"], undefined)
|
|
5105
|
+
},
|
|
5106
|
+
"messages.reply": {
|
|
5107
|
+
summary: "Reply to a Gmail message in the same thread.",
|
|
5108
|
+
inputSchema: replySchema,
|
|
5109
|
+
execute: async ({ context }, input) => replyToMessage(context.profile, getMessageId(input), input)
|
|
5110
|
+
},
|
|
5111
|
+
"attachments.list": {
|
|
5112
|
+
summary: "List Gmail message attachments.",
|
|
5113
|
+
inputSchema: attachmentListSchema,
|
|
5114
|
+
execute: async ({ context }, input) => {
|
|
5115
|
+
const message = await requestJson(context.profile, `/users/me/messages/${encodeURIComponent(getMessageId(input))}`, { format: "full" });
|
|
5116
|
+
return collectAttachments(message.payload);
|
|
5117
|
+
}
|
|
5118
|
+
},
|
|
5119
|
+
"attachments.download": {
|
|
5120
|
+
summary: "Download one or all Gmail message attachments to disk.",
|
|
5121
|
+
inputSchema: attachmentDownloadSchema,
|
|
5122
|
+
execute: async ({ context }, input) => downloadAttachments(context.profile, input)
|
|
5123
|
+
},
|
|
5124
|
+
"labels.list": {
|
|
5125
|
+
summary: "List Gmail labels.",
|
|
5126
|
+
execute: async ({ context }) => requestJson(context.profile, "/users/me/labels", {})
|
|
5127
|
+
},
|
|
5128
|
+
"history.list": {
|
|
5129
|
+
summary: "List Gmail mailbox history from a history id.",
|
|
5130
|
+
inputSchema: historyListSchema,
|
|
5131
|
+
execute: async ({ context }, input) => requestJson(context.profile, "/users/me/history", {
|
|
5132
|
+
startHistoryId: input.startHistoryId,
|
|
5133
|
+
historyTypes: normalizeStringArray(input.historyTypes).join(",") || undefined,
|
|
5134
|
+
labelId: input.labelId,
|
|
5135
|
+
maxResults: input.maxResults,
|
|
5136
|
+
pageToken: input.pageToken
|
|
5137
|
+
})
|
|
5138
|
+
}
|
|
5139
|
+
}
|
|
5140
|
+
});
|
|
5141
|
+
async function modifyMessage(profile, messageId, addLabelIds, removeLabelIds) {
|
|
5142
|
+
return requestJson(profile, `/users/me/messages/${encodeURIComponent(messageId)}/modify`, {}, {
|
|
5143
|
+
method: "POST",
|
|
5144
|
+
body: {
|
|
5145
|
+
addLabelIds: addLabelIds ?? [],
|
|
5146
|
+
removeLabelIds: removeLabelIds ?? []
|
|
5147
|
+
}
|
|
5148
|
+
});
|
|
5149
|
+
}
|
|
5150
|
+
async function replyToMessage(profile, messageId, input) {
|
|
5151
|
+
const original = await requestJson(profile, `/users/me/messages/${encodeURIComponent(messageId)}`, { format: "full" });
|
|
5152
|
+
const headers = headersToObject(original.payload?.headers ?? []);
|
|
5153
|
+
const subject = normalizeReplySubject(headers.Subject ?? headers.subject ?? "");
|
|
5154
|
+
const to = headers.From ?? headers.from ?? "";
|
|
5155
|
+
const messageIdHeader = headers["Message-ID"] ?? headers["Message-Id"] ?? headers["message-id"] ?? "";
|
|
5156
|
+
const references = [headers.References ?? headers.references, messageIdHeader].filter(Boolean).join(" ");
|
|
5157
|
+
const raw = buildRawEmail({
|
|
5158
|
+
to,
|
|
5159
|
+
cc: normalizeStringArray(input.cc),
|
|
5160
|
+
bcc: normalizeStringArray(input.bcc),
|
|
5161
|
+
subject,
|
|
5162
|
+
body: input.body,
|
|
5163
|
+
isHtml: Boolean(input.html ?? input.isHtml),
|
|
5164
|
+
inReplyTo: messageIdHeader,
|
|
5165
|
+
references
|
|
5166
|
+
});
|
|
5167
|
+
return requestJson(profile, "/users/me/messages/send", {}, {
|
|
5168
|
+
method: "POST",
|
|
5169
|
+
body: {
|
|
5170
|
+
raw: Buffer.from(raw).toString("base64url"),
|
|
5171
|
+
threadId: original.threadId
|
|
5172
|
+
}
|
|
5173
|
+
});
|
|
5174
|
+
}
|
|
5175
|
+
async function downloadAttachments(profile, input) {
|
|
5176
|
+
const messageId = getMessageId(input);
|
|
5177
|
+
const outputDir = input.dir ?? input.outputDir ?? join2(configDirs()[0], "attachments", messageId);
|
|
5178
|
+
mkdirSync(outputDir, { recursive: true });
|
|
5179
|
+
const attachments = input.attachmentId && input.filename ? [{
|
|
5180
|
+
attachmentId: input.attachmentId,
|
|
5181
|
+
filename: input.filename,
|
|
5182
|
+
mimeType: input.mimeType ?? "application/octet-stream",
|
|
5183
|
+
size: 0
|
|
5184
|
+
}] : collectAttachments((await requestJson(profile, `/users/me/messages/${encodeURIComponent(messageId)}`, { format: "full" })).payload);
|
|
5185
|
+
const downloaded = [];
|
|
5186
|
+
for (const attachment of attachments) {
|
|
5187
|
+
const data = await requestJson(profile, `/users/me/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachment.attachmentId)}`, {});
|
|
5188
|
+
const filename = safeFilename(attachment.filename);
|
|
5189
|
+
const path = join2(outputDir, filename);
|
|
5190
|
+
const buffer = Buffer.from(data.data, "base64url");
|
|
5191
|
+
writeFileSync(path, buffer);
|
|
5192
|
+
downloaded.push({
|
|
5193
|
+
filename,
|
|
5194
|
+
path,
|
|
5195
|
+
size: buffer.length,
|
|
5196
|
+
mimeType: attachment.mimeType
|
|
5197
|
+
});
|
|
5198
|
+
}
|
|
5199
|
+
return downloaded;
|
|
5200
|
+
}
|
|
5201
|
+
async function requestJson(profile, path, params, options = {}) {
|
|
5202
|
+
const token = await getValidAccessToken(profile);
|
|
5203
|
+
const url = new URL(`${GMAIL_API_BASE}${path}`);
|
|
5204
|
+
for (const [key, value] of Object.entries(params)) {
|
|
5205
|
+
if (value !== undefined && value !== null && value !== "")
|
|
5206
|
+
url.searchParams.append(key, String(value));
|
|
5207
|
+
}
|
|
5208
|
+
let lastError;
|
|
5209
|
+
for (let attempt = 0;attempt <= MAX_GMAIL_RETRIES; attempt++) {
|
|
5210
|
+
let response;
|
|
5211
|
+
try {
|
|
5212
|
+
response = await fetch(url, {
|
|
5213
|
+
method: options.method ?? "GET",
|
|
5214
|
+
headers: {
|
|
5215
|
+
Authorization: `Bearer ${token}`,
|
|
5216
|
+
Accept: "application/json",
|
|
5217
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
5218
|
+
},
|
|
5219
|
+
body: options.body ? JSON.stringify(options.body) : undefined
|
|
5220
|
+
});
|
|
5221
|
+
} catch (error2) {
|
|
5222
|
+
lastError = error2 instanceof Error ? error2.message : String(error2);
|
|
5223
|
+
if (attempt >= MAX_GMAIL_RETRIES)
|
|
5224
|
+
throw error2;
|
|
5225
|
+
await sleep(gmailBackoffDelayMs(attempt));
|
|
5226
|
+
continue;
|
|
5227
|
+
}
|
|
5228
|
+
const text = await response.text();
|
|
5229
|
+
const data = text ? JSON.parse(text) : {};
|
|
5230
|
+
if (response.ok)
|
|
5231
|
+
return data;
|
|
5232
|
+
const error = data;
|
|
5233
|
+
lastError = error.error?.message ?? response.statusText;
|
|
5234
|
+
if (!isRetryableGmailResponse(response.status, error) || attempt >= MAX_GMAIL_RETRIES) {
|
|
5235
|
+
throw new Error(`Gmail request failed (${response.status}): ${lastError}`);
|
|
5236
|
+
}
|
|
5237
|
+
await sleep(gmailBackoffDelayMs(attempt, response.headers.get("retry-after")));
|
|
5238
|
+
}
|
|
5239
|
+
throw new Error(`Gmail request failed: ${lastError ?? "unknown error"}`);
|
|
5240
|
+
}
|
|
5241
|
+
function isRetryableGmailResponse(status, error) {
|
|
5242
|
+
if ([429, 500, 502, 503, 504].includes(status))
|
|
5243
|
+
return true;
|
|
5244
|
+
const reasons = error.error?.errors?.map((entry) => entry.reason) ?? [];
|
|
5245
|
+
return reasons.some((reason) => reason === "rateLimitExceeded" || reason === "userRateLimitExceeded");
|
|
5246
|
+
}
|
|
5247
|
+
function gmailBackoffDelayMs(attempt, retryAfter = null) {
|
|
5248
|
+
if (retryAfter) {
|
|
5249
|
+
const seconds = Number(retryAfter);
|
|
5250
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
5251
|
+
return seconds * 1000;
|
|
5252
|
+
}
|
|
5253
|
+
const base = Number(process.env.CONNECTORS_GMAIL_RETRY_BASE_MS ?? "1000");
|
|
5254
|
+
const baseMs = Number.isFinite(base) && base >= 0 ? base : 1000;
|
|
5255
|
+
const jitterMs = Math.floor(Math.random() * 1000);
|
|
5256
|
+
return Math.min(2 ** attempt * baseMs + jitterMs, 64000);
|
|
5257
|
+
}
|
|
5258
|
+
function sleep(ms) {
|
|
5259
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5260
|
+
}
|
|
5261
|
+
async function getValidAccessToken(profile) {
|
|
5262
|
+
if (process.env.GMAIL_ACCESS_TOKEN)
|
|
5263
|
+
return process.env.GMAIL_ACCESS_TOKEN;
|
|
5264
|
+
const tokens = loadTokens(profile);
|
|
5265
|
+
if (!tokens?.accessToken && !tokens?.refreshToken) {
|
|
5266
|
+
throw new Error(`Gmail profile "${profile}" is not authenticated. Run: connectors auth gmail`);
|
|
5267
|
+
}
|
|
5268
|
+
if (tokens.accessToken && (!tokens.expiresAt || Date.now() < tokens.expiresAt - REFRESH_BUFFER_MS))
|
|
5269
|
+
return tokens.accessToken;
|
|
5270
|
+
if (!tokens.refreshToken)
|
|
5271
|
+
return tokens.accessToken ?? "";
|
|
5272
|
+
return (await refreshAccessToken(profile, tokens)).accessToken ?? "";
|
|
5273
|
+
}
|
|
5274
|
+
async function refreshAccessToken(profile, currentTokens) {
|
|
5275
|
+
const credentials = loadCredentials(profile);
|
|
5276
|
+
if (!credentials.clientId || !credentials.clientSecret)
|
|
5277
|
+
throw new Error("Gmail OAuth credentials are not configured. Run: connectors auth gmail");
|
|
5278
|
+
if (!currentTokens.refreshToken)
|
|
5279
|
+
throw new Error(`Gmail profile "${profile}" has no refresh token. Run: connectors auth gmail`);
|
|
5280
|
+
const response = await fetch(TOKEN_URL, {
|
|
5281
|
+
method: "POST",
|
|
5282
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
5283
|
+
body: new URLSearchParams({
|
|
5284
|
+
client_id: credentials.clientId,
|
|
5285
|
+
client_secret: credentials.clientSecret,
|
|
5286
|
+
refresh_token: currentTokens.refreshToken,
|
|
5287
|
+
grant_type: "refresh_token"
|
|
5288
|
+
})
|
|
5289
|
+
});
|
|
5290
|
+
const data = await response.json().catch(() => ({}));
|
|
5291
|
+
if (!response.ok || !data.access_token)
|
|
5292
|
+
throw new Error(`Gmail token refresh failed: ${data.error_description || data.error || response.statusText}`);
|
|
5293
|
+
const tokens = {
|
|
5294
|
+
accessToken: data.access_token,
|
|
5295
|
+
refreshToken: currentTokens.refreshToken,
|
|
5296
|
+
expiresAt: Date.now() + (data.expires_in ?? 3600) * 1000,
|
|
5297
|
+
tokenType: data.token_type ?? currentTokens.tokenType,
|
|
5298
|
+
scope: data.scope ?? currentTokens.scope
|
|
5299
|
+
};
|
|
5300
|
+
saveTokens(profile, tokens);
|
|
5301
|
+
return tokens;
|
|
5302
|
+
}
|
|
5303
|
+
function listProfiles() {
|
|
5304
|
+
const profiles = new Set;
|
|
5305
|
+
for (const baseDir of configDirs()) {
|
|
5306
|
+
const profilesDir = join2(baseDir, "profiles");
|
|
5307
|
+
if (!existsSync2(profilesDir))
|
|
5308
|
+
continue;
|
|
5309
|
+
for (const entry of readdirSync(profilesDir, { withFileTypes: true })) {
|
|
5310
|
+
if (entry.isDirectory())
|
|
5311
|
+
profiles.add(entry.name);
|
|
5312
|
+
if (entry.isFile() && entry.name.endsWith(".json"))
|
|
5313
|
+
profiles.add(basename(entry.name, ".json"));
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
return Array.from(profiles).sort((a, b) => a.localeCompare(b));
|
|
5317
|
+
}
|
|
5318
|
+
function loadCredentials(profile) {
|
|
5319
|
+
const envClientId = process.env.GMAIL_CLIENT_ID ?? process.env.GOOGLE_CLIENT_ID;
|
|
5320
|
+
const envClientSecret = process.env.GMAIL_CLIENT_SECRET ?? process.env.GOOGLE_CLIENT_SECRET;
|
|
5321
|
+
if (envClientId && envClientSecret)
|
|
5322
|
+
return { clientId: envClientId, clientSecret: envClientSecret };
|
|
5323
|
+
for (const baseDir of configDirs()) {
|
|
5324
|
+
const credentials = {
|
|
5325
|
+
...readJson(join2(baseDir, "credentials.json")),
|
|
5326
|
+
...readJson(join2(baseDir, "profiles", profile, "config.json"))
|
|
5327
|
+
};
|
|
5328
|
+
if (credentials.clientId || credentials.clientSecret)
|
|
5329
|
+
return credentials;
|
|
5330
|
+
}
|
|
5331
|
+
return {};
|
|
5332
|
+
}
|
|
5333
|
+
function loadTokens(profile) {
|
|
5334
|
+
for (const baseDir of configDirs()) {
|
|
5335
|
+
const fromProfile = readJson(join2(baseDir, "profiles", profile, "tokens.json"));
|
|
5336
|
+
if (fromProfile)
|
|
5337
|
+
return fromProfile;
|
|
5338
|
+
const flat = readJson(join2(baseDir, "profiles", `${profile}.json`));
|
|
5339
|
+
if (flat)
|
|
5340
|
+
return flat.tokens ?? (flat.accessToken || flat.refreshToken ? flat : null);
|
|
5341
|
+
}
|
|
5342
|
+
return null;
|
|
5343
|
+
}
|
|
5344
|
+
function saveTokens(profile, tokens) {
|
|
5345
|
+
const baseDir = configDirs().find((dir) => existsSync2(dir)) ?? configDirs()[0];
|
|
5346
|
+
const profileDir = join2(baseDir, "profiles", profile);
|
|
5347
|
+
mkdirSync(profileDir, { recursive: true });
|
|
5348
|
+
writeFileSync(join2(profileDir, "tokens.json"), JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
5349
|
+
}
|
|
5350
|
+
function configDirs() {
|
|
5351
|
+
const explicit = process.env.HASNA_GMAIL_CONNECTOR_DIR ?? process.env.GMAIL_CONNECTOR_DIR;
|
|
5352
|
+
if (explicit)
|
|
5353
|
+
return [explicit];
|
|
5354
|
+
const baseDir = process.env.HASNA_CONNECTORS_DIR ?? join2(homedir(), ".hasna", "connectors");
|
|
5355
|
+
return [join2(baseDir, "gmail"), join2(baseDir, "connect-gmail")];
|
|
5356
|
+
}
|
|
5357
|
+
function readJson(path) {
|
|
5358
|
+
if (!existsSync2(path))
|
|
5359
|
+
return null;
|
|
5360
|
+
try {
|
|
5361
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
5362
|
+
} catch {
|
|
5363
|
+
return null;
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5366
|
+
function getMessageId(input) {
|
|
5367
|
+
const id = input.messageId ?? (input.args?.[0] != null ? String(input.args[0]) : undefined);
|
|
5368
|
+
if (!id)
|
|
5369
|
+
throw new Error("Gmail messageId is required");
|
|
5370
|
+
return id;
|
|
5371
|
+
}
|
|
5372
|
+
function headersToObject(headers) {
|
|
5373
|
+
const out = {};
|
|
5374
|
+
for (const header of headers)
|
|
5375
|
+
out[header.name] = header.value;
|
|
5376
|
+
return out;
|
|
5377
|
+
}
|
|
5378
|
+
function extractBody(message, preferHtml = false) {
|
|
5379
|
+
if (!message.payload)
|
|
5380
|
+
return "";
|
|
5381
|
+
const targetType = preferHtml ? "text/html" : "text/plain";
|
|
5382
|
+
const parts = [];
|
|
5383
|
+
collectTextParts(message.payload, parts);
|
|
5384
|
+
return parts.find((part) => part.mimeType === targetType)?.data ?? parts.find((part) => part.mimeType.startsWith("text/"))?.data ?? "";
|
|
5385
|
+
}
|
|
5386
|
+
function collectTextParts(part, results) {
|
|
5387
|
+
const mimeType = (part.mimeType ?? "").split(";")[0].trim().toLowerCase();
|
|
5388
|
+
if (part.body?.data && mimeType.startsWith("text/")) {
|
|
5389
|
+
results.push({ mimeType, data: Buffer.from(part.body.data, "base64url").toString("utf8") });
|
|
5390
|
+
}
|
|
5391
|
+
for (const child of part.parts ?? [])
|
|
5392
|
+
collectTextParts(child, results);
|
|
5393
|
+
}
|
|
5394
|
+
function collectAttachments(part, attachments = []) {
|
|
5395
|
+
if (!part)
|
|
5396
|
+
return attachments;
|
|
5397
|
+
if (part.body?.attachmentId && part.filename) {
|
|
5398
|
+
attachments.push({
|
|
5399
|
+
attachmentId: part.body.attachmentId,
|
|
5400
|
+
filename: part.filename,
|
|
5401
|
+
mimeType: part.mimeType ?? "application/octet-stream",
|
|
5402
|
+
size: part.body.size ?? 0,
|
|
5403
|
+
partId: part.partId
|
|
5404
|
+
});
|
|
5405
|
+
}
|
|
5406
|
+
for (const child of part.parts ?? [])
|
|
5407
|
+
collectAttachments(child, attachments);
|
|
5408
|
+
return attachments;
|
|
5409
|
+
}
|
|
5410
|
+
function normalizeStringArray(value) {
|
|
5411
|
+
if (!value)
|
|
5412
|
+
return [];
|
|
5413
|
+
return Array.isArray(value) ? value : value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
5414
|
+
}
|
|
5415
|
+
function normalizeReplySubject(subject) {
|
|
5416
|
+
return subject.toLowerCase().startsWith("re:") ? subject : `Re: ${subject}`;
|
|
5417
|
+
}
|
|
5418
|
+
function buildRawEmail(input) {
|
|
5419
|
+
const headers = [
|
|
5420
|
+
`To: ${input.to}`,
|
|
5421
|
+
input.cc.length ? `Cc: ${input.cc.join(", ")}` : "",
|
|
5422
|
+
input.bcc.length ? `Bcc: ${input.bcc.join(", ")}` : "",
|
|
5423
|
+
`Subject: ${input.subject}`,
|
|
5424
|
+
input.inReplyTo ? `In-Reply-To: ${input.inReplyTo}` : "",
|
|
5425
|
+
input.references ? `References: ${input.references}` : "",
|
|
5426
|
+
"MIME-Version: 1.0",
|
|
5427
|
+
`Content-Type: ${input.isHtml ? "text/html" : "text/plain"}; charset=UTF-8`
|
|
5428
|
+
].filter(Boolean);
|
|
5429
|
+
return `${headers.join(`\r
|
|
5430
|
+
`)}\r
|
|
5431
|
+
\r
|
|
5432
|
+
${input.body}`;
|
|
5433
|
+
}
|
|
5434
|
+
function safeFilename(filename) {
|
|
5435
|
+
return basename(filename.replace(/[\u00A0\u2000-\u200B\u202F\u205F\u3000]/g, " ")).replace(/[\/\\]/g, "_");
|
|
5436
|
+
}
|
|
5437
|
+
|
|
5438
|
+
// src/core/connectors/googledrive.ts
|
|
5439
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
5440
|
+
import { homedir as homedir2 } from "os";
|
|
5441
|
+
import { basename as basename2, join as join3 } from "path";
|
|
5442
|
+
var DRIVE_API_BASE = "https://www.googleapis.com/drive/v3";
|
|
5443
|
+
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
5444
|
+
var REFRESH_BUFFER_MS2 = 5 * 60 * 1000;
|
|
5445
|
+
var DEFAULT_FILE_FIELDS = [
|
|
5446
|
+
"id",
|
|
5447
|
+
"name",
|
|
5448
|
+
"mimeType",
|
|
5449
|
+
"parents",
|
|
5450
|
+
"version",
|
|
5451
|
+
"md5Checksum",
|
|
5452
|
+
"size",
|
|
5453
|
+
"modifiedTime",
|
|
5454
|
+
"createdTime",
|
|
5455
|
+
"trashed",
|
|
5456
|
+
"webViewLink",
|
|
5457
|
+
"webContentLink"
|
|
5458
|
+
].join(",");
|
|
5459
|
+
var DEFAULT_EXPORT_FORMATS = {
|
|
5460
|
+
document: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
5461
|
+
spreadsheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
5462
|
+
presentation: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
5463
|
+
drawing: "image/png"
|
|
5464
|
+
};
|
|
5465
|
+
var EXPORT_EXTENSIONS = {
|
|
5466
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
|
|
5467
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
|
|
5468
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
|
|
5469
|
+
"application/pdf": ".pdf",
|
|
5470
|
+
"text/plain": ".txt",
|
|
5471
|
+
"text/csv": ".csv",
|
|
5472
|
+
"image/png": ".png",
|
|
5473
|
+
"image/jpeg": ".jpg",
|
|
5474
|
+
"image/svg+xml": ".svg"
|
|
5475
|
+
};
|
|
5476
|
+
var listFilesSchema = exports_external.object({
|
|
5477
|
+
pageSize: exports_external.coerce.number().int().positive().max(1000).optional(),
|
|
5478
|
+
pageToken: exports_external.string().optional(),
|
|
5479
|
+
q: exports_external.string().optional(),
|
|
5480
|
+
fields: exports_external.string().optional(),
|
|
5481
|
+
orderBy: exports_external.string().optional(),
|
|
5482
|
+
corpora: exports_external.enum(["user", "drive", "allDrives"]).optional(),
|
|
5483
|
+
driveId: exports_external.string().optional(),
|
|
5484
|
+
supportsAllDrives: exports_external.boolean().optional(),
|
|
5485
|
+
includeItemsFromAllDrives: exports_external.boolean().optional()
|
|
5486
|
+
});
|
|
5487
|
+
var listDrivesSchema = exports_external.object({
|
|
5488
|
+
pageSize: exports_external.coerce.number().int().positive().max(100).optional(),
|
|
5489
|
+
pageToken: exports_external.string().optional(),
|
|
5490
|
+
q: exports_external.string().optional()
|
|
5491
|
+
});
|
|
5492
|
+
var fileIdSchema = exports_external.object({
|
|
5493
|
+
fileId: exports_external.string(),
|
|
5494
|
+
fields: exports_external.string().optional()
|
|
5495
|
+
});
|
|
5496
|
+
var downloadSchema = exports_external.object({
|
|
5497
|
+
fileId: exports_external.string(),
|
|
5498
|
+
file: exports_external.object({
|
|
5499
|
+
id: exports_external.string(),
|
|
5500
|
+
name: exports_external.string(),
|
|
5501
|
+
mimeType: exports_external.string()
|
|
5502
|
+
}).optional(),
|
|
5503
|
+
exportMimeType: exports_external.string().optional()
|
|
5504
|
+
});
|
|
5505
|
+
var profilesStatusSchema = exports_external.object({
|
|
5506
|
+
profile: exports_external.string().optional()
|
|
5507
|
+
});
|
|
5508
|
+
var googleDriveConnector = defineConnector({
|
|
5509
|
+
meta: {
|
|
5510
|
+
name: "googledrive",
|
|
5511
|
+
displayName: "Google Drive",
|
|
5512
|
+
description: "Profile-aware Google Drive discovery and file download operations.",
|
|
5513
|
+
category: "storage",
|
|
5514
|
+
tags: ["google", "drive", "files", "storage"]
|
|
5515
|
+
},
|
|
5516
|
+
auth: {
|
|
5517
|
+
type: "oauth2",
|
|
5518
|
+
supportsProfiles: true,
|
|
5519
|
+
fields: [
|
|
5520
|
+
{ key: "clientId", env: "GOOGLE_CLIENT_ID", label: "OAuth client ID" },
|
|
5521
|
+
{ key: "clientSecret", env: "GOOGLE_CLIENT_SECRET", label: "OAuth client secret", secret: true }
|
|
5522
|
+
]
|
|
5523
|
+
},
|
|
5524
|
+
createContext: ({ profile }) => ({ profile: profile || "default" }),
|
|
5525
|
+
operations: {
|
|
5526
|
+
"profiles.list": {
|
|
5527
|
+
summary: "List configured Google Drive profiles.",
|
|
5528
|
+
execute: () => ({ profiles: listProfiles2() })
|
|
5529
|
+
},
|
|
5530
|
+
"profiles.status": {
|
|
5531
|
+
summary: "List Google Drive profile authentication status.",
|
|
5532
|
+
inputSchema: profilesStatusSchema,
|
|
5533
|
+
execute: (_ctx, input) => ({ profiles: listProfileStatuses(input.profile) })
|
|
5534
|
+
},
|
|
5535
|
+
"files.list": {
|
|
5536
|
+
summary: "List Google Drive files.",
|
|
5537
|
+
inputSchema: listFilesSchema,
|
|
5538
|
+
execute: async ({ context }, input) => requestJson2(context.profile, "/files", {
|
|
5539
|
+
pageSize: input.pageSize ?? 1000,
|
|
5540
|
+
pageToken: input.pageToken,
|
|
5541
|
+
q: input.q,
|
|
5542
|
+
fields: input.fields ?? `nextPageToken,files(${DEFAULT_FILE_FIELDS})`,
|
|
5543
|
+
orderBy: input.orderBy ?? "modifiedTime desc",
|
|
5544
|
+
corpora: input.corpora,
|
|
5545
|
+
driveId: input.driveId,
|
|
5546
|
+
supportsAllDrives: input.supportsAllDrives ?? true,
|
|
5547
|
+
includeItemsFromAllDrives: input.includeItemsFromAllDrives ?? false
|
|
5548
|
+
})
|
|
5549
|
+
},
|
|
5550
|
+
"files.get": {
|
|
5551
|
+
summary: "Get Google Drive file metadata.",
|
|
5552
|
+
inputSchema: fileIdSchema,
|
|
5553
|
+
execute: async ({ context }, input) => requestJson2(context.profile, `/files/${encodeURIComponent(input.fileId)}`, {
|
|
5554
|
+
fields: input.fields ?? DEFAULT_FILE_FIELDS,
|
|
5555
|
+
supportsAllDrives: true
|
|
5556
|
+
})
|
|
5557
|
+
},
|
|
5558
|
+
"files.download": {
|
|
5559
|
+
summary: "Download or export a Google Drive file as base64 content.",
|
|
5560
|
+
inputSchema: downloadSchema,
|
|
5561
|
+
execute: async ({ context }, input) => {
|
|
5562
|
+
const file = input.file ?? await requestJson2(context.profile, `/files/${encodeURIComponent(input.fileId)}`, { fields: DEFAULT_FILE_FIELDS, supportsAllDrives: true });
|
|
5563
|
+
const exportMimeType = file.mimeType.startsWith("application/vnd.google-apps.") ? input.exportMimeType ?? defaultExportMimeType(file.mimeType) : undefined;
|
|
5564
|
+
const data = await requestBinary(context.profile, exportMimeType ? `/files/${encodeURIComponent(file.id)}/export` : `/files/${encodeURIComponent(file.id)}`, exportMimeType ? { mimeType: exportMimeType, supportsAllDrives: true } : { alt: "media", supportsAllDrives: true });
|
|
5565
|
+
const mimeType = exportMimeType ?? file.mimeType ?? "application/octet-stream";
|
|
5566
|
+
return {
|
|
5567
|
+
dataBase64: Buffer.from(data).toString("base64"),
|
|
5568
|
+
filename: exportMimeType ? `${file.name}${extensionForMimeType(exportMimeType)}` : file.name,
|
|
5569
|
+
mimeType
|
|
5570
|
+
};
|
|
5571
|
+
}
|
|
5572
|
+
},
|
|
5573
|
+
"drives.list": {
|
|
5574
|
+
summary: "List Google shared drives.",
|
|
5575
|
+
inputSchema: listDrivesSchema,
|
|
5576
|
+
execute: async ({ context }, input) => requestJson2(context.profile, "/drives", {
|
|
5577
|
+
pageSize: input.pageSize ?? 100,
|
|
5578
|
+
pageToken: input.pageToken,
|
|
5579
|
+
q: input.q
|
|
5580
|
+
})
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
});
|
|
5584
|
+
async function requestJson2(profile, path, params) {
|
|
5585
|
+
const response = await request(profile, path, params);
|
|
5586
|
+
const text = await response.text();
|
|
5587
|
+
return text ? JSON.parse(text) : {};
|
|
5588
|
+
}
|
|
5589
|
+
async function requestBinary(profile, path, params) {
|
|
5590
|
+
return (await request(profile, path, params)).arrayBuffer();
|
|
5591
|
+
}
|
|
5592
|
+
async function request(profile, path, params) {
|
|
5593
|
+
const token = await getValidAccessToken2(profile);
|
|
5594
|
+
const url = new URL(`${DRIVE_API_BASE}${path}`);
|
|
5595
|
+
for (const [key, value] of Object.entries(params)) {
|
|
5596
|
+
if (value !== undefined && value !== null && value !== "")
|
|
5597
|
+
url.searchParams.set(key, String(value));
|
|
5598
|
+
}
|
|
5599
|
+
const response = await fetch(url, { headers: { Authorization: `Bearer ${token}`, Accept: "application/json" } });
|
|
5600
|
+
if (!response.ok) {
|
|
5601
|
+
const body = await response.text().catch(() => "");
|
|
5602
|
+
throw new Error(`Google Drive request failed (${response.status}): ${extractGoogleError(body) || response.statusText}`);
|
|
5603
|
+
}
|
|
5604
|
+
return response;
|
|
5605
|
+
}
|
|
5606
|
+
async function getValidAccessToken2(profile) {
|
|
5607
|
+
if (process.env.GOOGLE_ACCESS_TOKEN)
|
|
5608
|
+
return process.env.GOOGLE_ACCESS_TOKEN;
|
|
5609
|
+
const tokens = loadTokens2(profile);
|
|
5610
|
+
if (!tokens?.accessToken && !tokens?.refreshToken) {
|
|
5611
|
+
throw new Error(`Google Drive profile "${profile}" is not authenticated. Run: connectors auth googledrive`);
|
|
5612
|
+
}
|
|
5613
|
+
if (tokens.accessToken && (!tokens.expiresAt || Date.now() < tokens.expiresAt - REFRESH_BUFFER_MS2))
|
|
5614
|
+
return tokens.accessToken;
|
|
5615
|
+
if (!tokens.refreshToken)
|
|
5616
|
+
return tokens.accessToken ?? "";
|
|
5617
|
+
return (await refreshAccessToken2(profile, tokens)).accessToken ?? "";
|
|
5618
|
+
}
|
|
5619
|
+
async function refreshAccessToken2(profile, currentTokens) {
|
|
5620
|
+
const credentials = loadCredentials2(profile);
|
|
5621
|
+
if (!credentials.clientId || !credentials.clientSecret)
|
|
5622
|
+
throw new Error("Google Drive OAuth credentials are not configured. Run: connectors auth googledrive");
|
|
5623
|
+
if (!currentTokens.refreshToken)
|
|
5624
|
+
throw new Error(`Google Drive profile "${profile}" has no refresh token. Run: connectors auth googledrive`);
|
|
5625
|
+
const response = await fetch(TOKEN_URL2, {
|
|
5626
|
+
method: "POST",
|
|
5627
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
5628
|
+
body: new URLSearchParams({
|
|
5629
|
+
client_id: credentials.clientId,
|
|
5630
|
+
client_secret: credentials.clientSecret,
|
|
5631
|
+
refresh_token: currentTokens.refreshToken,
|
|
5632
|
+
grant_type: "refresh_token"
|
|
5633
|
+
})
|
|
5634
|
+
});
|
|
5635
|
+
const data = await response.json().catch(() => ({}));
|
|
5636
|
+
if (!response.ok || !data.access_token)
|
|
5637
|
+
throw new Error(`Google Drive token refresh failed: ${data.error_description || data.error || response.statusText}`);
|
|
5638
|
+
const tokens = {
|
|
5639
|
+
accessToken: data.access_token,
|
|
5640
|
+
refreshToken: currentTokens.refreshToken,
|
|
5641
|
+
expiresAt: Date.now() + (data.expires_in ?? 3600) * 1000,
|
|
5642
|
+
tokenType: data.token_type ?? currentTokens.tokenType,
|
|
5643
|
+
scope: data.scope ?? currentTokens.scope
|
|
5644
|
+
};
|
|
5645
|
+
saveTokens2(profile, tokens);
|
|
5646
|
+
return tokens;
|
|
5647
|
+
}
|
|
5648
|
+
function listProfiles2() {
|
|
5649
|
+
const profiles = new Set;
|
|
5650
|
+
for (const baseDir of configDirs2()) {
|
|
5651
|
+
const profilesDir = join3(baseDir, "profiles");
|
|
5652
|
+
if (!existsSync3(profilesDir))
|
|
5653
|
+
continue;
|
|
5654
|
+
for (const entry of readdirSync2(profilesDir, { withFileTypes: true })) {
|
|
5655
|
+
if (entry.isDirectory())
|
|
5656
|
+
profiles.add(entry.name);
|
|
5657
|
+
if (entry.isFile() && entry.name.endsWith(".json"))
|
|
5658
|
+
profiles.add(basename2(entry.name, ".json"));
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
return Array.from(profiles).sort((a, b) => a.localeCompare(b));
|
|
5662
|
+
}
|
|
5663
|
+
function listProfileStatuses(profile) {
|
|
5664
|
+
const profiles = profile ? [profile] : listProfiles2();
|
|
5665
|
+
const uniqueProfiles = profiles.length ? profiles : ["default"];
|
|
5666
|
+
const now = Date.now();
|
|
5667
|
+
return uniqueProfiles.map((name) => {
|
|
5668
|
+
const tokens = loadTokens2(name);
|
|
5669
|
+
const credentials = loadCredentials2(name);
|
|
5670
|
+
const hasAccessToken = Boolean(tokens?.accessToken || process.env.GOOGLE_ACCESS_TOKEN);
|
|
5671
|
+
const hasRefreshToken = Boolean(tokens?.refreshToken);
|
|
5672
|
+
const hasOAuthCredentials = Boolean(credentials.clientId && credentials.clientSecret);
|
|
5673
|
+
const expiresAt = tokens?.expiresAt ?? null;
|
|
5674
|
+
const expired = Boolean(expiresAt && now >= expiresAt - REFRESH_BUFFER_MS2);
|
|
5675
|
+
const authenticated = Boolean(process.env.GOOGLE_ACCESS_TOKEN || hasRefreshToken || hasAccessToken && !expired);
|
|
5676
|
+
const configured = authenticated || hasOAuthCredentials;
|
|
5677
|
+
const authRequired = !authenticated || expired && !hasRefreshToken;
|
|
5678
|
+
return {
|
|
5679
|
+
profile: name,
|
|
5680
|
+
configured,
|
|
5681
|
+
authenticated,
|
|
5682
|
+
expired,
|
|
5683
|
+
expiresAt,
|
|
5684
|
+
hasAccessToken,
|
|
5685
|
+
hasRefreshToken,
|
|
5686
|
+
hasOAuthCredentials,
|
|
5687
|
+
authRequired,
|
|
5688
|
+
message: authRequired ? `Google Drive profile "${name}" needs authentication. Run: connectors auth googledrive` : expired ? `Google Drive profile "${name}" access token is expired but can refresh.` : `Google Drive profile "${name}" is authenticated.`
|
|
5689
|
+
};
|
|
5690
|
+
});
|
|
5691
|
+
}
|
|
5692
|
+
function loadCredentials2(profile) {
|
|
5693
|
+
const envClientId = process.env.GOOGLE_CLIENT_ID;
|
|
5694
|
+
const envClientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
5695
|
+
if (envClientId && envClientSecret)
|
|
5696
|
+
return { clientId: envClientId, clientSecret: envClientSecret };
|
|
5697
|
+
for (const baseDir of configDirs2()) {
|
|
5698
|
+
const credentials = {
|
|
5699
|
+
...readJson2(join3(baseDir, "credentials.json")),
|
|
5700
|
+
...readJson2(join3(baseDir, "profiles", profile, "config.json"))
|
|
5701
|
+
};
|
|
5702
|
+
if (credentials.clientId || credentials.clientSecret)
|
|
5703
|
+
return credentials;
|
|
5704
|
+
}
|
|
5705
|
+
return {};
|
|
5706
|
+
}
|
|
5707
|
+
function loadTokens2(profile) {
|
|
5708
|
+
for (const baseDir of configDirs2()) {
|
|
5709
|
+
const fromProfile = readJson2(join3(baseDir, "profiles", profile, "tokens.json"));
|
|
5710
|
+
if (fromProfile)
|
|
5711
|
+
return fromProfile;
|
|
5712
|
+
const flat = readJson2(join3(baseDir, "profiles", `${profile}.json`));
|
|
5713
|
+
if (flat)
|
|
5714
|
+
return flat.tokens ?? (flat.accessToken || flat.refreshToken ? flat : null);
|
|
5715
|
+
}
|
|
5716
|
+
return null;
|
|
5717
|
+
}
|
|
5718
|
+
function saveTokens2(profile, tokens) {
|
|
5719
|
+
const baseDir = configDirs2().find((dir) => existsSync3(dir)) ?? configDirs2()[0];
|
|
5720
|
+
const profileDir = join3(baseDir, "profiles", profile);
|
|
5721
|
+
mkdirSync2(profileDir, { recursive: true });
|
|
5722
|
+
writeFileSync2(join3(profileDir, "tokens.json"), JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
5723
|
+
}
|
|
5724
|
+
function configDirs2() {
|
|
5725
|
+
const explicit = process.env.HASNA_GOOGLE_DRIVE_CONNECTOR_DIR ?? process.env.GOOGLE_DRIVE_CONNECTOR_DIR;
|
|
5726
|
+
if (explicit)
|
|
5727
|
+
return [explicit];
|
|
5728
|
+
const baseDir = process.env.HASNA_CONNECTORS_DIR ?? join3(homedir2(), ".hasna", "connectors");
|
|
5729
|
+
return [join3(baseDir, "googledrive"), join3(baseDir, "connect-googledrive")];
|
|
5730
|
+
}
|
|
5731
|
+
function readJson2(path) {
|
|
5732
|
+
if (!existsSync3(path))
|
|
5733
|
+
return null;
|
|
5734
|
+
try {
|
|
5735
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
5736
|
+
} catch {
|
|
5737
|
+
return null;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
function defaultExportMimeType(googleMimeType) {
|
|
5741
|
+
if (googleMimeType.endsWith(".document"))
|
|
5742
|
+
return DEFAULT_EXPORT_FORMATS.document;
|
|
5743
|
+
if (googleMimeType.endsWith(".spreadsheet"))
|
|
5744
|
+
return DEFAULT_EXPORT_FORMATS.spreadsheet;
|
|
5745
|
+
if (googleMimeType.endsWith(".presentation"))
|
|
5746
|
+
return DEFAULT_EXPORT_FORMATS.presentation;
|
|
5747
|
+
if (googleMimeType.endsWith(".drawing"))
|
|
5748
|
+
return DEFAULT_EXPORT_FORMATS.drawing;
|
|
5749
|
+
throw new Error(`Cannot export Google Workspace file type: ${googleMimeType}`);
|
|
5750
|
+
}
|
|
5751
|
+
function extensionForMimeType(mimeType) {
|
|
5752
|
+
return EXPORT_EXTENSIONS[mimeType] ?? "";
|
|
5753
|
+
}
|
|
5754
|
+
function extractGoogleError(body) {
|
|
5755
|
+
if (!body)
|
|
5756
|
+
return "";
|
|
5757
|
+
try {
|
|
5758
|
+
const parsed = JSON.parse(body);
|
|
5759
|
+
return parsed.error?.message ?? body;
|
|
5760
|
+
} catch {
|
|
5761
|
+
return body;
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
4969
5764
|
// package.json
|
|
4970
5765
|
var package_default = {
|
|
4971
5766
|
name: "@hasna/connectors",
|
|
4972
|
-
version: "1.3.
|
|
5767
|
+
version: "1.3.32",
|
|
4973
5768
|
description: "Open source connector library - Install API connectors with a single command",
|
|
4974
5769
|
type: "module",
|
|
4975
5770
|
bin: {
|
|
@@ -5057,35 +5852,39 @@ var package_default = {
|
|
|
5057
5852
|
|
|
5058
5853
|
// src/core/connectors/imessage.ts
|
|
5059
5854
|
import {
|
|
5060
|
-
existsSync as
|
|
5855
|
+
existsSync as existsSync7,
|
|
5061
5856
|
mkdirSync as mkdirSync5,
|
|
5062
|
-
readFileSync as
|
|
5063
|
-
readdirSync as
|
|
5857
|
+
readFileSync as readFileSync4,
|
|
5858
|
+
readdirSync as readdirSync6,
|
|
5064
5859
|
rmSync,
|
|
5065
|
-
statSync as
|
|
5066
|
-
writeFileSync as
|
|
5860
|
+
statSync as statSync3,
|
|
5861
|
+
writeFileSync as writeFileSync4
|
|
5067
5862
|
} from "fs";
|
|
5863
|
+
import { join as join9 } from "path";
|
|
5864
|
+
|
|
5865
|
+
// src/lib/connector-resolver.ts
|
|
5866
|
+
import { existsSync as existsSync6, readdirSync as readdirSync5, statSync as statSync2 } from "fs";
|
|
5068
5867
|
import { join as join7 } from "path";
|
|
5069
5868
|
|
|
5070
5869
|
// node_modules/@hasna/cloud/dist/index.js
|
|
5071
5870
|
import { createRequire } from "module";
|
|
5072
5871
|
import { Database } from "bun:sqlite";
|
|
5073
5872
|
import {
|
|
5074
|
-
existsSync as
|
|
5075
|
-
mkdirSync,
|
|
5076
|
-
readdirSync,
|
|
5873
|
+
existsSync as existsSync4,
|
|
5874
|
+
mkdirSync as mkdirSync3,
|
|
5875
|
+
readdirSync as readdirSync3,
|
|
5077
5876
|
copyFileSync
|
|
5078
5877
|
} from "fs";
|
|
5079
|
-
import { homedir } from "os";
|
|
5080
|
-
import { join as join2, relative } from "path";
|
|
5081
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
5082
|
-
import { homedir as homedir2 } from "os";
|
|
5083
|
-
import { join as join22 } from "path";
|
|
5084
|
-
import { readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
|
|
5085
|
-
import { join as join3 } from "path";
|
|
5086
5878
|
import { homedir as homedir3 } from "os";
|
|
5879
|
+
import { join as join4, relative } from "path";
|
|
5880
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5881
|
+
import { homedir as homedir22 } from "os";
|
|
5882
|
+
import { join as join22 } from "path";
|
|
5883
|
+
import { readdirSync as readdirSync22, existsSync as existsSync32 } from "fs";
|
|
5884
|
+
import { join as join32 } from "path";
|
|
5885
|
+
import { homedir as homedir32 } from "os";
|
|
5087
5886
|
import { homedir as homedir4 } from "os";
|
|
5088
|
-
import { join as
|
|
5887
|
+
import { join as join42 } from "path";
|
|
5089
5888
|
import { join as join6, dirname as dirname2 } from "path";
|
|
5090
5889
|
import { homedir as homedir5, platform } from "os";
|
|
5091
5890
|
var __create = Object.create;
|
|
@@ -14251,17 +15050,17 @@ var init_zod = __esm(() => {
|
|
|
14251
15050
|
init_external();
|
|
14252
15051
|
});
|
|
14253
15052
|
function getDataDir(serviceName) {
|
|
14254
|
-
const dir =
|
|
14255
|
-
|
|
15053
|
+
const dir = join4(HASNA_DIR, serviceName);
|
|
15054
|
+
mkdirSync3(dir, { recursive: true });
|
|
14256
15055
|
return dir;
|
|
14257
15056
|
}
|
|
14258
15057
|
function getDbPath(serviceName) {
|
|
14259
15058
|
const dir = getDataDir(serviceName);
|
|
14260
|
-
return
|
|
15059
|
+
return join4(dir, `${serviceName}.db`);
|
|
14261
15060
|
}
|
|
14262
15061
|
var HASNA_DIR;
|
|
14263
15062
|
var init_dotfile = __esm(() => {
|
|
14264
|
-
HASNA_DIR =
|
|
15063
|
+
HASNA_DIR = join4(homedir3(), ".hasna");
|
|
14265
15064
|
});
|
|
14266
15065
|
var exports_config = {};
|
|
14267
15066
|
__export2(exports_config, {
|
|
@@ -14284,15 +15083,15 @@ function getCloudConfig() {
|
|
|
14284
15083
|
return CloudConfigSchema.parse({});
|
|
14285
15084
|
}
|
|
14286
15085
|
try {
|
|
14287
|
-
const raw =
|
|
15086
|
+
const raw = readFileSync3(CONFIG_PATH, "utf-8");
|
|
14288
15087
|
return CloudConfigSchema.parse(JSON.parse(raw));
|
|
14289
15088
|
} catch {
|
|
14290
15089
|
return CloudConfigSchema.parse({});
|
|
14291
15090
|
}
|
|
14292
15091
|
}
|
|
14293
15092
|
function saveCloudConfig(config) {
|
|
14294
|
-
|
|
14295
|
-
|
|
15093
|
+
mkdirSync22(CONFIG_DIR, { recursive: true });
|
|
15094
|
+
writeFileSync3(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
14296
15095
|
`, "utf-8");
|
|
14297
15096
|
}
|
|
14298
15097
|
function getConnectionString(dbName) {
|
|
@@ -14340,7 +15139,7 @@ var init_config = __esm(() => {
|
|
|
14340
15139
|
schedule_minutes: exports_external2.number().default(0)
|
|
14341
15140
|
}).default({})
|
|
14342
15141
|
});
|
|
14343
|
-
CONFIG_DIR = join22(
|
|
15142
|
+
CONFIG_DIR = join22(homedir22(), ".hasna", "cloud");
|
|
14344
15143
|
CONFIG_PATH = join22(CONFIG_DIR, "config.json");
|
|
14345
15144
|
});
|
|
14346
15145
|
var exports_discover = {};
|
|
@@ -14356,11 +15155,11 @@ function isSyncExcludedTable(table) {
|
|
|
14356
15155
|
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
14357
15156
|
}
|
|
14358
15157
|
function discoverServices() {
|
|
14359
|
-
const dataDir =
|
|
14360
|
-
if (!
|
|
15158
|
+
const dataDir = join32(homedir32(), ".hasna");
|
|
15159
|
+
if (!existsSync32(dataDir))
|
|
14361
15160
|
return [];
|
|
14362
15161
|
try {
|
|
14363
|
-
const entries =
|
|
15162
|
+
const entries = readdirSync22(dataDir, { withFileTypes: true });
|
|
14364
15163
|
return entries.filter((e) => {
|
|
14365
15164
|
if (!e.isDirectory())
|
|
14366
15165
|
return false;
|
|
@@ -14378,24 +15177,24 @@ function discoverSyncableServices() {
|
|
|
14378
15177
|
return local.filter((s) => pgSet.has(s));
|
|
14379
15178
|
}
|
|
14380
15179
|
function getServiceDbPath(service) {
|
|
14381
|
-
const dataDir =
|
|
14382
|
-
if (!
|
|
15180
|
+
const dataDir = join32(homedir32(), ".hasna", service);
|
|
15181
|
+
if (!existsSync32(dataDir))
|
|
14383
15182
|
return null;
|
|
14384
15183
|
const candidates = [
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
15184
|
+
join32(dataDir, `${service}.db`),
|
|
15185
|
+
join32(dataDir, "data.db"),
|
|
15186
|
+
join32(dataDir, "database.db")
|
|
14388
15187
|
];
|
|
14389
15188
|
try {
|
|
14390
|
-
const files =
|
|
15189
|
+
const files = readdirSync22(dataDir);
|
|
14391
15190
|
for (const f of files) {
|
|
14392
15191
|
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
14393
|
-
candidates.push(
|
|
15192
|
+
candidates.push(join32(dataDir, f));
|
|
14394
15193
|
}
|
|
14395
15194
|
}
|
|
14396
15195
|
} catch {}
|
|
14397
15196
|
for (const p of candidates) {
|
|
14398
|
-
if (
|
|
15197
|
+
if (existsSync32(p))
|
|
14399
15198
|
return p;
|
|
14400
15199
|
}
|
|
14401
15200
|
return null;
|
|
@@ -14575,7 +15374,7 @@ class SyncProgressTracker {
|
|
|
14575
15374
|
init_adapter();
|
|
14576
15375
|
init_config();
|
|
14577
15376
|
init_discover();
|
|
14578
|
-
var AUTO_SYNC_CONFIG_PATH =
|
|
15377
|
+
var AUTO_SYNC_CONFIG_PATH = join42(homedir4(), ".hasna", "cloud", "config.json");
|
|
14579
15378
|
init_config();
|
|
14580
15379
|
init_adapter();
|
|
14581
15380
|
init_dotfile();
|
|
@@ -14595,13 +15394,13 @@ init_adapter();
|
|
|
14595
15394
|
// src/db/database.ts
|
|
14596
15395
|
import { dirname as dirname3, join as join5 } from "path";
|
|
14597
15396
|
import { homedir as homedir6 } from "os";
|
|
14598
|
-
import { mkdirSync as
|
|
15397
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync5, readdirSync as readdirSync4, copyFileSync as copyFileSync2, statSync } from "fs";
|
|
14599
15398
|
function mergeDirectoryContents(sourceDir, targetDir) {
|
|
14600
|
-
if (!
|
|
15399
|
+
if (!existsSync5(sourceDir)) {
|
|
14601
15400
|
return;
|
|
14602
15401
|
}
|
|
14603
|
-
|
|
14604
|
-
for (const entry of
|
|
15402
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
15403
|
+
for (const entry of readdirSync4(sourceDir)) {
|
|
14605
15404
|
const sourcePath = join5(sourceDir, entry);
|
|
14606
15405
|
const targetPath = join5(targetDir, entry);
|
|
14607
15406
|
try {
|
|
@@ -14610,7 +15409,7 @@ function mergeDirectoryContents(sourceDir, targetDir) {
|
|
|
14610
15409
|
mergeDirectoryContents(sourcePath, targetPath);
|
|
14611
15410
|
continue;
|
|
14612
15411
|
}
|
|
14613
|
-
if (!
|
|
15412
|
+
if (!existsSync5(targetPath)) {
|
|
14614
15413
|
copyFileSync2(sourcePath, targetPath);
|
|
14615
15414
|
}
|
|
14616
15415
|
} catch {}
|
|
@@ -14620,7 +15419,7 @@ function getConnectorsHome() {
|
|
|
14620
15419
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir6();
|
|
14621
15420
|
const newDir = join5(home, ".hasna", "connectors");
|
|
14622
15421
|
const legacyDirs = [join5(home, ".connectors"), join5(home, ".connect")];
|
|
14623
|
-
|
|
15422
|
+
mkdirSync4(newDir, { recursive: true });
|
|
14624
15423
|
for (const legacyDir of legacyDirs) {
|
|
14625
15424
|
try {
|
|
14626
15425
|
mergeDirectoryContents(legacyDir, newDir);
|
|
@@ -14631,8 +15430,85 @@ function getConnectorsHome() {
|
|
|
14631
15430
|
var DB_DIR = getConnectorsHome();
|
|
14632
15431
|
var DB_PATH = join5(DB_DIR, "connectors.db");
|
|
14633
15432
|
|
|
15433
|
+
// src/lib/connector-resolver.ts
|
|
15434
|
+
var LEGACY_CONNECTOR_PREFIX = "connect-";
|
|
15435
|
+
var CONNECTOR_NAME_RE2 = /^[a-z0-9-]+$/;
|
|
15436
|
+
function normalizeConnectorName(name) {
|
|
15437
|
+
const trimmed = name.trim().toLowerCase();
|
|
15438
|
+
return trimmed.startsWith(LEGACY_CONNECTOR_PREFIX) ? trimmed.slice(LEGACY_CONNECTOR_PREFIX.length) : trimmed;
|
|
15439
|
+
}
|
|
15440
|
+
function legacyConnectorName(name) {
|
|
15441
|
+
return `${LEGACY_CONNECTOR_PREFIX}${normalizeConnectorName(name)}`;
|
|
15442
|
+
}
|
|
15443
|
+
function resolveConnectorName(name) {
|
|
15444
|
+
const canonicalName = normalizeConnectorName(name);
|
|
15445
|
+
const legacyName = legacyConnectorName(canonicalName);
|
|
15446
|
+
return {
|
|
15447
|
+
input: name,
|
|
15448
|
+
canonicalName,
|
|
15449
|
+
legacyName,
|
|
15450
|
+
aliases: canonicalName === legacyName ? [canonicalName] : [canonicalName, legacyName],
|
|
15451
|
+
isLegacyInput: name.trim().toLowerCase().startsWith(LEGACY_CONNECTOR_PREFIX)
|
|
15452
|
+
};
|
|
15453
|
+
}
|
|
15454
|
+
function isValidConnectorName(name) {
|
|
15455
|
+
const trimmed = name.trim();
|
|
15456
|
+
if (trimmed !== trimmed.toLowerCase())
|
|
15457
|
+
return false;
|
|
15458
|
+
return CONNECTOR_NAME_RE2.test(normalizeConnectorName(trimmed));
|
|
15459
|
+
}
|
|
15460
|
+
function connectorPackageDirNames(name) {
|
|
15461
|
+
const { canonicalName, legacyName } = resolveConnectorName(name);
|
|
15462
|
+
if (!canonicalName)
|
|
15463
|
+
return [legacyName];
|
|
15464
|
+
return canonicalName === legacyName ? [canonicalName] : [canonicalName, legacyName];
|
|
15465
|
+
}
|
|
15466
|
+
function resolveConnectorPackagePath(connectorsDir, name) {
|
|
15467
|
+
const resolution = resolveConnectorName(name);
|
|
15468
|
+
const dirNames = connectorPackageDirNames(name);
|
|
15469
|
+
const checkedPaths = dirNames.map((dirName) => join7(connectorsDir, dirName));
|
|
15470
|
+
const existingPath = checkedPaths.find((path) => existsSync6(path)) ?? null;
|
|
15471
|
+
const existingDirName = existingPath ? dirNames[checkedPaths.indexOf(existingPath)] : null;
|
|
15472
|
+
return {
|
|
15473
|
+
...resolution,
|
|
15474
|
+
connectorsDir,
|
|
15475
|
+
preferredDirName: dirNames[0],
|
|
15476
|
+
preferredPath: checkedPaths[0],
|
|
15477
|
+
existingPath,
|
|
15478
|
+
existingDirName,
|
|
15479
|
+
checkedPaths
|
|
15480
|
+
};
|
|
15481
|
+
}
|
|
15482
|
+
function getConnectorPackagePath(connectorsDir, name) {
|
|
15483
|
+
const resolved = resolveConnectorPackagePath(connectorsDir, name);
|
|
15484
|
+
return resolved.existingPath ?? resolved.preferredPath;
|
|
15485
|
+
}
|
|
15486
|
+
function resolveConnectorConfigPaths(name, connectorsHome = getConnectorsHome()) {
|
|
15487
|
+
const resolution = resolveConnectorName(name);
|
|
15488
|
+
const preferredDirName = resolution.canonicalName || resolution.legacyName;
|
|
15489
|
+
const preferredPath = join7(connectorsHome, preferredDirName);
|
|
15490
|
+
const legacyPath = join7(connectorsHome, resolution.legacyName);
|
|
15491
|
+
const paths = preferredPath === legacyPath ? [preferredPath] : [preferredPath, legacyPath];
|
|
15492
|
+
const existingPaths = paths.filter((path) => existsSync6(path));
|
|
15493
|
+
return {
|
|
15494
|
+
...resolution,
|
|
15495
|
+
connectorsHome,
|
|
15496
|
+
preferredDirName,
|
|
15497
|
+
preferredPath,
|
|
15498
|
+
legacyPath,
|
|
15499
|
+
existingPaths,
|
|
15500
|
+
readPaths: paths
|
|
15501
|
+
};
|
|
15502
|
+
}
|
|
15503
|
+
function getConnectorConfigDir(name, connectorsHome = getConnectorsHome()) {
|
|
15504
|
+
return resolveConnectorConfigPaths(name, connectorsHome).preferredPath;
|
|
15505
|
+
}
|
|
15506
|
+
function getConnectorConfigReadDirs(name, connectorsHome = getConnectorsHome()) {
|
|
15507
|
+
return resolveConnectorConfigPaths(name, connectorsHome).readPaths;
|
|
15508
|
+
}
|
|
15509
|
+
|
|
14634
15510
|
// src/core/connectors/imessage.ts
|
|
14635
|
-
var
|
|
15511
|
+
var CONNECTOR_NAME = "imessage";
|
|
14636
15512
|
var COMMAND_SPECS = [
|
|
14637
15513
|
{
|
|
14638
15514
|
name: "profile",
|
|
@@ -14756,7 +15632,7 @@ API Key authentication is optional and depends on the bridge deployment. A bridg
|
|
|
14756
15632
|
|
|
14757
15633
|
## Data Storage
|
|
14758
15634
|
|
|
14759
|
-
Connector state is stored under \`~/.hasna/connectors/
|
|
15635
|
+
Connector state is stored under \`~/.hasna/connectors/imessage/\`. Profiles are read from both \`profiles/<name>.json\` and \`profiles/<name>/config.json\` so the connector stays compatible with the shared dashboard auth helpers while remaining fully internal to the one-product repo.
|
|
14760
15636
|
`;
|
|
14761
15637
|
var commandInputSchema = exports_external.object({
|
|
14762
15638
|
args: exports_external.array(exports_external.string()).default([]),
|
|
@@ -14792,36 +15668,44 @@ var messageReplyInputSchema = exports_external.object({
|
|
|
14792
15668
|
deviceId: exports_external.string().optional()
|
|
14793
15669
|
});
|
|
14794
15670
|
function getConfigDir2() {
|
|
14795
|
-
return
|
|
15671
|
+
return getConnectorConfigDir(CONNECTOR_NAME);
|
|
15672
|
+
}
|
|
15673
|
+
function getConfigReadDirs() {
|
|
15674
|
+
return getConnectorConfigReadDirs(CONNECTOR_NAME);
|
|
14796
15675
|
}
|
|
14797
15676
|
function getProfilesDir() {
|
|
14798
|
-
return
|
|
15677
|
+
return join9(getConfigDir2(), "profiles");
|
|
14799
15678
|
}
|
|
14800
15679
|
function getCurrentProfile() {
|
|
14801
|
-
const
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
14808
|
-
|
|
15680
|
+
for (const configDir of getConfigReadDirs()) {
|
|
15681
|
+
const currentProfileFile = join9(configDir, "current_profile");
|
|
15682
|
+
if (!existsSync7(currentProfileFile))
|
|
15683
|
+
continue;
|
|
15684
|
+
try {
|
|
15685
|
+
return readFileSync4(currentProfileFile, "utf-8").trim() || "default";
|
|
15686
|
+
} catch {
|
|
15687
|
+
return "default";
|
|
15688
|
+
}
|
|
14809
15689
|
}
|
|
15690
|
+
return "default";
|
|
14810
15691
|
}
|
|
14811
15692
|
function setCurrentProfile(profile) {
|
|
14812
15693
|
const configDir = getConfigDir2();
|
|
14813
15694
|
mkdirSync5(configDir, { recursive: true });
|
|
14814
|
-
|
|
15695
|
+
writeFileSync4(join9(configDir, "current_profile"), profile);
|
|
14815
15696
|
}
|
|
14816
15697
|
function getFlatProfilePath(profile) {
|
|
14817
|
-
return
|
|
15698
|
+
return join9(getProfilesDir(), `${profile}.json`);
|
|
15699
|
+
}
|
|
15700
|
+
function getFlatProfileReadPaths(profile) {
|
|
15701
|
+
return getConfigReadDirs().map((dir) => join9(dir, "profiles", `${profile}.json`));
|
|
14818
15702
|
}
|
|
14819
|
-
function
|
|
14820
|
-
return
|
|
15703
|
+
function getDirectoryProfileReadPaths(profile) {
|
|
15704
|
+
return getConfigReadDirs().map((dir) => join9(dir, "profiles", profile, "config.json"));
|
|
14821
15705
|
}
|
|
14822
15706
|
function loadJsonFile(path) {
|
|
14823
15707
|
try {
|
|
14824
|
-
return JSON.parse(
|
|
15708
|
+
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
14825
15709
|
} catch {
|
|
14826
15710
|
return {};
|
|
14827
15711
|
}
|
|
@@ -14837,8 +15721,8 @@ function sanitizeProfileConfig(config) {
|
|
|
14837
15721
|
};
|
|
14838
15722
|
}
|
|
14839
15723
|
function loadProfile(profile = getCurrentProfile()) {
|
|
14840
|
-
const flatConfig =
|
|
14841
|
-
const directoryConfig =
|
|
15724
|
+
const flatConfig = getFlatProfileReadPaths(profile).reverse().reduce((config, path) => ({ ...config, ...existsSync7(path) ? loadJsonFile(path) : {} }), {});
|
|
15725
|
+
const directoryConfig = getDirectoryProfileReadPaths(profile).reverse().reduce((config, path) => ({ ...config, ...existsSync7(path) ? loadJsonFile(path) : {} }), {});
|
|
14842
15726
|
return sanitizeProfileConfig({
|
|
14843
15727
|
...flatConfig,
|
|
14844
15728
|
...directoryConfig
|
|
@@ -14847,32 +15731,33 @@ function loadProfile(profile = getCurrentProfile()) {
|
|
|
14847
15731
|
function writeProfile(profile, config) {
|
|
14848
15732
|
const profilesDir = getProfilesDir();
|
|
14849
15733
|
mkdirSync5(profilesDir, { recursive: true });
|
|
14850
|
-
|
|
15734
|
+
writeFileSync4(getFlatProfilePath(profile), JSON.stringify(config, null, 2) + `
|
|
14851
15735
|
`);
|
|
14852
15736
|
}
|
|
14853
15737
|
function profileExists(profile) {
|
|
14854
15738
|
if (profile === "default") {
|
|
14855
15739
|
return true;
|
|
14856
15740
|
}
|
|
14857
|
-
return
|
|
15741
|
+
return getFlatProfileReadPaths(profile).some((path) => existsSync7(path)) || getConfigReadDirs().some((dir) => existsSync7(join9(dir, "profiles", profile)));
|
|
14858
15742
|
}
|
|
14859
|
-
function
|
|
14860
|
-
const profilesDir = getProfilesDir();
|
|
15743
|
+
function listProfiles3() {
|
|
14861
15744
|
const seen = new Set(["default"]);
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
const
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
15745
|
+
for (const configDir of getConfigReadDirs()) {
|
|
15746
|
+
const profilesDir = join9(configDir, "profiles");
|
|
15747
|
+
if (!existsSync7(profilesDir))
|
|
15748
|
+
continue;
|
|
15749
|
+
try {
|
|
15750
|
+
for (const entry of readdirSync6(profilesDir)) {
|
|
15751
|
+
const fullPath = join9(profilesDir, entry);
|
|
15752
|
+
const stat = statSync3(fullPath);
|
|
15753
|
+
if (stat.isDirectory()) {
|
|
15754
|
+
seen.add(entry);
|
|
15755
|
+
} else if (entry.endsWith(".json")) {
|
|
15756
|
+
seen.add(entry.replace(/\.json$/, ""));
|
|
15757
|
+
}
|
|
14873
15758
|
}
|
|
14874
|
-
}
|
|
14875
|
-
}
|
|
15759
|
+
} catch {}
|
|
15760
|
+
}
|
|
14876
15761
|
return [...seen].sort();
|
|
14877
15762
|
}
|
|
14878
15763
|
function createProfile(profile, config = {}) {
|
|
@@ -14885,11 +15770,11 @@ function createProfile(profile, config = {}) {
|
|
|
14885
15770
|
}
|
|
14886
15771
|
function clearProfile(profile = getCurrentProfile()) {
|
|
14887
15772
|
const flatPath = getFlatProfilePath(profile);
|
|
14888
|
-
const directoryPath =
|
|
14889
|
-
if (
|
|
15773
|
+
const directoryPath = join9(getProfilesDir(), profile);
|
|
15774
|
+
if (existsSync7(flatPath)) {
|
|
14890
15775
|
rmSync(flatPath);
|
|
14891
15776
|
}
|
|
14892
|
-
if (
|
|
15777
|
+
if (existsSync7(directoryPath)) {
|
|
14893
15778
|
rmSync(directoryPath, { recursive: true, force: true });
|
|
14894
15779
|
}
|
|
14895
15780
|
}
|
|
@@ -15185,7 +16070,7 @@ async function sendMessage(context, input) {
|
|
|
15185
16070
|
direction: "outbound"
|
|
15186
16071
|
});
|
|
15187
16072
|
}
|
|
15188
|
-
async function
|
|
16073
|
+
async function replyToMessage2(context, input) {
|
|
15189
16074
|
const payload = await bridgeRequest(context, "/messages/reply", {
|
|
15190
16075
|
method: "POST",
|
|
15191
16076
|
body: {
|
|
@@ -15299,7 +16184,7 @@ async function runProfileCommand2(args, context) {
|
|
|
15299
16184
|
switch (subcommand) {
|
|
15300
16185
|
case "list": {
|
|
15301
16186
|
const current = getCurrentProfile();
|
|
15302
|
-
const profiles =
|
|
16187
|
+
const profiles = listProfiles3();
|
|
15303
16188
|
return successOutput({ current, profiles }, context.format, () => profiles.map((profile) => `${profile}${profile === current ? " (active)" : ""}`).join(`
|
|
15304
16189
|
`));
|
|
15305
16190
|
}
|
|
@@ -15473,7 +16358,7 @@ async function runMessageCommand(args, context) {
|
|
|
15473
16358
|
if (!conversationId || !text) {
|
|
15474
16359
|
return failure2("Usage: connect-imessage message reply --conversation <id> --text <text>");
|
|
15475
16360
|
}
|
|
15476
|
-
return successOutput(await
|
|
16361
|
+
return successOutput(await replyToMessage2(resolved, {
|
|
15477
16362
|
conversationId,
|
|
15478
16363
|
text,
|
|
15479
16364
|
replyToMessageId: asString2(options.replyTo),
|
|
@@ -15631,7 +16516,7 @@ var imessageConnector = defineConnector({
|
|
|
15631
16516
|
summary: "Reply to an existing conversation through the bridge",
|
|
15632
16517
|
inputSchema: messageReplyInputSchema,
|
|
15633
16518
|
async execute({ context }, input) {
|
|
15634
|
-
return
|
|
16519
|
+
return replyToMessage2(context, input);
|
|
15635
16520
|
}
|
|
15636
16521
|
}
|
|
15637
16522
|
},
|
|
@@ -15694,19 +16579,19 @@ var imessageConnector = defineConnector({
|
|
|
15694
16579
|
});
|
|
15695
16580
|
|
|
15696
16581
|
// src/core/connectors/stripe.ts
|
|
15697
|
-
import { existsSync as
|
|
15698
|
-
import { dirname as dirname4, join as
|
|
16582
|
+
import { existsSync as existsSync8 } from "fs";
|
|
16583
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
15699
16584
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "url";
|
|
15700
16585
|
var __dirname3 = dirname4(fileURLToPath2(import.meta.url));
|
|
15701
16586
|
function resolveStripeConnectorDir() {
|
|
15702
16587
|
const candidates = [
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
16588
|
+
join10(__dirname3, "..", "..", "..", "connectors", "stripe"),
|
|
16589
|
+
join10(__dirname3, "..", "..", "connectors", "stripe"),
|
|
16590
|
+
join10(__dirname3, "..", "connectors", "stripe"),
|
|
16591
|
+
join10(process.cwd(), "connectors", "stripe")
|
|
15707
16592
|
];
|
|
15708
16593
|
for (const candidate of candidates) {
|
|
15709
|
-
if (
|
|
16594
|
+
if (existsSync8(candidate)) {
|
|
15710
16595
|
return candidate;
|
|
15711
16596
|
}
|
|
15712
16597
|
}
|
|
@@ -15945,10 +16830,10 @@ function buildCommandHelp2(spec) {
|
|
|
15945
16830
|
var ROOT_HELP3 = buildRootHelp2(COMMAND_SPECS2);
|
|
15946
16831
|
var COMMAND_HELP3 = Object.fromEntries(COMMAND_SPECS2.map((spec) => [spec.name, buildCommandHelp2(spec)]));
|
|
15947
16832
|
async function loadStripeApiModule() {
|
|
15948
|
-
return await import(pathToFileURL2(
|
|
16833
|
+
return await import(pathToFileURL2(join10(CONNECTOR_DIR2, "src", "api", "index.ts")).href);
|
|
15949
16834
|
}
|
|
15950
16835
|
async function loadStripeConfigModule() {
|
|
15951
|
-
return await import(pathToFileURL2(
|
|
16836
|
+
return await import(pathToFileURL2(join10(CONNECTOR_DIR2, "src", "utils", "config.ts")).href);
|
|
15952
16837
|
}
|
|
15953
16838
|
function extractGlobalArgs3(args) {
|
|
15954
16839
|
const remaining = [];
|
|
@@ -16560,7 +17445,9 @@ var stripeConnector = defineConnector({
|
|
|
16560
17445
|
|
|
16561
17446
|
// src/core/builtins.ts
|
|
16562
17447
|
var INTERNAL_CONNECTOR_DEFINITIONS = [
|
|
17448
|
+
gmailConnector,
|
|
16563
17449
|
githubConnector,
|
|
17450
|
+
googleDriveConnector,
|
|
16564
17451
|
imessageConnector,
|
|
16565
17452
|
stripeConnector
|
|
16566
17453
|
];
|
|
@@ -22834,7 +23721,10 @@ function searchConnectors(query, context) {
|
|
|
22834
23721
|
return results.slice(0, limit);
|
|
22835
23722
|
}
|
|
22836
23723
|
function getConnector(name) {
|
|
22837
|
-
|
|
23724
|
+
if (!isValidConnectorName(name))
|
|
23725
|
+
return;
|
|
23726
|
+
const normalizedName = normalizeConnectorName(name);
|
|
23727
|
+
return CONNECTORS.find((c) => c.name === normalizedName);
|
|
22838
23728
|
}
|
|
22839
23729
|
var versionsLoaded = false;
|
|
22840
23730
|
function loadConnectorVersions() {
|
|
@@ -22843,17 +23733,17 @@ function loadConnectorVersions() {
|
|
|
22843
23733
|
versionsLoaded = true;
|
|
22844
23734
|
const thisDir = dirname5(fileURLToPath3(import.meta.url));
|
|
22845
23735
|
const candidates = [
|
|
22846
|
-
|
|
22847
|
-
|
|
23736
|
+
join11(thisDir, "..", "connectors"),
|
|
23737
|
+
join11(thisDir, "..", "..", "connectors")
|
|
22848
23738
|
];
|
|
22849
|
-
const connectorsDir = candidates.find((d) =>
|
|
23739
|
+
const connectorsDir = candidates.find((d) => existsSync9(d));
|
|
22850
23740
|
if (!connectorsDir)
|
|
22851
23741
|
return;
|
|
22852
23742
|
for (const connector of CONNECTORS) {
|
|
22853
23743
|
try {
|
|
22854
|
-
const pkgPath =
|
|
22855
|
-
if (
|
|
22856
|
-
const pkg = JSON.parse(
|
|
23744
|
+
const pkgPath = join11(getConnectorPackagePath(connectorsDir, connector.name), "package.json");
|
|
23745
|
+
if (existsSync9(pkgPath)) {
|
|
23746
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
22857
23747
|
connector.version = pkg.version || "0.0.0";
|
|
22858
23748
|
continue;
|
|
22859
23749
|
}
|
|
@@ -22865,16 +23755,16 @@ function loadConnectorVersions() {
|
|
|
22865
23755
|
}
|
|
22866
23756
|
}
|
|
22867
23757
|
// src/lib/installer.ts
|
|
22868
|
-
import { existsSync as
|
|
22869
|
-
import { join as
|
|
23758
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync7, statSync as statSync4, rmSync as rmSync2 } from "fs";
|
|
23759
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
22870
23760
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
22871
23761
|
var __dirname4 = dirname6(fileURLToPath4(import.meta.url));
|
|
22872
23762
|
function resolveConnectorsDir() {
|
|
22873
|
-
const fromBin =
|
|
22874
|
-
if (
|
|
23763
|
+
const fromBin = join12(__dirname4, "..", "connectors");
|
|
23764
|
+
if (existsSync10(fromBin))
|
|
22875
23765
|
return fromBin;
|
|
22876
|
-
const fromSrc =
|
|
22877
|
-
if (
|
|
23766
|
+
const fromSrc = join12(__dirname4, "..", "..", "connectors");
|
|
23767
|
+
if (existsSync10(fromSrc))
|
|
22878
23768
|
return fromSrc;
|
|
22879
23769
|
return fromBin;
|
|
22880
23770
|
}
|
|
@@ -22882,25 +23772,22 @@ var CONNECTORS_DIR = resolveConnectorsDir();
|
|
|
22882
23772
|
var PROJECT_CONNECTORS_DIRNAME = ".connectors";
|
|
22883
23773
|
var ENABLEMENT_MANIFEST_FILENAME = "manifest.json";
|
|
22884
23774
|
var ENABLEMENT_INDEX_FILENAME = "index.ts";
|
|
22885
|
-
function normalizeConnectorName(name) {
|
|
22886
|
-
return name.startsWith("connect-") ? name.slice("connect-".length) : name;
|
|
22887
|
-
}
|
|
22888
23775
|
function getProjectConnectorsDir(targetDir) {
|
|
22889
|
-
return
|
|
23776
|
+
return join12(targetDir, PROJECT_CONNECTORS_DIRNAME);
|
|
22890
23777
|
}
|
|
22891
23778
|
function getEnablementManifestPath(targetDir) {
|
|
22892
|
-
return
|
|
23779
|
+
return join12(getProjectConnectorsDir(targetDir), ENABLEMENT_MANIFEST_FILENAME);
|
|
22893
23780
|
}
|
|
22894
23781
|
function getLegacyInstallPath(targetDir, name) {
|
|
22895
|
-
return
|
|
23782
|
+
return join12(getProjectConnectorsDir(targetDir), legacyConnectorName(name));
|
|
22896
23783
|
}
|
|
22897
23784
|
function loadEnablementManifest(targetDir) {
|
|
22898
23785
|
const manifestPath = getEnablementManifestPath(targetDir);
|
|
22899
|
-
if (!
|
|
23786
|
+
if (!existsSync10(manifestPath)) {
|
|
22900
23787
|
return null;
|
|
22901
23788
|
}
|
|
22902
23789
|
try {
|
|
22903
|
-
const raw = JSON.parse(
|
|
23790
|
+
const raw = JSON.parse(readFileSync6(manifestPath, "utf-8"));
|
|
22904
23791
|
if (!Array.isArray(raw.connectors)) {
|
|
22905
23792
|
return null;
|
|
22906
23793
|
}
|
|
@@ -22916,12 +23803,12 @@ function loadEnablementManifest(targetDir) {
|
|
|
22916
23803
|
}
|
|
22917
23804
|
function getLegacyInstalledConnectors(targetDir) {
|
|
22918
23805
|
const connectorsDir = getProjectConnectorsDir(targetDir);
|
|
22919
|
-
if (!
|
|
23806
|
+
if (!existsSync10(connectorsDir)) {
|
|
22920
23807
|
return [];
|
|
22921
23808
|
}
|
|
22922
|
-
return
|
|
22923
|
-
const fullPath =
|
|
22924
|
-
return entry.startsWith("connect-") &&
|
|
23809
|
+
return readdirSync7(connectorsDir).filter((entry) => {
|
|
23810
|
+
const fullPath = join12(connectorsDir, entry);
|
|
23811
|
+
return entry.startsWith("connect-") && statSync4(fullPath).isDirectory();
|
|
22925
23812
|
}).map((entry) => entry.replace("connect-", "")).sort();
|
|
22926
23813
|
}
|
|
22927
23814
|
function getEnabledConnectors(targetDir) {
|
|
@@ -22929,7 +23816,7 @@ function getEnabledConnectors(targetDir) {
|
|
|
22929
23816
|
return [...new Set([...manifestConnectors, ...getLegacyInstalledConnectors(targetDir)])].sort();
|
|
22930
23817
|
}
|
|
22931
23818
|
function updateConnectorsIndex(connectorsDir, connectors18) {
|
|
22932
|
-
const indexPath =
|
|
23819
|
+
const indexPath = join12(connectorsDir, ENABLEMENT_INDEX_FILENAME);
|
|
22933
23820
|
const connectorList = connectors18.map((connector) => ` "${connector}",`).join(`
|
|
22934
23821
|
`);
|
|
22935
23822
|
const content = `/**
|
|
@@ -22945,7 +23832,7 @@ ${connectorList}
|
|
|
22945
23832
|
|
|
22946
23833
|
export type EnabledConnectorName = typeof enabledConnectors[number];
|
|
22947
23834
|
`;
|
|
22948
|
-
|
|
23835
|
+
writeFileSync5(indexPath, content);
|
|
22949
23836
|
}
|
|
22950
23837
|
function writeEnablementManifest(targetDir, connectors18) {
|
|
22951
23838
|
const connectorsDir = getProjectConnectorsDir(targetDir);
|
|
@@ -22956,17 +23843,16 @@ function writeEnablementManifest(targetDir, connectors18) {
|
|
|
22956
23843
|
updatedAt: new Date().toISOString(),
|
|
22957
23844
|
connectors: [...new Set(connectors18.map((value) => normalizeConnectorName(value)))].sort()
|
|
22958
23845
|
};
|
|
22959
|
-
|
|
23846
|
+
writeFileSync5(getEnablementManifestPath(targetDir), JSON.stringify(manifest, null, 2) + `
|
|
22960
23847
|
`);
|
|
22961
23848
|
updateConnectorsIndex(connectorsDir, manifest.connectors);
|
|
22962
23849
|
}
|
|
22963
23850
|
function getConnectorPath(name) {
|
|
22964
|
-
|
|
22965
|
-
return join10(CONNECTORS_DIR, connectorName);
|
|
23851
|
+
return getConnectorPackagePath(CONNECTORS_DIR, name);
|
|
22966
23852
|
}
|
|
22967
23853
|
function connectorExists(name) {
|
|
22968
23854
|
const normalizedName = normalizeConnectorName(name);
|
|
22969
|
-
return hasInternalConnectorDefinition(normalizedName) ||
|
|
23855
|
+
return hasInternalConnectorDefinition(normalizedName) || existsSync10(getConnectorPath(normalizedName));
|
|
22970
23856
|
}
|
|
22971
23857
|
function installConnector(name, options = {}) {
|
|
22972
23858
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
@@ -22999,7 +23885,7 @@ function installConnector(name, options = {}) {
|
|
|
22999
23885
|
try {
|
|
23000
23886
|
const nextEnabled = [...new Set([...installed, normalizedName])].sort();
|
|
23001
23887
|
writeEnablementManifest(targetDir, nextEnabled);
|
|
23002
|
-
if (overwrite &&
|
|
23888
|
+
if (overwrite && existsSync10(legacyInstallPath)) {
|
|
23003
23889
|
rmSync2(legacyInstallPath, { recursive: true });
|
|
23004
23890
|
}
|
|
23005
23891
|
return {
|
|
@@ -23034,9 +23920,9 @@ function parseConnectorDocs(raw) {
|
|
|
23034
23920
|
function getConnectorDocs(name) {
|
|
23035
23921
|
const normalizedName = normalizeConnectorName(name);
|
|
23036
23922
|
const connectorPath = getConnectorPath(normalizedName);
|
|
23037
|
-
const claudeMdPath =
|
|
23038
|
-
if (
|
|
23039
|
-
return parseConnectorDocs(
|
|
23923
|
+
const claudeMdPath = join12(connectorPath, "CLAUDE.md");
|
|
23924
|
+
if (existsSync10(claudeMdPath)) {
|
|
23925
|
+
return parseConnectorDocs(readFileSync6(claudeMdPath, "utf-8"));
|
|
23040
23926
|
}
|
|
23041
23927
|
const internalDocs = getInternalConnectorDefinition(normalizedName)?.docsMarkdown;
|
|
23042
23928
|
if (internalDocs) {
|
|
@@ -23080,23 +23966,171 @@ function removeConnector(name, targetDir = process.cwd()) {
|
|
|
23080
23966
|
const nextEnabled = installed.filter((connector) => connector !== normalizedName);
|
|
23081
23967
|
writeEnablementManifest(targetDir, nextEnabled);
|
|
23082
23968
|
const legacyInstallPath = getLegacyInstallPath(targetDir, normalizedName);
|
|
23083
|
-
if (
|
|
23969
|
+
if (existsSync10(legacyInstallPath)) {
|
|
23084
23970
|
rmSync2(legacyInstallPath, { recursive: true });
|
|
23085
23971
|
}
|
|
23086
23972
|
return true;
|
|
23087
23973
|
}
|
|
23088
23974
|
// src/lib/runner.ts
|
|
23089
|
-
import { existsSync as
|
|
23090
|
-
import { join as
|
|
23975
|
+
import { existsSync as existsSync12, readdirSync as readdirSync9 } from "fs";
|
|
23976
|
+
import { join as join14, dirname as dirname7 } from "path";
|
|
23091
23977
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
23092
23978
|
import { spawn } from "child_process";
|
|
23979
|
+
|
|
23980
|
+
// src/server/auth.ts
|
|
23981
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7, readdirSync as readdirSync8, rmSync as rmSync3, statSync as statSync5 } from "fs";
|
|
23982
|
+
import { join as join13 } from "path";
|
|
23983
|
+
var oauthStateStore = new Map;
|
|
23984
|
+
var GOOGLE_SCOPES = {
|
|
23985
|
+
gmail: [
|
|
23986
|
+
"https://www.googleapis.com/auth/gmail.readonly",
|
|
23987
|
+
"https://www.googleapis.com/auth/gmail.send",
|
|
23988
|
+
"https://www.googleapis.com/auth/gmail.compose",
|
|
23989
|
+
"https://www.googleapis.com/auth/gmail.modify",
|
|
23990
|
+
"https://www.googleapis.com/auth/gmail.labels",
|
|
23991
|
+
"https://mail.google.com/"
|
|
23992
|
+
].join(" "),
|
|
23993
|
+
googlecalendar: [
|
|
23994
|
+
"https://www.googleapis.com/auth/calendar",
|
|
23995
|
+
"https://www.googleapis.com/auth/calendar.events"
|
|
23996
|
+
].join(" "),
|
|
23997
|
+
googledrive: [
|
|
23998
|
+
"https://www.googleapis.com/auth/drive",
|
|
23999
|
+
"https://www.googleapis.com/auth/drive.file"
|
|
24000
|
+
].join(" "),
|
|
24001
|
+
googledocs: [
|
|
24002
|
+
"https://www.googleapis.com/auth/documents",
|
|
24003
|
+
"https://www.googleapis.com/auth/drive.file"
|
|
24004
|
+
].join(" "),
|
|
24005
|
+
googlesheets: [
|
|
24006
|
+
"https://www.googleapis.com/auth/spreadsheets",
|
|
24007
|
+
"https://www.googleapis.com/auth/drive.file"
|
|
24008
|
+
].join(" "),
|
|
24009
|
+
googletasks: [
|
|
24010
|
+
"https://www.googleapis.com/auth/tasks"
|
|
24011
|
+
].join(" "),
|
|
24012
|
+
googlecontacts: [
|
|
24013
|
+
"https://www.googleapis.com/auth/contacts",
|
|
24014
|
+
"https://www.googleapis.com/auth/contacts.readonly"
|
|
24015
|
+
].join(" "),
|
|
24016
|
+
google: [
|
|
24017
|
+
"openid",
|
|
24018
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
24019
|
+
"https://www.googleapis.com/auth/userinfo.profile"
|
|
24020
|
+
].join(" ")
|
|
24021
|
+
};
|
|
24022
|
+
function getAuthType(name) {
|
|
24023
|
+
name = normalizeConnectorName(name);
|
|
24024
|
+
const docs = getConnectorDocs(name);
|
|
24025
|
+
if (!docs?.auth)
|
|
24026
|
+
return "apikey";
|
|
24027
|
+
const authLower = docs.auth.toLowerCase();
|
|
24028
|
+
if (authLower.includes("oauth"))
|
|
24029
|
+
return "oauth";
|
|
24030
|
+
if (authLower.includes("bearer token"))
|
|
24031
|
+
return "bearer";
|
|
24032
|
+
return "apikey";
|
|
24033
|
+
}
|
|
24034
|
+
function getConnectorConfigReadDirs2(name) {
|
|
24035
|
+
name = normalizeConnectorName(name);
|
|
24036
|
+
return getConnectorConfigReadDirs(name);
|
|
24037
|
+
}
|
|
24038
|
+
function getCurrentProfile2(name) {
|
|
24039
|
+
name = normalizeConnectorName(name);
|
|
24040
|
+
for (const configDir of getConnectorConfigReadDirs2(name)) {
|
|
24041
|
+
const currentProfileFile = join13(configDir, "current_profile");
|
|
24042
|
+
if (existsSync11(currentProfileFile)) {
|
|
24043
|
+
try {
|
|
24044
|
+
return readFileSync7(currentProfileFile, "utf-8").trim() || "default";
|
|
24045
|
+
} catch {
|
|
24046
|
+
return "default";
|
|
24047
|
+
}
|
|
24048
|
+
}
|
|
24049
|
+
}
|
|
24050
|
+
return "default";
|
|
24051
|
+
}
|
|
24052
|
+
function loadProfileConfigFromDir(configDir, profile) {
|
|
24053
|
+
let flatConfig = {};
|
|
24054
|
+
let dirConfig = {};
|
|
24055
|
+
const profileFile = join13(configDir, "profiles", `${profile}.json`);
|
|
24056
|
+
if (existsSync11(profileFile)) {
|
|
24057
|
+
try {
|
|
24058
|
+
flatConfig = JSON.parse(readFileSync7(profileFile, "utf-8"));
|
|
24059
|
+
} catch {}
|
|
24060
|
+
}
|
|
24061
|
+
const profileDirConfig = join13(configDir, "profiles", profile, "config.json");
|
|
24062
|
+
if (existsSync11(profileDirConfig)) {
|
|
24063
|
+
try {
|
|
24064
|
+
dirConfig = JSON.parse(readFileSync7(profileDirConfig, "utf-8"));
|
|
24065
|
+
} catch {}
|
|
24066
|
+
}
|
|
24067
|
+
return { ...flatConfig, ...dirConfig };
|
|
24068
|
+
}
|
|
24069
|
+
function loadProfileConfig(name) {
|
|
24070
|
+
name = normalizeConnectorName(name);
|
|
24071
|
+
const profile = getCurrentProfile2(name);
|
|
24072
|
+
const merged = {};
|
|
24073
|
+
for (const configDir of [...getConnectorConfigReadDirs2(name)].reverse()) {
|
|
24074
|
+
Object.assign(merged, loadProfileConfigFromDir(configDir, profile));
|
|
24075
|
+
}
|
|
24076
|
+
return merged;
|
|
24077
|
+
}
|
|
24078
|
+
function loadTokens3(name) {
|
|
24079
|
+
name = normalizeConnectorName(name);
|
|
24080
|
+
const profile = getCurrentProfile2(name);
|
|
24081
|
+
for (const configDir of getConnectorConfigReadDirs2(name)) {
|
|
24082
|
+
const tokensFile = join13(configDir, "profiles", profile, "tokens.json");
|
|
24083
|
+
if (existsSync11(tokensFile)) {
|
|
24084
|
+
try {
|
|
24085
|
+
return JSON.parse(readFileSync7(tokensFile, "utf-8"));
|
|
24086
|
+
} catch {
|
|
24087
|
+
return null;
|
|
24088
|
+
}
|
|
24089
|
+
}
|
|
24090
|
+
}
|
|
24091
|
+
const profileConfig = loadProfileConfig(name);
|
|
24092
|
+
if (profileConfig.refreshToken || profileConfig.accessToken) {
|
|
24093
|
+
return {
|
|
24094
|
+
accessToken: profileConfig.accessToken ?? "",
|
|
24095
|
+
refreshToken: profileConfig.refreshToken,
|
|
24096
|
+
expiresAt: profileConfig.expiresAt ?? 0,
|
|
24097
|
+
tokenType: profileConfig.tokenType,
|
|
24098
|
+
scope: profileConfig.scope
|
|
24099
|
+
};
|
|
24100
|
+
}
|
|
24101
|
+
return null;
|
|
24102
|
+
}
|
|
24103
|
+
function getEnvVars(name) {
|
|
24104
|
+
name = normalizeConnectorName(name);
|
|
24105
|
+
const docs = getConnectorDocs(name);
|
|
24106
|
+
return docs?.envVars || [];
|
|
24107
|
+
}
|
|
24108
|
+
function getOAuthConfig(name) {
|
|
24109
|
+
name = normalizeConnectorName(name);
|
|
24110
|
+
for (const configDir of getConnectorConfigReadDirs2(name)) {
|
|
24111
|
+
const credentialsFile = join13(configDir, "credentials.json");
|
|
24112
|
+
if (existsSync11(credentialsFile)) {
|
|
24113
|
+
try {
|
|
24114
|
+
const creds = JSON.parse(readFileSync7(credentialsFile, "utf-8"));
|
|
24115
|
+
return { clientId: creds.clientId, clientSecret: creds.clientSecret };
|
|
24116
|
+
} catch {}
|
|
24117
|
+
}
|
|
24118
|
+
}
|
|
24119
|
+
const config = loadProfileConfig(name);
|
|
24120
|
+
return {
|
|
24121
|
+
clientId: config.clientId,
|
|
24122
|
+
clientSecret: config.clientSecret
|
|
24123
|
+
};
|
|
24124
|
+
}
|
|
24125
|
+
|
|
24126
|
+
// src/lib/runner.ts
|
|
23093
24127
|
var __dirname5 = dirname7(fileURLToPath5(import.meta.url));
|
|
23094
24128
|
function resolveConnectorsDir2() {
|
|
23095
|
-
const fromBin =
|
|
23096
|
-
if (
|
|
24129
|
+
const fromBin = join14(__dirname5, "..", "connectors");
|
|
24130
|
+
if (existsSync12(fromBin))
|
|
23097
24131
|
return fromBin;
|
|
23098
|
-
const fromSrc =
|
|
23099
|
-
if (
|
|
24132
|
+
const fromSrc = join14(__dirname5, "..", "..", "connectors");
|
|
24133
|
+
if (existsSync12(fromSrc))
|
|
23100
24134
|
return fromSrc;
|
|
23101
24135
|
return fromBin;
|
|
23102
24136
|
}
|
|
@@ -23125,8 +24159,9 @@ var ENV_VAR_NAME_OVERRIDES = {
|
|
|
23125
24159
|
};
|
|
23126
24160
|
function buildEnvWithCredentials(connectorName, baseEnv) {
|
|
23127
24161
|
const env = { ...baseEnv };
|
|
23128
|
-
const
|
|
23129
|
-
|
|
24162
|
+
const normalizedConnectorName = normalizeConnectorName(connectorName);
|
|
24163
|
+
const prefixes = ENV_VAR_NAME_OVERRIDES[normalizedConnectorName] || [
|
|
24164
|
+
normalizedConnectorName.toUpperCase().replace(/-/g, "_")
|
|
23130
24165
|
];
|
|
23131
24166
|
for (const prefix of prefixes) {
|
|
23132
24167
|
const canonicalKey = `${prefix}_API_KEY`;
|
|
@@ -23147,13 +24182,32 @@ function buildEnvWithCredentials(connectorName, baseEnv) {
|
|
|
23147
24182
|
}
|
|
23148
24183
|
}
|
|
23149
24184
|
}
|
|
24185
|
+
if (getAuthType(normalizedConnectorName) === "oauth") {
|
|
24186
|
+
const oauthConfig = getOAuthConfig(normalizedConnectorName);
|
|
24187
|
+
const tokens = loadTokens3(normalizedConnectorName);
|
|
24188
|
+
for (const { variable } of getEnvVars(normalizedConnectorName)) {
|
|
24189
|
+
if (env[variable])
|
|
24190
|
+
continue;
|
|
24191
|
+
if (variable.endsWith("_CLIENT_ID") && oauthConfig.clientId) {
|
|
24192
|
+
env[variable] = oauthConfig.clientId;
|
|
24193
|
+
} else if (variable.endsWith("_CLIENT_SECRET") && oauthConfig.clientSecret) {
|
|
24194
|
+
env[variable] = oauthConfig.clientSecret;
|
|
24195
|
+
} else if (variable.endsWith("_ACCESS_TOKEN") && tokens?.accessToken) {
|
|
24196
|
+
env[variable] = tokens.accessToken;
|
|
24197
|
+
} else if (variable.endsWith("_REFRESH_TOKEN") && tokens?.refreshToken) {
|
|
24198
|
+
env[variable] = tokens.refreshToken;
|
|
24199
|
+
} else if (variable.endsWith("_TOKEN_EXPIRES_AT") && tokens?.expiresAt) {
|
|
24200
|
+
env[variable] = String(tokens.expiresAt);
|
|
24201
|
+
}
|
|
24202
|
+
}
|
|
24203
|
+
}
|
|
23150
24204
|
return env;
|
|
23151
24205
|
}
|
|
23152
24206
|
function getConnectorCliPath(name) {
|
|
23153
|
-
const safeName = name.replace(/[^a-z0-9-]/g, "");
|
|
23154
|
-
const connectorDir =
|
|
23155
|
-
const cliPath =
|
|
23156
|
-
if (
|
|
24207
|
+
const safeName = normalizeConnectorName(name).replace(/[^a-z0-9-]/g, "");
|
|
24208
|
+
const connectorDir = getConnectorPackagePath(CONNECTORS_DIR2, safeName);
|
|
24209
|
+
const cliPath = join14(connectorDir, "src", "cli", "index.ts");
|
|
24210
|
+
if (existsSync12(cliPath))
|
|
23157
24211
|
return cliPath;
|
|
23158
24212
|
return null;
|
|
23159
24213
|
}
|
|
@@ -23191,7 +24245,7 @@ function parseCommanderHelpOperations(helpText) {
|
|
|
23191
24245
|
inCommands = false;
|
|
23192
24246
|
continue;
|
|
23193
24247
|
}
|
|
23194
|
-
const row = line.match(/^ {2}(\S.*?)(?: {2,}(.+))?$/);
|
|
24248
|
+
const row = line.match(/^ {2}(?! )(\S.*?)(?: {2,}(.+))?$/);
|
|
23195
24249
|
if (!row)
|
|
23196
24250
|
continue;
|
|
23197
24251
|
const usage = row[1].trim();
|
|
@@ -23208,8 +24262,9 @@ function parseCommanderHelpOperations(helpText) {
|
|
|
23208
24262
|
return operations;
|
|
23209
24263
|
}
|
|
23210
24264
|
function runConnectorCommand(name, args, timeoutMs = 30000) {
|
|
24265
|
+
const connectorName = normalizeConnectorName(name);
|
|
23211
24266
|
getConnectorsHome();
|
|
23212
|
-
const internalRuntime = getInternalCommandRuntime(
|
|
24267
|
+
const internalRuntime = getInternalCommandRuntime(connectorName);
|
|
23213
24268
|
if (internalRuntime?.run) {
|
|
23214
24269
|
return Promise.resolve(internalRuntime.run(args)).then((result) => {
|
|
23215
24270
|
if (result) {
|
|
@@ -23223,17 +24278,125 @@ function runConnectorCommand(name, args, timeoutMs = 30000) {
|
|
|
23223
24278
|
success: result.success ?? exitCode === 0
|
|
23224
24279
|
};
|
|
23225
24280
|
}
|
|
23226
|
-
return runLegacyConnectorCommand(
|
|
24281
|
+
return runLegacyConnectorCommand(connectorName, args, timeoutMs);
|
|
23227
24282
|
});
|
|
23228
24283
|
}
|
|
23229
|
-
return runLegacyConnectorCommand(
|
|
24284
|
+
return runLegacyConnectorCommand(connectorName, args, timeoutMs);
|
|
24285
|
+
}
|
|
24286
|
+
async function runConnectorOperation(args) {
|
|
24287
|
+
const connectorName = normalizeConnectorName(args.connector);
|
|
24288
|
+
const internal = getInternalConnectorDefinition(connectorName);
|
|
24289
|
+
if (internal?.operations[args.operation]) {
|
|
24290
|
+
try {
|
|
24291
|
+
const operationData = await executeConnectorOperation(internal, {
|
|
24292
|
+
operation: args.operation,
|
|
24293
|
+
input: args.input,
|
|
24294
|
+
profile: args.profile
|
|
24295
|
+
});
|
|
24296
|
+
const commandResult = normalizeOperationReturn(operationData);
|
|
24297
|
+
return {
|
|
24298
|
+
connector: connectorName,
|
|
24299
|
+
operation: args.operation,
|
|
24300
|
+
profile: args.profile,
|
|
24301
|
+
...commandResult
|
|
24302
|
+
};
|
|
24303
|
+
} catch (error) {
|
|
24304
|
+
return {
|
|
24305
|
+
connector: connectorName,
|
|
24306
|
+
operation: args.operation,
|
|
24307
|
+
profile: args.profile,
|
|
24308
|
+
stdout: "",
|
|
24309
|
+
stderr: error.message,
|
|
24310
|
+
exitCode: 1,
|
|
24311
|
+
success: false
|
|
24312
|
+
};
|
|
24313
|
+
}
|
|
24314
|
+
}
|
|
24315
|
+
const commandArgs = buildConnectorOperationArgs(args);
|
|
24316
|
+
const result = await runConnectorCommand(connectorName, commandArgs, args.timeoutMs ?? 30000);
|
|
24317
|
+
const response = {
|
|
24318
|
+
...result,
|
|
24319
|
+
connector: connectorName,
|
|
24320
|
+
operation: args.operation,
|
|
24321
|
+
profile: args.profile
|
|
24322
|
+
};
|
|
24323
|
+
if (args.parseJson !== false && result.stdout) {
|
|
24324
|
+
try {
|
|
24325
|
+
response.data = JSON.parse(result.stdout);
|
|
24326
|
+
} catch {}
|
|
24327
|
+
}
|
|
24328
|
+
return response;
|
|
24329
|
+
}
|
|
24330
|
+
function normalizeOperationReturn(value) {
|
|
24331
|
+
if (isRunResult(value)) {
|
|
24332
|
+
const result = value;
|
|
24333
|
+
const response = {
|
|
24334
|
+
...result
|
|
24335
|
+
};
|
|
24336
|
+
if (result.stdout) {
|
|
24337
|
+
try {
|
|
24338
|
+
response.data = JSON.parse(result.stdout);
|
|
24339
|
+
} catch {}
|
|
24340
|
+
}
|
|
24341
|
+
return response;
|
|
24342
|
+
}
|
|
24343
|
+
return {
|
|
24344
|
+
data: value,
|
|
24345
|
+
stdout: JSON.stringify(value),
|
|
24346
|
+
stderr: "",
|
|
24347
|
+
exitCode: 0,
|
|
24348
|
+
success: true
|
|
24349
|
+
};
|
|
24350
|
+
}
|
|
24351
|
+
function isRunResult(value) {
|
|
24352
|
+
if (!value || typeof value !== "object")
|
|
24353
|
+
return false;
|
|
24354
|
+
const candidate = value;
|
|
24355
|
+
return typeof candidate.stdout === "string" && typeof candidate.stderr === "string" && typeof candidate.exitCode === "number" && typeof candidate.success === "boolean";
|
|
24356
|
+
}
|
|
24357
|
+
function buildConnectorOperationArgs(args) {
|
|
24358
|
+
const commandArgs = [];
|
|
24359
|
+
if (args.profile) {
|
|
24360
|
+
commandArgs.push("--profile", args.profile);
|
|
24361
|
+
}
|
|
24362
|
+
commandArgs.push(...args.operation.split(".").filter(Boolean));
|
|
24363
|
+
for (const positional of args.input?.args ?? []) {
|
|
24364
|
+
commandArgs.push(String(positional));
|
|
24365
|
+
}
|
|
24366
|
+
let hasFormat = false;
|
|
24367
|
+
for (const [key, value] of Object.entries(args.input ?? {})) {
|
|
24368
|
+
if (key === "args")
|
|
24369
|
+
continue;
|
|
24370
|
+
if (value === undefined || value === null)
|
|
24371
|
+
continue;
|
|
24372
|
+
const flag = `--${key.replace(/_/g, "-").replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}`;
|
|
24373
|
+
if (flag === "--format" || flag === "--json")
|
|
24374
|
+
hasFormat = true;
|
|
24375
|
+
if (typeof value === "boolean") {
|
|
24376
|
+
if (value)
|
|
24377
|
+
commandArgs.push(flag);
|
|
24378
|
+
continue;
|
|
24379
|
+
}
|
|
24380
|
+
if (Array.isArray(value)) {
|
|
24381
|
+
for (const item of value) {
|
|
24382
|
+
commandArgs.push(flag, String(item));
|
|
24383
|
+
}
|
|
24384
|
+
continue;
|
|
24385
|
+
}
|
|
24386
|
+
commandArgs.push(flag, String(value));
|
|
24387
|
+
}
|
|
24388
|
+
if (args.parseJson !== false && !hasFormat) {
|
|
24389
|
+
commandArgs.push("--format", "json");
|
|
24390
|
+
}
|
|
24391
|
+
return commandArgs;
|
|
23230
24392
|
}
|
|
23231
24393
|
function runLegacyConnectorCommand(name, args, timeoutMs = 30000) {
|
|
23232
|
-
const
|
|
24394
|
+
const connectorName = normalizeConnectorName(name);
|
|
24395
|
+
const cliPath = getConnectorCliPath(connectorName);
|
|
23233
24396
|
if (!cliPath) {
|
|
23234
24397
|
return Promise.resolve({
|
|
23235
24398
|
stdout: "",
|
|
23236
|
-
stderr: `Connector '${
|
|
24399
|
+
stderr: `Connector '${connectorName}' not found or has no CLI.`,
|
|
23237
24400
|
exitCode: 1,
|
|
23238
24401
|
success: false
|
|
23239
24402
|
});
|
|
@@ -23241,7 +24404,7 @@ function runLegacyConnectorCommand(name, args, timeoutMs = 30000) {
|
|
|
23241
24404
|
return new Promise((resolve) => {
|
|
23242
24405
|
const proc = spawn("bun", ["run", cliPath, ...args], {
|
|
23243
24406
|
timeout: timeoutMs,
|
|
23244
|
-
env: buildEnvWithCredentials(
|
|
24407
|
+
env: buildEnvWithCredentials(connectorName, process.env),
|
|
23245
24408
|
stdio: ["pipe", "pipe", "pipe"]
|
|
23246
24409
|
});
|
|
23247
24410
|
let stdout = "";
|
|
@@ -23271,7 +24434,8 @@ function runLegacyConnectorCommand(name, args, timeoutMs = 30000) {
|
|
|
23271
24434
|
});
|
|
23272
24435
|
}
|
|
23273
24436
|
async function getConnectorOperations(name) {
|
|
23274
|
-
const
|
|
24437
|
+
const connectorName = normalizeConnectorName(name);
|
|
24438
|
+
const internalRuntime = getInternalCommandRuntime(connectorName);
|
|
23275
24439
|
if (internalRuntime?.commands.length) {
|
|
23276
24440
|
const operations2 = internalRuntime.commands.map((command) => ({
|
|
23277
24441
|
name: command.name,
|
|
@@ -23283,15 +24447,15 @@ async function getConnectorOperations(name) {
|
|
|
23283
24447
|
return {
|
|
23284
24448
|
commands: operations2.map((operation) => operation.name),
|
|
23285
24449
|
operations: operations2,
|
|
23286
|
-
helpText: internalRuntime.helpText ?? buildInternalHelpText(
|
|
24450
|
+
helpText: internalRuntime.helpText ?? buildInternalHelpText(connectorName, internalRuntime.commands),
|
|
23287
24451
|
hasCli: true
|
|
23288
24452
|
};
|
|
23289
24453
|
}
|
|
23290
|
-
const cliPath = getConnectorCliPath(
|
|
24454
|
+
const cliPath = getConnectorCliPath(connectorName);
|
|
23291
24455
|
if (!cliPath) {
|
|
23292
24456
|
return { commands: [], operations: [], helpText: "", hasCli: false };
|
|
23293
24457
|
}
|
|
23294
|
-
const result = await runConnectorCommand(
|
|
24458
|
+
const result = await runConnectorCommand(connectorName, ["--help"]);
|
|
23295
24459
|
const helpText = result.stdout || result.stderr;
|
|
23296
24460
|
const operations = parseCommanderHelpOperations(helpText);
|
|
23297
24461
|
return {
|
|
@@ -23302,7 +24466,8 @@ async function getConnectorOperations(name) {
|
|
|
23302
24466
|
};
|
|
23303
24467
|
}
|
|
23304
24468
|
async function getConnectorCommandHelp(name, command) {
|
|
23305
|
-
const
|
|
24469
|
+
const connectorName = normalizeConnectorName(name);
|
|
24470
|
+
const internalRuntime = getInternalCommandRuntime(connectorName);
|
|
23306
24471
|
if (internalRuntime?.getHelp) {
|
|
23307
24472
|
const help = await internalRuntime.getHelp(command);
|
|
23308
24473
|
if (help) {
|
|
@@ -23313,17 +24478,15 @@ async function getConnectorCommandHelp(name, command) {
|
|
|
23313
24478
|
if (runtimeCommand?.helpText) {
|
|
23314
24479
|
return runtimeCommand.helpText;
|
|
23315
24480
|
}
|
|
23316
|
-
const result = await runConnectorCommand(
|
|
24481
|
+
const result = await runConnectorCommand(connectorName, [command, "--help"]);
|
|
23317
24482
|
return result.stdout || result.stderr;
|
|
23318
24483
|
}
|
|
23319
24484
|
function getConnectorsWithCli() {
|
|
23320
24485
|
const connectors18 = new Set;
|
|
23321
24486
|
try {
|
|
23322
|
-
const dirs =
|
|
24487
|
+
const dirs = readdirSync9(CONNECTORS_DIR2);
|
|
23323
24488
|
for (const dir of dirs) {
|
|
23324
|
-
|
|
23325
|
-
continue;
|
|
23326
|
-
const name = dir.replace("connect-", "");
|
|
24489
|
+
const name = normalizeConnectorName(dir);
|
|
23327
24490
|
if (getConnectorCliPath(name)) {
|
|
23328
24491
|
connectors18.add(name);
|
|
23329
24492
|
}
|
|
@@ -23336,8 +24499,74 @@ function getConnectorsWithCli() {
|
|
|
23336
24499
|
}
|
|
23337
24500
|
return [...connectors18].sort();
|
|
23338
24501
|
}
|
|
24502
|
+
// src/lib/manifest.ts
|
|
24503
|
+
function buildAliases(name) {
|
|
24504
|
+
const canonicalName = normalizeConnectorName(name);
|
|
24505
|
+
const legacyName = legacyConnectorName(canonicalName);
|
|
24506
|
+
return canonicalName === legacyName ? [canonicalName] : [canonicalName, legacyName];
|
|
24507
|
+
}
|
|
24508
|
+
async function loadOperations(name, includeOperations) {
|
|
24509
|
+
if (!includeOperations || !hasConnectorCommandSurface(name))
|
|
24510
|
+
return;
|
|
24511
|
+
return (await getConnectorOperations(name)).operations;
|
|
24512
|
+
}
|
|
24513
|
+
async function getConnectorCapability(name, options = {}) {
|
|
24514
|
+
const connectorName = normalizeConnectorName(name);
|
|
24515
|
+
const meta = getConnector(connectorName);
|
|
24516
|
+
if (!meta)
|
|
24517
|
+
return null;
|
|
24518
|
+
const docs = getConnectorDocs(connectorName);
|
|
24519
|
+
const configPaths = resolveConnectorConfigPaths(connectorName);
|
|
24520
|
+
const operations = await loadOperations(connectorName, options.includeOperations);
|
|
24521
|
+
return {
|
|
24522
|
+
...meta,
|
|
24523
|
+
id: connectorName,
|
|
24524
|
+
aliases: buildAliases(connectorName),
|
|
24525
|
+
runtime: {
|
|
24526
|
+
packageName: "@hasna/connectors",
|
|
24527
|
+
connectorId: connectorName,
|
|
24528
|
+
legacyConnectorId: legacyConnectorName(connectorName),
|
|
24529
|
+
packagePath: getConnectorPath(connectorName),
|
|
24530
|
+
configDirName: configPaths.preferredDirName,
|
|
24531
|
+
legacyConfigDirName: configPaths.legacyName,
|
|
24532
|
+
internal: Boolean(getInternalConnectorDefinition(connectorName)),
|
|
24533
|
+
packageDirectory: connectorExists(connectorName),
|
|
24534
|
+
commandSurface: hasConnectorCommandSurface(connectorName)
|
|
24535
|
+
},
|
|
24536
|
+
auth: {
|
|
24537
|
+
type: getAuthType(connectorName),
|
|
24538
|
+
summary: docs?.auth ?? null,
|
|
24539
|
+
envVars: docs?.envVars ?? []
|
|
24540
|
+
},
|
|
24541
|
+
docs: {
|
|
24542
|
+
overview: docs?.overview ?? "",
|
|
24543
|
+
cliCommands: docs?.cliCommands ?? "",
|
|
24544
|
+
dataStorage: docs?.dataStorage ?? null
|
|
24545
|
+
},
|
|
24546
|
+
...operations ? { operations } : {}
|
|
24547
|
+
};
|
|
24548
|
+
}
|
|
24549
|
+
async function getConnectorCapabilityManifest(options = {}) {
|
|
24550
|
+
const names = options.connectorNames ? [...new Set(options.connectorNames.map((name) => normalizeConnectorName(name)))].sort() : CONNECTORS.map((connector) => connector.name);
|
|
24551
|
+
const connectors18 = [];
|
|
24552
|
+
for (const name of names) {
|
|
24553
|
+
const capability = await getConnectorCapability(name, options);
|
|
24554
|
+
if (capability)
|
|
24555
|
+
connectors18.push(capability);
|
|
24556
|
+
}
|
|
24557
|
+
return {
|
|
24558
|
+
version: 1,
|
|
24559
|
+
packageName: "@hasna/connectors",
|
|
24560
|
+
packageVersion: package_default.version,
|
|
24561
|
+
generatedAt: new Date().toISOString(),
|
|
24562
|
+
categories: CATEGORIES,
|
|
24563
|
+
connectorCount: connectors18.length,
|
|
24564
|
+
connectors: connectors18
|
|
24565
|
+
};
|
|
24566
|
+
}
|
|
23339
24567
|
export {
|
|
23340
24568
|
searchConnectors,
|
|
24569
|
+
runConnectorOperation,
|
|
23341
24570
|
runConnectorCommand,
|
|
23342
24571
|
removeConnector,
|
|
23343
24572
|
loadConnectorVersions,
|
|
@@ -23354,10 +24583,13 @@ export {
|
|
|
23354
24583
|
getConnectorDocs,
|
|
23355
24584
|
getConnectorCommandHelp,
|
|
23356
24585
|
getConnectorCliPath,
|
|
24586
|
+
getConnectorCapabilityManifest,
|
|
24587
|
+
getConnectorCapability,
|
|
23357
24588
|
getConnector,
|
|
23358
24589
|
executeConnectorOperation,
|
|
23359
24590
|
defineConnector,
|
|
23360
24591
|
connectorExists,
|
|
24592
|
+
buildConnectorOperationArgs,
|
|
23361
24593
|
ConnectorOperationNotFoundError,
|
|
23362
24594
|
ConnectorDefinitionError,
|
|
23363
24595
|
CONNECTORS,
|