@commandable/mcp 0.11.0 → 0.12.0
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json +1 -0
- package/.output/server/chunks/build/styles.mjs +2 -2
- package/.output/server/chunks/nitro/nitro.mjs +1164 -112
- package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/package.json +2 -2
- package/.output/public/_nuxt/builds/meta/9441a86b-16e9-4000-bffc-3b2e78e57710.json +0 -1
|
@@ -4431,7 +4431,7 @@ function _expandFromEnv(value) {
|
|
|
4431
4431
|
const _inlineRuntimeConfig = {
|
|
4432
4432
|
"app": {
|
|
4433
4433
|
"baseURL": "/",
|
|
4434
|
-
"buildId": "
|
|
4434
|
+
"buildId": "886deef4-f3b5-464c-b4e2-11735eb5272e",
|
|
4435
4435
|
"buildAssetsDir": "/_nuxt/",
|
|
4436
4436
|
"cdnURL": ""
|
|
4437
4437
|
},
|
|
@@ -5034,7 +5034,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
5034
5034
|
}
|
|
5035
5035
|
]
|
|
5036
5036
|
},
|
|
5037
|
-
"
|
|
5037
|
+
"usageGuide": null,
|
|
5038
5038
|
"variants": {
|
|
5039
5039
|
"variants": {
|
|
5040
5040
|
"personal_access_token": {
|
|
@@ -5480,7 +5480,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
5480
5480
|
}
|
|
5481
5481
|
]
|
|
5482
5482
|
},
|
|
5483
|
-
"
|
|
5483
|
+
"usageGuide": '# Confluence usage guide\n\n## Recommended workflow\n\n1. Use `list_spaces` (or `search_pages`) to discover where content lives.\n2. Use `search_pages` with CQL to find the right page ID(s).\n3. Use `read_page` to get the page content as Confluence storage format (XHTML).\n4. For edits, use `update_page` with storage XHTML in `bodyStorage` (it automatically handles version increments).\n\n## Content format\n\nAll content is exchanged in **Confluence storage format (XHTML)**. This applies to both reads (`contentStorage` field in `read_page`) and writes (`bodyStorage` field in `create_page`, `update_page`, `add_comment`).\n\nCommon markup:\n\n- Headings: `<h1>Title</h1>`, `<h2>Section</h2>`\n- Paragraphs: `<p>Text</p>`\n- Lists: `<ul><li>Item</li></ul>`, `<ol><li>Item</li></ol>`\n- Inline code: `<code>const x = 1</code>`\n- Code blocks: `<pre><code>...</code></pre>`\n- Links: `<a href="https://example.com">Example</a>`\n- Tables: `<table><tr><th>A</th></tr><tr><td>1</td></tr></table>`\n- Macros: `<ac:structured-macro ac:name="info"><ac:rich-text-body><p>Note</p></ac:rich-text-body></ac:structured-macro>`\n\nUsing XHTML natively means round-tripping pages preserves all formatting, macros, and Confluence-specific markup.\n\n`read_page` accepts `outputMarkdown: true` to return content as Markdown instead of XHTML. \n\n## CQL (Confluence Query Language) quick reference\n\nCommon patterns for `search_pages.cql`:\n\n- Restrict to a space:\n - `space = "ENG" AND type = page`\n- Title match:\n - `title ~ "runbook" AND type = page`\n- Full-text match:\n - `text ~ "oncall" AND type = page`\n- Label match:\n - `label = "runbook" AND type = page`\n- Combine filters:\n - `space = "ENG" AND type = page AND (title ~ "onboarding" OR text ~ "onboarding")`\n- Sort:\n - `... ORDER BY lastmodified DESC`\n\nTips:\n- Prefer small `limit` (e.g. 10) and paginate with `start`.\n- Use labels as a stable way to group pages for later discovery.\n\n## Page hierarchy\n\n- Spaces contain pages.\n- Pages can be nested under a parent page (`parentId`).\n- Use `get_page_children` to traverse a documentation tree (e.g. a handbook or runbook index).\n\n',
|
|
5484
5484
|
"variants": {
|
|
5485
5485
|
"variants": {
|
|
5486
5486
|
"api_token": {
|
|
@@ -6381,7 +6381,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
6381
6381
|
}
|
|
6382
6382
|
]
|
|
6383
6383
|
},
|
|
6384
|
-
"
|
|
6384
|
+
"usageGuide": "# GitHub coding workflow\n\n## Branch-based workflow\n\nAlways work on a feature branch, never commit directly to main:\n\n1. `create_branch` from the default branch\n2. Make changes with `edit_file`, `edit_files`, `create_file`, or `delete_file` -- each call auto-commits to the branch\n3. `create_pull_request` when done\n4. `merge_pull_request` with `merge_method: \"squash\"` to collapse all commits into one clean commit on main\n\nMultiple small commits on a feature branch are fine -- they get squash-merged into a single commit.\n\n## Choosing the right write tool\n\n- **`edit_file`** -- Surgical edits to a single existing file. Use for most code changes. Each call is a commit.\n- **`edit_files`** -- Atomic multi-file changes (create + edit + delete in one commit). Use when files must change together to stay consistent (e.g. renaming across files, adding a module + updating imports).\n- **`create_file`** -- Create a new file or completely replace an existing file's content. Use for new files or full rewrites.\n- **`delete_file`** -- Remove a file.\n\n## Search/replace rules for edit_file and edit_files\n\nThe `old_text` field must be an **exact match** of the text currently in the file:\n\n- Whitespace matters: spaces, tabs, and indentation must match exactly\n- Line breaks matter: include the exact newline characters\n- Include enough surrounding context to uniquely identify the location\n- Each edit replaces the **first occurrence** only. To replace multiple occurrences, use separate edits.\n\n**Before editing**, call `get_file_contents` to see the file's current content. This avoids failed edits from stale or incorrect assumptions about file content.\n\n## Reading before writing\n\n- Use `get_repo_tree` to discover the project structure and file paths\n- Use `get_file_contents` to read a file before editing it\n- Use `search_code` to find where something is defined or used across the repo\n",
|
|
6385
6385
|
"variants": {
|
|
6386
6386
|
"variants": {
|
|
6387
6387
|
"classic_pat": {
|
|
@@ -8436,7 +8436,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
8436
8436
|
}
|
|
8437
8437
|
]
|
|
8438
8438
|
},
|
|
8439
|
-
"
|
|
8439
|
+
"usageGuide": '## Calendar IDs\n\n- Use `calendarId=\'primary\'` for the authenticated user\'s main calendar\n- Use `list_calendars` to discover other calendar IDs (work, shared, subscribed calendars)\n- Calendar IDs typically look like email addresses (e.g. `user@example.com`) or opaque strings for subscribed calendars\n\n## Date and time format\n\nAll times must be in RFC3339 format:\n- Timed events: `\'2024-01-15T10:00:00-05:00\'` (with timezone offset) or `\'2024-01-15T15:00:00Z\'` (UTC)\n- All-day events use date-only format: `\'2024-01-15\'`\n\n## Creating events\n\nFor `create_event`, required fields are `calendarId`, `summary`, `start`, and `end`:\n\n**Timed event:**\n```json\n{\n "calendarId": "primary",\n "summary": "Team Meeting",\n "start": { "dateTime": "2024-01-15T10:00:00", "timeZone": "America/New_York" },\n "end": { "dateTime": "2024-01-15T11:00:00", "timeZone": "America/New_York" }\n}\n```\n\n**All-day event:**\n```json\n{\n "calendarId": "primary",\n "summary": "Company Holiday",\n "start": { "date": "2024-01-15" },\n "end": { "date": "2024-01-16" }\n}\n```\n\nNote: For all-day events, `end.date` should be the day *after* the last day (exclusive end).\n\n## Listing events in chronological order\n\nTo list upcoming events in start-time order (e.g. "what\'s on my calendar this week"):\n- Set `singleEvents=true` to expand recurring events into individual instances\n- Set `orderBy=\'startTime\'` (requires `singleEvents=true`)\n- Set `timeMin` to now (current ISO timestamp) and `timeMax` to the end of the desired range\n\n## Quick add\n\n`quick_add` parses natural language:\n- `"Meeting with Bob tomorrow at 3pm for 1 hour"`\n- `"Dentist appointment on Friday at 2pm"`\n- `"Weekly standup every Monday at 9am"`\n\n## Free/busy queries\n\nUse `freebusy_query` to check availability before scheduling:\n```json\n{\n "timeMin": "2024-01-15T00:00:00Z",\n "timeMax": "2024-01-15T23:59:59Z",\n "items": [{ "id": "primary" }, { "id": "colleague@example.com" }]\n}\n```\n\n## Updating events\n\n- Use `update_event` for a full replacement (all fields must be provided)\n- Use `patch_event` for partial updates (only provide the fields you want to change in `body`)\n- `patch_event` is preferred when modifying one or two fields to avoid accidentally clearing others\n',
|
|
8440
8440
|
"variants": {
|
|
8441
8441
|
"variants": {
|
|
8442
8442
|
"service_account": {
|
|
@@ -9242,7 +9242,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
9242
9242
|
}
|
|
9243
9243
|
]
|
|
9244
9244
|
},
|
|
9245
|
-
"
|
|
9245
|
+
"usageGuide": "## Gmail search query syntax\n\nGmail's `q` parameter supports a powerful search language. Key operators:\n\n- `is:unread` / `is:read` \u2014 filter by read status\n- `is:starred`, `is:important` \u2014 filter by markers\n- `from:user@example.com` \u2014 sender filter\n- `to:user@example.com`, `cc:user@example.com` \u2014 recipient filters\n- `subject:keyword` \u2014 subject line search\n- `has:attachment` \u2014 messages with attachments\n- `filename:report.pdf` \u2014 specific attachment filename\n- `label:INBOX` \u2014 filter by label (use label name or ID)\n- `after:2024/01/01`, `before:2024/12/31` \u2014 date range (YYYY/MM/DD)\n- `newer_than:7d`, `older_than:1y` \u2014 relative time (d=days, m=months, y=years)\n- `in:sent`, `in:drafts`, `in:trash`, `in:spam` \u2014 folder filters\n- `larger:5M`, `smaller:1M` \u2014 size filters\n\nCombine operators with spaces (implicit AND): `from:alice is:unread has:attachment`\n\n## Recommended workflows\n\n**Reading emails:**\n1. Use `list_messages` with a `q` query to find relevant message IDs\n2. Use `read_email` on each ID to get decoded subject, from, to, date, and body text\n3. For raw access or advanced format options, use `get_message` with `format='full'`\n\n**Searching for threads:**\n1. Use `list_threads` with `q` to find conversation threads\n2. Use `get_thread` to retrieve all messages in a conversation at once\n\n**Sending email:**\n- Use `send_email` for the vast majority of cases -- it accepts plain `to`, `subject`, `body` fields\n- Use `create_draft_email` + `send_draft` when you want to create a draft for review before sending\n\n**Replying to an email:**\n1. Get the original message with `read_email` to obtain its `threadId` and `id`\n2. Call `send_email` with `replyToMessageId` = original message `id` and `threadId` = original `threadId`\n3. The reply will appear in the same conversation thread\n\n## Label IDs\n\nSystem label IDs (always uppercase): `INBOX`, `UNREAD`, `STARRED`, `IMPORTANT`, `SENT`, `DRAFT`, `SPAM`, `TRASH`, `CATEGORY_PERSONAL`, `CATEGORY_SOCIAL`, `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_FORUMS`\n\nUser-created labels have auto-generated IDs. Use `list_labels` to discover them.\n\n## Archiving and organizing\n\n- Archive a message: `modify_message` with `removeLabelIds=['INBOX']`\n- Mark as read: `modify_message` with `removeLabelIds=['UNREAD']`\n- Star a message: `modify_message` with `addLabelIds=['STARRED']`\n- Apply a label: `modify_message` with `addLabelIds=['<labelId>']`\n- Use `modify_thread` to apply the same operation to all messages in a thread at once\n",
|
|
9246
9246
|
"variants": {
|
|
9247
9247
|
"variants": {
|
|
9248
9248
|
"service_account": {
|
|
@@ -10627,7 +10627,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
10627
10627
|
}
|
|
10628
10628
|
]
|
|
10629
10629
|
},
|
|
10630
|
-
"
|
|
10630
|
+
"usageGuide": null,
|
|
10631
10631
|
"variants": {
|
|
10632
10632
|
"variants": {
|
|
10633
10633
|
"service_account": {
|
|
@@ -10827,11 +10827,17 @@ const GENERATED_INTEGRATIONS = {
|
|
|
10827
10827
|
"mimeType": {
|
|
10828
10828
|
"type": "string",
|
|
10829
10829
|
"description": "Optional Drive MIME type from get_file_meta or search_files."
|
|
10830
|
+
},
|
|
10831
|
+
"previewPages": {
|
|
10832
|
+
"type": "integer",
|
|
10833
|
+
"minimum": 1,
|
|
10834
|
+
"maximum": 10,
|
|
10835
|
+
"description": "Number of pages to render as images and return alongside the text (PDF only). Omit or set to 0 to skip. Useful for visually checking signatures, logos, or layout."
|
|
10830
10836
|
}
|
|
10831
10837
|
},
|
|
10832
10838
|
"additionalProperties": false
|
|
10833
10839
|
},
|
|
10834
|
-
"handlerCode": "async (input) => {\n const googleNativeExports = {\n 'application/vnd.google-apps.document': 'text/markdown',\n 'application/vnd.google-apps.spreadsheet': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.google-apps.presentation': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.google-apps.drawing': 'image/svg+xml',\n 'application/vnd.google-apps.script': 'application/vnd.google-apps.script+json',\n }\n const isTextLikeMimeType = (value) => {\n const mimeType = String(value || '').split(';', 1)[0].trim().toLowerCase()\n return mimeType.startsWith('text/')\n || mimeType.includes('json')\n || mimeType.includes('csv')\n || mimeType === 'application/xml'\n || mimeType === 'text/xml'\n || mimeType.endsWith('+xml')\n || mimeType.includes('javascript')\n || mimeType.includes('svg')\n }\n const resolveMimeType = async () => {\n if (typeof input.mimeType === 'string' && input.mimeType.trim())\n return input.mimeType.trim()\n\n const metaRes = await integration.fetch(`/files/${fileId}?fields=id,name,mimeType`)\n const meta = await metaRes.json()\n return meta?.mimeType || ''\n }\n const readTextContent = async (source) => {\n const res = await integration.fetch(source)\n const contentMimeType = res.headers?.get?.('content-type') || ''\n const content = await res.text()\n return { contentMimeType, content }\n }\n\n const fileId = encodeURIComponent(input.fileId)\n const mimeType = await resolveMimeType()\n\n if (!mimeType) {\n return {\n fileId: input.fileId,\n mimeType: null,\n content: null,\n message: 'Could not determine the Drive file MIME type.',\n }\n }\n\n if (mimeType === 'application/vnd.google-apps.folder') {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const isGoogleNative = mimeType.startsWith('application/vnd.google-apps.')\n const exportMimeType = isGoogleNative\n ? (typeof input.exportMimeType === 'string' && input.exportMimeType.trim())\n ? input.exportMimeType.trim()\n : googleNativeExports[mimeType] || null\n : null\n\n if (isGoogleNative && !exportMimeType) {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'This Google-native file type does not have a configured export path for read_file_content.',\n }\n }\n\n const source = isGoogleNative\n ? `/files/${fileId}/export?mimeType=${encodeURIComponent(exportMimeType)}`\n : `/files/${fileId}?alt=media`\n\n if (isTextLikeMimeType(exportMimeType || mimeType)) {\n const textResult = await readTextContent(source)\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: textResult.contentMimeType || exportMimeType || mimeType,\n content: textResult.content,\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source,\n })\n\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: exportMimeType || mimeType,\n ...extracted,\n }\n}",
|
|
10840
|
+
"handlerCode": "async (input) => {\n const googleNativeExports = {\n 'application/vnd.google-apps.document': 'text/markdown',\n 'application/vnd.google-apps.spreadsheet': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.google-apps.presentation': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.google-apps.drawing': 'image/svg+xml',\n 'application/vnd.google-apps.script': 'application/vnd.google-apps.script+json',\n }\n const isTextLikeMimeType = (value) => {\n const mimeType = String(value || '').split(';', 1)[0].trim().toLowerCase()\n return mimeType.startsWith('text/')\n || mimeType.includes('json')\n || mimeType.includes('csv')\n || mimeType === 'application/xml'\n || mimeType === 'text/xml'\n || mimeType.endsWith('+xml')\n || mimeType.includes('javascript')\n || mimeType.includes('svg')\n }\n const resolveMimeType = async () => {\n if (typeof input.mimeType === 'string' && input.mimeType.trim())\n return input.mimeType.trim()\n\n const metaRes = await integration.fetch(`/files/${fileId}?fields=id,name,mimeType`)\n const meta = await metaRes.json()\n return meta?.mimeType || ''\n }\n const readTextContent = async (source) => {\n const res = await integration.fetch(source)\n const contentMimeType = res.headers?.get?.('content-type') || ''\n const content = await res.text()\n return { contentMimeType, content }\n }\n\n const fileId = encodeURIComponent(input.fileId)\n const mimeType = await resolveMimeType()\n\n if (!mimeType) {\n return {\n fileId: input.fileId,\n mimeType: null,\n content: null,\n message: 'Could not determine the Drive file MIME type.',\n }\n }\n\n if (mimeType === 'application/vnd.google-apps.folder') {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const isGoogleNative = mimeType.startsWith('application/vnd.google-apps.')\n const exportMimeType = isGoogleNative\n ? (typeof input.exportMimeType === 'string' && input.exportMimeType.trim())\n ? input.exportMimeType.trim()\n : googleNativeExports[mimeType] || null\n : null\n\n if (isGoogleNative && !exportMimeType) {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'This Google-native file type does not have a configured export path for read_file_content.',\n }\n }\n\n const source = isGoogleNative\n ? `/files/${fileId}/export?mimeType=${encodeURIComponent(exportMimeType)}`\n : `/files/${fileId}?alt=media`\n\n if (isTextLikeMimeType(exportMimeType || mimeType)) {\n const textResult = await readTextContent(source)\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: textResult.contentMimeType || exportMimeType || mimeType,\n content: textResult.content,\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source,\n previewPages: input.previewPages || 0,\n })\n\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: exportMimeType || mimeType,\n ...extracted,\n }\n}",
|
|
10835
10841
|
"scope": "read",
|
|
10836
10842
|
"toolset": "drive"
|
|
10837
10843
|
},
|
|
@@ -12523,7 +12529,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
12523
12529
|
}
|
|
12524
12530
|
]
|
|
12525
12531
|
},
|
|
12526
|
-
"
|
|
12532
|
+
"usageGuide": '## HubSpot guidance\n\nThis integration uses HubSpot CRM v3 object endpoints and CRM v4 association endpoints.\n\n- Prefer `search_*` tools for discovery (they support free-text `query` and structured `filters`).\n- Use `get_*` tools when you already have an object ID and want full details / associations.\n\n### Search filters\n\nAll `search_*` tools accept:\n\n- `query`: free-text search (optional)\n- `filters`: property-based filtering. Each filter is `{ propertyName, operator, value? }`.\n\nCommon operators:\n\n- `EQ`, `NEQ`\n- `LT`, `LTE`, `GT`, `GTE` (numbers or millisecond timestamps)\n- `CONTAINS_TOKEN` (tokenized contains)\n- `HAS_PROPERTY`, `NOT_HAS_PROPERTY` (value ignored)\n- `BETWEEN` (pass `value` as a string `"low,high"`; timestamps in ms recommended)\n\n### Common property names (quick reference)\n\nContacts:\n- `firstname`, `lastname`, `email`\n\nCompanies:\n- `name`, `domain`\n\nDeals:\n- `dealname`, `amount`, `pipeline`, `dealstage`, `closedate`\n\nTickets:\n- `subject`, `content`, `hs_pipeline`, `hs_pipeline_stage`\n\n### Pipelines and stages (deals/tickets)\n\nPipelines and stages are stored as IDs (not human-friendly names). Recommended workflow:\n\n1. Call `list_pipelines` with `objectType: "deals"` or `objectType: "tickets"`.\n2. Pick a pipeline ID and stage ID from the response.\n3. Use those IDs when calling `create_deal` / `update_deal` (via `pipeline` / `dealstage`) or `create_ticket` / `update_ticket` (via `hs_pipeline` / `hs_pipeline_stage`).\n\n### Associations\n\n- Use `get_associations` to list linked records (returns associated IDs).\n- Use `create_association` to link two records (default/unlabeled association).\n- Use `remove_association` to unlink records.\n\n### Engagement objects\n\nNotes:\n- Content: `hs_note_body`\n- Timestamp: `hs_timestamp` (milliseconds)\n\nTasks:\n- Subject: `hs_task_subject`\n- Body: `hs_task_body`\n- Status: `hs_task_status` (`NOT_STARTED` or `COMPLETED`)\n- Priority: `hs_task_priority` (`LOW`, `MEDIUM`, `HIGH`)\n- Due timestamp: `hs_timestamp` (milliseconds)\n\nThe `create_note` and `create_task` tools can also associate the engagement to CRM records in the same call.\n\n### Pagination\n\nHubSpot uses cursor-based pagination. When a response includes `paging.next.after`, pass that value back as `after` in your next call.\n\n',
|
|
12527
12533
|
"variants": {
|
|
12528
12534
|
"variants": {
|
|
12529
12535
|
"private_app_token": {
|
|
@@ -14100,7 +14106,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
14100
14106
|
}
|
|
14101
14107
|
]
|
|
14102
14108
|
},
|
|
14103
|
-
"
|
|
14109
|
+
"usageGuide": "# Jira usage guide\n\n## Core workflow patterns\n\n### Discover projects and issue types (before creating issues)\n\n1. Call `list_projects` to find the project key (e.g. `PROJ`).\n2. Call `get_project` with `projectIdOrKey=PROJ` to see available `issueTypes`.\n3. Use the returned issue type name with `create_issue.issueTypeName`.\n\n### Search issues (JQL)\n\nUse `search_issues` with JQL. Common examples:\n\n- My open issues:\n - `assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC`\n- Recently updated issues in a project:\n - `project = PROJ ORDER BY updated DESC`\n- Unassigned bugs:\n - `project = PROJ AND issuetype = Bug AND assignee is EMPTY ORDER BY created DESC`\n- Blocked issues (label-based):\n - `project = PROJ AND labels = blocked ORDER BY priority DESC, updated DESC`\n\nPagination:\n- `search_issues` uses `nextPageToken`. If `nextPageToken` is returned and `isLast=false`, pass it back to get the next page.\n\n### Read issue content\n\n- Use `get_issue` to read a compact issue summary.\n- `get_issue` converts Jira's ADF description into `descriptionMarkdown` when possible (fallback: `descriptionText`).\n- Use `get_issue_comments` to read the comment thread (comment bodies are converted to Markdown).\n\n### Transition an issue (change status)\n\nJira workflows are project-specific, so you must discover valid transitions:\n\n1. Call `get_transitions` to see available transition names/IDs for the issue.\n2. Call `transition_issue` using either `transitionId` (preferred) or `transitionName`.\n\n### Assigning issues\n\n1. Call `search_users` to find the user's `accountId`.\n2. Assign:\n - `assign_issue { issueIdOrKey, accountId }`\n3. Unassign:\n - `assign_issue { issueIdOrKey, accountId: null }`\n\n## Notes on Jira rich text (ADF)\n\nJira Cloud REST API v3 uses **Atlassian Document Format (ADF)** for fields like `description` and comment bodies.\n\n- Read tools convert ADF to Markdown so you can read it directly.\n- Write tools accept **Markdown** in their `*Text` fields and convert it to ADF (`descriptionText`, `bodyText`, `commentText`).\n\n### Round-trip Markdown workflow (recommended)\n\nYou can safely do a Markdown round-trip:\n\n1. `get_issue` -> edit `descriptionMarkdown`\n2. `update_issue { descriptionText: <your edited Markdown> }`\n\nSupported Markdown features when writing:\n\n- Headings (`#`, `##`, ...)\n- Bold/italic/strikethrough (`**bold**`, `*italic*`, `~~strike~~`)\n- Inline code and fenced code blocks (```), including optional language fences\n- Lists (ordered/unordered), nested lists\n- Blockquotes (`>`)\n- Horizontal rules (`---`)\n- Tables\n- Links (`[text](url)`)\n\n## Boards & sprints\n\nIf you\u2019re using Jira Software:\n\n1. Call `list_boards` (optionally filter by `projectKeyOrId`).\n2. Call `list_sprints` for a board to find active/future sprints.\n3. Use `move_issues_to_sprint` to pull work into a sprint (sprint planning).\n\n",
|
|
14104
14110
|
"variants": {
|
|
14105
14111
|
"variants": {
|
|
14106
14112
|
"api_token": {
|
|
@@ -15188,7 +15194,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
15188
15194
|
}
|
|
15189
15195
|
]
|
|
15190
15196
|
},
|
|
15191
|
-
"
|
|
15197
|
+
"usageGuide": '## Appending paragraph blocks\n\nWhen appending a paragraph block to a Notion page, ensure the `rich_text` field is correctly defined within the `paragraph` type. Example format:\n\n```json\n{\n "block_id": "<page_id>",\n "children": [\n {\n "object": "block",\n "type": "paragraph",\n "paragraph": {\n "rich_text": [\n {\n "type": "text",\n "text": {\n "content": "Your text here"\n }\n }\n ]\n }\n }\n ]\n}\n```\n\n',
|
|
15192
15198
|
"variants": {
|
|
15193
15199
|
"variants": {
|
|
15194
15200
|
"internal_integration": {
|
|
@@ -15929,7 +15935,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
15929
15935
|
}
|
|
15930
15936
|
]
|
|
15931
15937
|
},
|
|
15932
|
-
"
|
|
15938
|
+
"usageGuide": "Use this integration for SharePoint document libraries and files.\n\nRecommended workflow:\n\n1. If you know the SharePoint hostname and path, start with `get_site_by_path`.\n2. Otherwise use `search_sites` to discover the correct site.\n3. Use `list_site_drives` to find the relevant document library for that site.\n4. Use `list_drive_children` for deterministic folder browsing or `search_files` for broader file discovery.\n5. Use `get_drive_item_meta` when you need compact metadata for a specific file or folder.\n6. Use `read_file_content` to consume the actual contents of a file in agent-friendly text.\n\nNotes:\n\n- `search_files` uses Microsoft Graph search. The `query` field accepts normal keywords and Graph KQL syntax.\n- `siteId` and `driveId` filters on `search_files` are applied to the flattened search results after Graph returns them.\n- `read_file_content` is for files only. Folders do not have readable file content.\n- This v1 integration is intentionally focused on SharePoint sites, document libraries, folders, and files. It does not include classic SharePoint list/list-item tools or file upload.\n",
|
|
15933
15939
|
"variants": {
|
|
15934
15940
|
"variants": {
|
|
15935
15941
|
"app_credentials": {
|
|
@@ -16043,44 +16049,481 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16043
16049
|
"description": "Microsoft Graph SharePoint site ID."
|
|
16044
16050
|
}
|
|
16045
16051
|
},
|
|
16046
|
-
"additionalProperties": false
|
|
16047
|
-
},
|
|
16048
|
-
"handlerCode": "async (input) => {\n const res = await integration.fetch(`/sites/${encodeURIComponent(input.siteId)}`)\n const site = await res.json()\n return {\n id: site.id,\n name: site.displayName || site.name || null,\n displayName: site.displayName || site.name || null,\n description: site.description || '',\n webUrl: site.webUrl || null,\n createdDateTime: site.createdDateTime || null,\n lastModifiedDateTime: site.lastModifiedDateTime || null,\n }\n}",
|
|
16049
|
-
"scope": "read"
|
|
16052
|
+
"additionalProperties": false
|
|
16053
|
+
},
|
|
16054
|
+
"handlerCode": "async (input) => {\n const res = await integration.fetch(`/sites/${encodeURIComponent(input.siteId)}`)\n const site = await res.json()\n return {\n id: site.id,\n name: site.displayName || site.name || null,\n displayName: site.displayName || site.name || null,\n description: site.description || '',\n webUrl: site.webUrl || null,\n createdDateTime: site.createdDateTime || null,\n lastModifiedDateTime: site.lastModifiedDateTime || null,\n }\n}",
|
|
16055
|
+
"scope": "read"
|
|
16056
|
+
},
|
|
16057
|
+
{
|
|
16058
|
+
"name": "list_site_drives",
|
|
16059
|
+
"description": "List document libraries (drives) for a SharePoint site. Returns compact drive summaries including IDs, names, web URLs, and drive type. Use this after resolving a site to discover the right document library before browsing folders or reading files.",
|
|
16060
|
+
"inputSchema": {
|
|
16061
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16062
|
+
"type": "object",
|
|
16063
|
+
"required": [
|
|
16064
|
+
"siteId"
|
|
16065
|
+
],
|
|
16066
|
+
"properties": {
|
|
16067
|
+
"siteId": {
|
|
16068
|
+
"type": "string",
|
|
16069
|
+
"description": "Microsoft Graph SharePoint site ID."
|
|
16070
|
+
},
|
|
16071
|
+
"top": {
|
|
16072
|
+
"type": "integer",
|
|
16073
|
+
"minimum": 1,
|
|
16074
|
+
"maximum": 200,
|
|
16075
|
+
"description": "Maximum number of drives to return."
|
|
16076
|
+
},
|
|
16077
|
+
"includeSystem": {
|
|
16078
|
+
"type": "boolean",
|
|
16079
|
+
"description": "Set true to include hidden/system drives."
|
|
16080
|
+
}
|
|
16081
|
+
},
|
|
16082
|
+
"additionalProperties": false
|
|
16083
|
+
},
|
|
16084
|
+
"handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set('$select', input.includeSystem\n ? 'id,name,webUrl,driveType,createdDateTime,lastModifiedDateTime,system'\n : 'id,name,webUrl,driveType,createdDateTime,lastModifiedDateTime')\n if (input.top)\n params.set('$top', String(input.top))\n\n const res = await integration.fetch(`/sites/${encodeURIComponent(input.siteId)}/drives?${params.toString()}`)\n const data = await res.json()\n const drives = Array.isArray(data?.value)\n ? data.value.map(drive => ({\n id: drive.id,\n name: drive.name || null,\n webUrl: drive.webUrl || null,\n driveType: drive.driveType || null,\n createdDateTime: drive.createdDateTime || null,\n lastModifiedDateTime: drive.lastModifiedDateTime || null,\n isSystem: Boolean(drive.system),\n }))\n : []\n\n return {\n siteId: input.siteId,\n drives,\n nextLink: data?.['@odata.nextLink'] || null,\n }\n}",
|
|
16085
|
+
"scope": "read"
|
|
16086
|
+
},
|
|
16087
|
+
{
|
|
16088
|
+
"name": "list_drive_children",
|
|
16089
|
+
"description": "List the files and folders inside a SharePoint document library folder. By default this lists the root of the drive. Provide itemId to browse a specific folder. Returns compact entries with file-or-folder flags, MIME type when available, and parent references. Use get_drive_item_meta when you need one specific item.",
|
|
16090
|
+
"inputSchema": {
|
|
16091
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16092
|
+
"type": "object",
|
|
16093
|
+
"required": [
|
|
16094
|
+
"driveId"
|
|
16095
|
+
],
|
|
16096
|
+
"properties": {
|
|
16097
|
+
"driveId": {
|
|
16098
|
+
"type": "string",
|
|
16099
|
+
"description": "Document library drive ID."
|
|
16100
|
+
},
|
|
16101
|
+
"itemId": {
|
|
16102
|
+
"type": "string",
|
|
16103
|
+
"description": "Optional folder item ID. Omit to list the drive root."
|
|
16104
|
+
},
|
|
16105
|
+
"top": {
|
|
16106
|
+
"type": "integer",
|
|
16107
|
+
"minimum": 1,
|
|
16108
|
+
"maximum": 200,
|
|
16109
|
+
"description": "Maximum number of children to return."
|
|
16110
|
+
},
|
|
16111
|
+
"orderBy": {
|
|
16112
|
+
"type": "string",
|
|
16113
|
+
"description": "Optional Microsoft Graph orderBy expression, such as name or lastModifiedDateTime desc."
|
|
16114
|
+
}
|
|
16115
|
+
},
|
|
16116
|
+
"additionalProperties": false
|
|
16117
|
+
},
|
|
16118
|
+
"handlerCode": "async (input) => {\n const flattenItem = item => ({\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n eTag: item.eTag || null,\n cTag: item.cTag || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n })\n\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,eTag,cTag,parentReference,file,folder,package',\n )\n if (input.top)\n params.set('$top', String(input.top))\n if (input.orderBy)\n params.set('$orderby', input.orderBy)\n\n const basePath = input.itemId\n ? `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/children`\n : `/drives/${encodeURIComponent(input.driveId)}/root/children`\n\n const res = await integration.fetch(`${basePath}?${params.toString()}`)\n const data = await res.json()\n\n return {\n driveId: input.driveId,\n itemId: input.itemId || null,\n children: Array.isArray(data?.value) ? data.value.map(flattenItem) : [],\n nextLink: data?.['@odata.nextLink'] || null,\n }\n}",
|
|
16119
|
+
"scope": "read"
|
|
16120
|
+
},
|
|
16121
|
+
{
|
|
16122
|
+
"name": "get_drive_item_meta",
|
|
16123
|
+
"description": "Get metadata for a single SharePoint file or folder by drive ID and item ID. Returns a compact item summary including IDs, name, type flags, web URL, size, timestamps, and parent reference. Use read_file_content to read the actual file contents.",
|
|
16124
|
+
"inputSchema": {
|
|
16125
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16126
|
+
"type": "object",
|
|
16127
|
+
"required": [
|
|
16128
|
+
"driveId",
|
|
16129
|
+
"itemId"
|
|
16130
|
+
],
|
|
16131
|
+
"properties": {
|
|
16132
|
+
"driveId": {
|
|
16133
|
+
"type": "string",
|
|
16134
|
+
"description": "Document library drive ID."
|
|
16135
|
+
},
|
|
16136
|
+
"itemId": {
|
|
16137
|
+
"type": "string",
|
|
16138
|
+
"description": "Drive item ID for the file or folder."
|
|
16139
|
+
}
|
|
16140
|
+
},
|
|
16141
|
+
"additionalProperties": false
|
|
16142
|
+
},
|
|
16143
|
+
"handlerCode": "async (input) => {\n const flattenItem = item => ({\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n eTag: item.eTag || null,\n cTag: item.cTag || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n })\n\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,eTag,cTag,parentReference,file,folder,package',\n )\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}?${params.toString()}`)\n return flattenItem(await res.json())\n}",
|
|
16144
|
+
"scope": "read"
|
|
16145
|
+
},
|
|
16146
|
+
{
|
|
16147
|
+
"name": "search_files",
|
|
16148
|
+
"description": "Search SharePoint and OneDrive content through Microsoft Graph search and return flattened file hits. Provide a query string; Graph KQL syntax is supported. Optional siteId and driveId filters narrow the flattened results after search. Use this for broad file discovery when folder-by-folder browsing is too narrow.",
|
|
16149
|
+
"inputSchema": {
|
|
16150
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16151
|
+
"type": "object",
|
|
16152
|
+
"required": [
|
|
16153
|
+
"query"
|
|
16154
|
+
],
|
|
16155
|
+
"properties": {
|
|
16156
|
+
"query": {
|
|
16157
|
+
"type": "string",
|
|
16158
|
+
"description": "Search query string. Microsoft Graph KQL syntax is supported."
|
|
16159
|
+
},
|
|
16160
|
+
"siteId": {
|
|
16161
|
+
"type": "string",
|
|
16162
|
+
"description": "Optional site ID to keep only hits from a specific SharePoint site."
|
|
16163
|
+
},
|
|
16164
|
+
"driveId": {
|
|
16165
|
+
"type": "string",
|
|
16166
|
+
"description": "Optional drive ID to keep only hits from a specific document library."
|
|
16167
|
+
},
|
|
16168
|
+
"from": {
|
|
16169
|
+
"type": "integer",
|
|
16170
|
+
"minimum": 0,
|
|
16171
|
+
"description": "Offset into the Graph search results."
|
|
16172
|
+
},
|
|
16173
|
+
"size": {
|
|
16174
|
+
"type": "integer",
|
|
16175
|
+
"minimum": 1,
|
|
16176
|
+
"maximum": 50,
|
|
16177
|
+
"description": "Maximum number of hits to request from Graph."
|
|
16178
|
+
}
|
|
16179
|
+
},
|
|
16180
|
+
"additionalProperties": false
|
|
16181
|
+
},
|
|
16182
|
+
"handlerCode": "async (input) => {\n const extractFallbackRegion = (error) => {\n const texts = [error?.data?.body, error?.message].filter(s => typeof s === 'string')\n for (const text of texts) {\n const match = text.match(/Only valid regions are ([A-Z,\\s]+)/i)\n const region = match?.[1]?.split(',').map(r => r.trim().toUpperCase()).filter(Boolean)[0]\n if (region)\n return region\n }\n return null\n }\n\n const runSearch = async (region) => {\n const res = await integration.fetch('/search/query', {\n method: 'POST',\n body: {\n requests: [{\n entityTypes: ['driveItem'],\n query: { queryString: input.query },\n from: typeof input.from === 'number' ? input.from : 0,\n size: typeof input.size === 'number' ? input.size : 25,\n region,\n }],\n },\n })\n return res.json()\n }\n\n const flattenHit = (hit) => {\n const resource = hit?.resource || {}\n const parentReference = resource.parentReference || {}\n return {\n id: resource.id || hit?.hitId || null,\n name: resource.name || null,\n webUrl: resource.webUrl || null,\n summary: hit?.summary || '',\n rank: hit?.rank ?? null,\n createdDateTime: resource.createdDateTime || null,\n lastModifiedDateTime: resource.lastModifiedDateTime || null,\n mimeType: resource.file?.mimeType || null,\n size: resource.size ?? null,\n isFolder: Boolean(resource.folder || resource.package),\n isFile: Boolean(resource.file),\n driveId: parentReference.driveId || null,\n siteId: parentReference.siteId || null,\n parentReference,\n }\n }\n\n let data\n try {\n data = await runSearch('NAM')\n }\n catch (error) {\n const fallback = extractFallbackRegion(error)\n if (!fallback)\n throw error\n data = await runSearch(fallback)\n }\n\n const container = data?.value?.[0]?.hitsContainers?.[0]\n const allHits = Array.isArray(container?.hits) ? container.hits.map(flattenHit) : []\n const hits = allHits.filter((hit) => {\n if (input.siteId && hit.siteId !== input.siteId)\n return false\n if (input.driveId && hit.driveId !== input.driveId)\n return false\n return true\n })\n\n return {\n query: input.query,\n hits,\n total: container?.total ?? hits.length,\n moreResultsAvailable: Boolean(container?.moreResultsAvailable),\n }\n}",
|
|
16183
|
+
"scope": "read"
|
|
16184
|
+
},
|
|
16185
|
+
{
|
|
16186
|
+
"name": "read_file_content",
|
|
16187
|
+
"description": "Read a SharePoint file into agent-friendly text using the shared file extraction pipeline. This is the standard way to consume document contents such as PDF, DOCX, XLSX, PPTX, and text-like files stored in SharePoint document libraries. Provide driveId and itemId. Folders are rejected; use list_drive_children to browse them.",
|
|
16188
|
+
"inputSchema": {
|
|
16189
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16190
|
+
"type": "object",
|
|
16191
|
+
"required": [
|
|
16192
|
+
"driveId",
|
|
16193
|
+
"itemId"
|
|
16194
|
+
],
|
|
16195
|
+
"properties": {
|
|
16196
|
+
"driveId": {
|
|
16197
|
+
"type": "string",
|
|
16198
|
+
"description": "Document library drive ID."
|
|
16199
|
+
},
|
|
16200
|
+
"itemId": {
|
|
16201
|
+
"type": "string",
|
|
16202
|
+
"description": "Drive item ID for the file."
|
|
16203
|
+
},
|
|
16204
|
+
"mimeType": {
|
|
16205
|
+
"type": "string",
|
|
16206
|
+
"description": "Optional MIME type from get_drive_item_meta or search_files."
|
|
16207
|
+
},
|
|
16208
|
+
"previewPages": {
|
|
16209
|
+
"type": "integer",
|
|
16210
|
+
"minimum": 1,
|
|
16211
|
+
"maximum": 10,
|
|
16212
|
+
"description": "Number of pages to render as images and return alongside the text (PDF only). Omit or set to 0 to skip. Useful for visually checking signatures, logos, or layout."
|
|
16213
|
+
}
|
|
16214
|
+
},
|
|
16215
|
+
"additionalProperties": false
|
|
16216
|
+
},
|
|
16217
|
+
"handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,parentReference,file,folder,package',\n )\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}?${params.toString()}`)\n const item = await res.json()\n const mimeType = input.mimeType || item?.file?.mimeType || null\n\n if (item?.folder || item?.package) {\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source: `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/content`,\n previewPages: input.previewPages || 0,\n })\n\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n webUrl: item?.webUrl || null,\n mimeType,\n ...extracted,\n }\n}",
|
|
16218
|
+
"scope": "read"
|
|
16219
|
+
},
|
|
16220
|
+
{
|
|
16221
|
+
"name": "create_folder",
|
|
16222
|
+
"description": "Create a new folder in a SharePoint document library. By default the folder is created in the drive root. Provide parentItemId to create it inside an existing folder. Returns the created folder metadata including its item ID for later browsing or moves.",
|
|
16223
|
+
"inputSchema": {
|
|
16224
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16225
|
+
"type": "object",
|
|
16226
|
+
"required": [
|
|
16227
|
+
"driveId",
|
|
16228
|
+
"name"
|
|
16229
|
+
],
|
|
16230
|
+
"properties": {
|
|
16231
|
+
"driveId": {
|
|
16232
|
+
"type": "string",
|
|
16233
|
+
"description": "Document library drive ID."
|
|
16234
|
+
},
|
|
16235
|
+
"name": {
|
|
16236
|
+
"type": "string",
|
|
16237
|
+
"description": "Folder name to create."
|
|
16238
|
+
},
|
|
16239
|
+
"parentItemId": {
|
|
16240
|
+
"type": "string",
|
|
16241
|
+
"description": "Optional parent folder item ID. Omit to create in the drive root."
|
|
16242
|
+
},
|
|
16243
|
+
"conflictBehavior": {
|
|
16244
|
+
"type": "string",
|
|
16245
|
+
"enum": [
|
|
16246
|
+
"rename",
|
|
16247
|
+
"replace",
|
|
16248
|
+
"fail"
|
|
16249
|
+
],
|
|
16250
|
+
"description": "How Graph should handle name conflicts. Defaults to rename."
|
|
16251
|
+
}
|
|
16252
|
+
},
|
|
16253
|
+
"additionalProperties": false
|
|
16254
|
+
},
|
|
16255
|
+
"handlerCode": "async (input) => {\n const path = input.parentItemId\n ? `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.parentItemId)}/children`\n : `/drives/${encodeURIComponent(input.driveId)}/root/children`\n\n const res = await integration.fetch(path, {\n method: 'POST',\n body: {\n name: input.name,\n folder: {},\n '@microsoft.graph.conflictBehavior': input.conflictBehavior || 'rename',\n },\n })\n const item = await res.json()\n return {\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n }\n}",
|
|
16256
|
+
"scope": "write"
|
|
16257
|
+
},
|
|
16258
|
+
{
|
|
16259
|
+
"name": "move_drive_item",
|
|
16260
|
+
"description": "Move a SharePoint file or folder to a different parent folder in the same drive. Provide destinationParentId and optionally a newName to rename during the move. Use get_drive_item_meta or list_drive_children first to discover the current item and destination IDs.",
|
|
16261
|
+
"inputSchema": {
|
|
16262
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16263
|
+
"type": "object",
|
|
16264
|
+
"required": [
|
|
16265
|
+
"driveId",
|
|
16266
|
+
"itemId",
|
|
16267
|
+
"destinationParentId"
|
|
16268
|
+
],
|
|
16269
|
+
"properties": {
|
|
16270
|
+
"driveId": {
|
|
16271
|
+
"type": "string",
|
|
16272
|
+
"description": "Document library drive ID."
|
|
16273
|
+
},
|
|
16274
|
+
"itemId": {
|
|
16275
|
+
"type": "string",
|
|
16276
|
+
"description": "Drive item ID for the file or folder to move."
|
|
16277
|
+
},
|
|
16278
|
+
"destinationParentId": {
|
|
16279
|
+
"type": "string",
|
|
16280
|
+
"description": "Destination folder item ID."
|
|
16281
|
+
},
|
|
16282
|
+
"newName": {
|
|
16283
|
+
"type": "string",
|
|
16284
|
+
"description": "Optional new item name to apply during the move."
|
|
16285
|
+
}
|
|
16286
|
+
},
|
|
16287
|
+
"additionalProperties": false
|
|
16288
|
+
},
|
|
16289
|
+
"handlerCode": "async (input) => {\n const body = {\n parentReference: {\n id: input.destinationParentId,\n },\n }\n if (input.newName)\n body.name = input.newName\n\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}`, {\n method: 'PATCH',\n body,\n })\n const item = await res.json()\n return {\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n }\n}",
|
|
16290
|
+
"scope": "write"
|
|
16291
|
+
},
|
|
16292
|
+
{
|
|
16293
|
+
"name": "delete_drive_item",
|
|
16294
|
+
"description": "Delete a SharePoint file or folder by drive ID and item ID. This is a destructive operation. Use get_drive_item_meta or list_drive_children first to confirm you have the correct item before deleting it.",
|
|
16295
|
+
"inputSchema": {
|
|
16296
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16297
|
+
"type": "object",
|
|
16298
|
+
"required": [
|
|
16299
|
+
"driveId",
|
|
16300
|
+
"itemId"
|
|
16301
|
+
],
|
|
16302
|
+
"properties": {
|
|
16303
|
+
"driveId": {
|
|
16304
|
+
"type": "string",
|
|
16305
|
+
"description": "Document library drive ID."
|
|
16306
|
+
},
|
|
16307
|
+
"itemId": {
|
|
16308
|
+
"type": "string",
|
|
16309
|
+
"description": "Drive item ID for the file or folder to delete."
|
|
16310
|
+
}
|
|
16311
|
+
},
|
|
16312
|
+
"additionalProperties": false
|
|
16313
|
+
},
|
|
16314
|
+
"handlerCode": "async (input) => {\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}`, {\n method: 'DELETE',\n })\n if (res.status === 204)\n return { success: true, status: 204 }\n try {\n return await res.json()\n }\n catch {\n return { success: res.ok, status: res.status }\n }\n}",
|
|
16315
|
+
"scope": "write"
|
|
16316
|
+
}
|
|
16317
|
+
],
|
|
16318
|
+
"variantOwnerType": null
|
|
16319
|
+
},
|
|
16320
|
+
"sharepoint-folder": {
|
|
16321
|
+
"manifest": {
|
|
16322
|
+
"name": "sharepoint",
|
|
16323
|
+
"version": "0.1.0",
|
|
16324
|
+
"baseUrl": "https://graph.microsoft.com/v1.0",
|
|
16325
|
+
"variantLabel": "Single folder",
|
|
16326
|
+
"variantConfig": [
|
|
16327
|
+
{
|
|
16328
|
+
"key": "site",
|
|
16329
|
+
"label": "Site",
|
|
16330
|
+
"selectionMode": "single",
|
|
16331
|
+
"listHandler": "async (config) => {\n const res = await integration.fetch('/sites?search=*&$select=id,displayName,name&$top=50')\n const data = await res.json()\n if (!Array.isArray(data?.value)) return []\n return data.value\n .map(site => ({ id: site.id, name: site.displayName || site.name || site.id }))\n .filter(s => s.id && s.name)\n}"
|
|
16332
|
+
},
|
|
16333
|
+
{
|
|
16334
|
+
"key": "drive",
|
|
16335
|
+
"label": "Document library",
|
|
16336
|
+
"selectionMode": "single",
|
|
16337
|
+
"listHandler": "async (config) => {\n if (!config.siteId) return []\n const res = await integration.fetch(`/sites/${encodeURIComponent(config.siteId)}/drives?$select=id,name,driveType,system`)\n const data = await res.json()\n if (!Array.isArray(data?.value)) return []\n return data.value\n .filter(d => !d.system)\n .map(d => ({ id: d.id, name: d.name || d.id }))\n .filter(d => d.id && d.name)\n}"
|
|
16338
|
+
},
|
|
16339
|
+
{
|
|
16340
|
+
"key": "folder",
|
|
16341
|
+
"label": "Folder",
|
|
16342
|
+
"selectionMode": "single",
|
|
16343
|
+
"listHandler": "async (config) => {\n if (!config.driveId) return []\n const res = await integration.fetch(`/drives/${encodeURIComponent(config.driveId)}/root/children?$select=id,name,folder&$top=100`)\n const data = await res.json()\n if (!Array.isArray(data?.value)) return []\n return data.value\n .filter(item => item.folder)\n .map(item => ({ id: item.id, name: item.name || item.id }))\n .filter(item => item.id && item.name)\n}"
|
|
16344
|
+
}
|
|
16345
|
+
],
|
|
16346
|
+
"tools": [
|
|
16347
|
+
{
|
|
16348
|
+
"name": "list_folder",
|
|
16349
|
+
"description": "List files and folders inside the connected SharePoint folder.",
|
|
16350
|
+
"inputSchema": "../../schemas/list_drive_children.json",
|
|
16351
|
+
"handler": "../../handlers/list_drive_children.js",
|
|
16352
|
+
"scope": "read",
|
|
16353
|
+
"injectFromConfig": {
|
|
16354
|
+
"driveId": "driveId",
|
|
16355
|
+
"itemId": "folderId"
|
|
16356
|
+
}
|
|
16357
|
+
},
|
|
16358
|
+
{
|
|
16359
|
+
"name": "browse_folder",
|
|
16360
|
+
"description": "List files and folders inside a sub-folder by item ID. Use list_folder first to discover sub-folder IDs.",
|
|
16361
|
+
"inputSchema": "../../schemas/list_drive_children.json",
|
|
16362
|
+
"handler": "../../handlers/list_drive_children.js",
|
|
16363
|
+
"scope": "read",
|
|
16364
|
+
"injectFromConfig": {
|
|
16365
|
+
"driveId": "driveId"
|
|
16366
|
+
}
|
|
16367
|
+
},
|
|
16368
|
+
{
|
|
16369
|
+
"name": "get_drive_item_meta",
|
|
16370
|
+
"description": "Get metadata for a file or folder by item ID.",
|
|
16371
|
+
"inputSchema": "../../schemas/get_drive_item.json",
|
|
16372
|
+
"handler": "../../handlers/get_drive_item.js",
|
|
16373
|
+
"scope": "read",
|
|
16374
|
+
"injectFromConfig": {
|
|
16375
|
+
"driveId": "driveId"
|
|
16376
|
+
}
|
|
16377
|
+
},
|
|
16378
|
+
{
|
|
16379
|
+
"name": "search_files",
|
|
16380
|
+
"description": "Search files within the connected document library. KQL syntax is supported.",
|
|
16381
|
+
"inputSchema": "../../schemas/search_files.json",
|
|
16382
|
+
"handler": "../../handlers/search_files.js",
|
|
16383
|
+
"scope": "read",
|
|
16384
|
+
"injectFromConfig": {
|
|
16385
|
+
"siteId": "siteId",
|
|
16386
|
+
"driveId": "driveId"
|
|
16387
|
+
}
|
|
16388
|
+
},
|
|
16389
|
+
{
|
|
16390
|
+
"name": "read_file_content",
|
|
16391
|
+
"description": "Read a file's contents. Provide the file item ID. Use list_folder or search_files to discover file IDs.",
|
|
16392
|
+
"inputSchema": "../../schemas/read_file_content.json",
|
|
16393
|
+
"handler": "../../handlers/read_file_content.js",
|
|
16394
|
+
"scope": "read",
|
|
16395
|
+
"injectFromConfig": {
|
|
16396
|
+
"driveId": "driveId"
|
|
16397
|
+
}
|
|
16398
|
+
},
|
|
16399
|
+
{
|
|
16400
|
+
"name": "create_folder",
|
|
16401
|
+
"description": "Create a new sub-folder inside the connected folder. Omit parentItemId to create directly inside the connected folder.",
|
|
16402
|
+
"inputSchema": "../../schemas/create_folder.json",
|
|
16403
|
+
"handler": "../../handlers/create_folder.js",
|
|
16404
|
+
"scope": "write",
|
|
16405
|
+
"injectFromConfig": {
|
|
16406
|
+
"driveId": "driveId",
|
|
16407
|
+
"parentItemId": "folderId"
|
|
16408
|
+
}
|
|
16409
|
+
},
|
|
16410
|
+
{
|
|
16411
|
+
"name": "move_drive_item",
|
|
16412
|
+
"description": "Move a file or folder to a different parent folder within the document library.",
|
|
16413
|
+
"inputSchema": "../../schemas/move_drive_item.json",
|
|
16414
|
+
"handler": "../../handlers/move_drive_item.js",
|
|
16415
|
+
"scope": "write",
|
|
16416
|
+
"injectFromConfig": {
|
|
16417
|
+
"driveId": "driveId"
|
|
16418
|
+
}
|
|
16419
|
+
},
|
|
16420
|
+
{
|
|
16421
|
+
"name": "delete_drive_item",
|
|
16422
|
+
"description": "Delete a file or folder by item ID.",
|
|
16423
|
+
"inputSchema": "../../schemas/delete_drive_item.json",
|
|
16424
|
+
"handler": "../../handlers/delete_drive_item.js",
|
|
16425
|
+
"scope": "write",
|
|
16426
|
+
"injectFromConfig": {
|
|
16427
|
+
"driveId": "driveId"
|
|
16428
|
+
}
|
|
16429
|
+
}
|
|
16430
|
+
]
|
|
16431
|
+
},
|
|
16432
|
+
"usageGuide": "Use this integration for SharePoint document libraries and files.\n\nRecommended workflow:\n\n1. If you know the SharePoint hostname and path, start with `get_site_by_path`.\n2. Otherwise use `search_sites` to discover the correct site.\n3. Use `list_site_drives` to find the relevant document library for that site.\n4. Use `list_drive_children` for deterministic folder browsing or `search_files` for broader file discovery.\n5. Use `get_drive_item_meta` when you need compact metadata for a specific file or folder.\n6. Use `read_file_content` to consume the actual contents of a file in agent-friendly text.\n\nNotes:\n\n- `search_files` uses Microsoft Graph search. The `query` field accepts normal keywords and Graph KQL syntax.\n- `siteId` and `driveId` filters on `search_files` are applied to the flattened search results after Graph returns them.\n- `read_file_content` is for files only. Folders do not have readable file content.\n- This v1 integration is intentionally focused on SharePoint sites, document libraries, folders, and files. It does not include classic SharePoint list/list-item tools or file upload.\n",
|
|
16433
|
+
"variants": {
|
|
16434
|
+
"variants": {
|
|
16435
|
+
"app_credentials": {
|
|
16436
|
+
"label": "Microsoft Graph App Credentials",
|
|
16437
|
+
"schema": {
|
|
16438
|
+
"type": "object",
|
|
16439
|
+
"properties": {
|
|
16440
|
+
"tenantId": {
|
|
16441
|
+
"type": "string",
|
|
16442
|
+
"title": "Tenant ID",
|
|
16443
|
+
"description": "Microsoft Entra tenant ID (GUID) that owns the SharePoint tenant."
|
|
16444
|
+
},
|
|
16445
|
+
"clientId": {
|
|
16446
|
+
"type": "string",
|
|
16447
|
+
"title": "Client ID",
|
|
16448
|
+
"description": "Application (client) ID of the Microsoft Entra app registration."
|
|
16449
|
+
},
|
|
16450
|
+
"clientSecret": {
|
|
16451
|
+
"type": "string",
|
|
16452
|
+
"title": "Client Secret",
|
|
16453
|
+
"description": "Client secret value for the Microsoft Entra app registration.",
|
|
16454
|
+
"format": "password"
|
|
16455
|
+
}
|
|
16456
|
+
},
|
|
16457
|
+
"required": [
|
|
16458
|
+
"tenantId",
|
|
16459
|
+
"clientId",
|
|
16460
|
+
"clientSecret"
|
|
16461
|
+
],
|
|
16462
|
+
"additionalProperties": false
|
|
16463
|
+
},
|
|
16464
|
+
"preprocess": {
|
|
16465
|
+
"type": "handler",
|
|
16466
|
+
"handlerCode": "async (creds, utils) => {\n const tenantId = String(creds?.tenantId || '').trim()\n const clientId = String(creds?.clientId || '').trim()\n const clientSecret = String(creds?.clientSecret || '').trim()\n\n if (!tenantId)\n throw new Error('Missing tenantId')\n if (!clientId)\n throw new Error('Missing clientId')\n if (!clientSecret)\n throw new Error('Missing clientSecret')\n\n const response = await utils.tokenFetch(\n `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`,\n {\n method: 'POST',\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: clientId,\n client_secret: clientSecret,\n scope: 'https://graph.microsoft.com/.default',\n }),\n },\n )\n\n const data = await response.json()\n if (!response.ok) {\n const message = typeof data?.error_description === 'string'\n ? data.error_description\n : (typeof data?.error === 'string' ? data.error : `Token request failed with status ${response.status}`)\n throw new Error(message)\n }\n\n const token = typeof data?.access_token === 'string' ? data.access_token : ''\n if (!token)\n throw new Error('Microsoft token response did not include access_token')\n\n return {\n token,\n expiresIn: data?.expires_in,\n }\n}",
|
|
16467
|
+
"allowedOrigins": [
|
|
16468
|
+
"https://login.microsoftonline.com"
|
|
16469
|
+
]
|
|
16470
|
+
},
|
|
16471
|
+
"injection": {
|
|
16472
|
+
"headers": {
|
|
16473
|
+
"Authorization": "Bearer {{token}}"
|
|
16474
|
+
}
|
|
16475
|
+
},
|
|
16476
|
+
"healthCheck": {
|
|
16477
|
+
"notViable": true
|
|
16478
|
+
}
|
|
16479
|
+
}
|
|
16050
16480
|
},
|
|
16481
|
+
"default": "app_credentials"
|
|
16482
|
+
},
|
|
16483
|
+
"hint": "1. Create or use a Microsoft Entra app registration for Microsoft Graph at https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\n2. Create a client secret for that app and copy the **tenant ID**, **client ID**, and **client secret value**.\n3. In Microsoft Graph **Application permissions**, grant at least `Sites.Read.All` and `Files.Read.All`.\n4. If you intend to use write actions such as folder creation, moves, and deletes, also grant `Sites.ReadWrite.All` and `Files.ReadWrite.All`.\n5. Grant admin consent for those application permissions in the tenant.\n6. Paste the tenant ID, client ID, and client secret into this integration. The integration exchanges them for short-lived Microsoft Graph access tokens automatically.",
|
|
16484
|
+
"hintsByVariant": {},
|
|
16485
|
+
"tools": [
|
|
16051
16486
|
{
|
|
16052
|
-
"name": "
|
|
16053
|
-
"description": "List
|
|
16487
|
+
"name": "list_folder",
|
|
16488
|
+
"description": "List files and folders inside the connected SharePoint folder.",
|
|
16054
16489
|
"inputSchema": {
|
|
16055
16490
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16056
16491
|
"type": "object",
|
|
16057
16492
|
"required": [
|
|
16058
|
-
"
|
|
16493
|
+
"driveId"
|
|
16059
16494
|
],
|
|
16060
16495
|
"properties": {
|
|
16061
|
-
"
|
|
16496
|
+
"driveId": {
|
|
16062
16497
|
"type": "string",
|
|
16063
|
-
"description": "
|
|
16498
|
+
"description": "Document library drive ID."
|
|
16499
|
+
},
|
|
16500
|
+
"itemId": {
|
|
16501
|
+
"type": "string",
|
|
16502
|
+
"description": "Optional folder item ID. Omit to list the drive root."
|
|
16064
16503
|
},
|
|
16065
16504
|
"top": {
|
|
16066
16505
|
"type": "integer",
|
|
16067
16506
|
"minimum": 1,
|
|
16068
16507
|
"maximum": 200,
|
|
16069
|
-
"description": "Maximum number of
|
|
16508
|
+
"description": "Maximum number of children to return."
|
|
16070
16509
|
},
|
|
16071
|
-
"
|
|
16072
|
-
"type": "
|
|
16073
|
-
"description": "
|
|
16510
|
+
"orderBy": {
|
|
16511
|
+
"type": "string",
|
|
16512
|
+
"description": "Optional Microsoft Graph orderBy expression, such as name or lastModifiedDateTime desc."
|
|
16074
16513
|
}
|
|
16075
16514
|
},
|
|
16076
16515
|
"additionalProperties": false
|
|
16077
16516
|
},
|
|
16078
|
-
"handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set('$select'
|
|
16079
|
-
"scope": "read"
|
|
16517
|
+
"handlerCode": "async (input) => {\n const flattenItem = item => ({\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n eTag: item.eTag || null,\n cTag: item.cTag || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n })\n\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,eTag,cTag,parentReference,file,folder,package',\n )\n if (input.top)\n params.set('$top', String(input.top))\n if (input.orderBy)\n params.set('$orderby', input.orderBy)\n\n const basePath = input.itemId\n ? `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/children`\n : `/drives/${encodeURIComponent(input.driveId)}/root/children`\n\n const res = await integration.fetch(`${basePath}?${params.toString()}`)\n const data = await res.json()\n\n return {\n driveId: input.driveId,\n itemId: input.itemId || null,\n children: Array.isArray(data?.value) ? data.value.map(flattenItem) : [],\n nextLink: data?.['@odata.nextLink'] || null,\n }\n}",
|
|
16518
|
+
"scope": "read",
|
|
16519
|
+
"injectFromConfig": {
|
|
16520
|
+
"driveId": "driveId",
|
|
16521
|
+
"itemId": "folderId"
|
|
16522
|
+
}
|
|
16080
16523
|
},
|
|
16081
16524
|
{
|
|
16082
|
-
"name": "
|
|
16083
|
-
"description": "List
|
|
16525
|
+
"name": "browse_folder",
|
|
16526
|
+
"description": "List files and folders inside a sub-folder by item ID. Use list_folder first to discover sub-folder IDs.",
|
|
16084
16527
|
"inputSchema": {
|
|
16085
16528
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16086
16529
|
"type": "object",
|
|
@@ -16110,11 +16553,14 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16110
16553
|
"additionalProperties": false
|
|
16111
16554
|
},
|
|
16112
16555
|
"handlerCode": "async (input) => {\n const flattenItem = item => ({\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n eTag: item.eTag || null,\n cTag: item.cTag || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n })\n\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,eTag,cTag,parentReference,file,folder,package',\n )\n if (input.top)\n params.set('$top', String(input.top))\n if (input.orderBy)\n params.set('$orderby', input.orderBy)\n\n const basePath = input.itemId\n ? `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/children`\n : `/drives/${encodeURIComponent(input.driveId)}/root/children`\n\n const res = await integration.fetch(`${basePath}?${params.toString()}`)\n const data = await res.json()\n\n return {\n driveId: input.driveId,\n itemId: input.itemId || null,\n children: Array.isArray(data?.value) ? data.value.map(flattenItem) : [],\n nextLink: data?.['@odata.nextLink'] || null,\n }\n}",
|
|
16113
|
-
"scope": "read"
|
|
16556
|
+
"scope": "read",
|
|
16557
|
+
"injectFromConfig": {
|
|
16558
|
+
"driveId": "driveId"
|
|
16559
|
+
}
|
|
16114
16560
|
},
|
|
16115
16561
|
{
|
|
16116
16562
|
"name": "get_drive_item_meta",
|
|
16117
|
-
"description": "Get metadata for a
|
|
16563
|
+
"description": "Get metadata for a file or folder by item ID.",
|
|
16118
16564
|
"inputSchema": {
|
|
16119
16565
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16120
16566
|
"type": "object",
|
|
@@ -16135,11 +16581,14 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16135
16581
|
"additionalProperties": false
|
|
16136
16582
|
},
|
|
16137
16583
|
"handlerCode": "async (input) => {\n const flattenItem = item => ({\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n eTag: item.eTag || null,\n cTag: item.cTag || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n })\n\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,eTag,cTag,parentReference,file,folder,package',\n )\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}?${params.toString()}`)\n return flattenItem(await res.json())\n}",
|
|
16138
|
-
"scope": "read"
|
|
16584
|
+
"scope": "read",
|
|
16585
|
+
"injectFromConfig": {
|
|
16586
|
+
"driveId": "driveId"
|
|
16587
|
+
}
|
|
16139
16588
|
},
|
|
16140
16589
|
{
|
|
16141
16590
|
"name": "search_files",
|
|
16142
|
-
"description": "Search
|
|
16591
|
+
"description": "Search files within the connected document library. KQL syntax is supported.",
|
|
16143
16592
|
"inputSchema": {
|
|
16144
16593
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16145
16594
|
"type": "object",
|
|
@@ -16174,11 +16623,15 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16174
16623
|
"additionalProperties": false
|
|
16175
16624
|
},
|
|
16176
16625
|
"handlerCode": "async (input) => {\n const extractFallbackRegion = (error) => {\n const texts = [error?.data?.body, error?.message].filter(s => typeof s === 'string')\n for (const text of texts) {\n const match = text.match(/Only valid regions are ([A-Z,\\s]+)/i)\n const region = match?.[1]?.split(',').map(r => r.trim().toUpperCase()).filter(Boolean)[0]\n if (region)\n return region\n }\n return null\n }\n\n const runSearch = async (region) => {\n const res = await integration.fetch('/search/query', {\n method: 'POST',\n body: {\n requests: [{\n entityTypes: ['driveItem'],\n query: { queryString: input.query },\n from: typeof input.from === 'number' ? input.from : 0,\n size: typeof input.size === 'number' ? input.size : 25,\n region,\n }],\n },\n })\n return res.json()\n }\n\n const flattenHit = (hit) => {\n const resource = hit?.resource || {}\n const parentReference = resource.parentReference || {}\n return {\n id: resource.id || hit?.hitId || null,\n name: resource.name || null,\n webUrl: resource.webUrl || null,\n summary: hit?.summary || '',\n rank: hit?.rank ?? null,\n createdDateTime: resource.createdDateTime || null,\n lastModifiedDateTime: resource.lastModifiedDateTime || null,\n mimeType: resource.file?.mimeType || null,\n size: resource.size ?? null,\n isFolder: Boolean(resource.folder || resource.package),\n isFile: Boolean(resource.file),\n driveId: parentReference.driveId || null,\n siteId: parentReference.siteId || null,\n parentReference,\n }\n }\n\n let data\n try {\n data = await runSearch('NAM')\n }\n catch (error) {\n const fallback = extractFallbackRegion(error)\n if (!fallback)\n throw error\n data = await runSearch(fallback)\n }\n\n const container = data?.value?.[0]?.hitsContainers?.[0]\n const allHits = Array.isArray(container?.hits) ? container.hits.map(flattenHit) : []\n const hits = allHits.filter((hit) => {\n if (input.siteId && hit.siteId !== input.siteId)\n return false\n if (input.driveId && hit.driveId !== input.driveId)\n return false\n return true\n })\n\n return {\n query: input.query,\n hits,\n total: container?.total ?? hits.length,\n moreResultsAvailable: Boolean(container?.moreResultsAvailable),\n }\n}",
|
|
16177
|
-
"scope": "read"
|
|
16626
|
+
"scope": "read",
|
|
16627
|
+
"injectFromConfig": {
|
|
16628
|
+
"siteId": "siteId",
|
|
16629
|
+
"driveId": "driveId"
|
|
16630
|
+
}
|
|
16178
16631
|
},
|
|
16179
16632
|
{
|
|
16180
16633
|
"name": "read_file_content",
|
|
16181
|
-
"description": "Read a
|
|
16634
|
+
"description": "Read a file's contents. Provide the file item ID. Use list_folder or search_files to discover file IDs.",
|
|
16182
16635
|
"inputSchema": {
|
|
16183
16636
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16184
16637
|
"type": "object",
|
|
@@ -16198,16 +16651,25 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16198
16651
|
"mimeType": {
|
|
16199
16652
|
"type": "string",
|
|
16200
16653
|
"description": "Optional MIME type from get_drive_item_meta or search_files."
|
|
16654
|
+
},
|
|
16655
|
+
"previewPages": {
|
|
16656
|
+
"type": "integer",
|
|
16657
|
+
"minimum": 1,
|
|
16658
|
+
"maximum": 10,
|
|
16659
|
+
"description": "Number of pages to render as images and return alongside the text (PDF only). Omit or set to 0 to skip. Useful for visually checking signatures, logos, or layout."
|
|
16201
16660
|
}
|
|
16202
16661
|
},
|
|
16203
16662
|
"additionalProperties": false
|
|
16204
16663
|
},
|
|
16205
|
-
"handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,parentReference,file,folder,package',\n )\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}?${params.toString()}`)\n const item = await res.json()\n const mimeType = input.mimeType || item?.file?.mimeType || null\n\n if (item?.folder || item?.package) {\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source: `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/content`,\n })\n\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n webUrl: item?.webUrl || null,\n mimeType,\n ...extracted,\n }\n}",
|
|
16206
|
-
"scope": "read"
|
|
16664
|
+
"handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,parentReference,file,folder,package',\n )\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}?${params.toString()}`)\n const item = await res.json()\n const mimeType = input.mimeType || item?.file?.mimeType || null\n\n if (item?.folder || item?.package) {\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source: `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/content`,\n previewPages: input.previewPages || 0,\n })\n\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n webUrl: item?.webUrl || null,\n mimeType,\n ...extracted,\n }\n}",
|
|
16665
|
+
"scope": "read",
|
|
16666
|
+
"injectFromConfig": {
|
|
16667
|
+
"driveId": "driveId"
|
|
16668
|
+
}
|
|
16207
16669
|
},
|
|
16208
16670
|
{
|
|
16209
16671
|
"name": "create_folder",
|
|
16210
|
-
"description": "Create a new folder
|
|
16672
|
+
"description": "Create a new sub-folder inside the connected folder. Omit parentItemId to create directly inside the connected folder.",
|
|
16211
16673
|
"inputSchema": {
|
|
16212
16674
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16213
16675
|
"type": "object",
|
|
@@ -16241,11 +16703,15 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16241
16703
|
"additionalProperties": false
|
|
16242
16704
|
},
|
|
16243
16705
|
"handlerCode": "async (input) => {\n const path = input.parentItemId\n ? `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.parentItemId)}/children`\n : `/drives/${encodeURIComponent(input.driveId)}/root/children`\n\n const res = await integration.fetch(path, {\n method: 'POST',\n body: {\n name: input.name,\n folder: {},\n '@microsoft.graph.conflictBehavior': input.conflictBehavior || 'rename',\n },\n })\n const item = await res.json()\n return {\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n }\n}",
|
|
16244
|
-
"scope": "write"
|
|
16706
|
+
"scope": "write",
|
|
16707
|
+
"injectFromConfig": {
|
|
16708
|
+
"driveId": "driveId",
|
|
16709
|
+
"parentItemId": "folderId"
|
|
16710
|
+
}
|
|
16245
16711
|
},
|
|
16246
16712
|
{
|
|
16247
16713
|
"name": "move_drive_item",
|
|
16248
|
-
"description": "Move a
|
|
16714
|
+
"description": "Move a file or folder to a different parent folder within the document library.",
|
|
16249
16715
|
"inputSchema": {
|
|
16250
16716
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16251
16717
|
"type": "object",
|
|
@@ -16275,11 +16741,14 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16275
16741
|
"additionalProperties": false
|
|
16276
16742
|
},
|
|
16277
16743
|
"handlerCode": "async (input) => {\n const body = {\n parentReference: {\n id: input.destinationParentId,\n },\n }\n if (input.newName)\n body.name = input.newName\n\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}`, {\n method: 'PATCH',\n body,\n })\n const item = await res.json()\n return {\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n }\n}",
|
|
16278
|
-
"scope": "write"
|
|
16744
|
+
"scope": "write",
|
|
16745
|
+
"injectFromConfig": {
|
|
16746
|
+
"driveId": "driveId"
|
|
16747
|
+
}
|
|
16279
16748
|
},
|
|
16280
16749
|
{
|
|
16281
16750
|
"name": "delete_drive_item",
|
|
16282
|
-
"description": "Delete a
|
|
16751
|
+
"description": "Delete a file or folder by item ID.",
|
|
16283
16752
|
"inputSchema": {
|
|
16284
16753
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
16285
16754
|
"type": "object",
|
|
@@ -16300,10 +16769,13 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16300
16769
|
"additionalProperties": false
|
|
16301
16770
|
},
|
|
16302
16771
|
"handlerCode": "async (input) => {\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}`, {\n method: 'DELETE',\n })\n if (res.status === 204)\n return { success: true, status: 204 }\n try {\n return await res.json()\n }\n catch {\n return { success: res.ok, status: res.status }\n }\n}",
|
|
16303
|
-
"scope": "write"
|
|
16772
|
+
"scope": "write",
|
|
16773
|
+
"injectFromConfig": {
|
|
16774
|
+
"driveId": "driveId"
|
|
16775
|
+
}
|
|
16304
16776
|
}
|
|
16305
16777
|
],
|
|
16306
|
-
"variantOwnerType":
|
|
16778
|
+
"variantOwnerType": "sharepoint"
|
|
16307
16779
|
},
|
|
16308
16780
|
"trello": {
|
|
16309
16781
|
"manifest": {
|
|
@@ -16551,7 +17023,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
16551
17023
|
}
|
|
16552
17024
|
]
|
|
16553
17025
|
},
|
|
16554
|
-
"
|
|
17026
|
+
"usageGuide": null,
|
|
16555
17027
|
"variants": {
|
|
16556
17028
|
"variants": {
|
|
16557
17029
|
"api_key_token": {
|
|
@@ -17532,7 +18004,7 @@ const GENERATED_INTEGRATIONS = {
|
|
|
17532
18004
|
}
|
|
17533
18005
|
]
|
|
17534
18006
|
},
|
|
17535
|
-
"
|
|
18007
|
+
"usageGuide": null,
|
|
17536
18008
|
"variants": {
|
|
17537
18009
|
"variants": {
|
|
17538
18010
|
"api_key_token": {
|
|
@@ -18250,9 +18722,9 @@ function loadIntegrationManifest(type) {
|
|
|
18250
18722
|
const entry = getIntegration(type);
|
|
18251
18723
|
return entry ? cloneManifest(entry.manifest) : null;
|
|
18252
18724
|
}
|
|
18253
|
-
function
|
|
18725
|
+
function loadIntegrationUsageGuide(type) {
|
|
18254
18726
|
var _a, _b;
|
|
18255
|
-
return (_b = (_a = getIntegration(type)) == null ? void 0 : _a.
|
|
18727
|
+
return (_b = (_a = getIntegration(type)) == null ? void 0 : _a.usageGuide) != null ? _b : null;
|
|
18256
18728
|
}
|
|
18257
18729
|
function loadIntegrationToolsets(type) {
|
|
18258
18730
|
var _a;
|
|
@@ -18457,6 +18929,35 @@ function makeIntegrationToolName(type, name, nodeId) {
|
|
|
18457
18929
|
return `${base}${suffix}`;
|
|
18458
18930
|
}
|
|
18459
18931
|
|
|
18932
|
+
const hoistedArtifactsByResult = /* @__PURE__ */ new WeakMap();
|
|
18933
|
+
function attachHoistedArtifacts(result, artifacts) {
|
|
18934
|
+
if (artifacts.length)
|
|
18935
|
+
hoistedArtifactsByResult.set(result, artifacts);
|
|
18936
|
+
return result;
|
|
18937
|
+
}
|
|
18938
|
+
function getHoistedArtifacts(result) {
|
|
18939
|
+
var _a;
|
|
18940
|
+
return (_a = hoistedArtifactsByResult.get(result)) != null ? _a : [];
|
|
18941
|
+
}
|
|
18942
|
+
function hoistExtractFileContentArtifacts(result) {
|
|
18943
|
+
if (!result || typeof result !== "object" || Array.isArray(result))
|
|
18944
|
+
return { cleanedResult: result, artifacts: [] };
|
|
18945
|
+
const images = Array.isArray(result.pageImages) ? result.pageImages.filter((value) => typeof value === "string") : [];
|
|
18946
|
+
if (!images.length)
|
|
18947
|
+
return { cleanedResult: result, artifacts: [] };
|
|
18948
|
+
const { pageImages: _dropped, ...rest } = result;
|
|
18949
|
+
return {
|
|
18950
|
+
cleanedResult: rest,
|
|
18951
|
+
artifacts: images.map((data) => ({
|
|
18952
|
+
type: "image",
|
|
18953
|
+
mimeType: "image/jpeg",
|
|
18954
|
+
data
|
|
18955
|
+
}))
|
|
18956
|
+
};
|
|
18957
|
+
}
|
|
18958
|
+
|
|
18959
|
+
const EXECUTION_RESULT = /* @__PURE__ */ Symbol("executionResult");
|
|
18960
|
+
const EXECUTION_HOISTED_ARTIFACTS = /* @__PURE__ */ Symbol("executionHoistedArtifacts");
|
|
18460
18961
|
function createSafeHandlerFromString(handlerString, getIntegration, utils) {
|
|
18461
18962
|
const realConsole = console;
|
|
18462
18963
|
const isolatedConsole = {
|
|
@@ -18526,7 +19027,15 @@ function createSafeHandlerFromString(handlerString, getIntegration, utils) {
|
|
|
18526
19027
|
const code = `module.exports = async function(input) { return (${handlerString})(input) }`;
|
|
18527
19028
|
const script = new vm.Script(code);
|
|
18528
19029
|
script.runInContext(context);
|
|
18529
|
-
return withLogging(
|
|
19030
|
+
return withLogging(async (args) => {
|
|
19031
|
+
const hoistedArtifacts = [];
|
|
19032
|
+
context.utils = createRuntimeUtils(utils, hoistedArtifacts);
|
|
19033
|
+
const result = await context.module.exports(args);
|
|
19034
|
+
return {
|
|
19035
|
+
[EXECUTION_RESULT]: result,
|
|
19036
|
+
[EXECUTION_HOISTED_ARTIFACTS]: hoistedArtifacts
|
|
19037
|
+
};
|
|
19038
|
+
}, isolatedConsole);
|
|
18530
19039
|
}
|
|
18531
19040
|
function withLogging(handler, vmConsole) {
|
|
18532
19041
|
function safeSerializeForLog(value) {
|
|
@@ -18564,8 +19073,14 @@ function withLogging(handler, vmConsole) {
|
|
|
18564
19073
|
logs.push(line);
|
|
18565
19074
|
originalLog.apply(vmConsole, args2);
|
|
18566
19075
|
};
|
|
18567
|
-
const
|
|
18568
|
-
|
|
19076
|
+
const execution = await handler(args);
|
|
19077
|
+
const { result, hoistedArtifacts } = unwrapExecutionEnvelope(execution);
|
|
19078
|
+
const toolRunResult = {
|
|
19079
|
+
success: true,
|
|
19080
|
+
result,
|
|
19081
|
+
logs
|
|
19082
|
+
};
|
|
19083
|
+
return hoistedArtifacts.length ? attachHoistedArtifacts(toolRunResult, hoistedArtifacts) : toolRunResult;
|
|
18569
19084
|
} catch (err) {
|
|
18570
19085
|
logs.push((err == null ? void 0 : err.stack) || String(err));
|
|
18571
19086
|
const result = err && typeof err === "object" ? {
|
|
@@ -18580,6 +19095,32 @@ function withLogging(handler, vmConsole) {
|
|
|
18580
19095
|
}
|
|
18581
19096
|
};
|
|
18582
19097
|
}
|
|
19098
|
+
function createRuntimeUtils(baseUtils, hoistedArtifacts) {
|
|
19099
|
+
const runtimeUtils = { ...baseUtils || {} };
|
|
19100
|
+
const extractFileContent = runtimeUtils.extractFileContent;
|
|
19101
|
+
if (typeof extractFileContent === "function") {
|
|
19102
|
+
runtimeUtils.extractFileContent = async (...args) => {
|
|
19103
|
+
const extracted = await extractFileContent(...args);
|
|
19104
|
+
const { cleanedResult, artifacts } = hoistExtractFileContentArtifacts(extracted);
|
|
19105
|
+
hoistedArtifacts.push(...artifacts);
|
|
19106
|
+
return cleanedResult;
|
|
19107
|
+
};
|
|
19108
|
+
}
|
|
19109
|
+
return runtimeUtils;
|
|
19110
|
+
}
|
|
19111
|
+
function unwrapExecutionEnvelope(value) {
|
|
19112
|
+
if (!value || typeof value !== "object" || !(EXECUTION_RESULT in value)) {
|
|
19113
|
+
return {
|
|
19114
|
+
result: value,
|
|
19115
|
+
hoistedArtifacts: []
|
|
19116
|
+
};
|
|
19117
|
+
}
|
|
19118
|
+
const envelope = value;
|
|
19119
|
+
return {
|
|
19120
|
+
result: envelope[EXECUTION_RESULT],
|
|
19121
|
+
hoistedArtifacts: Array.isArray(envelope[EXECUTION_HOISTED_ARTIFACTS]) ? envelope[EXECUTION_HOISTED_ARTIFACTS] : []
|
|
19122
|
+
};
|
|
19123
|
+
}
|
|
18583
19124
|
|
|
18584
19125
|
const turndown = new TurndownService({
|
|
18585
19126
|
headingStyle: "atx",
|
|
@@ -19074,7 +19615,10 @@ function createGetIntegration(integrations, proxy) {
|
|
|
19074
19615
|
|
|
19075
19616
|
const EXTRACT_FILE_PY = `#!/usr/bin/env python3
|
|
19076
19617
|
import argparse
|
|
19618
|
+
import base64
|
|
19077
19619
|
import csv
|
|
19620
|
+
import email
|
|
19621
|
+
import email.policy
|
|
19078
19622
|
import html
|
|
19079
19623
|
import json
|
|
19080
19624
|
import mimetypes
|
|
@@ -19084,6 +19628,18 @@ from pathlib import Path
|
|
|
19084
19628
|
|
|
19085
19629
|
from markitdown import MarkItDown
|
|
19086
19630
|
|
|
19631
|
+
try:
|
|
19632
|
+
import fitz # PyMuPDF
|
|
19633
|
+
_FITZ_AVAILABLE = True
|
|
19634
|
+
except ImportError:
|
|
19635
|
+
_FITZ_AVAILABLE = False
|
|
19636
|
+
|
|
19637
|
+
try:
|
|
19638
|
+
import extract_msg as _extract_msg
|
|
19639
|
+
_EXTRACT_MSG_AVAILABLE = True
|
|
19640
|
+
except ImportError:
|
|
19641
|
+
_EXTRACT_MSG_AVAILABLE = False
|
|
19642
|
+
|
|
19087
19643
|
|
|
19088
19644
|
def collapse_whitespace(value: str) -> str:
|
|
19089
19645
|
return re.sub(r"\\s+", " ", value or "").strip()
|
|
@@ -19128,6 +19684,12 @@ def sniff_kind(path: Path) -> str:
|
|
|
19128
19684
|
suffix = path.suffix.lower()
|
|
19129
19685
|
if suffix in DIRECT_TEXT_EXTENSIONS | MARKITDOWN_EXTENSIONS:
|
|
19130
19686
|
return suffix.lstrip(".")
|
|
19687
|
+
if suffix == ".msg":
|
|
19688
|
+
return "msg"
|
|
19689
|
+
if suffix == ".eml":
|
|
19690
|
+
return "eml"
|
|
19691
|
+
if suffix == ".zip":
|
|
19692
|
+
return "zip"
|
|
19131
19693
|
|
|
19132
19694
|
mime_guess, _ = mimetypes.guess_type(path.name)
|
|
19133
19695
|
if mime_guess == "application/pdf":
|
|
@@ -19147,10 +19709,78 @@ def sniff_kind(path: Path) -> str:
|
|
|
19147
19709
|
return "xlsx"
|
|
19148
19710
|
if "ppt/presentation.xml" in names:
|
|
19149
19711
|
return "pptx"
|
|
19712
|
+
return "zip"
|
|
19150
19713
|
|
|
19151
19714
|
return "unknown"
|
|
19152
19715
|
|
|
19153
19716
|
|
|
19717
|
+
def parse_pdf_date(date_str: str):
|
|
19718
|
+
"""Parse a PDF date string (D:YYYYMMDDHHmmSS...) to ISO-8601."""
|
|
19719
|
+
if not date_str or not isinstance(date_str, str) or not date_str.startswith("D:"):
|
|
19720
|
+
return None
|
|
19721
|
+
try:
|
|
19722
|
+
raw = date_str[2:]
|
|
19723
|
+
m = re.match(r"(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})", raw)
|
|
19724
|
+
if not m:
|
|
19725
|
+
return None
|
|
19726
|
+
y, mo, d, h, mi, s = m.groups()
|
|
19727
|
+
return f"{y}-{mo}-{d}T{h}:{mi}:{s}Z"
|
|
19728
|
+
except Exception:
|
|
19729
|
+
return None
|
|
19730
|
+
|
|
19731
|
+
|
|
19732
|
+
def get_xlsx_sheet_names(path: Path) -> list:
|
|
19733
|
+
try:
|
|
19734
|
+
with zipfile.ZipFile(path, "r") as archive:
|
|
19735
|
+
if "xl/workbook.xml" not in archive.namelist():
|
|
19736
|
+
return []
|
|
19737
|
+
content = archive.read("xl/workbook.xml").decode("utf-8", errors="replace")
|
|
19738
|
+
return re.findall(r'<sheet\\b[^>]+\\bname="([^"]+)"', content)
|
|
19739
|
+
except Exception:
|
|
19740
|
+
return []
|
|
19741
|
+
|
|
19742
|
+
|
|
19743
|
+
def get_pptx_slide_count(path: Path) -> int:
|
|
19744
|
+
try:
|
|
19745
|
+
with zipfile.ZipFile(path, "r") as archive:
|
|
19746
|
+
return sum(
|
|
19747
|
+
1 for name in archive.namelist()
|
|
19748
|
+
if re.match(r"ppt/slides/slide\\d+\\.xml$", name)
|
|
19749
|
+
)
|
|
19750
|
+
except Exception:
|
|
19751
|
+
return 0
|
|
19752
|
+
|
|
19753
|
+
|
|
19754
|
+
def get_office_core_props(path: Path) -> dict:
|
|
19755
|
+
"""Read author and lastModifiedBy from docProps/core.xml (all Office formats)."""
|
|
19756
|
+
try:
|
|
19757
|
+
with zipfile.ZipFile(path, "r") as archive:
|
|
19758
|
+
if "docProps/core.xml" not in archive.namelist():
|
|
19759
|
+
return {}
|
|
19760
|
+
content = archive.read("docProps/core.xml").decode("utf-8", errors="replace")
|
|
19761
|
+
props = {}
|
|
19762
|
+
m = re.search(r"<dc:creator[^>]*>([^<]+)</dc:creator>", content)
|
|
19763
|
+
if m:
|
|
19764
|
+
props["author"] = m.group(1).strip()
|
|
19765
|
+
m = re.search(r"<cp:lastModifiedBy[^>]*>([^<]+)</cp:lastModifiedBy>", content)
|
|
19766
|
+
if m:
|
|
19767
|
+
props["lastModifiedBy"] = m.group(1).strip()
|
|
19768
|
+
return props
|
|
19769
|
+
except Exception:
|
|
19770
|
+
return {}
|
|
19771
|
+
|
|
19772
|
+
|
|
19773
|
+
def has_tracked_changes_docx(path: Path) -> bool:
|
|
19774
|
+
try:
|
|
19775
|
+
with zipfile.ZipFile(path, "r") as archive:
|
|
19776
|
+
if "word/document.xml" not in archive.namelist():
|
|
19777
|
+
return False
|
|
19778
|
+
content = archive.read("word/document.xml").decode("utf-8", errors="replace")
|
|
19779
|
+
return bool(re.search(r"<w:(del|ins)\\s", content))
|
|
19780
|
+
except Exception:
|
|
19781
|
+
return False
|
|
19782
|
+
|
|
19783
|
+
|
|
19154
19784
|
def read_text(path: Path) -> dict:
|
|
19155
19785
|
content = path.read_text(encoding="utf-8", errors="replace").strip()
|
|
19156
19786
|
return {"kind": "text", "content": content, "metadata": {"filename": path.name}}
|
|
@@ -19214,6 +19844,278 @@ def read_csv_file(path: Path) -> dict:
|
|
|
19214
19844
|
}
|
|
19215
19845
|
|
|
19216
19846
|
|
|
19847
|
+
def _strip_html_to_text(raw_html: str) -> str:
|
|
19848
|
+
"""Strip HTML to plain text, removing style/script blocks first."""
|
|
19849
|
+
text = re.sub(r"<style[^>]*>[\\s\\S]*?</style>", " ", raw_html, flags=re.IGNORECASE)
|
|
19850
|
+
text = re.sub(r"<script[^>]*>[\\s\\S]*?<\/script>", " ", text, flags=re.IGNORECASE)
|
|
19851
|
+
text = re.sub(r"<br\\s*/?>", "\\n", text, flags=re.IGNORECASE)
|
|
19852
|
+
text = re.sub(r"</(p|div|li|tr|h[1-6])>", "\\n", text, flags=re.IGNORECASE)
|
|
19853
|
+
text = re.sub(r"<[^>]+>", " ", text)
|
|
19854
|
+
text = html.unescape(text)
|
|
19855
|
+
return re.sub(r"\\s+", " ", text).strip()
|
|
19856
|
+
|
|
19857
|
+
|
|
19858
|
+
def read_msg(path: Path) -> dict:
|
|
19859
|
+
import tempfile as _tempfile
|
|
19860
|
+
|
|
19861
|
+
if not _EXTRACT_MSG_AVAILABLE:
|
|
19862
|
+
raise RuntimeError(
|
|
19863
|
+
f"extract-msg is not installed; cannot read {path.name}. "
|
|
19864
|
+
"Install with: pip install extract-msg"
|
|
19865
|
+
)
|
|
19866
|
+
|
|
19867
|
+
try:
|
|
19868
|
+
msg = _extract_msg.openMsg(str(path))
|
|
19869
|
+
except Exception as exc:
|
|
19870
|
+
raise RuntimeError(f"MSG extraction failed for {path.name}: {exc}") from exc
|
|
19871
|
+
|
|
19872
|
+
try:
|
|
19873
|
+
subject = (msg.subject or "").strip() or None
|
|
19874
|
+
sender = (msg.sender or "").strip() or None
|
|
19875
|
+
to = (msg.to or "").strip() or None
|
|
19876
|
+
cc = (msg.cc or "").strip() or None
|
|
19877
|
+
date = str(msg.date).strip() if msg.date else None
|
|
19878
|
+
body = (msg.body or "").strip()
|
|
19879
|
+
|
|
19880
|
+
# Fall back to HTML body stripped to plain text when no plain-text body.
|
|
19881
|
+
if not body:
|
|
19882
|
+
raw_html = getattr(msg, "htmlBody", None) or b""
|
|
19883
|
+
if isinstance(raw_html, bytes):
|
|
19884
|
+
raw_html = raw_html.decode("utf-8", errors="replace")
|
|
19885
|
+
if raw_html:
|
|
19886
|
+
body = _strip_html_to_text(raw_html)
|
|
19887
|
+
|
|
19888
|
+
blocks = []
|
|
19889
|
+
if subject:
|
|
19890
|
+
blocks.append(f"# {subject}")
|
|
19891
|
+
header_lines = []
|
|
19892
|
+
if sender:
|
|
19893
|
+
header_lines.append(f"From: {sender}")
|
|
19894
|
+
if to:
|
|
19895
|
+
header_lines.append(f"To: {to}")
|
|
19896
|
+
if cc:
|
|
19897
|
+
header_lines.append(f"Cc: {cc}")
|
|
19898
|
+
if date:
|
|
19899
|
+
header_lines.append(f"Date: {date}")
|
|
19900
|
+
if header_lines:
|
|
19901
|
+
blocks.append("\\n".join(header_lines))
|
|
19902
|
+
if body:
|
|
19903
|
+
blocks.append(body)
|
|
19904
|
+
|
|
19905
|
+
attachments = list(msg.attachments or [])
|
|
19906
|
+
att_names = []
|
|
19907
|
+
warnings = []
|
|
19908
|
+
|
|
19909
|
+
with _tempfile.TemporaryDirectory() as tmp:
|
|
19910
|
+
tmp_path = Path(tmp)
|
|
19911
|
+
for i, att in enumerate(attachments):
|
|
19912
|
+
att_name = (
|
|
19913
|
+
getattr(att, "longFilename", None)
|
|
19914
|
+
or getattr(att, "shortFilename", None)
|
|
19915
|
+
or ""
|
|
19916
|
+
).strip() or "attachment"
|
|
19917
|
+
att_names.append(att_name)
|
|
19918
|
+
try:
|
|
19919
|
+
att_path = None
|
|
19920
|
+
|
|
19921
|
+
# Primary: use save() \u2014 handles all attachment types including
|
|
19922
|
+
# OLE embedded objects where .data returns None.
|
|
19923
|
+
att_dir = tmp_path / f"att_{i}"
|
|
19924
|
+
att_dir.mkdir()
|
|
19925
|
+
try:
|
|
19926
|
+
att.save(customPath=str(att_dir))
|
|
19927
|
+
saved = list(att_dir.iterdir())
|
|
19928
|
+
if saved:
|
|
19929
|
+
att_path = saved[0]
|
|
19930
|
+
except Exception:
|
|
19931
|
+
pass
|
|
19932
|
+
|
|
19933
|
+
# Fallback: direct .data bytes.
|
|
19934
|
+
if att_path is None:
|
|
19935
|
+
data = att.data
|
|
19936
|
+
if data is None:
|
|
19937
|
+
warnings.append(f"Attachment '{att_name}' has no data.")
|
|
19938
|
+
continue
|
|
19939
|
+
att_path = att_dir / att_name
|
|
19940
|
+
att_path.write_bytes(data)
|
|
19941
|
+
|
|
19942
|
+
inner = extract(att_path)
|
|
19943
|
+
inner_content = (inner.get("content") or "").strip()
|
|
19944
|
+
if inner_content:
|
|
19945
|
+
blocks.append(f"## Attachment: {att_name}\\n\\n{inner_content}")
|
|
19946
|
+
except Exception as exc:
|
|
19947
|
+
warnings.append(f"Could not extract attachment '{att_name}': {exc}")
|
|
19948
|
+
|
|
19949
|
+
content = join_blocks(blocks)
|
|
19950
|
+
metadata: dict = {"filename": path.name, "attachmentCount": len(attachments)}
|
|
19951
|
+
if subject:
|
|
19952
|
+
metadata["subject"] = subject
|
|
19953
|
+
if sender:
|
|
19954
|
+
metadata["sender"] = sender
|
|
19955
|
+
if to:
|
|
19956
|
+
metadata["to"] = to
|
|
19957
|
+
if cc:
|
|
19958
|
+
metadata["cc"] = cc
|
|
19959
|
+
if date:
|
|
19960
|
+
metadata["date"] = date
|
|
19961
|
+
if att_names:
|
|
19962
|
+
metadata["attachmentNames"] = att_names
|
|
19963
|
+
|
|
19964
|
+
result: dict = {"kind": "msg", "content": content, "metadata": metadata}
|
|
19965
|
+
if warnings:
|
|
19966
|
+
result["warnings"] = warnings
|
|
19967
|
+
return result
|
|
19968
|
+
|
|
19969
|
+
finally:
|
|
19970
|
+
msg.close()
|
|
19971
|
+
|
|
19972
|
+
|
|
19973
|
+
def read_eml(path: Path) -> dict:
|
|
19974
|
+
import tempfile as _tempfile
|
|
19975
|
+
|
|
19976
|
+
raw = path.read_bytes()
|
|
19977
|
+
msg = email.message_from_bytes(raw, policy=email.policy.compat32)
|
|
19978
|
+
|
|
19979
|
+
subject = collapse_whitespace(msg.get("Subject", "") or "")
|
|
19980
|
+
sender = collapse_whitespace(msg.get("From", "") or "")
|
|
19981
|
+
to = collapse_whitespace(msg.get("To", "") or "")
|
|
19982
|
+
cc = collapse_whitespace(msg.get("Cc", "") or "")
|
|
19983
|
+
date = collapse_whitespace(msg.get("Date", "") or "")
|
|
19984
|
+
|
|
19985
|
+
# Walk parts: collect plain-text body and attachments.
|
|
19986
|
+
body_parts = []
|
|
19987
|
+
attachments = [] # list of (filename, bytes)
|
|
19988
|
+
|
|
19989
|
+
for part in msg.walk():
|
|
19990
|
+
content_type = part.get_content_type()
|
|
19991
|
+
disposition = (part.get("Content-Disposition") or "").lower()
|
|
19992
|
+
filename = part.get_filename()
|
|
19993
|
+
|
|
19994
|
+
if filename:
|
|
19995
|
+
try:
|
|
19996
|
+
data = part.get_payload(decode=True)
|
|
19997
|
+
if data:
|
|
19998
|
+
attachments.append((filename, data))
|
|
19999
|
+
except Exception:
|
|
20000
|
+
pass
|
|
20001
|
+
elif content_type == "text/plain" and "attachment" not in disposition:
|
|
20002
|
+
try:
|
|
20003
|
+
charset = part.get_content_charset() or "utf-8"
|
|
20004
|
+
payload = part.get_payload(decode=True)
|
|
20005
|
+
if payload:
|
|
20006
|
+
body_parts.append(payload.decode(charset, errors="replace"))
|
|
20007
|
+
except Exception:
|
|
20008
|
+
pass
|
|
20009
|
+
|
|
20010
|
+
body = "\\n\\n".join(p.strip() for p in body_parts if p.strip())
|
|
20011
|
+
|
|
20012
|
+
blocks = []
|
|
20013
|
+
if subject:
|
|
20014
|
+
blocks.append(f"# {subject}")
|
|
20015
|
+
header_lines = []
|
|
20016
|
+
if sender:
|
|
20017
|
+
header_lines.append(f"From: {sender}")
|
|
20018
|
+
if to:
|
|
20019
|
+
header_lines.append(f"To: {to}")
|
|
20020
|
+
if cc:
|
|
20021
|
+
header_lines.append(f"Cc: {cc}")
|
|
20022
|
+
if date:
|
|
20023
|
+
header_lines.append(f"Date: {date}")
|
|
20024
|
+
if header_lines:
|
|
20025
|
+
blocks.append("\\n".join(header_lines))
|
|
20026
|
+
if body:
|
|
20027
|
+
blocks.append(body)
|
|
20028
|
+
|
|
20029
|
+
att_names = []
|
|
20030
|
+
warnings = []
|
|
20031
|
+
|
|
20032
|
+
with _tempfile.TemporaryDirectory() as tmp:
|
|
20033
|
+
tmp_path = Path(tmp)
|
|
20034
|
+
for att_name, data in attachments:
|
|
20035
|
+
att_names.append(att_name)
|
|
20036
|
+
try:
|
|
20037
|
+
att_path = tmp_path / Path(att_name).name
|
|
20038
|
+
att_path.write_bytes(data)
|
|
20039
|
+
inner = extract(att_path)
|
|
20040
|
+
inner_content = (inner.get("content") or "").strip()
|
|
20041
|
+
if inner_content:
|
|
20042
|
+
blocks.append(f"## Attachment: {att_name}\\n\\n{inner_content}")
|
|
20043
|
+
except Exception as exc:
|
|
20044
|
+
warnings.append(f"Could not extract attachment '{att_name}': {exc}")
|
|
20045
|
+
|
|
20046
|
+
content = join_blocks(blocks)
|
|
20047
|
+
metadata: dict = {"filename": path.name, "attachmentCount": len(attachments)}
|
|
20048
|
+
if subject:
|
|
20049
|
+
metadata["subject"] = subject
|
|
20050
|
+
if sender:
|
|
20051
|
+
metadata["sender"] = sender
|
|
20052
|
+
if to:
|
|
20053
|
+
metadata["to"] = to
|
|
20054
|
+
if cc:
|
|
20055
|
+
metadata["cc"] = cc
|
|
20056
|
+
if date:
|
|
20057
|
+
metadata["date"] = date
|
|
20058
|
+
if att_names:
|
|
20059
|
+
metadata["attachmentNames"] = att_names
|
|
20060
|
+
|
|
20061
|
+
result: dict = {"kind": "eml", "content": content, "metadata": metadata}
|
|
20062
|
+
if warnings:
|
|
20063
|
+
result["warnings"] = warnings
|
|
20064
|
+
return result
|
|
20065
|
+
|
|
20066
|
+
|
|
20067
|
+
def read_zip(path: Path) -> dict:
|
|
20068
|
+
import tempfile as _tempfile
|
|
20069
|
+
|
|
20070
|
+
try:
|
|
20071
|
+
with zipfile.ZipFile(path, "r") as archive:
|
|
20072
|
+
all_names = archive.namelist()
|
|
20073
|
+
except Exception as exc:
|
|
20074
|
+
raise RuntimeError(f"ZIP extraction failed for {path.name}: {exc}") from exc
|
|
20075
|
+
|
|
20076
|
+
# Skip directories and hidden/system files.
|
|
20077
|
+
file_entries = [
|
|
20078
|
+
n for n in all_names
|
|
20079
|
+
if not n.endswith("/") and not Path(n).name.startswith(".")
|
|
20080
|
+
]
|
|
20081
|
+
|
|
20082
|
+
blocks = []
|
|
20083
|
+
warnings = []
|
|
20084
|
+
|
|
20085
|
+
try:
|
|
20086
|
+
with zipfile.ZipFile(path, "r") as archive:
|
|
20087
|
+
with _tempfile.TemporaryDirectory() as tmp:
|
|
20088
|
+
tmp_path = Path(tmp)
|
|
20089
|
+
for entry in file_entries:
|
|
20090
|
+
entry_name = Path(entry).name
|
|
20091
|
+
if not entry_name:
|
|
20092
|
+
continue
|
|
20093
|
+
try:
|
|
20094
|
+
data = archive.read(entry)
|
|
20095
|
+
entry_path = tmp_path / entry_name
|
|
20096
|
+
entry_path.write_bytes(data)
|
|
20097
|
+
inner = extract(entry_path)
|
|
20098
|
+
inner_content = (inner.get("content") or "").strip()
|
|
20099
|
+
if inner_content:
|
|
20100
|
+
blocks.append(f"## {entry}\\n\\n{inner_content}")
|
|
20101
|
+
except Exception as exc:
|
|
20102
|
+
warnings.append(f"Could not extract '{entry}': {exc}")
|
|
20103
|
+
except Exception as exc:
|
|
20104
|
+
raise RuntimeError(f"ZIP extraction failed for {path.name}: {exc}") from exc
|
|
20105
|
+
|
|
20106
|
+
content = join_blocks(blocks)
|
|
20107
|
+
metadata: dict = {
|
|
20108
|
+
"filename": path.name,
|
|
20109
|
+
"fileCount": len(file_entries),
|
|
20110
|
+
"fileNames": file_entries,
|
|
20111
|
+
}
|
|
20112
|
+
|
|
20113
|
+
result: dict = {"kind": "zip", "content": content, "metadata": metadata}
|
|
20114
|
+
if warnings:
|
|
20115
|
+
result["warnings"] = warnings
|
|
20116
|
+
return result
|
|
20117
|
+
|
|
20118
|
+
|
|
19217
20119
|
def read_with_markitdown(path: Path, kind: str) -> dict:
|
|
19218
20120
|
try:
|
|
19219
20121
|
result = MarkItDown().convert(str(path))
|
|
@@ -19221,14 +20123,132 @@ def read_with_markitdown(path: Path, kind: str) -> dict:
|
|
|
19221
20123
|
raise RuntimeError(f"MarkItDown extraction failed for {path.name}: {exc}") from exc
|
|
19222
20124
|
|
|
19223
20125
|
content = (getattr(result, "text_content", "") or "").strip()
|
|
20126
|
+
metadata: dict = {"filename": path.name, "parser": "markitdown"}
|
|
20127
|
+
|
|
20128
|
+
# XLSX: surface sheet names so the agent knows the workbook structure upfront.
|
|
20129
|
+
if kind == "xlsx":
|
|
20130
|
+
sheet_names = get_xlsx_sheet_names(path)
|
|
20131
|
+
if sheet_names:
|
|
20132
|
+
metadata["sheetNames"] = sheet_names
|
|
20133
|
+
|
|
20134
|
+
# DOCX: tracked changes flag + author metadata from core properties.
|
|
20135
|
+
if kind == "docx":
|
|
20136
|
+
metadata["hasTrackedChanges"] = has_tracked_changes_docx(path)
|
|
20137
|
+
core = get_office_core_props(path)
|
|
20138
|
+
if core.get("author"):
|
|
20139
|
+
metadata["author"] = core["author"]
|
|
20140
|
+
if core.get("lastModifiedBy"):
|
|
20141
|
+
metadata["lastModifiedBy"] = core["lastModifiedBy"]
|
|
20142
|
+
|
|
20143
|
+
# PPTX: slide count.
|
|
20144
|
+
if kind == "pptx":
|
|
20145
|
+
count = get_pptx_slide_count(path)
|
|
20146
|
+
if count:
|
|
20147
|
+
metadata["slideCount"] = count
|
|
20148
|
+
|
|
19224
20149
|
return {
|
|
19225
20150
|
"kind": kind,
|
|
19226
20151
|
"content": content,
|
|
19227
|
-
"metadata":
|
|
20152
|
+
"metadata": metadata,
|
|
19228
20153
|
}
|
|
19229
20154
|
|
|
19230
20155
|
|
|
19231
|
-
def
|
|
20156
|
+
def read_pdf(path: Path, preview_pages: int = 0) -> dict:
|
|
20157
|
+
# Text extraction via MarkItDown (higher quality than pymupdf for most PDFs).
|
|
20158
|
+
try:
|
|
20159
|
+
md_result = MarkItDown().convert(str(path))
|
|
20160
|
+
content = (getattr(md_result, "text_content", "") or "").strip()
|
|
20161
|
+
except Exception as exc:
|
|
20162
|
+
raise RuntimeError(f"MarkItDown extraction failed for {path.name}: {exc}") from exc
|
|
20163
|
+
|
|
20164
|
+
metadata: dict = {"filename": path.name, "parser": "markitdown"}
|
|
20165
|
+
|
|
20166
|
+
if not _FITZ_AVAILABLE:
|
|
20167
|
+
return {"kind": "pdf", "content": content, "metadata": metadata}
|
|
20168
|
+
|
|
20169
|
+
page_images = []
|
|
20170
|
+
|
|
20171
|
+
try:
|
|
20172
|
+
doc = fitz.open(str(path))
|
|
20173
|
+
page_count = doc.page_count
|
|
20174
|
+
metadata["pageCount"] = page_count
|
|
20175
|
+
|
|
20176
|
+
# Document info dictionary (title, author, etc.)
|
|
20177
|
+
info = doc.metadata or {}
|
|
20178
|
+
for src_key, out_key in [
|
|
20179
|
+
("title", "title"),
|
|
20180
|
+
("author", "author"),
|
|
20181
|
+
("subject", "subject"),
|
|
20182
|
+
("keywords", "keywords"),
|
|
20183
|
+
("creator", "creator"),
|
|
20184
|
+
]:
|
|
20185
|
+
val = (info.get(src_key) or "").strip()
|
|
20186
|
+
if val:
|
|
20187
|
+
metadata[out_key] = val
|
|
20188
|
+
|
|
20189
|
+
created = parse_pdf_date(info.get("creationDate", ""))
|
|
20190
|
+
if created:
|
|
20191
|
+
metadata["createdAt"] = created
|
|
20192
|
+
modified = parse_pdf_date(info.get("modDate", ""))
|
|
20193
|
+
if modified:
|
|
20194
|
+
metadata["modifiedAt"] = modified
|
|
20195
|
+
|
|
20196
|
+
metadata["isEncrypted"] = bool(doc.is_encrypted)
|
|
20197
|
+
|
|
20198
|
+
# Single pass over all pages: signatures, annotations, form fields.
|
|
20199
|
+
sig_widget_type = getattr(fitz, "PDF_WIDGET_TYPE_SIGNATURE", 7)
|
|
20200
|
+
non_content_annot_types = {"Widget", "Link"}
|
|
20201
|
+
has_annotations = False
|
|
20202
|
+
has_form_fields = False
|
|
20203
|
+
signatures = []
|
|
20204
|
+
|
|
20205
|
+
for i in range(page_count):
|
|
20206
|
+
page = doc.load_page(i)
|
|
20207
|
+
|
|
20208
|
+
# Widgets: split into signatures and fillable form fields.
|
|
20209
|
+
for widget in (page.widgets() or []):
|
|
20210
|
+
if widget.field_type == sig_widget_type:
|
|
20211
|
+
signer = (widget.field_value or "").strip() or None
|
|
20212
|
+
signatures.append({
|
|
20213
|
+
"fieldName": (widget.field_name or "").strip() or None,
|
|
20214
|
+
"signer": signer,
|
|
20215
|
+
"isSigned": bool(signer),
|
|
20216
|
+
})
|
|
20217
|
+
else:
|
|
20218
|
+
has_form_fields = True
|
|
20219
|
+
|
|
20220
|
+
# Annotations: exclude widget and link annotations.
|
|
20221
|
+
if not has_annotations:
|
|
20222
|
+
for annot in page.annots():
|
|
20223
|
+
if annot.type[1] not in non_content_annot_types:
|
|
20224
|
+
has_annotations = True
|
|
20225
|
+
break
|
|
20226
|
+
|
|
20227
|
+
metadata["hasAnnotations"] = has_annotations
|
|
20228
|
+
metadata["hasFormFields"] = has_form_fields
|
|
20229
|
+
if signatures:
|
|
20230
|
+
metadata["signatures"] = signatures
|
|
20231
|
+
|
|
20232
|
+
# Page preview images (opt-in via previewPages > 0).
|
|
20233
|
+
if preview_pages > 0:
|
|
20234
|
+
n = min(preview_pages, page_count)
|
|
20235
|
+
for i in range(n):
|
|
20236
|
+
pixmap = doc.load_page(i).get_pixmap(dpi=96)
|
|
20237
|
+
jpeg_bytes = pixmap.tobytes("jpeg")
|
|
20238
|
+
page_images.append(base64.b64encode(jpeg_bytes).decode("ascii"))
|
|
20239
|
+
|
|
20240
|
+
doc.close()
|
|
20241
|
+
|
|
20242
|
+
except Exception:
|
|
20243
|
+
pass # Metadata and image enhancement is best-effort; don't fail the extraction.
|
|
20244
|
+
|
|
20245
|
+
result: dict = {"kind": "pdf", "content": content, "metadata": metadata}
|
|
20246
|
+
if page_images:
|
|
20247
|
+
result["pageImages"] = page_images
|
|
20248
|
+
return result
|
|
20249
|
+
|
|
20250
|
+
|
|
20251
|
+
def extract(path: Path, preview_pages: int = 0) -> dict:
|
|
19232
20252
|
kind = sniff_kind(path)
|
|
19233
20253
|
if kind == "txt":
|
|
19234
20254
|
return read_text(path)
|
|
@@ -19240,7 +20260,15 @@ def extract(path: Path) -> dict:
|
|
|
19240
20260
|
return read_html(path)
|
|
19241
20261
|
if kind == "csv":
|
|
19242
20262
|
return read_csv_file(path)
|
|
19243
|
-
if
|
|
20263
|
+
if kind == "pdf":
|
|
20264
|
+
return read_pdf(path, preview_pages)
|
|
20265
|
+
if kind == "msg":
|
|
20266
|
+
return read_msg(path)
|
|
20267
|
+
if kind == "eml":
|
|
20268
|
+
return read_eml(path)
|
|
20269
|
+
if kind == "zip":
|
|
20270
|
+
return read_zip(path)
|
|
20271
|
+
if path.suffix.lower() in MARKITDOWN_EXTENSIONS or kind in {"doc", "docx", "xls", "xlsx", "ppt", "pptx"}:
|
|
19244
20272
|
return read_with_markitdown(path, kind)
|
|
19245
20273
|
|
|
19246
20274
|
raw = path.read_text(encoding="utf-8", errors="replace")
|
|
@@ -19256,12 +20284,13 @@ def main() -> int:
|
|
|
19256
20284
|
parser = argparse.ArgumentParser()
|
|
19257
20285
|
parser.add_argument("--input", required=True)
|
|
19258
20286
|
parser.add_argument("--output", required=True)
|
|
20287
|
+
parser.add_argument("--preview-pages", type=int, default=0)
|
|
19259
20288
|
args = parser.parse_args()
|
|
19260
20289
|
|
|
19261
20290
|
input_path = Path(args.input)
|
|
19262
20291
|
output_path = Path(args.output)
|
|
19263
20292
|
|
|
19264
|
-
result = extract(input_path)
|
|
20293
|
+
result = extract(input_path, preview_pages=args.preview_pages)
|
|
19265
20294
|
output_path.write_text(json.dumps(result, ensure_ascii=False), encoding="utf-8")
|
|
19266
20295
|
return 0
|
|
19267
20296
|
|
|
@@ -19269,10 +20298,10 @@ def main() -> int:
|
|
|
19269
20298
|
if __name__ == "__main__":
|
|
19270
20299
|
raise SystemExit(main())
|
|
19271
20300
|
`;
|
|
19272
|
-
const EXTRACT_FILE_PY_HASH = "
|
|
20301
|
+
const EXTRACT_FILE_PY_HASH = "bc00c9206b20fc39f1ab718271dd49ae335f95c0b1683bf393d0b3f1376d8f33";
|
|
19273
20302
|
|
|
19274
20303
|
const execFile$1 = promisify(execFile$2);
|
|
19275
|
-
const INSTALL_COMMAND = "pip3 install markitdown[all]";
|
|
20304
|
+
const INSTALL_COMMAND = "pip3 install markitdown[all] pymupdf";
|
|
19276
20305
|
const DOCKER_HINT = "Run Commandable with Docker to use the preinstalled extraction runtime.";
|
|
19277
20306
|
let cachedScriptPath = null;
|
|
19278
20307
|
function ensureExtractorScript() {
|
|
@@ -19491,14 +20520,19 @@ function createExtractFileContent(getIntegration, defaultIntegrationId) {
|
|
|
19491
20520
|
const outputPath = join(tempDir, "result.json");
|
|
19492
20521
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
19493
20522
|
await writeFile$1(filePath, bytes);
|
|
19494
|
-
|
|
20523
|
+
const pythonArgs = [extractorScriptPath(), "--input", filePath, "--output", outputPath];
|
|
20524
|
+
const previewPages = typeof resolvedArgs.previewPages === "number" && resolvedArgs.previewPages > 0 ? Math.floor(resolvedArgs.previewPages) : 0;
|
|
20525
|
+
if (previewPages > 0)
|
|
20526
|
+
pythonArgs.push("--preview-pages", String(previewPages));
|
|
20527
|
+
await execFile(pythonExecutable(), pythonArgs, { cwd: tempDir, maxBuffer: 50 * 1024 * 1024 });
|
|
19495
20528
|
const raw = await readFile$1(outputPath, "utf8");
|
|
19496
20529
|
const parsed = JSON.parse(raw);
|
|
19497
20530
|
return {
|
|
19498
20531
|
kind: typeof (parsed == null ? void 0 : parsed.kind) === "string" ? parsed.kind : "unknown",
|
|
19499
20532
|
content: typeof (parsed == null ? void 0 : parsed.content) === "string" ? parsed.content : "",
|
|
19500
20533
|
warnings: Array.isArray(parsed == null ? void 0 : parsed.warnings) ? parsed.warnings.map((item) => String(item)) : void 0,
|
|
19501
|
-
metadata: (parsed == null ? void 0 : parsed.metadata) && typeof parsed.metadata === "object" ? parsed.metadata : void 0
|
|
20534
|
+
metadata: (parsed == null ? void 0 : parsed.metadata) && typeof parsed.metadata === "object" ? parsed.metadata : void 0,
|
|
20535
|
+
pageImages: Array.isArray(parsed == null ? void 0 : parsed.pageImages) ? parsed.pageImages.filter((v) => typeof v === "string") : void 0
|
|
19502
20536
|
};
|
|
19503
20537
|
} catch (error) {
|
|
19504
20538
|
const stderr = typeof (error == null ? void 0 : error.stderr) === "string" ? error.stderr.trim() : "";
|
|
@@ -21457,7 +22491,7 @@ ${lines ? `${lines}
|
|
|
21457
22491
|
` : "No integrations configured yet.\n"}`;
|
|
21458
22492
|
}
|
|
21459
22493
|
try {
|
|
21460
|
-
return
|
|
22494
|
+
return loadIntegrationUsageGuide(ability.integrationtype);
|
|
21461
22495
|
} catch {
|
|
21462
22496
|
return null;
|
|
21463
22497
|
}
|
|
@@ -22057,6 +23091,32 @@ function formatAsText(value) {
|
|
|
22057
23091
|
return String(value);
|
|
22058
23092
|
}
|
|
22059
23093
|
}
|
|
23094
|
+
function renderArtifact(artifact) {
|
|
23095
|
+
if (artifact.type === "image" && artifact.data) {
|
|
23096
|
+
return [{
|
|
23097
|
+
type: "image",
|
|
23098
|
+
data: artifact.data,
|
|
23099
|
+
mimeType: artifact.mimeType
|
|
23100
|
+
}];
|
|
23101
|
+
}
|
|
23102
|
+
if (artifact.type === "image" && artifact.url) {
|
|
23103
|
+
return [{
|
|
23104
|
+
type: "text",
|
|
23105
|
+
text: `Image artifact URL (${artifact.mimeType}): ${artifact.url}`
|
|
23106
|
+
}];
|
|
23107
|
+
}
|
|
23108
|
+
return [];
|
|
23109
|
+
}
|
|
23110
|
+
function buildToolSuccessContent(res) {
|
|
23111
|
+
var _a;
|
|
23112
|
+
const artifacts = getHoistedArtifacts(res);
|
|
23113
|
+
return [
|
|
23114
|
+
{ type: "text", text: formatAsText(res.result) },
|
|
23115
|
+
...artifacts.flatMap(renderArtifact),
|
|
23116
|
+
...((_a = res.logs) == null ? void 0 : _a.length) ? [{ type: "text", text: `Logs:
|
|
23117
|
+
${res.logs.join("\n")}` }] : []
|
|
23118
|
+
];
|
|
23119
|
+
}
|
|
22060
23120
|
function registerToolHandlers(server, tools, options = { mode: "static" }) {
|
|
22061
23121
|
const metaToolDefs = getMetaToolDefinitions();
|
|
22062
23122
|
const { mode, dynamicMode } = options;
|
|
@@ -22074,7 +23134,7 @@ function registerToolHandlers(server, tools, options = { mode: "static" }) {
|
|
|
22074
23134
|
return { tools: [...metaToolDefs, ...toolDefs] };
|
|
22075
23135
|
});
|
|
22076
23136
|
server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
|
|
22077
|
-
var _a, _b, _c
|
|
23137
|
+
var _a, _b, _c;
|
|
22078
23138
|
const name = req.params.name;
|
|
22079
23139
|
const args = (_a = req.params.arguments) != null ? _a : {};
|
|
22080
23140
|
const sessionId = extra == null ? void 0 : extra.sessionId;
|
|
@@ -22112,11 +23172,7 @@ ${res2.logs.join("\n")}` }] : []
|
|
|
22112
23172
|
};
|
|
22113
23173
|
}
|
|
22114
23174
|
return {
|
|
22115
|
-
content:
|
|
22116
|
-
{ type: "text", text: formatAsText(res2.result) },
|
|
22117
|
-
...((_c = res2.logs) == null ? void 0 : _c.length) ? [{ type: "text", text: `Logs:
|
|
22118
|
-
${res2.logs.join("\n")}` }] : []
|
|
22119
|
-
]
|
|
23175
|
+
content: buildToolSuccessContent(res2)
|
|
22120
23176
|
};
|
|
22121
23177
|
}
|
|
22122
23178
|
const staticMeta = handleStaticReadmeCall(name);
|
|
@@ -22133,17 +23189,13 @@ ${res2.logs.join("\n")}` }] : []
|
|
|
22133
23189
|
return {
|
|
22134
23190
|
content: [
|
|
22135
23191
|
{ type: "text", text: `Tool error: ${formatAsText(res.result)}` },
|
|
22136
|
-
...((
|
|
23192
|
+
...((_c = res.logs) == null ? void 0 : _c.length) ? [{ type: "text", text: `Logs:
|
|
22137
23193
|
${res.logs.join("\n")}` }] : []
|
|
22138
23194
|
]
|
|
22139
23195
|
};
|
|
22140
23196
|
}
|
|
22141
23197
|
return {
|
|
22142
|
-
content:
|
|
22143
|
-
{ type: "text", text: formatAsText(res.result) },
|
|
22144
|
-
...((_e = res.logs) == null ? void 0 : _e.length) ? [{ type: "text", text: `Logs:
|
|
22145
|
-
${res.logs.join("\n")}` }] : []
|
|
22146
|
-
]
|
|
23198
|
+
content: buildToolSuccessContent(res)
|
|
22147
23199
|
};
|
|
22148
23200
|
});
|
|
22149
23201
|
}
|
|
@@ -22563,140 +23615,140 @@ const assets = {
|
|
|
22563
23615
|
"/favicon.ico": {
|
|
22564
23616
|
"type": "image/vnd.microsoft.icon",
|
|
22565
23617
|
"etag": "\"10be-n8egyE9tcb7sKGr/pYCaQ4uWqxI\"",
|
|
22566
|
-
"mtime": "2026-04-
|
|
23618
|
+
"mtime": "2026-04-12T18:58:38.513Z",
|
|
22567
23619
|
"size": 4286,
|
|
22568
23620
|
"path": "../public/favicon.ico"
|
|
22569
23621
|
},
|
|
22570
23622
|
"/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2": {
|
|
22571
23623
|
"type": "font/woff2",
|
|
22572
23624
|
"etag": "\"4b5c-TAo9mx7r3xQs52+HbHcHJ52z8Qo\"",
|
|
22573
|
-
"mtime": "2026-04-
|
|
23625
|
+
"mtime": "2026-04-12T18:58:38.507Z",
|
|
22574
23626
|
"size": 19292,
|
|
22575
23627
|
"path": "../public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2"
|
|
22576
23628
|
},
|
|
22577
23629
|
"/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2": {
|
|
22578
23630
|
"type": "font/woff2",
|
|
22579
23631
|
"etag": "\"212c-FshXJibFzNhd2HEIMP8C3JR5PYg\"",
|
|
22580
|
-
"mtime": "2026-04-
|
|
23632
|
+
"mtime": "2026-04-12T18:58:38.507Z",
|
|
22581
23633
|
"size": 8492,
|
|
22582
23634
|
"path": "../public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2"
|
|
22583
23635
|
},
|
|
22584
23636
|
"/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2": {
|
|
22585
23637
|
"type": "font/woff2",
|
|
22586
23638
|
"etag": "\"680c-mJtsV33lkTAKSmfq5k3lKHSllcU\"",
|
|
22587
|
-
"mtime": "2026-04-
|
|
23639
|
+
"mtime": "2026-04-12T18:58:38.507Z",
|
|
22588
23640
|
"size": 26636,
|
|
22589
23641
|
"path": "../public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2"
|
|
22590
23642
|
},
|
|
22591
23643
|
"/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2": {
|
|
22592
23644
|
"type": "font/woff2",
|
|
22593
23645
|
"etag": "\"6ec4-8OoFFPZKF1grqmfGVjh5JDE6DOU\"",
|
|
22594
|
-
"mtime": "2026-04-
|
|
23646
|
+
"mtime": "2026-04-12T18:58:38.507Z",
|
|
22595
23647
|
"size": 28356,
|
|
22596
23648
|
"path": "../public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2"
|
|
22597
23649
|
},
|
|
22598
23650
|
"/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2": {
|
|
22599
23651
|
"type": "font/woff2",
|
|
22600
23652
|
"etag": "\"1d98-cDZfMibtk4T04FTTAmlfhWDpkN0\"",
|
|
22601
|
-
"mtime": "2026-04-
|
|
23653
|
+
"mtime": "2026-04-12T18:58:38.507Z",
|
|
22602
23654
|
"size": 7576,
|
|
22603
23655
|
"path": "../public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2"
|
|
22604
23656
|
},
|
|
22605
23657
|
"/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2": {
|
|
22606
23658
|
"type": "font/woff2",
|
|
22607
23659
|
"etag": "\"47c4-5xyngHnzzhetUee74tMx9OTgqNQ\"",
|
|
22608
|
-
"mtime": "2026-04-
|
|
23660
|
+
"mtime": "2026-04-12T18:58:38.507Z",
|
|
22609
23661
|
"size": 18372,
|
|
22610
23662
|
"path": "../public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2"
|
|
22611
23663
|
},
|
|
22612
23664
|
"/_nuxt/BD6mASiY.js": {
|
|
22613
23665
|
"type": "text/javascript; charset=utf-8",
|
|
22614
23666
|
"etag": "\"ab-ScyLcA/4r5aOxEv1YY+kqXazCHI\"",
|
|
22615
|
-
"mtime": "2026-04-
|
|
23667
|
+
"mtime": "2026-04-12T18:58:38.509Z",
|
|
22616
23668
|
"size": 171,
|
|
22617
23669
|
"path": "../public/_nuxt/BD6mASiY.js"
|
|
22618
23670
|
},
|
|
22619
|
-
"/_nuxt/CjAs3eBq.js": {
|
|
22620
|
-
"type": "text/javascript; charset=utf-8",
|
|
22621
|
-
"etag": "\"1df7-cTFKdH9K34T9NixeUm/CLQ8lWUc\"",
|
|
22622
|
-
"mtime": "2026-04-07T07:13:36.308Z",
|
|
22623
|
-
"size": 7671,
|
|
22624
|
-
"path": "../public/_nuxt/CjAs3eBq.js"
|
|
22625
|
-
},
|
|
22626
23671
|
"/_nuxt/D9wFDhac.js": {
|
|
22627
23672
|
"type": "text/javascript; charset=utf-8",
|
|
22628
23673
|
"etag": "\"e99-sUFV1wmMOK2XGfzDXJyP2NA8TG4\"",
|
|
22629
|
-
"mtime": "2026-04-
|
|
23674
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
22630
23675
|
"size": 3737,
|
|
22631
23676
|
"path": "../public/_nuxt/D9wFDhac.js"
|
|
22632
23677
|
},
|
|
22633
|
-
"/_nuxt/
|
|
23678
|
+
"/_nuxt/CjAs3eBq.js": {
|
|
22634
23679
|
"type": "text/javascript; charset=utf-8",
|
|
22635
|
-
"etag": "\"
|
|
22636
|
-
"mtime": "2026-04-
|
|
22637
|
-
"size":
|
|
22638
|
-
"path": "../public/_nuxt/
|
|
23680
|
+
"etag": "\"1df7-cTFKdH9K34T9NixeUm/CLQ8lWUc\"",
|
|
23681
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
23682
|
+
"size": 7671,
|
|
23683
|
+
"path": "../public/_nuxt/CjAs3eBq.js"
|
|
22639
23684
|
},
|
|
22640
23685
|
"/_nuxt/DSWYWRXT.js": {
|
|
22641
23686
|
"type": "text/javascript; charset=utf-8",
|
|
22642
23687
|
"etag": "\"10875-8b+YwIvP6QkcBFnHXqxd+WeZ05o\"",
|
|
22643
|
-
"mtime": "2026-04-
|
|
23688
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
22644
23689
|
"size": 67701,
|
|
22645
23690
|
"path": "../public/_nuxt/DSWYWRXT.js"
|
|
22646
23691
|
},
|
|
23692
|
+
"/_nuxt/DRfk9W3W.js": {
|
|
23693
|
+
"type": "text/javascript; charset=utf-8",
|
|
23694
|
+
"etag": "\"194dc-Oj5Ixz12+pq4yqDtF/N+YAPzoWw\"",
|
|
23695
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
23696
|
+
"size": 103644,
|
|
23697
|
+
"path": "../public/_nuxt/DRfk9W3W.js"
|
|
23698
|
+
},
|
|
22647
23699
|
"/_nuxt/VvnbcAzZ.js": {
|
|
22648
23700
|
"type": "text/javascript; charset=utf-8",
|
|
22649
23701
|
"etag": "\"d7b-hU4O5jppM7Ou3kZAYy3iYXlgoa8\"",
|
|
22650
|
-
"mtime": "2026-04-
|
|
23702
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
22651
23703
|
"size": 3451,
|
|
22652
23704
|
"path": "../public/_nuxt/VvnbcAzZ.js"
|
|
22653
23705
|
},
|
|
22654
23706
|
"/_nuxt/_id_.DhlLK-mY.css": {
|
|
22655
23707
|
"type": "text/css; charset=utf-8",
|
|
22656
23708
|
"etag": "\"2f4-xtV37kE566jU74wpZnFHA29RoAY\"",
|
|
22657
|
-
"mtime": "2026-04-
|
|
23709
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
22658
23710
|
"size": 756,
|
|
22659
23711
|
"path": "../public/_nuxt/_id_.DhlLK-mY.css"
|
|
22660
23712
|
},
|
|
22661
|
-
"/_nuxt/BUmYUDQu.js": {
|
|
22662
|
-
"type": "text/javascript; charset=utf-8",
|
|
22663
|
-
"etag": "\"66cba-d/pdEXVc78H3VlgFN3kVzKpvD1Q\"",
|
|
22664
|
-
"mtime": "2026-04-07T07:13:36.308Z",
|
|
22665
|
-
"size": 421050,
|
|
22666
|
-
"path": "../public/_nuxt/BUmYUDQu.js"
|
|
22667
|
-
},
|
|
22668
23713
|
"/_nuxt/error-404.C7fg894-.css": {
|
|
22669
23714
|
"type": "text/css; charset=utf-8",
|
|
22670
23715
|
"etag": "\"97e-fiQ3o7A11L9BuXRBr0GJldkx0AU\"",
|
|
22671
|
-
"mtime": "2026-04-
|
|
23716
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
22672
23717
|
"size": 2430,
|
|
22673
23718
|
"path": "../public/_nuxt/error-404.C7fg894-.css"
|
|
22674
23719
|
},
|
|
22675
23720
|
"/_nuxt/error-500.DjUK_N2Y.css": {
|
|
22676
23721
|
"type": "text/css; charset=utf-8",
|
|
22677
23722
|
"etag": "\"773-Qf61bSDos4KtmZDaA06FmZyUYNo\"",
|
|
22678
|
-
"mtime": "2026-04-
|
|
23723
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
22679
23724
|
"size": 1907,
|
|
22680
23725
|
"path": "../public/_nuxt/error-500.DjUK_N2Y.css"
|
|
22681
23726
|
},
|
|
22682
23727
|
"/_nuxt/builds/latest.json": {
|
|
22683
23728
|
"type": "application/json",
|
|
22684
|
-
"etag": "\"47-
|
|
22685
|
-
"mtime": "2026-04-
|
|
23729
|
+
"etag": "\"47-e/86GxbuxL65vE0JCcC0T3jGAp4\"",
|
|
23730
|
+
"mtime": "2026-04-12T18:58:38.505Z",
|
|
22686
23731
|
"size": 71,
|
|
22687
23732
|
"path": "../public/_nuxt/builds/latest.json"
|
|
22688
23733
|
},
|
|
22689
|
-
"/_nuxt/builds/meta/
|
|
23734
|
+
"/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json": {
|
|
22690
23735
|
"type": "application/json",
|
|
22691
|
-
"etag": "\"58-
|
|
22692
|
-
"mtime": "2026-04-
|
|
23736
|
+
"etag": "\"58-BEsLMe80cnxppLc/ZshfoAR3oqs\"",
|
|
23737
|
+
"mtime": "2026-04-12T18:58:38.503Z",
|
|
22693
23738
|
"size": 88,
|
|
22694
|
-
"path": "../public/_nuxt/builds/meta/
|
|
23739
|
+
"path": "../public/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json"
|
|
23740
|
+
},
|
|
23741
|
+
"/_nuxt/BUmYUDQu.js": {
|
|
23742
|
+
"type": "text/javascript; charset=utf-8",
|
|
23743
|
+
"etag": "\"66cba-d/pdEXVc78H3VlgFN3kVzKpvD1Q\"",
|
|
23744
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
23745
|
+
"size": 421050,
|
|
23746
|
+
"path": "../public/_nuxt/BUmYUDQu.js"
|
|
22695
23747
|
},
|
|
22696
23748
|
"/_nuxt/entry.Y3mA4bzA.css": {
|
|
22697
23749
|
"type": "text/css; charset=utf-8",
|
|
22698
23750
|
"etag": "\"2d46b-zfrD3Ny9WW6qm4fCXAfX5eIAxPA\"",
|
|
22699
|
-
"mtime": "2026-04-
|
|
23751
|
+
"mtime": "2026-04-12T18:58:38.510Z",
|
|
22700
23752
|
"size": 185451,
|
|
22701
23753
|
"path": "../public/_nuxt/entry.Y3mA4bzA.css"
|
|
22702
23754
|
}
|