@jini-ai/agent-plugins 0.6.17 → 0.6.19
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/CHANGELOG.md +29 -1
- package/NOTICE +14 -0
- package/README.md +35 -14
- package/charts-echarts/plugin.json +39 -0
- package/charts-echarts/skills/charts-echarts/SKILL.md +110 -0
- package/charts-recharts/plugin.json +32 -0
- package/charts-recharts/skills/charts-recharts/SKILL.md +108 -0
- package/cloudflare/NOTES.md +16 -0
- package/cloudflare/form-destinations/cloudflare-d1.mjs +9 -0
- package/cloudflare/lib/api.mjs +96 -0
- package/cloudflare/lib/assets.mjs +65 -0
- package/cloudflare/lib/dns.mjs +32 -0
- package/cloudflare/lib/domain-inventory.mjs +61 -0
- package/cloudflare/lib/domain-status.mjs +48 -0
- package/cloudflare/lib/domains.mjs +95 -0
- package/cloudflare/lib/forms.mjs +254 -0
- package/cloudflare/lib/pages-upload.mjs +51 -0
- package/cloudflare/lib/target.mjs +165 -0
- package/cloudflare/lib/workers-upload.mjs +48 -0
- package/cloudflare/lib/zones.mjs +25 -0
- package/cloudflare/mcp.json +4 -0
- package/cloudflare/plugin.json +96 -0
- package/cloudflare/skills/cloudflare/SKILL.md +57 -0
- package/cloudflare/skills/cloudflare/references/domains.md +31 -0
- package/cloudflare/skills/cloudflare/references/pages.md +18 -0
- package/cloudflare/skills/cloudflare/references/permissions.md +29 -0
- package/cloudflare/skills/cloudflare/references/troubleshooting.md +20 -0
- package/cloudflare/skills/cloudflare/references/workers.md +42 -0
- package/cloudflare/targets/cloudflare-pages.mjs +12 -0
- package/cloudflare/targets/cloudflare-workers.mjs +17 -0
- package/cloudflare/tovu-consent.json +6 -0
- package/cloudflare/tovu-deploy-targets.json +630 -0
- package/cloudflare/tovu-form-destinations.json +12 -0
- package/cloudflare/worker/forms-receiver.mjs +249 -0
- package/composio/mcp.json +10 -0
- package/composio/plugin.json +68 -0
- package/composio/skills/composio/SKILL.md +85 -0
- package/dist/core/identity-namespace.d.ts +12 -0
- package/dist/core/identity-namespace.js +20 -0
- package/dist/folder-zip-node.js +29 -7
- package/dist/lifecycle/bundled-digests.js +4 -3
- package/dist/lifecycle/install.d.ts +7 -0
- package/dist/lifecycle/install.js +45 -3
- package/dist/lifecycle/manifest.d.ts +2 -2
- package/dist/lifecycle/manifest.js +5 -3
- package/dist/lifecycle/node.js +2 -0
- package/dist/lifecycle/persistent-state.js +1 -1
- package/dist/lifecycle/ports.d.ts +8 -0
- package/dist/lifecycle/seed-bundled.js +4 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +3 -2
- package/fly/deploy-configs/fly.mjs +115 -0
- package/fly/deploy-ops/fly.mjs +102 -0
- package/fly/mcp.json +4 -0
- package/fly/plugin.json +81 -0
- package/fly/scripts/render-deploy-workflow.mjs +150 -0
- package/fly/skills/fly/SKILL.md +43 -0
- package/fly/skills/fly/references/fly-deploy.template.yml +117 -0
- package/fly/skills/fly/references/fly.template.toml +81 -0
- package/fly/tovu-credential-schemes.json +11 -0
- package/fly/tovu-deploy-configs.json +21 -0
- package/fly/tovu-deploy-ops.json +14 -0
- package/github/deploy-ops/github-actions.mjs +164 -0
- package/github/deployments/github.mjs +415 -0
- package/github/mcp.json +4 -0
- package/github/plugin.json +105 -0
- package/github/skills/github/SKILL.md +249 -0
- package/github/skills/github/references/actions.md +224 -0
- package/github/skills/github/references/auth-and-tokens.md +140 -0
- package/github/skills/github/references/repo-files.md +135 -0
- package/github/skills/github/references/site-backup.md +159 -0
- package/github/skills/github/references/source-control.md +123 -0
- package/github/source-control/backup-push.mjs +242 -0
- package/github/source-control/commit-site.mjs +459 -0
- package/github/source-control/github.mjs +93 -0
- package/github/source-control/write-files.mjs +468 -0
- package/github/tovu-deploy-ops.json +13 -0
- package/github/tovu-source-control.json +117 -0
- package/github-pages/NOTES.md +19 -0
- package/github-pages/mcp.json +4 -0
- package/github-pages/plugin.json +48 -0
- package/github-pages/skills/github-pages/SKILL.md +44 -0
- package/github-pages/targets/github-pages.mjs +543 -0
- package/github-pages/tovu-deploy-targets.json +327 -0
- package/google-login/exports/google-g.svg +1 -0
- package/google-login/exports/sign-in-button.css +68 -0
- package/google-login/exports/sign-in-button.html +4 -0
- package/google-login/mcp.json +4 -0
- package/google-login/plugin.json +59 -0
- package/google-login/skills/google-login/SKILL.md +96 -0
- package/google-login/tovu-login-providers.json +21 -0
- package/google-workspace/README.md +132 -0
- package/google-workspace/form-destinations/README.md +87 -0
- package/google-workspace/form-destinations/dedupe.mjs +43 -0
- package/google-workspace/form-destinations/delivery-result.mjs +69 -0
- package/google-workspace/form-destinations/mapping.mjs +152 -0
- package/google-workspace/form-destinations/sheets.mjs +202 -0
- package/google-workspace/lib/auth.mjs +81 -0
- package/google-workspace/lib/drive-files.mjs +54 -0
- package/google-workspace/lib/form-destination-settings.mjs +72 -0
- package/google-workspace/lib/http.mjs +168 -0
- package/google-workspace/lib/picker-flow.mjs +171 -0
- package/google-workspace/lib/picker-selection.mjs +107 -0
- package/google-workspace/lib/product-state.mjs +93 -0
- package/google-workspace/lib/request-policy.mjs +163 -0
- package/google-workspace/lib/sheets-a1.mjs +79 -0
- package/google-workspace/lib/sheets-admin-csv.mjs +82 -0
- package/google-workspace/lib/sheets-admin-request-kinds.mjs +183 -0
- package/google-workspace/lib/sheets-client.mjs +286 -0
- package/google-workspace/lib/surface-actions.mjs +107 -0
- package/google-workspace/mcp.json +4 -0
- package/google-workspace/plugin.json +77 -0
- package/google-workspace/products.json +12 -0
- package/google-workspace/skills/google-workspace/SKILL.md +82 -0
- package/google-workspace/skills/google-workspace/references/connection.md +102 -0
- package/google-workspace/skills/google-workspace/references/form-delivery.md +50 -0
- package/google-workspace/skills/google-workspace/references/sheets.md +149 -0
- package/google-workspace/skills/google-workspace/references/troubleshooting.md +31 -0
- package/google-workspace/tools/account-info.mjs +18 -0
- package/google-workspace/tools/sheets/README.md +54 -0
- package/google-workspace/tools/sheets/copy-tab.mjs +23 -0
- package/google-workspace/tools/sheets/copy.mjs +23 -0
- package/google-workspace/tools/sheets/create.mjs +35 -0
- package/google-workspace/tools/sheets/find-replace.mjs +38 -0
- package/google-workspace/tools/sheets/find.mjs +52 -0
- package/google-workspace/tools/sheets/get-by-data-filter.mjs +23 -0
- package/google-workspace/tools/sheets/get.mjs +22 -0
- package/google-workspace/tools/sheets/values-append.mjs +27 -0
- package/google-workspace/tools/sheets/values-batch-clear.mjs +20 -0
- package/google-workspace/tools/sheets/values-batch-get.mjs +20 -0
- package/google-workspace/tools/sheets/values-batch-update.mjs +29 -0
- package/google-workspace/tools/sheets/values-by-data-filter.mjs +62 -0
- package/google-workspace/tools/sheets/values-clear.mjs +20 -0
- package/google-workspace/tools/sheets/values-get.mjs +21 -0
- package/google-workspace/tools/sheets/values-update.mjs +26 -0
- package/google-workspace/tools/sheets-admin/README.md +45 -0
- package/google-workspace/tools/sheets-admin/basic-filter.mjs +32 -0
- package/google-workspace/tools/sheets-admin/batch-update.mjs +25 -0
- package/google-workspace/tools/sheets-admin/developer-metadata.mjs +28 -0
- package/google-workspace/tools/sheets-admin/export-csv.mjs +67 -0
- package/google-workspace/tools/sheets-admin/filter-views.mjs +38 -0
- package/google-workspace/tools/sheets-admin/format.mjs +70 -0
- package/google-workspace/tools/sheets-admin/named-ranges.mjs +42 -0
- package/google-workspace/tools/sheets-admin/open-existing.mjs +34 -0
- package/google-workspace/tools/sheets-admin/protected-ranges-get.mjs +18 -0
- package/google-workspace/tools/sheets-admin/sort.mjs +18 -0
- package/google-workspace/tools/sheets-admin/tab-modify.mjs +36 -0
- package/google-workspace/tovu-admin-surfaces.json +63 -0
- package/google-workspace/tovu-agent-tools.json +1178 -0
- package/google-workspace/tovu-connections.json +35 -0
- package/google-workspace/tovu-form-destinations.json +10 -0
- package/google-workspace/ui/README.md +73 -0
- package/google-workspace/ui/connection-panel.handler.mjs +167 -0
- package/google-workspace/ui/connection-panel.html +17 -0
- package/google-workspace/ui/connection-panel.mjs +189 -0
- package/google-workspace/ui/connection-view.mjs +120 -0
- package/google-workspace/ui/destination-panel.handler.mjs +106 -0
- package/google-workspace/ui/destination-panel.html +17 -0
- package/google-workspace/ui/destination-panel.mjs +150 -0
- package/google-workspace/ui/dom.mjs +140 -0
- package/google-workspace/ui/panel.css +170 -0
- package/google-workspace/ui/picker-flow.mjs +87 -0
- package/google-workspace/ui/picker-view.mjs +44 -0
- package/google-workspace/ui/picker.handler.mjs +56 -0
- package/google-workspace/ui/picker.html +17 -0
- package/google-workspace/ui/picker.mjs +88 -0
- package/google-workspace/ui/surface-client.mjs +227 -0
- package/higgsfield-media/mcp.json +9 -0
- package/higgsfield-media/plugin.json +109 -0
- package/higgsfield-media/skills/higgsfield-media/SKILL.md +325 -0
- package/higgsfield-media/skills/higgsfield-media/references/failure-modes.md +116 -0
- package/higgsfield-media/skills/higgsfield-media/references/models-and-plan-gates.md +142 -0
- package/jev/mcp.json +4 -0
- package/jev/plugin.json +44 -0
- package/jev/skills/jev/SKILL.md +308 -0
- package/jev/skills/jev/references/cookbooks.md +197 -0
- package/jev/skills/jev/references/jaggedness.md +135 -0
- package/jev/skills/jev/references/javascript-sdk.md +146 -0
- package/jev/skills/jev/references/patterns.md +123 -0
- package/jev/skills/jev/references/primitives-reference.md +187 -0
- package/langfuse/plugin.json +22 -0
- package/meilisearch/mcp.json +4 -0
- package/meilisearch/plugin.json +54 -0
- package/meilisearch/providers/meilisearch.mjs +651 -0
- package/meilisearch/skills/meilisearch/SKILL.md +43 -0
- package/meilisearch/tovu-search-providers.json +10 -0
- package/netlify/NOTES.md +13 -0
- package/netlify/mcp.json +4 -0
- package/netlify/plugin.json +47 -0
- package/netlify/skills/netlify/SKILL.md +39 -0
- package/netlify/targets/netlify.mjs +384 -0
- package/netlify/tovu-consent.json +6 -0
- package/netlify/tovu-deploy-targets.json +163 -0
- package/opentelemetry/mcp.json +4 -0
- package/opentelemetry/plugin.json +52 -0
- package/opentelemetry/skills/opentelemetry/SKILL.md +39 -0
- package/package.json +161 -6
- package/posthog/mcp.json +4 -0
- package/posthog/plugin.json +46 -0
- package/posthog/skills/posthog/SKILL.md +36 -0
- package/railway/deploy-configs/railway.mjs +135 -0
- package/railway/deploy-ops/railway.mjs +167 -0
- package/railway/mcp.json +4 -0
- package/railway/plugin.json +45 -0
- package/railway/skills/railway/SKILL.md +66 -0
- package/railway/tovu-deploy-configs.json +16 -0
- package/railway/tovu-deploy-ops.json +11 -0
- package/render/deploy-configs/render.mjs +141 -0
- package/render/deploy-ops/render.mjs +152 -0
- package/render/mcp.json +4 -0
- package/render/plugin.json +47 -0
- package/render/skills/render/SKILL.md +63 -0
- package/render/tovu-deploy-configs.json +16 -0
- package/render/tovu-deploy-ops.json +11 -0
- package/resend/NOTES.md +52 -0
- package/resend/adapters/resend.mjs +161 -0
- package/resend/lib/domains.mjs +137 -0
- package/resend/lib/errors.mjs +25 -0
- package/resend/lib/feedback.mjs +118 -0
- package/resend/mcp.json +13 -0
- package/resend/plugin.json +192 -0
- package/resend/skills/resend/SKILL.md +107 -0
- package/resend/tovu-mail-adapters.json +22 -0
- package/s3-compatible/NOTES.md +11 -0
- package/s3-compatible/mcp.json +4 -0
- package/s3-compatible/object-stores/s3-compatible.mjs +287 -0
- package/s3-compatible/plugin.json +56 -0
- package/s3-compatible/skills/s3-compatible/SKILL.md +39 -0
- package/s3-compatible/targets/s3-compatible.mjs +1054 -0
- package/s3-compatible/tovu-deploy-targets.json +432 -0
- package/s3-compatible/tovu-object-stores.json +29 -0
- package/sentry/mcp.json +4 -0
- package/sentry/plugin.json +49 -0
- package/sentry/skills/sentry/SKILL.md +34 -0
- package/shopify/mcp.json +4 -0
- package/shopify/plugin.json +22 -0
- package/shopify/skills/shopify/SKILL.md +103 -0
- package/shopify/skills/shopify/references/catalog-sync.md +74 -0
- package/shopify/skills/shopify/references/domains.md +26 -0
- package/shopify/skills/shopify/references/live-storefront.md +34 -0
- package/shopify/skills/shopify/references/storefront-snippets.md +21 -0
- package/site-compliance/mcp.json +4 -0
- package/site-compliance/plugin.json +34 -0
- package/site-compliance/skills/site-compliance/SKILL.md +220 -0
- package/site-compliance/skills/site-compliance/references/ccpa-cpra.md +115 -0
- package/site-compliance/skills/site-compliance/references/gdpr-eu-uk.md +143 -0
- package/site-compliance/skills/site-compliance/references/wcag-2-2.md +129 -0
- package/site-import/mcp.json +4 -0
- package/site-import/plugin.json +41 -0
- package/site-import/skills/site-import/SKILL.md +232 -0
- package/site-import/skills/site-import/references/content-mapping.md +166 -0
- package/site-import/skills/site-import/references/discovery-and-classification.md +117 -0
- package/site-import/skills/site-import/references/theme-extraction.md +34 -0
- package/slide-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
- package/slide-design/mcp.json +4 -0
- package/slide-design/plugin.json +26 -0
- package/slide-design/skills/slide-design/SKILL.md +103 -0
- package/slide-design/skills/slide-design/references/charts-and-exhibits.md +100 -0
- package/slide-design/skills/slide-design/references/html-deck.md +175 -0
- package/slide-design/skills/slide-design/references/layout-and-style.md +131 -0
- package/slide-design/skills/slide-design/references/review.md +93 -0
- package/slide-design/skills/slide-design/references/storyline.md +118 -0
- package/stripe/plugin.json +22 -0
- package/supabase/mcp.json +26 -0
- package/supabase/plugin.json +78 -0
- package/supabase/skills/supabase/SKILL.md +169 -0
- package/supabase/skills/supabase/references/failure-modes.md +101 -0
- package/ui-ux-design/CHANGELOG.md +28 -0
- package/ui-ux-design/LICENSE +201 -0
- package/ui-ux-design/NOTICE +14 -0
- package/ui-ux-design/README.md +62 -0
- package/ui-ux-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
- package/ui-ux-design/mcp.json +4 -0
- package/ui-ux-design/plugin.json +26 -0
- package/ui-ux-design/skills/frontend-accessibility/SKILL.md +60 -0
- package/ui-ux-design/skills/gstack-design/SKILL.md +53 -0
- package/ui-ux-design/skills/gstack-design/references/consultation.md +30 -0
- package/ui-ux-design/skills/gstack-design/references/html.md +29 -0
- package/ui-ux-design/skills/gstack-design/references/review.md +28 -0
- package/ui-ux-design/skills/gstack-design/references/shotgun.md +33 -0
- package/ui-ux-design/skills/gstack-design/references/upstream-notes.md +23 -0
- package/ui-ux-design/skills/interface-design/ORIGINAL.md +16 -0
- package/ui-ux-design/skills/interface-design/SKILL.md +41 -0
- package/ui-ux-design/skills/interface-design/references/commands/audit.md +40 -0
- package/ui-ux-design/skills/interface-design/references/commands/critique.md +21 -0
- package/ui-ux-design/skills/interface-design/references/commands/extract.md +34 -0
- package/ui-ux-design/skills/interface-design/references/commands/init.md +31 -0
- package/ui-ux-design/skills/interface-design/references/commands/status.md +22 -0
- package/ui-ux-design/skills/interface-design/references/examples/system-precision.md +29 -0
- package/ui-ux-design/skills/interface-design/references/examples/system-warmth.md +29 -0
- package/ui-ux-design/skills/interface-design/references/system-template.md +57 -0
- package/ui-ux-design/skills/shadcn-ui/README.md +244 -0
- package/ui-ux-design/skills/shadcn-ui/SKILL.md +333 -0
- package/ui-ux-design/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
- package/ui-ux-design/skills/shadcn-ui/examples/data-table.tsx +313 -0
- package/ui-ux-design/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
- package/ui-ux-design/skills/shadcn-ui/resources/component-catalog.md +481 -0
- package/ui-ux-design/skills/shadcn-ui/resources/customization-guide.md +516 -0
- package/ui-ux-design/skills/shadcn-ui/resources/migration-guide.md +463 -0
- package/ui-ux-design/skills/shadcn-ui/resources/setup-guide.md +412 -0
- package/ui-ux-design/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
- package/ui-ux-design/skills/ui-ux-design/SKILL.md +171 -0
- package/ui-ux-design/skills/ui-ux-design/references/brand-and-voice.md +78 -0
- package/ui-ux-design/skills/ui-ux-design/references/chrome-mobile-test-matrix.md +137 -0
- package/ui-ux-design/skills/ui-ux-design/references/components-and-states.md +112 -0
- package/ui-ux-design/skills/ui-ux-design/references/delight-and-motion.md +54 -0
- package/ui-ux-design/skills/ui-ux-design/references/foundations.md +145 -0
- package/ui-ux-design/skills/ui-ux-design/references/inclusive-ai-imagery.md +57 -0
- package/ui-ux-design/skills/ui-ux-design/references/kole-jain-uiux-concepts.md +104 -0
- package/ui-ux-design/skills/ui-ux-design/references/kuinetic-reference.md +184 -0
- package/ui-ux-design/skills/ui-ux-design/references/openai-frontend-skill.md +139 -0
- package/ui-ux-design/skills/ui-ux-design/references/premium-ui.md +55 -0
- package/ui-ux-design/skills/ui-ux-design/references/research-and-validation.md +88 -0
- package/ui-ux-design/skills/ui-ux-design/references/sam-crawford-premium-websites.md +103 -0
- package/ui-ux-design/skills/ui-ux-design/references/self-made-web-designer-core-skills.md +119 -0
- package/ui-ux-design/skills/ui-ux-design/references/ui-animation.md +173 -0
- package/ui-ux-design/skills/ui-ux-design/references/ui-loop.md +234 -0
- package/ui-ux-design/skills/ui-ux-design/references/visual-storytelling.md +54 -0
- package/ui-ux-design/skills/vercel-web-design-guidelines/SKILL.md +39 -0
- package/ui-ux-design/skills/web-compliance/SKILL.md +134 -0
- package/ui-ux-design/skills/web-compliance/references/audit-evidence.md +59 -0
- package/vercel/NOTES.md +23 -0
- package/vercel/mcp.json +4 -0
- package/vercel/object-stores/vercel-blob.mjs +147 -0
- package/vercel/plugin.json +47 -0
- package/vercel/skills/vercel/SKILL.md +48 -0
- package/vercel/targets/vercel.mjs +586 -0
- package/vercel/tovu-consent.json +6 -0
- package/vercel/tovu-deploy-targets.json +219 -0
- package/vercel/tovu-object-stores.json +10 -0
- package/wordpress/NOTES.md +33 -0
- package/wordpress/plugin.json +30 -0
- package/wordpress/skills/wordpress/SKILL.md +43 -0
- package/wordpress/skills/wordpress/references/free-site-redirect.md +46 -0
- package/wordpress/skills/wordpress/references/subdomain.md +66 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_copy` (`p_google_workspace__sheets_copy`): a whole-spreadsheet copy through
|
|
3
|
+
* Drive `files.copy` (the shared Drive helper), optionally into a folder the connection can open.
|
|
4
|
+
*
|
|
5
|
+
* A copy is not idempotent: a lost answer is `uncertain-outcome` and is never replayed.
|
|
6
|
+
*/
|
|
7
|
+
import { copyFile } from '../../lib/drive-files.mjs';
|
|
8
|
+
import { sheetsClientFor, resourceLines } from '../../lib/sheets-client.mjs';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, title: string, parentFolderId?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
12
|
+
async invoke({ input, capabilities, signal }) {
|
|
13
|
+
const copied = await copyFile(
|
|
14
|
+
{ client: sheetsClientFor({ capabilities }), connectionRef: input.connectionRef, fileId: input.spreadsheetId, name: input.title },
|
|
15
|
+
{ ...(input.parentFolderId ? { parentId: input.parentFolderId } : {}), ...(signal ? { signal } : {}) },
|
|
16
|
+
);
|
|
17
|
+
return { spreadsheetId: copied.fileId, title: copied.name, url: copied.url };
|
|
18
|
+
},
|
|
19
|
+
describeAffectedResources({ input }) {
|
|
20
|
+
const where = input.parentFolderId ? ` in folder ${input.parentFolderId}` : '';
|
|
21
|
+
return resourceLines({ lines: [`New spreadsheet "${input.title}" copied from spreadsheet ${input.spreadsheetId}${where}`] });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_create` (`p_google_workspace__sheets_create`): a new spreadsheet, optionally
|
|
3
|
+
* with named tabs. Under `drive.file`, a file the site creates is one the site may open from then on.
|
|
4
|
+
*
|
|
5
|
+
* A create is not idempotent: if Google's answer is lost the outcome is `unknown`
|
|
6
|
+
* (`uncertain-outcome`) and the call is never replayed, so a lost answer cannot make two files.
|
|
7
|
+
*/
|
|
8
|
+
import { resourceLines, SHEETS_API, sendSheetsRequest } from '../../lib/sheets-client.mjs';
|
|
9
|
+
|
|
10
|
+
const CREATE_FIELDS = 'spreadsheetId,spreadsheetUrl,properties(title),sheets(properties(sheetId,title,index))';
|
|
11
|
+
|
|
12
|
+
/** The new file's properties: the title plus the locale and time zone only when given. @complexity O(1). */
|
|
13
|
+
function propertiesOf(input) {
|
|
14
|
+
return { title: input.title, ...(input.locale ? { locale: input.locale } : {}), ...(input.timeZone ? { timeZone: input.timeZone } : {}) };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** What chat needs from Google's answer: ids, URL and tabs. @complexity O(tabs). */
|
|
18
|
+
function created(body) {
|
|
19
|
+
return { spreadsheetId: body?.spreadsheetId, url: body?.spreadsheetUrl, title: body?.properties?.title, tabs: (body?.sheets ?? []).map((sheet) => sheet.properties) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
/** @param {{ input: { connectionRef: string, title: string, tabs?: string[], locale?: string, timeZone?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
24
|
+
async invoke(call) {
|
|
25
|
+
const { input } = call;
|
|
26
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
27
|
+
kind: 'write', method: 'POST', url: SHEETS_API, query: { fields: CREATE_FIELDS },
|
|
28
|
+
body: { properties: propertiesOf(input), ...(input.tabs ? { sheets: input.tabs.map((title) => ({ properties: { title } })) } : {}) },
|
|
29
|
+
} });
|
|
30
|
+
return created(body);
|
|
31
|
+
},
|
|
32
|
+
describeAffectedResources({ input }) {
|
|
33
|
+
return resourceLines({ lines: [`New spreadsheet "${input.title}" in the connected Google Drive`] });
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_find_replace` (`p_google_workspace__sheets_find_replace`): replaces every match
|
|
3
|
+
* of a text on one tab or on every tab (`batchUpdate` with one `findReplace` request). Destructive:
|
|
4
|
+
* matched cells are overwritten and the site cannot undo it.
|
|
5
|
+
*
|
|
6
|
+
* Not idempotent (replacing `a` with `aa` twice differs), so a lost answer is `uncertain-outcome` and
|
|
7
|
+
* is never replayed.
|
|
8
|
+
*/
|
|
9
|
+
import { resourceLines, sendSheetsRequest, sheetsUrl } from '../../lib/sheets-client.mjs';
|
|
10
|
+
|
|
11
|
+
/** The request's scope: the named tab, else every tab (the schema requires exactly one). @complexity O(1). */
|
|
12
|
+
const scopeOf = (input) => (input.sheetId === undefined ? { allSheets: true } : { sheetId: input.sheetId });
|
|
13
|
+
const MATCH_OPTIONS = ['matchCase', 'matchEntireCell', 'searchByRegex', 'includeFormulas'];
|
|
14
|
+
const COUNTS = ['occurrencesChanged', 'valuesChanged', 'formulasChanged', 'rowsChanged', 'sheetsChanged'];
|
|
15
|
+
|
|
16
|
+
/** The one `findReplace` request; every match option is sent explicitly (absent = false). @complexity O(1). */
|
|
17
|
+
function findReplaceRequest(input) {
|
|
18
|
+
const options = Object.fromEntries(MATCH_OPTIONS.map((name) => [name, input[name] === true]));
|
|
19
|
+
return { find: input.find, replacement: input.replacement, ...options, ...scopeOf(input) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, find: string, replacement: string, sheetId?: number, allSheets?: true, matchCase?: boolean, matchEntireCell?: boolean, searchByRegex?: boolean, includeFormulas?: boolean }, capabilities: object, signal?: AbortSignal }} call */
|
|
24
|
+
async invoke(call) {
|
|
25
|
+
const { input } = call;
|
|
26
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
27
|
+
kind: 'write', method: 'POST', url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { method: ':batchUpdate' }),
|
|
28
|
+
body: { requests: [{ findReplace: findReplaceRequest(input) }], includeSpreadsheetInResponse: false },
|
|
29
|
+
} });
|
|
30
|
+
// Google omits a zero count from the reply.
|
|
31
|
+
const reply = body?.replies?.[0]?.findReplace ?? {};
|
|
32
|
+
return Object.fromEntries(COUNTS.map((name) => [name, reply[name] ?? 0]));
|
|
33
|
+
},
|
|
34
|
+
describeAffectedResources({ input }) {
|
|
35
|
+
const where = input.sheetId === undefined ? 'every tab' : `tab ${input.sheetId}`;
|
|
36
|
+
return resourceLines({ lines: [`Replace every match of "${input.find}" with "${input.replacement}" on ${where} of spreadsheet ${input.spreadsheetId} (matched cells are overwritten)`] });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_find` (`p_google_workspace__sheets_find`): finds cells whose text contains (or
|
|
3
|
+
* equals) a search text in up to 20 explicit ranges, returning cell references and short excerpts.
|
|
4
|
+
*
|
|
5
|
+
* The Sheets API has no read-only search, so this reads the ranges (`values.batchGet`) and matches
|
|
6
|
+
* plain text locally (no regular expressions, so no pathological patterns). It scans at most 50,000
|
|
7
|
+
* cells (more is an error, never a silent partial scan) and returns at most 100 matches with
|
|
8
|
+
* `truncated` set when there were more.
|
|
9
|
+
*/
|
|
10
|
+
import { GoogleWorkspaceError } from '../../lib/request-policy.mjs';
|
|
11
|
+
import { columnIndex, columnName, parseA1Range } from '../../lib/sheets-a1.mjs';
|
|
12
|
+
import { cellCount, readOptions, sendSheetsRequest, sheetsUrl, SHEETS_LIMITS } from '../../lib/sheets-client.mjs';
|
|
13
|
+
|
|
14
|
+
/** The matcher for the caller's options. @complexity O(1) to build; O(text) per cell. */
|
|
15
|
+
function matcherFor(input) {
|
|
16
|
+
const fold = (text) => (input.matchCase ? text : text.toLowerCase());
|
|
17
|
+
const needle = fold(input.text);
|
|
18
|
+
return input.matchEntireCell ? (cell) => fold(cell) === needle : (cell) => fold(cell).includes(needle);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Matching cells of one value range, appended to `found` until it holds one more than the cap (so
|
|
23
|
+
* the caller knows whether there were more).
|
|
24
|
+
* @complexity O(cells).
|
|
25
|
+
*/
|
|
26
|
+
function collectMatches(valueRange, matches, found) {
|
|
27
|
+
const start = parseA1Range({ range: valueRange.range });
|
|
28
|
+
if (!start) throw new GoogleWorkspaceError({ code: 'unexpected-response' });
|
|
29
|
+
const firstColumn = columnIndex({ column: start.startColumn });
|
|
30
|
+
(valueRange.values ?? []).forEach((row, rowOffset) => row.forEach((value, columnOffset) => {
|
|
31
|
+
if (found.length > SHEETS_LIMITS.maxMatches || !matches(String(value))) return;
|
|
32
|
+
found.push({ cell: `${start.sheet}!${columnName({ index: firstColumn + columnOffset })}${start.startRow + rowOffset}`, excerpt: String(value).slice(0, SHEETS_LIMITS.maxExcerptChars) });
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, ranges: string[], text: string, matchCase?: boolean, matchEntireCell?: boolean, valueRenderOption?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
38
|
+
async invoke(call) {
|
|
39
|
+
const { input } = call;
|
|
40
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
41
|
+
kind: 'read', url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values'], method: ':batchGet' }),
|
|
42
|
+
query: { ranges: input.ranges, ...readOptions({ input }) },
|
|
43
|
+
} });
|
|
44
|
+
const valueRanges = body?.valueRanges ?? [];
|
|
45
|
+
const scannedCells = valueRanges.reduce((total, valueRange) => total + cellCount({ values: valueRange.values ?? [] }), 0);
|
|
46
|
+
if (scannedCells > SHEETS_LIMITS.maxScannedCells) throw new GoogleWorkspaceError({ code: 'limit-exceeded' });
|
|
47
|
+
const matches = matcherFor(input);
|
|
48
|
+
const found = [];
|
|
49
|
+
for (const valueRange of valueRanges) collectMatches(valueRange, matches, found);
|
|
50
|
+
return { matches: found.slice(0, SHEETS_LIMITS.maxMatches), truncated: found.length > SHEETS_LIMITS.maxMatches, scannedCells };
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_get_by_data_filter` (`p_google_workspace__sheets_get_by_data_filter`): the
|
|
3
|
+
* spreadsheet data matching data filters (A1 ranges, grid ranges or developer-metadata lookups),
|
|
4
|
+
* via `spreadsheets.getByDataFilter`.
|
|
5
|
+
*
|
|
6
|
+
* Exists for reads addressed by metadata rather than by position. The result has no range
|
|
7
|
+
* continuation, so a result over the 64 KiB tool-output bound is an error, never a cut result.
|
|
8
|
+
*/
|
|
9
|
+
import { assertOutputWithin, sendSheetsRequest, sheetsUrl, spreadsheetFields } from '../../lib/sheets-client.mjs';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, dataFilters: object[], includeGridData?: boolean, fields?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
13
|
+
async invoke(call) {
|
|
14
|
+
const { input } = call;
|
|
15
|
+
const includeGridData = input.includeGridData === true;
|
|
16
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
17
|
+
kind: 'read', method: 'POST', url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { method: ':getByDataFilter' }),
|
|
18
|
+
query: { fields: spreadsheetFields({ fields: input.fields, includeGridData }) },
|
|
19
|
+
body: { dataFilters: input.dataFilters, includeGridData },
|
|
20
|
+
} });
|
|
21
|
+
return assertOutputWithin({ value: body });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_get` (`p_google_workspace__sheets_get`): a spreadsheet's title, URL and tabs,
|
|
3
|
+
* with cell data only when asked for and only for the ranges named.
|
|
4
|
+
*
|
|
5
|
+
* Exists so chat can learn tab names and ids before it reads or writes. Grid data is opt-in and
|
|
6
|
+
* needs explicit ranges, and the result must fit the 64 KiB tool-output bound or the call errors.
|
|
7
|
+
*/
|
|
8
|
+
import { GoogleWorkspaceError } from '../../lib/request-policy.mjs';
|
|
9
|
+
import { assertOutputWithin, sendSheetsRequest, sheetsUrl, spreadsheetFields } from '../../lib/sheets-client.mjs';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, ranges?: string[], includeGridData?: boolean, fields?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
13
|
+
async invoke(call) {
|
|
14
|
+
const { input } = call;
|
|
15
|
+
const includeGridData = input.includeGridData === true;
|
|
16
|
+
// Grid data for a whole spreadsheet is unbounded; it is only read for the ranges the caller names.
|
|
17
|
+
if (includeGridData && !input.ranges?.length) throw new GoogleWorkspaceError({ code: 'invalid-input' });
|
|
18
|
+
const query = { fields: spreadsheetFields({ fields: input.fields, includeGridData }), includeGridData, ...(input.ranges ? { ranges: input.ranges } : {}) };
|
|
19
|
+
const body = await sendSheetsRequest({ call, request: { kind: 'read', url: sheetsUrl({ spreadsheetId: input.spreadsheetId }), query } });
|
|
20
|
+
return assertOutputWithin({ value: body });
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_append` (`p_google_workspace__sheets_values_append`): appends rows after
|
|
3
|
+
* the table found in a tab or range (`values.append`), inserting new rows by default so nothing
|
|
4
|
+
* below the table is written over.
|
|
5
|
+
*
|
|
6
|
+
* Values go in `RAW` unless the caller explicitly asks for `USER_ENTERED`. An append is not
|
|
7
|
+
* idempotent: a lost answer is `uncertain-outcome` and is never replayed (it could add the rows twice).
|
|
8
|
+
*/
|
|
9
|
+
import { assertPayloadWithin, cellCount, resourceLines, sendSheetsRequest, sheetsUrl, valueInputOption } from '../../lib/sheets-client.mjs';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, range: string, values: unknown[][], valueInputOption?: string, insertDataOption?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
13
|
+
async invoke(call) {
|
|
14
|
+
const { input } = call;
|
|
15
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
16
|
+
kind: 'write', method: 'POST',
|
|
17
|
+
url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values', input.range], method: ':append' }),
|
|
18
|
+
query: { valueInputOption: valueInputOption({ option: input.valueInputOption }), insertDataOption: input.insertDataOption ?? 'INSERT_ROWS' },
|
|
19
|
+
body: assertPayloadWithin({ value: { range: input.range, majorDimension: 'ROWS', values: input.values } }),
|
|
20
|
+
} });
|
|
21
|
+
const updates = body?.updates ?? {};
|
|
22
|
+
return { tableRange: body?.tableRange, updatedRange: updates.updatedRange, updatedRows: updates.updatedRows, updatedCells: updates.updatedCells };
|
|
23
|
+
},
|
|
24
|
+
describeAffectedResources({ input }) {
|
|
25
|
+
return resourceLines({ lines: [`Append ${input.values.length} row(s), ${cellCount({ values: input.values })} cell(s), after the table in ${input.range} of spreadsheet ${input.spreadsheetId}`] });
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_batch_clear` (`p_google_workspace__sheets_values_batch_clear`): clears
|
|
3
|
+
* the values of up to 100 explicit ranges in one request (`values.batchClear`). Destructive.
|
|
4
|
+
*/
|
|
5
|
+
import { resourceLines, sendSheetsRequest, sheetsUrl } from '../../lib/sheets-client.mjs';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, ranges: string[] }, capabilities: object, signal?: AbortSignal }} call */
|
|
9
|
+
async invoke(call) {
|
|
10
|
+
const { input } = call;
|
|
11
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
12
|
+
kind: 'idempotent-write', method: 'POST', body: { ranges: input.ranges },
|
|
13
|
+
url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values'], method: ':batchClear' }),
|
|
14
|
+
} });
|
|
15
|
+
return { clearedRanges: body?.clearedRanges ?? [] };
|
|
16
|
+
},
|
|
17
|
+
describeAffectedResources({ input }) {
|
|
18
|
+
return resourceLines({ lines: input.ranges.map((range) => `Clear every value in ${range} of spreadsheet ${input.spreadsheetId}`) });
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_batch_get` (`p_google_workspace__sheets_values_batch_get`): the values
|
|
3
|
+
* of up to 20 explicit ranges in one request (`values.batchGet`).
|
|
4
|
+
*
|
|
5
|
+
* All ranges share ONE output budget (500 rows, 5,000 cells, 64 KiB); every range that was cut
|
|
6
|
+
* carries its own `continuation` range.
|
|
7
|
+
*/
|
|
8
|
+
import { boundValueRanges, readOptions, sendSheetsRequest, sheetsUrl } from '../../lib/sheets-client.mjs';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, ranges: string[], valueRenderOption?: string, dateTimeRenderOption?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
12
|
+
async invoke(call) {
|
|
13
|
+
const { input } = call;
|
|
14
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
15
|
+
kind: 'read', url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values'], method: ':batchGet' }),
|
|
16
|
+
query: { ranges: input.ranges, ...readOptions({ input }) },
|
|
17
|
+
} });
|
|
18
|
+
return { valueRanges: boundValueRanges({ valueRanges: body?.valueRanges ?? [] }) };
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_batch_update` (`p_google_workspace__sheets_values_batch_update`): writes
|
|
3
|
+
* values over up to 100 explicit ranges in one request (`values.batchUpdate`). Destructive: the
|
|
4
|
+
* current values of every listed range are replaced.
|
|
5
|
+
*
|
|
6
|
+
* Values go in `RAW` unless the caller explicitly asks for `USER_ENTERED`; the whole body stays under
|
|
7
|
+
* 1 MiB. Repeating the same batch gives the same cells, so a transient failure may be retried.
|
|
8
|
+
*/
|
|
9
|
+
import { assertPayloadWithin, cellCount, resourceLines, sendSheetsRequest, sheetsUrl, valueInputOption } from '../../lib/sheets-client.mjs';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, data: { range: string, values: unknown[][] }[], valueInputOption?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
13
|
+
async invoke(call) {
|
|
14
|
+
const { input } = call;
|
|
15
|
+
const data = input.data.map(({ range, values }) => ({ range, majorDimension: 'ROWS', values }));
|
|
16
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
17
|
+
kind: 'idempotent-write', method: 'POST',
|
|
18
|
+
url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values'], method: ':batchUpdate' }),
|
|
19
|
+
body: assertPayloadWithin({ value: { valueInputOption: valueInputOption({ option: input.valueInputOption }), data, includeValuesInResponse: false } }),
|
|
20
|
+
} });
|
|
21
|
+
return {
|
|
22
|
+
totalUpdatedRows: body?.totalUpdatedRows, totalUpdatedCells: body?.totalUpdatedCells, totalUpdatedSheets: body?.totalUpdatedSheets,
|
|
23
|
+
responses: (body?.responses ?? []).map(({ updatedRange, updatedCells }) => ({ updatedRange, updatedCells })),
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
describeAffectedResources({ input }) {
|
|
27
|
+
return resourceLines({ lines: input.data.map(({ range, values }) => `Overwrite ${range} of spreadsheet ${input.spreadsheetId} (${cellCount({ values })} cell(s); current values are replaced)`) });
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_by_data_filter` (`p_google_workspace__sheets_values_by_data_filter`):
|
|
3
|
+
* reads, overwrites or clears the values matching data filters (`values.batchGetByDataFilter`,
|
|
4
|
+
* `batchUpdateByDataFilter`, `batchClearByDataFilter`), chosen by `action`.
|
|
5
|
+
*
|
|
6
|
+
* One tool covers three actions, so it is declared destructive (the strictest of them): even a `get`
|
|
7
|
+
* shows the confirmation card. Reads share the values-read output budget and carry `continuation`;
|
|
8
|
+
* writes go in `RAW` unless `USER_ENTERED` is asked for and stay under 1 MiB.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
assertPayloadWithin, boundValueRanges, cellCount, readOptions, resourceLines, sendSheetsRequest, sheetsUrl, valueInputOption,
|
|
12
|
+
} from '../../lib/sheets-client.mjs';
|
|
13
|
+
|
|
14
|
+
/** Per action: endpoint, request kind, body and result. Inputs already passed the action's schema branch. */
|
|
15
|
+
const ACTIONS = {
|
|
16
|
+
get: {
|
|
17
|
+
method: ':batchGetByDataFilter', kind: 'read',
|
|
18
|
+
body: (input) => ({ dataFilters: input.dataFilters, ...readOptions({ input }) }),
|
|
19
|
+
result: (body) => ({ valueRanges: boundValueRanges({ valueRanges: (body?.valueRanges ?? []).map((matched) => matched.valueRange ?? { range: '', values: [] }) }) }),
|
|
20
|
+
},
|
|
21
|
+
update: {
|
|
22
|
+
method: ':batchUpdateByDataFilter', kind: 'idempotent-write',
|
|
23
|
+
body: (input) => assertPayloadWithin({ value: {
|
|
24
|
+
valueInputOption: valueInputOption({ option: input.valueInputOption }), includeValuesInResponse: false,
|
|
25
|
+
data: input.data.map(({ dataFilter, values }) => ({ dataFilter, majorDimension: 'ROWS', values })),
|
|
26
|
+
} }),
|
|
27
|
+
result: (body) => ({ totalUpdatedRows: body?.totalUpdatedRows, totalUpdatedCells: body?.totalUpdatedCells, totalUpdatedSheets: body?.totalUpdatedSheets }),
|
|
28
|
+
},
|
|
29
|
+
clear: {
|
|
30
|
+
method: ':batchClearByDataFilter', kind: 'idempotent-write',
|
|
31
|
+
body: (input) => ({ dataFilters: input.dataFilters }),
|
|
32
|
+
result: (body) => ({ clearedRanges: body?.clearedRanges ?? [] }),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** A data filter as a card phrase; never cell values. @complexity O(1). */
|
|
37
|
+
function describeFilter(filter) {
|
|
38
|
+
if (filter.a1Range) return `range ${filter.a1Range}`;
|
|
39
|
+
// No fallback tab id: the input schema requires gridRange.sheetId, and the projection only hands
|
|
40
|
+
// schema-checked input to describeAffectedResources (pinned in sheets.contract.test.mjs).
|
|
41
|
+
if (filter.gridRange) return `grid range on tab ${filter.gridRange.sheetId}`;
|
|
42
|
+
return 'cells found by developer metadata';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default {
|
|
46
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, action: 'get' | 'update' | 'clear', dataFilters?: object[], data?: { dataFilter: object, values: unknown[][] }[], valueInputOption?: string, valueRenderOption?: string, dateTimeRenderOption?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
47
|
+
async invoke(call) {
|
|
48
|
+
const { input } = call;
|
|
49
|
+
const action = ACTIONS[input.action];
|
|
50
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
51
|
+
kind: action.kind, method: 'POST', body: action.body(input),
|
|
52
|
+
url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values'], method: action.method }),
|
|
53
|
+
} });
|
|
54
|
+
return action.result(body);
|
|
55
|
+
},
|
|
56
|
+
describeAffectedResources({ input }) {
|
|
57
|
+
const where = `of spreadsheet ${input.spreadsheetId}`;
|
|
58
|
+
if (input.action === 'update') return resourceLines({ lines: input.data.map(({ dataFilter, values }) => `Overwrite ${describeFilter(dataFilter)} ${where} (${cellCount({ values })} cell(s); current values are replaced)`) });
|
|
59
|
+
const verb = input.action === 'clear' ? 'Clear every value in' : 'Read values in';
|
|
60
|
+
return resourceLines({ lines: input.dataFilters.map((filter) => `${verb} ${describeFilter(filter)} ${where}`) });
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_clear` (`p_google_workspace__sheets_values_clear`): clears the values of
|
|
3
|
+
* one explicit range (`values.clear`; formatting stays). Destructive and not undoable from the site.
|
|
4
|
+
*/
|
|
5
|
+
import { resourceLines, sendSheetsRequest, sheetsUrl } from '../../lib/sheets-client.mjs';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, range: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
9
|
+
async invoke(call) {
|
|
10
|
+
const { input } = call;
|
|
11
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
12
|
+
kind: 'idempotent-write', method: 'POST', body: {},
|
|
13
|
+
url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values', input.range], method: ':clear' }),
|
|
14
|
+
} });
|
|
15
|
+
return { clearedRange: body?.clearedRange };
|
|
16
|
+
},
|
|
17
|
+
describeAffectedResources({ input }) {
|
|
18
|
+
return resourceLines({ lines: [`Clear every value in ${input.range} of spreadsheet ${input.spreadsheetId}`] });
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_get` (`p_google_workspace__sheets_values_get`): the values of one
|
|
3
|
+
* explicit A1 range (`values.get`), rows-major.
|
|
4
|
+
*
|
|
5
|
+
* Exists for bounded reads: at most 500 rows, 5,000 cells and 64 KiB come back; when the range holds
|
|
6
|
+
* more, the result carries `continuation`, the exact range to read next (Sheets values reads have no
|
|
7
|
+
* page tokens).
|
|
8
|
+
*/
|
|
9
|
+
import { boundValueRanges, readOptions, sendSheetsRequest, sheetsUrl } from '../../lib/sheets-client.mjs';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, range: string, valueRenderOption?: string, dateTimeRenderOption?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
13
|
+
async invoke(call) {
|
|
14
|
+
const { input } = call;
|
|
15
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
16
|
+
kind: 'read', url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values', input.range] }), query: readOptions({ input }),
|
|
17
|
+
} });
|
|
18
|
+
const [bounded] = boundValueRanges({ valueRanges: [{ range: body?.range ?? input.range, values: body?.values }] });
|
|
19
|
+
return bounded;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_values_update` (`p_google_workspace__sheets_values_update`): writes values over
|
|
3
|
+
* one explicit range (`values.update`). Destructive: what was in those cells is replaced, and the site
|
|
4
|
+
* cannot undo a remote change.
|
|
5
|
+
*
|
|
6
|
+
* Values go in `RAW` unless the caller explicitly asks for `USER_ENTERED`. The same update twice
|
|
7
|
+
* gives the same cells, so a transient failure may be retried.
|
|
8
|
+
*/
|
|
9
|
+
import { assertPayloadWithin, cellCount, resourceLines, sendSheetsRequest, sheetsUrl, valueInputOption } from '../../lib/sheets-client.mjs';
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
/** @param {{ input: { connectionRef: string, spreadsheetId: string, range: string, values: unknown[][], valueInputOption?: string }, capabilities: object, signal?: AbortSignal }} call */
|
|
13
|
+
async invoke(call) {
|
|
14
|
+
const { input } = call;
|
|
15
|
+
const body = await sendSheetsRequest({ call, request: {
|
|
16
|
+
kind: 'idempotent-write', method: 'PUT',
|
|
17
|
+
url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['values', input.range] }),
|
|
18
|
+
query: { valueInputOption: valueInputOption({ option: input.valueInputOption }) },
|
|
19
|
+
body: assertPayloadWithin({ value: { range: input.range, majorDimension: 'ROWS', values: input.values } }),
|
|
20
|
+
} });
|
|
21
|
+
return { updatedRange: body?.updatedRange, updatedRows: body?.updatedRows, updatedColumns: body?.updatedColumns, updatedCells: body?.updatedCells };
|
|
22
|
+
},
|
|
23
|
+
describeAffectedResources({ input }) {
|
|
24
|
+
return resourceLines({ lines: [`Overwrite ${input.range} of spreadsheet ${input.spreadsheetId} (${cellCount({ values: input.values })} cell(s); current values are replaced)`] });
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Sheets admin tools (`tools/sheets-admin/`)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The structural and formatting half of the Google Sheets chat tools: everything beyond reading and
|
|
6
|
+
writing values. Each file is one tool module declared in `../../tovu-agent-tools.json` and reached
|
|
7
|
+
by the assistant under its `name` with the host's plugin prefix (in Tovu, `p_google_workspace__<name>`).
|
|
8
|
+
|
|
9
|
+
| Module | Tool | Effect |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `tab-modify.mjs` | `sheets_tab_modify` | destructive |
|
|
12
|
+
| `sort.mjs` | `sheets_sort` | destructive |
|
|
13
|
+
| `basic-filter.mjs` | `sheets_basic_filter` | write |
|
|
14
|
+
| `filter-views.mjs` | `sheets_filter_views` | destructive |
|
|
15
|
+
| `format.mjs` | `sheets_format` | write |
|
|
16
|
+
| `named-ranges.mjs` | `sheets_named_ranges` | destructive |
|
|
17
|
+
| `protected-ranges-get.mjs` | `sheets_protected_ranges_get` | read |
|
|
18
|
+
| `developer-metadata.mjs` | `sheets_developer_metadata` | read |
|
|
19
|
+
| `batch-update.mjs` | `sheets_batch_update` | destructive |
|
|
20
|
+
| `export-csv.mjs` | `sheets_export_csv` | read |
|
|
21
|
+
| `open-existing.mjs` | `sheets_open_existing` | read |
|
|
22
|
+
|
|
23
|
+
## How it fits
|
|
24
|
+
|
|
25
|
+
- Every change is ONE `spreadsheets.batchUpdate`: named tools build reviewed requests and call
|
|
26
|
+
`runBatchUpdate` (`../../lib/sheets-client.mjs`), the same path as `sheets_batch_update`, so
|
|
27
|
+
every request passes the inventory in `../../lib/sheets-admin-request-kinds.mjs` first. A batch
|
|
28
|
+
holding a non-replayable kind is a `write` for the shared client (`../../lib/http.mjs`): an
|
|
29
|
+
unknown outcome is `uncertain-outcome` and is never replayed.
|
|
30
|
+
- The host confirms `write` and `destructive` calls with the card built from each module's
|
|
31
|
+
`describeAffectedResources` (spreadsheet first, then what changes; for a raw batch, every kind).
|
|
32
|
+
- Tokens come from the host capability inside the shared client; no module sees or returns one.
|
|
33
|
+
- Bounds: tool answers at most 64 KiB (refused, not cut); batches at most 100 requests, 1 MiB and
|
|
34
|
+
48 different kinds; CSV export at most 20,000 rows, 100,000 cells and 4 MiB, read in 10,000-cell
|
|
35
|
+
chunks through `values.batchGet` (ranges in the query, never the path), written through the host
|
|
36
|
+
`artifacts` capability (which refuses until the host binds a store).
|
|
37
|
+
- `open_existing` verifies a picked file through Drive `files.get` (granted, not trashed, a
|
|
38
|
+
spreadsheet) and returns metadata only; the Picker itself is a later lot.
|
|
39
|
+
|
|
40
|
+
## Extension points
|
|
41
|
+
|
|
42
|
+
A new request kind is one reviewed row in `REQUEST_KINDS` (effect, replay class, required fields)
|
|
43
|
+
plus a line in `skills/google-workspace/references/sheets.md`. A new named wrapper is a module here
|
|
44
|
+
that builds inventory requests and calls `runBatchUpdate`, plus a declaration appended to
|
|
45
|
+
`tovu-agent-tools.json`. Phase B may fold the shared helpers into `lib/sheets-client.mjs`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_basic_filter` (`p_google_workspace__sheets_basic_filter`): set or clear the basic
|
|
3
|
+
* filter of one tab. A filter only hides rows, so the tool is a `write` (confirmed, never destructive).
|
|
4
|
+
*
|
|
5
|
+
* Exists as a named wrapper over the reviewed `setBasicFilter` and `clearBasicFilter` request kinds.
|
|
6
|
+
* Reading a tab's filter is NOT an action here: a tool's effect is declared per tool, so a read
|
|
7
|
+
* inside this write tool would ask for a confirmation it does not need. `sheets_get` with
|
|
8
|
+
* `fields: "sheets(properties(sheetId),basicFilter)"` reads it without one.
|
|
9
|
+
*/
|
|
10
|
+
import { resourceLines, runBatchUpdate, sheetsClientFor, spreadsheetLine } from '../../lib/sheets-client.mjs';
|
|
11
|
+
import { gridRangeLabel } from '../../lib/sheets-a1.mjs';
|
|
12
|
+
|
|
13
|
+
/** The set/clear request. @complexity O(1). */
|
|
14
|
+
function changeRequest(input) {
|
|
15
|
+
if (input.action === 'clear') return { clearBasicFilter: { sheetId: input.sheetId } };
|
|
16
|
+
// No sortSpecs: a basic-filter sort physically reorders rows (destructive), so sorting stays in `sheets_sort`.
|
|
17
|
+
const filter = { range: input.range, ...(input.filterSpecs ? { filterSpecs: input.filterSpecs } : {}) };
|
|
18
|
+
return { setBasicFilter: { filter } };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
async invoke({ input, capabilities, signal }) {
|
|
23
|
+
const client = sheetsClientFor({ capabilities });
|
|
24
|
+
await runBatchUpdate({ client, connectionRef: input.connectionRef, spreadsheetId: input.spreadsheetId, requests: [changeRequest(input)] }, { signal });
|
|
25
|
+
return { action: input.action, sheetId: input.sheetId ?? input.range.sheetId };
|
|
26
|
+
},
|
|
27
|
+
describeAffectedResources({ input }) {
|
|
28
|
+
const where = input.action === 'set' ? gridRangeLabel({ range: input.range }) : `tab ${input.sheetId}`;
|
|
29
|
+
const what = input.action === 'set' ? 'replace the basic filter on' : 'remove the basic filter from';
|
|
30
|
+
return resourceLines({ lines: [spreadsheetLine(input), `${what} ${where}`] });
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_batch_update` (`p_google_workspace__sheets_batch_update`): send up to 100 reviewed Sheets
|
|
3
|
+
* request objects in one atomic `spreadsheets.batchUpdate`. Always destructive: the card lists every
|
|
4
|
+
* request kind in the batch with its count and effect.
|
|
5
|
+
*
|
|
6
|
+
* Exists for broad coverage without a wrapper per request family (dimensions, merges, validation,
|
|
7
|
+
* conditional formats, charts, banding, groups, pivots, slicers, tables, metadata, protection). Only
|
|
8
|
+
* kinds in `lib/sheets-admin-request-kinds.mjs` pass; anything else fails closed before any request.
|
|
9
|
+
*/
|
|
10
|
+
import { outputFits, resourceLines, runBatchUpdate, sheetsClientFor, spreadsheetLine } from '../../lib/sheets-client.mjs';
|
|
11
|
+
import { checkRequests, describeRequests } from '../../lib/sheets-admin-request-kinds.mjs';
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
async invoke({ input, capabilities, signal }) {
|
|
15
|
+
const { kinds } = checkRequests({ requests: input.requests });
|
|
16
|
+
const { replies } = await runBatchUpdate({ client: sheetsClientFor({ capabilities }), connectionRef: input.connectionRef, spreadsheetId: input.spreadsheetId, requests: input.requests }, { signal });
|
|
17
|
+
const summary = { applied: input.requests.length, kinds: Object.fromEntries([...kinds].map(([kind, { count }]) => [kind, count])) };
|
|
18
|
+
// The batch has already been applied: an oversized reply list is dropped, not turned into an error.
|
|
19
|
+
// 4 KiB of headroom keeps room for the summary next to the replies.
|
|
20
|
+
return outputFits({ value: replies }, { headroomBytes: 4096 }) ? { summary, replies } : { summary, repliesOmitted: 'larger than 64 KiB' };
|
|
21
|
+
},
|
|
22
|
+
describeAffectedResources({ input }) {
|
|
23
|
+
return resourceLines({ lines: [spreadsheetLine(input), ...describeRequests({ requests: input.requests })] });
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tool `sheets_developer_metadata` (`p_google_workspace__sheets_developer_metadata`): read developer
|
|
3
|
+
* metadata by id (`get`) or by data filters (`search`). Read only; creating, changing and deleting
|
|
4
|
+
* metadata go through `batch_update`.
|
|
5
|
+
*
|
|
6
|
+
* Exists so chat can find the rows, columns or tabs an integration tagged. The metadata id is an
|
|
7
|
+
* integer (input schema and the check here) before it becomes a URL path segment.
|
|
8
|
+
*/
|
|
9
|
+
import { assertOutputWithin, sheetsClientFor, sheetsUrl } from '../../lib/sheets-client.mjs';
|
|
10
|
+
import { GoogleWorkspaceError } from '../../lib/request-policy.mjs';
|
|
11
|
+
|
|
12
|
+
/** The request for one action; the metadata id is checked before it reaches the path. @complexity O(1). */
|
|
13
|
+
function requestFor(input) {
|
|
14
|
+
if (input.action === 'search') {
|
|
15
|
+
return { url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['developerMetadata'], method: ':search' }), method: 'POST', body: { dataFilters: input.dataFilters } };
|
|
16
|
+
}
|
|
17
|
+
if (!Number.isSafeInteger(input.metadataId) || input.metadataId < 0) throw new GoogleWorkspaceError({ code: 'invalid-input' }, { detail: 'metadataId' });
|
|
18
|
+
return { url: sheetsUrl({ spreadsheetId: input.spreadsheetId }, { segments: ['developerMetadata', String(input.metadataId)] }) };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
async invoke({ input, capabilities, signal }) {
|
|
23
|
+
// A search is a POST that changes nothing: `read` lets the client retry it.
|
|
24
|
+
const body = await sheetsClientFor({ capabilities }).request({ connectionRef: input.connectionRef, kind: 'read', ...requestFor(input) }, signal ? { signal } : {});
|
|
25
|
+
const value = input.action === 'search' ? { matches: body?.matchedDeveloperMetadata ?? [] } : { metadata: body };
|
|
26
|
+
return assertOutputWithin({ value: { action: input.action, ...value } });
|
|
27
|
+
},
|
|
28
|
+
};
|