wizards 0.1.9
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/LICENSE +110 -0
- package/README.md +170 -0
- package/apps/runtime/dist/agents/architect.js +453 -0
- package/apps/runtime/dist/agents/project-assistant.js +354 -0
- package/apps/runtime/dist/agents/subscription.js +206 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +197 -0
- package/apps/runtime/dist/app.js +153 -0
- package/apps/runtime/dist/auth/account.js +121 -0
- package/apps/runtime/dist/auth/index.js +329 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +23 -0
- package/apps/runtime/dist/authoring/example.js +73 -0
- package/apps/runtime/dist/authoring/guide.js +259 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/cli/autostart.js +134 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/environment.js +81 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/home.js +74 -0
- package/apps/runtime/dist/cli/machine.js +145 -0
- package/apps/runtime/dist/cli/main.js +125 -0
- package/apps/runtime/dist/cli/setup.js +273 -0
- package/apps/runtime/dist/cli/start.js +190 -0
- package/apps/runtime/dist/cli/status.js +120 -0
- package/apps/runtime/dist/cli/ui.js +12 -0
- package/apps/runtime/dist/cli/update.js +58 -0
- package/apps/runtime/dist/connectors/builtin.js +70 -0
- package/apps/runtime/dist/connectors/external.js +400 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +217 -0
- package/apps/runtime/dist/connectors/index.js +266 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/credits/credits.js +103 -0
- package/apps/runtime/dist/credits/estimate.js +132 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +146 -0
- package/apps/runtime/dist/db/control-schema.js +70 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/schema.js +278 -0
- package/apps/runtime/dist/documents/parse.js +230 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +60 -0
- package/apps/runtime/dist/engine/events.js +46 -0
- package/apps/runtime/dist/engine/input.js +170 -0
- package/apps/runtime/dist/engine/prepare.js +51 -0
- package/apps/runtime/dist/engine/requirements.js +124 -0
- package/apps/runtime/dist/engine/resources.js +89 -0
- package/apps/runtime/dist/engine/runner.js +560 -0
- package/apps/runtime/dist/engine/steps.js +598 -0
- package/apps/runtime/dist/engine/template.js +183 -0
- package/apps/runtime/dist/engine/types.js +3 -0
- package/apps/runtime/dist/env.js +224 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +95 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/claude.js +138 -0
- package/apps/runtime/dist/harness/codex.js +228 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/gemini.js +126 -0
- package/apps/runtime/dist/harness/index.js +99 -0
- package/apps/runtime/dist/harness/model.js +267 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +193 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +76 -0
- package/apps/runtime/dist/limits.js +57 -0
- package/apps/runtime/dist/link-preview.js +59 -0
- package/apps/runtime/dist/manage.js +151 -0
- package/apps/runtime/dist/mcp/auth.js +102 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/handler.js +42 -0
- package/apps/runtime/dist/mcp/scopes.js +5 -0
- package/apps/runtime/dist/mcp/tools.js +401 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +30 -0
- package/apps/runtime/dist/model-errors.js +3 -0
- package/apps/runtime/dist/models.js +489 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/render/chromium.js +85 -0
- package/apps/runtime/dist/render/convert.js +161 -0
- package/apps/runtime/dist/render/downloads.js +277 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/routes/api.js +74 -0
- package/apps/runtime/dist/routes/delivery.js +52 -0
- package/apps/runtime/dist/routes/internal.js +49 -0
- package/apps/runtime/dist/routes/marketplace.js +36 -0
- package/apps/runtime/dist/routes/runs.js +805 -0
- package/apps/runtime/dist/routes/studio.js +702 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/agentos.js +191 -0
- package/apps/runtime/dist/sandbox/docker.js +77 -0
- package/apps/runtime/dist/sandbox/index.js +30 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +34 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/architect.js +92 -0
- package/apps/runtime/dist/services/brand.js +20 -0
- package/apps/runtime/dist/services/cloud.js +52 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/errors.js +24 -0
- package/apps/runtime/dist/services/files.js +129 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +292 -0
- package/apps/runtime/dist/services/package.js +202 -0
- package/apps/runtime/dist/services/profile.js +23 -0
- package/apps/runtime/dist/services/project-files.js +266 -0
- package/apps/runtime/dist/services/project-index.js +109 -0
- package/apps/runtime/dist/services/projects.js +181 -0
- package/apps/runtime/dist/services/runs.js +224 -0
- package/apps/runtime/dist/services/shares.js +129 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizards.js +342 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/index.js +279 -0
- package/apps/runtime/dist/tenants/control.js +106 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/tools/browser.js +380 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +234 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +59 -0
- package/apps/runtime/dist/tools/project.js +53 -0
- package/apps/runtime/dist/tools/shared.js +42 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +118 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +312 -0
- package/apps/runtime/dist/widgets/runtime.js +84 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +192 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
- package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
- package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
- package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
- package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
- package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
- package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
- package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
- package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
- package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
- package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
- package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
- package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
- package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
- package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
- package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
- package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
- package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
- package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
- package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
- package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
- package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
- package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
- package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
- package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
- package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
- package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
- package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
- package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
- package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
- package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
- package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
- package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
- package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
- package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
- package/apps/web/dist/embed.js +102 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +46 -0
- package/apps/web/dist/install.sh +532 -0
- package/apps/web/dist/manifest.webmanifest +20 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +13 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +62 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { defineConnector, } from "../../shims/connections-sdk.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { connectorAction, GOOGLE_OAUTH2, googleFetch, googleJson, } from "../shared.js";
|
|
4
|
+
const CALENDAR_API = "https://www.googleapis.com/calendar/v3";
|
|
5
|
+
const SCOPE_READONLY = "https://www.googleapis.com/auth/calendar.readonly";
|
|
6
|
+
const SCOPE_EVENTS = "https://www.googleapis.com/auth/calendar.events";
|
|
7
|
+
const ALL_DAY_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
8
|
+
function eventTime(t) {
|
|
9
|
+
return t?.dateTime ?? t?.date ?? null;
|
|
10
|
+
}
|
|
11
|
+
function toEventSummary(e) {
|
|
12
|
+
return {
|
|
13
|
+
all_day: Boolean(e.start?.date),
|
|
14
|
+
end: eventTime(e.end),
|
|
15
|
+
etag: e.etag ?? null,
|
|
16
|
+
event_id: e.id,
|
|
17
|
+
html_link: e.htmlLink ?? null,
|
|
18
|
+
location: e.location ?? null,
|
|
19
|
+
private_properties: e.extendedProperties?.private ?? null,
|
|
20
|
+
start: eventTime(e.start),
|
|
21
|
+
status: e.status ?? null,
|
|
22
|
+
summary: e.summary ?? null,
|
|
23
|
+
// RFC3339 last-modified stamp; drives the pull-back conflict rule.
|
|
24
|
+
updated: e.updated ?? null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Bounds the incremental-sync page walk so a huge changelog can't hang. */
|
|
28
|
+
const MAX_SYNC_PAGES = 20;
|
|
29
|
+
const privatePropertiesField = z
|
|
30
|
+
.record(z.string(), z.string())
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Private key/value metadata stored on the event (extendedProperties.private), not visible to attendees. Used to tag events created by an integration.");
|
|
33
|
+
/**
|
|
34
|
+
* "2026-07-04" → { date } (all-day); a timestamp → { dateTime }. When
|
|
35
|
+
* `timeZone` is given, a zone-less local timestamp is resolved by Google in
|
|
36
|
+
* that IANA zone (DST-safe) instead of requiring an explicit offset.
|
|
37
|
+
*/
|
|
38
|
+
function toEventDateTime(value, timeZone) {
|
|
39
|
+
if (ALL_DAY_DATE.test(value)) {
|
|
40
|
+
return { date: value };
|
|
41
|
+
}
|
|
42
|
+
return timeZone ? { dateTime: value, timeZone } : { dateTime: value };
|
|
43
|
+
}
|
|
44
|
+
const timeZoneField = z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('IANA time zone (e.g. "Europe/Vienna") for timed start/end given without an offset. Optional when start/end already carry an offset.');
|
|
48
|
+
const calendarIdField = z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('Calendar id (default "primary", the user\'s main calendar).');
|
|
52
|
+
const startField = z
|
|
53
|
+
.string()
|
|
54
|
+
.describe('Event start: ISO 8601 timestamp with offset (e.g. "2026-07-04T10:00:00+02:00") or a plain date "YYYY-MM-DD" for an all-day event.');
|
|
55
|
+
const endField = z
|
|
56
|
+
.string()
|
|
57
|
+
.describe('Event end: ISO 8601 timestamp, or a plain date "YYYY-MM-DD" (exclusive) for an all-day event.');
|
|
58
|
+
const attendeesField = z
|
|
59
|
+
.array(z.string().describe("Attendee email address."))
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Optional list of attendee email addresses.");
|
|
62
|
+
export const calendarConnector = defineConnector({
|
|
63
|
+
actions: [
|
|
64
|
+
connectorAction({
|
|
65
|
+
description: "List the calendars the connected account can see, with id, name, primary flag and access role.",
|
|
66
|
+
group: "read",
|
|
67
|
+
handler: async (_input, ctx) => {
|
|
68
|
+
const data = await googleJson(ctx, `${CALENDAR_API}/users/me/calendarList?maxResults=100`);
|
|
69
|
+
return {
|
|
70
|
+
calendars: (data.items ?? []).map((c) => ({
|
|
71
|
+
access_role: c.accessRole ?? null,
|
|
72
|
+
description: c.description ?? null,
|
|
73
|
+
id: c.id,
|
|
74
|
+
primary: Boolean(c.primary),
|
|
75
|
+
summary: c.summary ?? null,
|
|
76
|
+
time_zone: c.timeZone ?? null,
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
id: "list_calendars",
|
|
81
|
+
inputSchema: z.object({}),
|
|
82
|
+
providerScopes: [SCOPE_READONLY],
|
|
83
|
+
summary: "List Google calendars",
|
|
84
|
+
}),
|
|
85
|
+
connectorAction({
|
|
86
|
+
description: "List events from a calendar in a time window, ordered by start time (recurring events expanded to single instances). For incremental two-way sync, pass a prior `sync_token` to fetch only changes since then, or set `return_sync_token` to receive a `next_sync_token` for the next incremental pull.",
|
|
87
|
+
group: "read",
|
|
88
|
+
handler: async (input, ctx) => {
|
|
89
|
+
const calendarId = input.calendar_id ?? "primary";
|
|
90
|
+
const eventsUrl = `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events`;
|
|
91
|
+
// Sync mode: a token was supplied, or the caller wants one back. Both
|
|
92
|
+
// require draining every page so the terminal nextSyncToken is captured
|
|
93
|
+
// (Google only returns it on the last page of a completed sync).
|
|
94
|
+
const syncMode = input.sync_token !== undefined || input.return_sync_token === true;
|
|
95
|
+
if (!syncMode) {
|
|
96
|
+
const url = new URL(eventsUrl);
|
|
97
|
+
url.searchParams.set("singleEvents", "true");
|
|
98
|
+
url.searchParams.set("orderBy", "startTime");
|
|
99
|
+
url.searchParams.set("maxResults", String(input.max_results ?? 10));
|
|
100
|
+
if (input.time_min) {
|
|
101
|
+
url.searchParams.set("timeMin", input.time_min);
|
|
102
|
+
}
|
|
103
|
+
if (input.time_max) {
|
|
104
|
+
url.searchParams.set("timeMax", input.time_max);
|
|
105
|
+
}
|
|
106
|
+
const data = await googleJson(ctx, url.toString());
|
|
107
|
+
return {
|
|
108
|
+
calendar_id: calendarId,
|
|
109
|
+
next_sync_token: null,
|
|
110
|
+
sync_token_expired: false,
|
|
111
|
+
events: (data.items ?? []).map((e) => ({
|
|
112
|
+
...toEventSummary(e),
|
|
113
|
+
attendee_count: e.attendees?.length ?? 0,
|
|
114
|
+
})),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const items = [];
|
|
118
|
+
let pageToken;
|
|
119
|
+
let nextSyncToken = null;
|
|
120
|
+
for (let page = 0; page < MAX_SYNC_PAGES; page += 1) {
|
|
121
|
+
const url = new URL(eventsUrl);
|
|
122
|
+
// singleEvents must stay constant across a sync series; syncToken is
|
|
123
|
+
// incompatible with orderBy/timeMin/timeMax (they only seed the
|
|
124
|
+
// initial full sync when no token is present).
|
|
125
|
+
url.searchParams.set("singleEvents", "true");
|
|
126
|
+
url.searchParams.set("maxResults", String(input.max_results ?? 250));
|
|
127
|
+
if (input.sync_token) {
|
|
128
|
+
url.searchParams.set("syncToken", input.sync_token);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
url.searchParams.set("orderBy", "startTime");
|
|
132
|
+
if (input.time_min) {
|
|
133
|
+
url.searchParams.set("timeMin", input.time_min);
|
|
134
|
+
}
|
|
135
|
+
if (input.time_max) {
|
|
136
|
+
url.searchParams.set("timeMax", input.time_max);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (pageToken) {
|
|
140
|
+
url.searchParams.set("pageToken", pageToken);
|
|
141
|
+
}
|
|
142
|
+
const res = await ctx.fetchImpl(url.toString(), {
|
|
143
|
+
headers: { Authorization: `Bearer ${ctx.accessToken}` },
|
|
144
|
+
});
|
|
145
|
+
// 410 GONE ⇒ the sync token expired; the caller must re-list in full.
|
|
146
|
+
if (res.status === 410) {
|
|
147
|
+
return {
|
|
148
|
+
calendar_id: calendarId,
|
|
149
|
+
next_sync_token: null,
|
|
150
|
+
sync_token_expired: true,
|
|
151
|
+
events: [],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (!res.ok) {
|
|
155
|
+
const body = (await res.text().catch(() => "")).slice(0, 200);
|
|
156
|
+
throw new Error(`google_api_error (${res.status}): ${body}`);
|
|
157
|
+
}
|
|
158
|
+
const data = (await res.json());
|
|
159
|
+
items.push(...(data.items ?? []));
|
|
160
|
+
if (data.nextSyncToken) {
|
|
161
|
+
nextSyncToken = data.nextSyncToken;
|
|
162
|
+
}
|
|
163
|
+
if (!data.nextPageToken) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
pageToken = data.nextPageToken;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
calendar_id: calendarId,
|
|
170
|
+
next_sync_token: nextSyncToken,
|
|
171
|
+
sync_token_expired: false,
|
|
172
|
+
events: items.map((e) => ({
|
|
173
|
+
...toEventSummary(e),
|
|
174
|
+
attendee_count: e.attendees?.length ?? 0,
|
|
175
|
+
})),
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
id: "list_events",
|
|
179
|
+
inputSchema: z.object({
|
|
180
|
+
calendar_id: calendarIdField,
|
|
181
|
+
max_results: z
|
|
182
|
+
.number()
|
|
183
|
+
.int()
|
|
184
|
+
.min(1)
|
|
185
|
+
.max(250)
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Maximum number of events to return (1-250, default 10)."),
|
|
188
|
+
return_sync_token: z
|
|
189
|
+
.boolean()
|
|
190
|
+
.optional()
|
|
191
|
+
.describe("Drain all pages and return a `next_sync_token` to seed the next incremental pull (used for two-way sync)."),
|
|
192
|
+
sync_token: z
|
|
193
|
+
.string()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("Incremental sync token from a prior `next_sync_token`. Returns only events changed since then (incompatible with time_min/time_max/order). If expired, the response sets `sync_token_expired`."),
|
|
196
|
+
time_max: z
|
|
197
|
+
.string()
|
|
198
|
+
.optional()
|
|
199
|
+
.describe('Only events starting before this ISO 8601 timestamp (e.g. "2026-07-11T00:00:00Z").'),
|
|
200
|
+
time_min: z
|
|
201
|
+
.string()
|
|
202
|
+
.optional()
|
|
203
|
+
.describe('Only events ending after this ISO 8601 timestamp (e.g. "2026-07-04T00:00:00Z").'),
|
|
204
|
+
}),
|
|
205
|
+
providerScopes: [SCOPE_READONLY],
|
|
206
|
+
summary: "List calendar events",
|
|
207
|
+
}),
|
|
208
|
+
connectorAction({
|
|
209
|
+
description: "Fetch one calendar event by id with full details: description, organizer, attendees and their response status.",
|
|
210
|
+
group: "read",
|
|
211
|
+
handler: async (input, ctx) => {
|
|
212
|
+
const calendarId = input.calendar_id ?? "primary";
|
|
213
|
+
const e = await googleJson(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`);
|
|
214
|
+
return {
|
|
215
|
+
...toEventSummary(e),
|
|
216
|
+
attendees: (e.attendees ?? []).map((a) => ({
|
|
217
|
+
display_name: a.displayName ?? null,
|
|
218
|
+
email: a.email ?? null,
|
|
219
|
+
optional: Boolean(a.optional),
|
|
220
|
+
response_status: a.responseStatus ?? null,
|
|
221
|
+
})),
|
|
222
|
+
calendar_id: calendarId,
|
|
223
|
+
description: e.description ?? null,
|
|
224
|
+
organizer: e.organizer?.email ?? null,
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
id: "get_event",
|
|
228
|
+
inputSchema: z.object({
|
|
229
|
+
calendar_id: calendarIdField,
|
|
230
|
+
event_id: z.string().describe("Calendar event id."),
|
|
231
|
+
}),
|
|
232
|
+
providerScopes: [SCOPE_READONLY],
|
|
233
|
+
summary: "Get a calendar event",
|
|
234
|
+
}),
|
|
235
|
+
connectorAction({
|
|
236
|
+
description: "Create a calendar event (timed or all-day), optionally inviting attendees by email.",
|
|
237
|
+
group: "write",
|
|
238
|
+
handler: async (input, ctx) => {
|
|
239
|
+
const calendarId = input.calendar_id ?? "primary";
|
|
240
|
+
const created = await googleJson(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events`, {
|
|
241
|
+
body: JSON.stringify({
|
|
242
|
+
end: toEventDateTime(input.end, input.time_zone),
|
|
243
|
+
start: toEventDateTime(input.start, input.time_zone),
|
|
244
|
+
summary: input.summary,
|
|
245
|
+
...(input.description ? { description: input.description } : {}),
|
|
246
|
+
...(input.attendees?.length
|
|
247
|
+
? { attendees: input.attendees.map((email) => ({ email })) }
|
|
248
|
+
: {}),
|
|
249
|
+
...(input.private_properties
|
|
250
|
+
? { extendedProperties: { private: input.private_properties } }
|
|
251
|
+
: {}),
|
|
252
|
+
}),
|
|
253
|
+
headers: { "Content-Type": "application/json" },
|
|
254
|
+
method: "POST",
|
|
255
|
+
});
|
|
256
|
+
return { ...toEventSummary(created), calendar_id: calendarId };
|
|
257
|
+
},
|
|
258
|
+
id: "create_event",
|
|
259
|
+
inputSchema: z.object({
|
|
260
|
+
attendees: attendeesField,
|
|
261
|
+
calendar_id: calendarIdField,
|
|
262
|
+
description: z
|
|
263
|
+
.string()
|
|
264
|
+
.optional()
|
|
265
|
+
.describe("Optional event description/notes."),
|
|
266
|
+
end: endField,
|
|
267
|
+
private_properties: privatePropertiesField,
|
|
268
|
+
start: startField,
|
|
269
|
+
summary: z.string().describe("Event title."),
|
|
270
|
+
time_zone: timeZoneField,
|
|
271
|
+
}),
|
|
272
|
+
providerScopes: [SCOPE_EVENTS],
|
|
273
|
+
summary: "Create a calendar event",
|
|
274
|
+
}),
|
|
275
|
+
connectorAction({
|
|
276
|
+
description: "Update fields of an existing calendar event (patch semantics: only the provided fields change).",
|
|
277
|
+
group: "write",
|
|
278
|
+
handler: async (input, ctx) => {
|
|
279
|
+
const calendarId = input.calendar_id ?? "primary";
|
|
280
|
+
const patch = {};
|
|
281
|
+
if (input.summary !== undefined) {
|
|
282
|
+
patch.summary = input.summary;
|
|
283
|
+
}
|
|
284
|
+
if (input.description !== undefined) {
|
|
285
|
+
patch.description = input.description;
|
|
286
|
+
}
|
|
287
|
+
if (input.start !== undefined) {
|
|
288
|
+
patch.start = toEventDateTime(input.start, input.time_zone);
|
|
289
|
+
}
|
|
290
|
+
if (input.end !== undefined) {
|
|
291
|
+
patch.end = toEventDateTime(input.end, input.time_zone);
|
|
292
|
+
}
|
|
293
|
+
if (input.attendees !== undefined) {
|
|
294
|
+
patch.attendees = input.attendees.map((email) => ({ email }));
|
|
295
|
+
}
|
|
296
|
+
if (input.private_properties !== undefined) {
|
|
297
|
+
patch.extendedProperties = { private: input.private_properties };
|
|
298
|
+
}
|
|
299
|
+
const updated = await googleJson(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`, {
|
|
300
|
+
body: JSON.stringify(patch),
|
|
301
|
+
headers: { "Content-Type": "application/json" },
|
|
302
|
+
method: "PATCH",
|
|
303
|
+
});
|
|
304
|
+
return { ...toEventSummary(updated), calendar_id: calendarId };
|
|
305
|
+
},
|
|
306
|
+
id: "update_event",
|
|
307
|
+
inputSchema: z
|
|
308
|
+
.object({
|
|
309
|
+
attendees: attendeesField,
|
|
310
|
+
calendar_id: calendarIdField,
|
|
311
|
+
description: z
|
|
312
|
+
.string()
|
|
313
|
+
.optional()
|
|
314
|
+
.describe("New event description (replaces the existing one)."),
|
|
315
|
+
end: endField.optional(),
|
|
316
|
+
event_id: z.string().describe("Calendar event id to update."),
|
|
317
|
+
private_properties: privatePropertiesField,
|
|
318
|
+
start: startField.optional(),
|
|
319
|
+
summary: z.string().optional().describe("New event title."),
|
|
320
|
+
time_zone: timeZoneField,
|
|
321
|
+
})
|
|
322
|
+
.refine((v) => v.attendees !== undefined ||
|
|
323
|
+
v.description !== undefined ||
|
|
324
|
+
v.end !== undefined ||
|
|
325
|
+
v.private_properties !== undefined ||
|
|
326
|
+
v.start !== undefined ||
|
|
327
|
+
v.summary !== undefined, { message: "Provide at least one field to update." }),
|
|
328
|
+
providerScopes: [SCOPE_EVENTS],
|
|
329
|
+
summary: "Update a calendar event",
|
|
330
|
+
}),
|
|
331
|
+
connectorAction({
|
|
332
|
+
description: "Delete a calendar event permanently (attendees are notified of the cancellation).",
|
|
333
|
+
group: "destructive",
|
|
334
|
+
handler: async (input, ctx) => {
|
|
335
|
+
const calendarId = input.calendar_id ?? "primary";
|
|
336
|
+
await googleFetch(ctx, `${CALENDAR_API}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(input.event_id)}`, { method: "DELETE" });
|
|
337
|
+
return {
|
|
338
|
+
calendar_id: calendarId,
|
|
339
|
+
deleted: true,
|
|
340
|
+
event_id: input.event_id,
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
id: "delete_event",
|
|
344
|
+
inputSchema: z.object({
|
|
345
|
+
calendar_id: calendarIdField,
|
|
346
|
+
event_id: z.string().describe("Calendar event id to delete."),
|
|
347
|
+
}),
|
|
348
|
+
providerScopes: [SCOPE_EVENTS],
|
|
349
|
+
summary: "Delete a calendar event",
|
|
350
|
+
}),
|
|
351
|
+
],
|
|
352
|
+
auth: { kind: "oauth2", oauth2: GOOGLE_OAUTH2 },
|
|
353
|
+
description: "Read and manage events in a connected Google Calendar account.",
|
|
354
|
+
icon: "logo:google-calendar",
|
|
355
|
+
id: "google-calendar",
|
|
356
|
+
moduleId: "connections-google",
|
|
357
|
+
name: "Google Calendar",
|
|
358
|
+
toolPrefix: "gcal",
|
|
359
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { defineConnector, } from "../../shims/connections-sdk.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { connectorAction, GOOGLE_OAUTH2, googleJson } from "../shared.js";
|
|
4
|
+
const PEOPLE_API = "https://people.googleapis.com/v1";
|
|
5
|
+
const SCOPE_READONLY = "https://www.googleapis.com/auth/contacts.readonly";
|
|
6
|
+
function mapPerson(person) {
|
|
7
|
+
const name = person.names?.[0];
|
|
8
|
+
const org = person.organizations?.[0];
|
|
9
|
+
return {
|
|
10
|
+
display_name: name?.displayName ?? null,
|
|
11
|
+
email: person.emailAddresses?.[0]?.value ?? null,
|
|
12
|
+
family_name: name?.familyName ?? null,
|
|
13
|
+
given_name: name?.givenName ?? null,
|
|
14
|
+
organization: org?.name ?? null,
|
|
15
|
+
phone: person.phoneNumbers?.[0]?.value ?? null,
|
|
16
|
+
resource_name: person.resourceName ?? null,
|
|
17
|
+
title: org?.title ?? null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export const contactsConnector = defineConnector({
|
|
21
|
+
actions: [
|
|
22
|
+
connectorAction({
|
|
23
|
+
description: "List contacts from Google Contacts (People API). Returns compact rows suitable for import: display name, given/family name, email, phone, organization, title.",
|
|
24
|
+
group: "read",
|
|
25
|
+
handler: async (input, ctx) => {
|
|
26
|
+
const url = new URL(`${PEOPLE_API}/people/me/connections`);
|
|
27
|
+
url.searchParams.set("personFields", "names,emailAddresses,phoneNumbers,organizations");
|
|
28
|
+
url.searchParams.set("pageSize", String(input.page_size ?? 100));
|
|
29
|
+
if (input.page_token) {
|
|
30
|
+
url.searchParams.set("pageToken", input.page_token);
|
|
31
|
+
}
|
|
32
|
+
const data = await googleJson(ctx, url.toString());
|
|
33
|
+
return {
|
|
34
|
+
contacts: (data.connections ?? []).map(mapPerson),
|
|
35
|
+
next_page_token: data.nextPageToken ?? null,
|
|
36
|
+
total_people: data.totalPeople ?? null,
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
id: "list_contacts",
|
|
40
|
+
inputSchema: z.object({
|
|
41
|
+
page_size: z
|
|
42
|
+
.number()
|
|
43
|
+
.int()
|
|
44
|
+
.min(1)
|
|
45
|
+
.max(1000)
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Page size (1–1000). Defaults to 100."),
|
|
48
|
+
page_token: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Pagination token from a previous list_contacts call."),
|
|
52
|
+
}),
|
|
53
|
+
providerScopes: [SCOPE_READONLY],
|
|
54
|
+
summary: "List Google Contacts",
|
|
55
|
+
}),
|
|
56
|
+
connectorAction({
|
|
57
|
+
description: "Search Google Contacts by a query string (name, email, phone). Returns the same compact contact rows as list_contacts.",
|
|
58
|
+
group: "read",
|
|
59
|
+
handler: async (input, ctx) => {
|
|
60
|
+
const url = new URL(`${PEOPLE_API}/people:searchContacts`);
|
|
61
|
+
url.searchParams.set("query", input.query);
|
|
62
|
+
url.searchParams.set("readMask", "names,emailAddresses,phoneNumbers,organizations");
|
|
63
|
+
url.searchParams.set("pageSize", String(input.page_size ?? 30));
|
|
64
|
+
const data = await googleJson(ctx, url.toString());
|
|
65
|
+
return {
|
|
66
|
+
contacts: (data.results ?? [])
|
|
67
|
+
.map((r) => r.person)
|
|
68
|
+
.filter((p) => Boolean(p))
|
|
69
|
+
.map(mapPerson),
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
id: "search_contacts",
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
page_size: z
|
|
75
|
+
.number()
|
|
76
|
+
.int()
|
|
77
|
+
.min(1)
|
|
78
|
+
.max(30)
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Max results (1–30). Defaults to 30."),
|
|
81
|
+
query: z.string().min(1).describe("Search query."),
|
|
82
|
+
}),
|
|
83
|
+
providerScopes: [SCOPE_READONLY],
|
|
84
|
+
summary: "Search Google Contacts",
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
auth: { kind: "oauth2", oauth2: GOOGLE_OAUTH2 },
|
|
88
|
+
description: "Import and search people from Google Contacts via the People API.",
|
|
89
|
+
icon: "logo:gmail",
|
|
90
|
+
id: "google-contacts",
|
|
91
|
+
moduleId: "connections-google",
|
|
92
|
+
name: "Google Contacts",
|
|
93
|
+
toolPrefix: "gcontacts",
|
|
94
|
+
});
|