@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/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 existsSync8, readFileSync as readFileSync3 } from "fs";
19
- import { join as join9, dirname as dirname5 } from "path";
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 = /^[a-z0-9:_-]+$/;
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", "connect-github"),
4117
- join(__dirname2, "..", "..", "connectors", "connect-github"),
4118
- join(__dirname2, "..", "connectors", "connect-github"),
4119
- join(process.cwd(), "connectors", "connect-github")
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.24",
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 existsSync6,
5855
+ existsSync as existsSync7,
5061
5856
  mkdirSync as mkdirSync5,
5062
- readFileSync as readFileSync2,
5063
- readdirSync as readdirSync5,
5857
+ readFileSync as readFileSync4,
5858
+ readdirSync as readdirSync6,
5064
5859
  rmSync,
5065
- statSync as statSync2,
5066
- writeFileSync as writeFileSync2
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 existsSync2,
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 join4 } from "path";
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 = join2(HASNA_DIR, serviceName);
14255
- mkdirSync(dir, { recursive: true });
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 join2(dir, `${serviceName}.db`);
15059
+ return join4(dir, `${serviceName}.db`);
14261
15060
  }
14262
15061
  var HASNA_DIR;
14263
15062
  var init_dotfile = __esm(() => {
14264
- HASNA_DIR = join2(homedir(), ".hasna");
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 = readFileSync(CONFIG_PATH, "utf-8");
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
- mkdirSync2(CONFIG_DIR, { recursive: true });
14295
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
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(homedir2(), ".hasna", "cloud");
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 = join3(homedir3(), ".hasna");
14360
- if (!existsSync3(dataDir))
15158
+ const dataDir = join32(homedir32(), ".hasna");
15159
+ if (!existsSync32(dataDir))
14361
15160
  return [];
14362
15161
  try {
14363
- const entries = readdirSync2(dataDir, { withFileTypes: true });
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 = join3(homedir3(), ".hasna", service);
14382
- if (!existsSync3(dataDir))
15180
+ const dataDir = join32(homedir32(), ".hasna", service);
15181
+ if (!existsSync32(dataDir))
14383
15182
  return null;
14384
15183
  const candidates = [
14385
- join3(dataDir, `${service}.db`),
14386
- join3(dataDir, "data.db"),
14387
- join3(dataDir, "database.db")
15184
+ join32(dataDir, `${service}.db`),
15185
+ join32(dataDir, "data.db"),
15186
+ join32(dataDir, "database.db")
14388
15187
  ];
14389
15188
  try {
14390
- const files = readdirSync2(dataDir);
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(join3(dataDir, f));
15192
+ candidates.push(join32(dataDir, f));
14394
15193
  }
14395
15194
  }
14396
15195
  } catch {}
14397
15196
  for (const p of candidates) {
14398
- if (existsSync3(p))
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 = join4(homedir4(), ".hasna", "cloud", "config.json");
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 mkdirSync3, existsSync as existsSync4, readdirSync as readdirSync3, copyFileSync as copyFileSync2, statSync } from "fs";
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 (!existsSync4(sourceDir)) {
15399
+ if (!existsSync5(sourceDir)) {
14601
15400
  return;
14602
15401
  }
14603
- mkdirSync3(targetDir, { recursive: true });
14604
- for (const entry of readdirSync3(sourceDir)) {
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 (!existsSync4(targetPath)) {
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
- mkdirSync3(newDir, { recursive: true });
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 CONNECTOR_DIRNAME = "connect-imessage";
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/connect-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.
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 join7(getConnectorsHome(), CONNECTOR_DIRNAME);
15671
+ return getConnectorConfigDir(CONNECTOR_NAME);
15672
+ }
15673
+ function getConfigReadDirs() {
15674
+ return getConnectorConfigReadDirs(CONNECTOR_NAME);
14796
15675
  }
14797
15676
  function getProfilesDir() {
14798
- return join7(getConfigDir2(), "profiles");
15677
+ return join9(getConfigDir2(), "profiles");
14799
15678
  }
14800
15679
  function getCurrentProfile() {
14801
- const currentProfileFile = join7(getConfigDir2(), "current_profile");
14802
- if (!existsSync6(currentProfileFile)) {
14803
- return "default";
14804
- }
14805
- try {
14806
- return readFileSync2(currentProfileFile, "utf-8").trim() || "default";
14807
- } catch {
14808
- return "default";
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
- writeFileSync2(join7(configDir, "current_profile"), profile);
15695
+ writeFileSync4(join9(configDir, "current_profile"), profile);
14815
15696
  }
14816
15697
  function getFlatProfilePath(profile) {
14817
- return join7(getProfilesDir(), `${profile}.json`);
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 getDirectoryProfilePath(profile) {
14820
- return join7(getProfilesDir(), profile, "config.json");
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(readFileSync2(path, "utf-8"));
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 = existsSync6(getFlatProfilePath(profile)) ? loadJsonFile(getFlatProfilePath(profile)) : {};
14841
- const directoryConfig = existsSync6(getDirectoryProfilePath(profile)) ? loadJsonFile(getDirectoryProfilePath(profile)) : {};
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
- writeFileSync2(getFlatProfilePath(profile), JSON.stringify(config, null, 2) + `
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 existsSync6(getFlatProfilePath(profile)) || existsSync6(join7(getProfilesDir(), profile));
15741
+ return getFlatProfileReadPaths(profile).some((path) => existsSync7(path)) || getConfigReadDirs().some((dir) => existsSync7(join9(dir, "profiles", profile)));
14858
15742
  }
14859
- function listProfiles() {
14860
- const profilesDir = getProfilesDir();
15743
+ function listProfiles3() {
14861
15744
  const seen = new Set(["default"]);
14862
- if (!existsSync6(profilesDir)) {
14863
- return [...seen];
14864
- }
14865
- try {
14866
- for (const entry of readdirSync5(profilesDir)) {
14867
- const fullPath = join7(profilesDir, entry);
14868
- const stat = statSync2(fullPath);
14869
- if (stat.isDirectory()) {
14870
- seen.add(entry);
14871
- } else if (entry.endsWith(".json")) {
14872
- seen.add(entry.replace(/\.json$/, ""));
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
- } catch {}
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 = join7(getProfilesDir(), profile);
14889
- if (existsSync6(flatPath)) {
15773
+ const directoryPath = join9(getProfilesDir(), profile);
15774
+ if (existsSync7(flatPath)) {
14890
15775
  rmSync(flatPath);
14891
15776
  }
14892
- if (existsSync6(directoryPath)) {
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 replyToMessage(context, input) {
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 = listProfiles();
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 replyToMessage(resolved, {
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 replyToMessage(context, input);
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 existsSync7 } from "fs";
15698
- import { dirname as dirname4, join as join8 } from "path";
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
- join8(__dirname3, "..", "..", "..", "connectors", "connect-stripe"),
15704
- join8(__dirname3, "..", "..", "connectors", "connect-stripe"),
15705
- join8(__dirname3, "..", "connectors", "connect-stripe"),
15706
- join8(process.cwd(), "connectors", "connect-stripe")
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 (existsSync7(candidate)) {
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(join8(CONNECTOR_DIR2, "src", "api", "index.ts")).href);
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(join8(CONNECTOR_DIR2, "src", "utils", "config.ts")).href);
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
- return CONNECTORS.find((c) => c.name === name);
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
- join9(thisDir, "..", "connectors"),
22847
- join9(thisDir, "..", "..", "connectors")
23736
+ join11(thisDir, "..", "connectors"),
23737
+ join11(thisDir, "..", "..", "connectors")
22848
23738
  ];
22849
- const connectorsDir = candidates.find((d) => existsSync8(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 = join9(connectorsDir, `connect-${connector.name}`, "package.json");
22855
- if (existsSync8(pkgPath)) {
22856
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
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 existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, readdirSync as readdirSync6, statSync as statSync3, rmSync as rmSync2 } from "fs";
22869
- import { join as join10, dirname as dirname6 } from "path";
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 = join10(__dirname4, "..", "connectors");
22874
- if (existsSync9(fromBin))
23763
+ const fromBin = join12(__dirname4, "..", "connectors");
23764
+ if (existsSync10(fromBin))
22875
23765
  return fromBin;
22876
- const fromSrc = join10(__dirname4, "..", "..", "connectors");
22877
- if (existsSync9(fromSrc))
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 join10(targetDir, PROJECT_CONNECTORS_DIRNAME);
23776
+ return join12(targetDir, PROJECT_CONNECTORS_DIRNAME);
22890
23777
  }
22891
23778
  function getEnablementManifestPath(targetDir) {
22892
- return join10(getProjectConnectorsDir(targetDir), ENABLEMENT_MANIFEST_FILENAME);
23779
+ return join12(getProjectConnectorsDir(targetDir), ENABLEMENT_MANIFEST_FILENAME);
22893
23780
  }
22894
23781
  function getLegacyInstallPath(targetDir, name) {
22895
- return join10(getProjectConnectorsDir(targetDir), `connect-${normalizeConnectorName(name)}`);
23782
+ return join12(getProjectConnectorsDir(targetDir), legacyConnectorName(name));
22896
23783
  }
22897
23784
  function loadEnablementManifest(targetDir) {
22898
23785
  const manifestPath = getEnablementManifestPath(targetDir);
22899
- if (!existsSync9(manifestPath)) {
23786
+ if (!existsSync10(manifestPath)) {
22900
23787
  return null;
22901
23788
  }
22902
23789
  try {
22903
- const raw = JSON.parse(readFileSync4(manifestPath, "utf-8"));
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 (!existsSync9(connectorsDir)) {
23806
+ if (!existsSync10(connectorsDir)) {
22920
23807
  return [];
22921
23808
  }
22922
- return readdirSync6(connectorsDir).filter((entry) => {
22923
- const fullPath = join10(connectorsDir, entry);
22924
- return entry.startsWith("connect-") && statSync3(fullPath).isDirectory();
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 = join10(connectorsDir, ENABLEMENT_INDEX_FILENAME);
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
- writeFileSync3(indexPath, content);
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
- writeFileSync3(getEnablementManifestPath(targetDir), JSON.stringify(manifest, null, 2) + `
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
- const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
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) || existsSync9(getConnectorPath(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 && existsSync9(legacyInstallPath)) {
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 = join10(connectorPath, "CLAUDE.md");
23038
- if (existsSync9(claudeMdPath)) {
23039
- return parseConnectorDocs(readFileSync4(claudeMdPath, "utf-8"));
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 (existsSync9(legacyInstallPath)) {
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 existsSync10, readdirSync as readdirSync7 } from "fs";
23090
- import { join as join11, dirname as dirname7 } from "path";
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 = join11(__dirname5, "..", "connectors");
23096
- if (existsSync10(fromBin))
24129
+ const fromBin = join14(__dirname5, "..", "connectors");
24130
+ if (existsSync12(fromBin))
23097
24131
  return fromBin;
23098
- const fromSrc = join11(__dirname5, "..", "..", "connectors");
23099
- if (existsSync10(fromSrc))
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 prefixes = ENV_VAR_NAME_OVERRIDES[connectorName] || [
23129
- connectorName.toUpperCase().replace(/-/g, "_")
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 = join11(CONNECTORS_DIR2, `connect-${safeName}`);
23155
- const cliPath = join11(connectorDir, "src", "cli", "index.ts");
23156
- if (existsSync10(cliPath))
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(name);
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(name, args, timeoutMs);
24281
+ return runLegacyConnectorCommand(connectorName, args, timeoutMs);
23227
24282
  });
23228
24283
  }
23229
- return runLegacyConnectorCommand(name, args, timeoutMs);
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 cliPath = getConnectorCliPath(name);
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 '${name}' not found or has no CLI.`,
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(name, process.env),
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 internalRuntime = getInternalCommandRuntime(name);
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(name, internalRuntime.commands),
24450
+ helpText: internalRuntime.helpText ?? buildInternalHelpText(connectorName, internalRuntime.commands),
23287
24451
  hasCli: true
23288
24452
  };
23289
24453
  }
23290
- const cliPath = getConnectorCliPath(name);
24454
+ const cliPath = getConnectorCliPath(connectorName);
23291
24455
  if (!cliPath) {
23292
24456
  return { commands: [], operations: [], helpText: "", hasCli: false };
23293
24457
  }
23294
- const result = await runConnectorCommand(name, ["--help"]);
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 internalRuntime = getInternalCommandRuntime(name);
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(name, [command, "--help"]);
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 = readdirSync7(CONNECTORS_DIR2);
24487
+ const dirs = readdirSync9(CONNECTORS_DIR2);
23323
24488
  for (const dir of dirs) {
23324
- if (!dir.startsWith("connect-"))
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,