commit e0404617d25385b151c8ae8106f2bd5d471ab407 Author: Mark Kats Date: Thu Sep 17 21:53:09 2026 +0300 chore: keep widget sources only diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..346194d --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "shd-public", + "interface": { + "displayName": "SHD Public Plugins" + }, + "plugins": [ + { + "name": "shd-mcp-plugin", + "source": { + "source": "local", + "path": "./plugins/shd-mcp-plugin" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4db27e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.env +.env.* +!.env.example +.DS_Store +.idea/ +.vscode/ +*.log +__pycache__/ +*.py[cod] + +# MCP widget runtime artifacts are generated from the sources before serving. +/plugins/shd-mcp-plugin/widgets/*/v1/ diff --git a/CHATGPT-SETUP.md b/CHATGPT-SETUP.md new file mode 100644 index 0000000..cbc20a0 --- /dev/null +++ b/CHATGPT-SETUP.md @@ -0,0 +1,100 @@ +# SHD MCP in ChatGPT + +This file describes the user-side connection. The repository cannot create an +app inside another person’s ChatGPT account and never stores OAuth tokens. + +## Custom app + +1. Open ChatGPT settings and enable Developer mode for Apps/Connectors. +2. Create a custom app with this MCP endpoint: + + `https://shd.xyz.su/mcp` + +3. Complete the SHD OAuth sign-in and consent screen. +4. Start a new chat or refresh the app connection. +5. Test with: `Покажи активные проекты с датой завершения и статусом.` + +The server publishes OAuth 2.1 discovery for the root resource and supports +ChatGPT Client ID Metadata Documents (CIMD), PKCE S256 and the +`private_key_jwt` token-endpoint authentication method. Keep the app URL as +`https://shd.xyz.su/mcp`; the retired `/mcp/system-data` resource is not a +valid replacement for the root MCP resource. + +The expected flow is: + +1. ChatGPT calls `shd_list_projects` with `archived: false`. +2. ChatGPT calls the corresponding module render tool after the list tool + before the final response when MCP Apps UI is supported: + `shd_render_projects_widget`, `shd_render_documents_widget`, + `shd_render_tasks_widget`, `shd_render_finance_widget`, + `shd_render_crm_widget`, `shd_render_discussions_widget`, + `shd_render_notifications_widget`, `shd_render_scheduling_widget`, + `shd_render_inventory_widget`, `shd_render_agents_widget`, + `shd_render_status_page_widget`, `shd_render_airbase_widget`, + `shd_render_notes_widget`, `shd_render_project_overview_widget`, + `shd_render_files_widget`, `shd_render_proposals_widget`, + `shd_render_activity_widget`, `shd_render_organizations_widget`, + `shd_render_gitea_widget`, `shd_render_schemes_widget`, `shd_render_task_kanban_widget`, + `shd_render_team_workload_widget`, `shd_render_project_timeline_widget`, + `shd_render_crm_funnel_widget`, `shd_render_finance_dashboard_widget`, + `shd_render_proposal_approvals_widget`, `shd_render_documents_completeness_widget`, + `shd_render_agents_health_widget`, `shd_render_schedule_calendar_widget`, + `shd_render_acl_matrix_widget`, `shd_render_inventory_warnings_widget`, + `shd_render_gitea_board_widget`, `shd_render_schemes_progress_widget`, + `shd_render_notes_tree_widget` or `shd_render_airbase_preview_widget`. +3. The selected widget renders a register, can call the matching detail tool for + one selected row and exposes controlled write actions when declared by the + render metadata. +4. For controller requests, use the explicit controller data→render pair: + `shd_get_controller_source_inventory` → `shd_render_controller_devices_widget`, + `shd_get_home_live_state` → `shd_render_controller_live_state_widget`, + `shd_get_controller_source_history` → `shd_render_controller_history_widget`, + or `shd_get_controller_state_statistics` → + `shd_render_controller_statistics_widget`. Pass the complete data result and + preserve `project`, `controller` and `item_id` in `meta.sourceArgs`. + +The widget resources are versioned at `ui://shd/active-projects/v1.html`, +`ui://shd/documents/v1.html`, `ui://shd/tasks/v1.html`, +`ui://shd/finance/v1.html`, `ui://shd/crm/v1.html`, +`ui://shd/discussions/v1.html`, `ui://shd/notifications/v1.html`, +`ui://shd/scheduling/v1.html`, `ui://shd/inventory/v1.html`, +`ui://shd/agents/v1.html`, `ui://shd/status-page/v1.html`, +`ui://shd/controller-observability/v1.html`, +`ui://shd/airbase/v1.html`, `ui://shd/notes/v1.html`, +`ui://shd/project-overview/v1.html`, `ui://shd/files/v1.html`, +`ui://shd/proposals/v1.html`, +`ui://shd/activity/v1.html`, `ui://shd/organizations/v1.html`, +`ui://shd/gitea/v1.html`, `ui://shd/pdf-scheme-editor/v1.html`, +`ui://shd/task-kanban/v1.html`, `ui://shd/team-workload/v1.html`, +`ui://shd/project-timeline/v1.html`, `ui://shd/crm-funnel/v1.html`, +`ui://shd/finance-dashboard/v1.html`, `ui://shd/proposal-approvals/v1.html`, +`ui://shd/documents-completeness/v1.html`, `ui://shd/agents-health/v1.html`, +`ui://shd/schedule-calendar/v1.html`, `ui://shd/acl-matrix/v1.html`, +`ui://shd/inventory-warnings/v1.html`, `ui://shd/gitea-board/v1.html`, +`ui://shd/pdf-scheme-editor-progress/v1.html`, `ui://shd/notes-tree/v1.html` and +`ui://shd/airbase-preview/v1.html`. + +The MCP tools remain usable without the widget. A failed OAuth connection is +an account/endpoint configuration problem, not a missing token in this repo. + +## Public directory + +Directory submission is a separate optional release step. It is needed for +public discovery and reviewed installation, not for internal use or a custom +app added manually in ChatGPT. Before submission, the owner must provide the +production HTTPS endpoint, OAuth metadata, privacy/support URLs, accurate app +metadata, test prompts and any review credentials requested by OpenAI. + +Use the official [submission guide](https://developers.openai.com/plugins/deploy/submission) +and [plugin guidelines](https://developers.openai.com/plugins/app-guidelines). + +## License choice + +This repository currently uses the proprietary SHD MCP Plugin License. Public +visibility means the source can be viewed; it does not grant permission to +redistribute, resell, modify or use it outside an authorized SHD account. + +MIT would grant anyone permission to use, copy, modify, publish, sublicense and +sell the plugin, including forks, while retaining only the copyright notice and +license text. Switching to MIT is a product/legal decision and is not done +implicitly by publishing the repository. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b964915 --- /dev/null +++ b/LICENSE @@ -0,0 +1,37 @@ +SHD MCP PLUGIN LICENSE + +Copyright (c) 2026 SHD + +This license applies to the SHD MCP Plugin package, its Skills, manifests, +documentation and SHD branding in this repository. The SHD MCP service and +its backend are separate systems and remain subject to their own terms. + +1. Permission + +SHD grants a limited, non-exclusive, non-transferable permission to install and +use an unmodified copy of this plugin with an authorized SHD account and a +compatible MCP client. + +2. Restrictions + +Unless SHD gives prior written permission, you may not sell, sublicense, +redistribute, publish modified versions of, remove notices from, or use this +package and its branding to provide a competing service. Public access to this +repository does not transfer copyright, trademark or other intellectual +property rights. + +3. No credentials or access grant + +This package contains no access token and grants no access to SHD data. Access +is controlled by SHD authentication, organization membership and server-side +permissions. + +4. Warranty and liability + +The package is provided "as is" to the maximum extent permitted by law, +without warranties of any kind. SHD is not liable for indirect, incidental or +consequential damages arising from its use. + +5. Third-party materials + +Third-party software and assets, if any, remain subject to their own licenses. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d8e4db0 --- /dev/null +++ b/README.md @@ -0,0 +1,189 @@ +# SHD MCP Plugin + +Public installable plugin package for SHD project workflows. It combines Codex +Skills with the authenticated SHD MCP server and keeps business logic, ACLs, +OAuth and persisted data in SHD itself. + +## What the user installs + +The package contains: + +- `.codex-plugin/plugin.json` — plugin metadata; +- `.mcp.json` — the official SHD Streamable HTTP MCP endpoint; +- `skills/` — routing, project, ProjectBase, task, file, finance, CRM, + analytics, estimates, scheduling, entity resolution, Wiki, discussions, + documents, organizations/ACL, notifications, inventory, agents, status-page, + realtime/activity, Terms/contracts and Gitea workflows; +- `assets/` — plugin branding; +- `widgets/` — versioned MCP Apps resources with manifest, checksum and + provenance metadata; +- `.agents/plugins/marketplace.json` — a ready local marketplace entry. + +It does not contain the SHD Laravel application, database code or credentials. + +## Install in Codex + +Clone this repository and register its marketplace: + +```bash +git clone https://github.com/bulava92/shd-mcp-plugin.git +cd shd-mcp-plugin +codex plugin marketplace add "$PWD" +codex plugin add shd-mcp-plugin@shd-public +``` + +After installation, start a new Codex thread so the plugin Skills are loaded. + +## ChatGPT custom app + +ChatGPT uses the same SHD MCP server connection, but it is added separately in +the ChatGPT account: open Settings → Apps/Connectors → Developer mode, create +a custom app and set the MCP URL to `https://shd.xyz.su/mcp`. Complete the SHD +OAuth flow and refresh the app after server metadata changes. + +The active-projects result can render an inline MCP Apps widget. It is a small +dashboard with: + +- `Проект → Дата завершения → Статус`; +- sorting by the nearest deadline; +- local search by project name/code; +- a refresh action, a project-details panel and controlled project actions. + +If the host does not support MCP Apps UI, the data tool and the plain Markdown +table remain fully usable. + +The backend serves the generated widget artifact at +`ui://shd/active-projects/v1.html`. Generate the ignored runtime files before +starting the backend: + +```sh +node scripts/generate-widget-artifacts.mjs +``` + +The source is kept in `widgets/active-projects/source.html` and +`widgets/active-projects/widget.json`; generated `v1/` files are not committed. + +The same data/render contract is available for Documents, Tasks, +Finance, CRM, Discussions, Notifications, Scheduling, Inventory, Agents, +Status Page, AirBase, Notes, Project overview, Files, Proposals, Activity, +Organizations and access, Gitea Issues and PDF Scheme Editor: + +Controller observability uses one parameterized resource for device inventory, +current state, server-computed statistics, source history, cross-controller +comparisons, expert settings, complete project topology, telemetry health and +source audit: + +- `shd_render_controller_history_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_statistics_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_devices_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_live_state_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_compare_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_settings_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_topology_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_telemetry_health_widget` → `ui://shd/controller-observability/v1.html`; +- `shd_render_controller_audit_widget` → `ui://shd/controller-observability/v1.html`. + +The controller topology keeps Loxone/WB source controllers separate from +Raspberry Pi and mini PC controller rows without source inventory; it does not +substitute the separate agent register for the project controller list. +The agent overview is the inverse boundary: it lists agents/runtime access and +does not become a controller list merely because an agent runs on a controller. + +- `shd_render_documents_widget` → `ui://shd/documents/v1.html`; +- `shd_render_tasks_widget` → `ui://shd/tasks/v1.html`; +- `shd_render_finance_widget` → `ui://shd/finance/v1.html`; +- `shd_render_crm_widget` → `ui://shd/crm/v1.html`; +- `shd_render_discussions_widget` → `ui://shd/discussions/v1.html`; +- `shd_render_notifications_widget` → `ui://shd/notifications/v1.html`; +- `shd_render_scheduling_widget` → `ui://shd/scheduling/v1.html`; +- `shd_render_inventory_widget` → `ui://shd/inventory/v1.html`; +- `shd_render_agents_widget` → `ui://shd/agents/v1.html`; +- `shd_render_status_page_widget` → `ui://shd/status-page/v1.html`; +- `shd_render_airbase_widget` → `ui://shd/airbase/v1.html`; +- `shd_render_notes_widget` → `ui://shd/notes/v1.html`; +- `shd_render_project_overview_widget` → `ui://shd/project-overview/v1.html`; +- `shd_render_files_widget` → `ui://shd/files/v1.html`; +- `shd_render_proposals_widget` → `ui://shd/proposals/v1.html`; +- `shd_render_activity_widget` → `ui://shd/activity/v1.html`; +- `shd_render_organizations_widget` → `ui://shd/organizations/v1.html`; +- `shd_render_gitea_widget` → `ui://shd/gitea/v1.html`; +- `shd_render_schemes_widget` → `ui://shd/pdf-scheme-editor/v1.html`; +- `shd_render_task_kanban_widget` → `ui://shd/task-kanban/v1.html`; +- `shd_render_team_workload_widget` → `ui://shd/team-workload/v1.html`; +- `shd_render_project_timeline_widget` → `ui://shd/project-timeline/v1.html`; +- `shd_render_crm_funnel_widget` → `ui://shd/crm-funnel/v1.html`; +- `shd_render_finance_dashboard_widget` → `ui://shd/finance-dashboard/v1.html`; +- `shd_render_proposal_approvals_widget` → `ui://shd/proposal-approvals/v1.html`; +- `shd_render_documents_completeness_widget` → `ui://shd/documents-completeness/v1.html`; +- `shd_render_agents_health_widget` → `ui://shd/agents-health/v1.html`; +- `shd_render_schedule_calendar_widget` → `ui://shd/schedule-calendar/v1.html`; +- `shd_render_acl_matrix_widget` → `ui://shd/acl-matrix/v1.html`; +- `shd_render_inventory_warnings_widget` → `ui://shd/inventory-warnings/v1.html`; +- `shd_render_gitea_board_widget` → `ui://shd/gitea-board/v1.html`; +- `shd_render_schemes_progress_widget` → `ui://shd/pdf-scheme-editor-progress/v1.html`; +- `shd_render_notes_tree_widget` → `ui://shd/notes-tree/v1.html`; +- `shd_render_airbase_preview_widget` → `ui://shd/airbase-preview/v1.html`. + +Each widget accepts normalized records from its list tool, keeps the plain +structured response as a fallback and uses the MCP Apps bridge before the +`window.openai` compatibility API. + +The official OpenAI directory is optional. It is useful for public discovery, +one-click installation and review of a public app; it is not required for an +internal team or a manually added ChatGPT custom app. See the official +[MCP server guide](https://developers.openai.com/plugins/build/mcp-server), +[UI guide](https://developers.openai.com/plugins/build/chatgpt-ui), and +[submission guide](https://developers.openai.com/plugins/deploy/submission) +when public listing is the goal. + +## Connect SHD + +Installation and authorization are separate steps: + +1. The client reads `.mcp.json` and opens the SHD OAuth flow. +2. The user signs in to SHD and approves the requested MCP connection. +3. SHD issues the client an authorization; the client stores it securely. +4. Every tool call is checked again by SHD for organization, project and + module permissions. + +The user does not need to put a token in this repository. A personal access +token may be used only if the selected MCP client and SHD deployment +explicitly support it; enter it in that client's secure connection settings, +never in `.mcp.json`, Skills or an issue. + +SHD OAuth supports ChatGPT CIMD clients with PKCE S256 and +`private_key_jwt` client authentication. The server validates the client +assertion against ChatGPT's published JWKS; the plugin package never stores +the assertion, access token or refresh token. + +For ChatGPT or another MCP client without Codex plugin installation, add the +same HTTPS endpoint as a custom MCP app and complete OAuth there. The public +repository packages the workflows; it does not grant access to the server. + +## Troubleshooting + +- **Plugin installs but tools are unavailable:** reconnect SHD OAuth and start + a new thread. +- **Projects are missing:** the SHD account or organization lacks access, or + the project is archived; verify permissions in SHD. +- **A write is rejected:** the server ACL, required role, version or conflict + check rejected it. Do not bypass the error with a different token. +- **A module is not covered by a dedicated skill:** use `shd-routing`; the MCP + server still exposes the current authenticated tool catalog, while skills + provide focused workflow and safety guidance for common module operations. +- **Another SHD installation is required:** use its approved MCP URL and its + OAuth resource in the client's secure connection configuration. + +## Development + +Keep changes inside the plugin package. The authenticated MCP server, OAuth +deployment and runtime verification remain private server-side concerns. Do not +place credentials, private host details or backend implementation in Skills or +widget assets. + +## License + +The repository is public source, but the plugin and SHD branding are distributed +under the proprietary [SHD MCP Plugin License](LICENSE). Public visibility does +not grant rights to redistribute, resell, modify or reuse the package outside +the permitted SHD use. diff --git a/plugins/shd-mcp-plugin/.codex-plugin/plugin.json b/plugins/shd-mcp-plugin/.codex-plugin/plugin.json new file mode 100644 index 0000000..14e1af4 --- /dev/null +++ b/plugins/shd-mcp-plugin/.codex-plugin/plugin.json @@ -0,0 +1,49 @@ +{ + "name": "shd-mcp-plugin", + "version": "0.5.1", + "description": "Ready-to-install SHD workflows backed by the authenticated SHD MCP server.", + "author": { + "name": "SHD", + "url": "https://shd.xyz.su" + }, + "homepage": "https://shd.xyz.su", + "repository": "https://github.com/bulava92/shd-mcp-plugin", + "license": "Proprietary", + "keywords": [ + "SHD", + "projects", + "ProjectBase", + "tasks", + "finance", + "CRM", + "documents", + "inventory", + "organizations", + "notifications", + "realtime", + "Gitea", + "MCP" + ], + "skills": "./skills/", + "interface": { + "displayName": "SHD MCP", + "shortDescription": "SHD projects and operations", + "longDescription": "Installable SHD MCP workflows for projects, ProjectBase, tasks, files, finance, CRM, documents, access control, inventory, notifications, realtime, status monitoring and linked Gitea operations.", + "developerName": "SHD", + "category": "Productivity", + "capabilities": [ + "Read", + "Write" + ], + "websiteURL": "https://shd.xyz.su", + "brandColor": "#1E88E5", + "composerIcon": "./assets/logo.png", + "logo": "./assets/logo.png", + "defaultPrompt": [ + "Покажи активные проекты с датой завершения и статусом.", + "Сделай сводку по проекту и его рискам.", + "Проверь ProjectBase без изменения данных." + ] + }, + "mcpServers": "./.mcp.json" +} diff --git a/plugins/shd-mcp-plugin/.mcp.json b/plugins/shd-mcp-plugin/.mcp.json new file mode 100644 index 0000000..4e7ada6 --- /dev/null +++ b/plugins/shd-mcp-plugin/.mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "shd": { + "type": "http", + "url": "https://shd.xyz.su/mcp", + "oauth_resource": "https://shd.xyz.su/mcp" + } + } +} diff --git a/plugins/shd-mcp-plugin/README.md b/plugins/shd-mcp-plugin/README.md new file mode 100644 index 0000000..71f0282 --- /dev/null +++ b/plugins/shd-mcp-plugin/README.md @@ -0,0 +1,99 @@ +# SHD MCP plugin package + +This directory is the installable Codex plugin. It packages reusable SHD +workflow Skills and the official SHD MCP connection; it does not contain the +SHD backend or a second MCP server. + +The plugin requires an SHD account authorized for the requested projects and +modules. Skills do not grant permissions or bypass server-side ACLs. + +## Included workflows + +- active projects with completion dates and statuses; +- project status and risk summaries; +- ProjectBase schema and data-quality audits; +- guarded task workflows; +- project file inspection and actions; +- finance and CRM read/audit workflows; +- finance account, balance, payment and settlement audits; +- analytics, comparisons, reports and task/proposal metrics; +- entity resolution and duplicate-candidate analysis; +- estimates, proposals and estimate-document-contract workflows; +- scheduling events, slots and booking positions; +- Wiki, notes, revisions and note-database workflows; +- discussions, channels, topics, messages and attachments; +- documents, templates, PDFs, revisions and public-link workflows; +- organizations, members, invitations, ACL and two-factor policies; +- notifications, preferences and read-state workflows; +- inventory, assets, stock, procurement and stocktake workflows; +- agent/controller diagnostics, tunnels, endpoints and runtime actions; +- status-page monitors, groups, history and incidents; +- realtime sessions, event cursors and operational activity; +- Terms documents, revisions, completeness and contract discussions; +- linked Gitea issues, labels, comments and attachments; +- safe bulk-change planning and validation rules. + +When the connected MCP host supports MCP Apps UI, the 44 SHD render tools have +inline interactive widgets: active projects, module registers and specialized +Kanban, workload, timeline, funnel, dashboard, calendar, matrix, tree and +Project DB views. Each widget sorts or filters normalized records, shows +status/value fields, can load one selected record’s details where supported and +exposes only the write actions declared by its render metadata. A write always +requires an explicit form submission; destructive actions also require a +confirmation checkbox. After a successful mutation, the widget refreshes its +source list. +The same tools still return normal structured data for hosts that do not render +widgets. + +The controller observability render tools share the versioned +`widgets/controller-observability/v1/` resource. It exposes controller device +inventory and profiles, normalized live states, server-computed binary/numeric +statistics, explicit native vs Influx/Grafana provenance, universal comparison +presets, complete controller topology, telemetry health and operation audit. +Expert-setting controls are enabled only for source-provided writable descriptors. + +The active-projects source is `widgets/active-projects/source.html` with its +resource metadata in `widgets/active-projects/widget.json`. Before starting +the backend, generate the ignored runtime files with: + +```sh +node scripts/generate-widget-artifacts.mjs +``` + +The backend then reads the generated `widgets/active-projects/v1/` artifact for +the `ui://shd/active-projects/v1.html` resource. + +The standard module resources are generated from the single +`widgets/_shared/universal-widget.html` source by +`scripts/generate-widget-artifacts.mjs`. Each generated artifact still keeps +its own URI, manifest, SHA256 checksum and provenance note: this preserves +resource cache keys and module-scope registration while removing duplicated +HTML, bridge and action runtime code. The generator also injects the shared +`widgets/_shared/widget-theme.css` and `widgets/_shared/write-actions.css` +into the specialized `active-projects` and `controller-observability` +renderers, so every widget uses the same spacing, surfaces, controls, tables +and responsive rules without changing their data contracts. The generator +supports `--check` for a no-write consistency check. Module copy and fallback +tool configuration lives in the sibling +`widgets/_shared/universal-widget-config.json` source and is injected into +each universal standalone artifact by the generator. For an older template +that still contains the inline configuration block, run `--extract-config` +once and then `--migrate-template` once. The specialized applications remain +separate only where their interaction contracts differ. Render tools are +deliberately separate from list tools so data access and UI rendering remain +auditable. + +The generated `widgets/*/v1/` directories are ignored and must not be +committed. A fresh checkout or deployment must run the generator before the +MCP backend starts; `--check` can then verify that the local runtime output is +current. + +## MCP connection + +The package points to `https://shd.xyz.su/mcp` and requests OAuth for that MCP +resource. The client stores the resulting authorization; this repository never +contains an access token. + +If a client or SHD installation uses another official endpoint, change the +connection in `.mcp.json` in that client's secure configuration. Do not commit +tokens, cookies, client secrets or private host credentials. diff --git a/plugins/shd-mcp-plugin/assets/logo.png b/plugins/shd-mcp-plugin/assets/logo.png new file mode 100644 index 0000000..9318b99 Binary files /dev/null and b/plugins/shd-mcp-plugin/assets/logo.png differ diff --git a/plugins/shd-mcp-plugin/skills/shd-active-projects/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-active-projects/SKILL.md new file mode 100644 index 0000000..b2eb254 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-active-projects/SKILL.md @@ -0,0 +1,41 @@ +--- +name: shd-active-projects +description: Use when the user asks for all active SHD projects, project deadlines, project statuses, or a project register sorted by the nearest completion date. +--- + +# Active SHD projects + +## Workflow + +1. Call `shd_list_projects` once with `archived: false` and `limit: 100`. +2. If the user supplied a search term, pass it as `q`; otherwise do not add a + search filter. +3. Use the project fields returned by that tool. Do not call + `shd_get_project` once per row just to repeat list data. +4. Sort projects with a non-null `deadline` in ascending order. Put projects + without a deadline after dated projects; do not invent a date. +5. If the user is in a UI-capable MCP host, call `shd_render_projects_widget` + after the data call, passing the returned `data` array as `projects` and the + returned `meta` object as `meta`. When rendering succeeds, keep the project + rows inside the widget and give only a short confirmation in the final + response; do not repeat the rows as prose or a Markdown table. +6. If the UI host is unavailable or the render tool returns an error, return + the compact fallback table: + + `Проект | Дата завершения | Статус` + +7. Preserve the server's project name/code and status. If a deadline is a + datetime, format it with its returned timezone when available; otherwise + keep the value unmodified and state that timezone data was unavailable. + +## Boundaries + +- “Активные” defaults to non-archived projects (`archived: false`). If the user + means a particular business status rather than archive state, ask which + status or report the returned statuses without filtering them locally. +- Do not use task due dates as project completion dates. +- If the server indicates more records than the tool can return, report that + the current tool limit was reached; do not claim a complete list. +- This workflow is read-only and must not update, archive or delete projects. + +See `references/project-widget.md` for the data/render boundary. diff --git a/plugins/shd-mcp-plugin/skills/shd-active-projects/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-active-projects/agents/openai.yaml new file mode 100644 index 0000000..2ef4b53 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-active-projects/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: Active SHD projects + short_description: Project deadlines and statuses +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-active-projects/references/project-widget.md b/plugins/shd-mcp-plugin/skills/shd-active-projects/references/project-widget.md new file mode 100644 index 0000000..95e6468 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-active-projects/references/project-widget.md @@ -0,0 +1,8 @@ +# Project widget contract + +Call the data tool first. Pass only the returned project array and metadata to +the render tool. The widget is a presentation layer: it must not invent dates, +statuses or project rows. When the render succeeds, keep the rows in the +widget and do not repeat them in the assistant response. Keep the plain +structured table only as the fallback for hosts without MCP Apps UI or when +the render tool fails. diff --git a/plugins/shd-mcp-plugin/skills/shd-agents-operations/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-agents-operations/SKILL.md new file mode 100644 index 0000000..869fd0b --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-agents-operations/SKILL.md @@ -0,0 +1,43 @@ +--- +name: shd-agents-operations +description: Use when the user asks to inspect SHD agents, controllers, diagnostics, tunnels, access rules, endpoints, monitoring or agent runtime actions. +--- + +# SHD agents operations + +See `references/agent-operations.md` before touching runtime, network or access +configuration. + +## Diagnose first + +- Resolve the controller, user agent or tunnel identity from + `shd_agents_overview`, status and diagnostic tools. +- Read current access policies, settings, history and async status before + proposing an action. +- Keep diagnostics, configuration, endpoint publication and network routing as + separate operations. Never expose credentials, private keys or tunnel data. + +## Controller data and widgets + +- For a controller device list, call `shd_get_controller_source_inventory`, then + `shd_render_controller_devices_widget` with the complete result and scoped + `meta.sourceArgs`. +- For current states across controllers, call `shd_get_home_live_state`, then + `shd_render_controller_live_state_widget`. +- For one presence, binary state or numeric sensor statistics, call + `shd_get_controller_state_statistics`, then + `shd_render_controller_statistics_widget`. The server result is authoritative + for counts, intervals, durations, percentages, medians and hourly buckets; + do not calculate them from raw history in the model. +- For raw time-series history, call `shd_get_controller_source_history`, then + `shd_render_controller_history_widget`. Keep the detailed rows in the widget + after a successful render and use plain text only when the UI call is + unavailable or fails. + +## High-impact actions + +Creating/closing tunnels, publishing or revoking endpoints, replacing access +policies, rebooting an agent, changing exit-network routing or enabling +monitoring requires an explicit target and user request. For asynchronous +actions, return the operation ID, poll only as needed and report terminal +status. Do not retry a network or reboot action blindly. diff --git a/plugins/shd-mcp-plugin/skills/shd-agents-operations/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-agents-operations/agents/openai.yaml new file mode 100644 index 0000000..ed94667 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-agents-operations/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD agents operations" + short_description: "Diagnose agents and guarded runtime actions" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-agents-operations/references/agent-operations.md b/plugins/shd-mcp-plugin/skills/shd-agents-operations/references/agent-operations.md new file mode 100644 index 0000000..40eeb56 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-agents-operations/references/agent-operations.md @@ -0,0 +1,14 @@ +# Agent operations contract + +Start with `shd_agents_overview`, `shd_get_controller_status`, +`shd_agents_get_controller_diagnostics`, `shd_agents_get_diagnostics`, history +and the relevant access-policy tools. Use `shd_agents_get_async_status` for an +operation already returned by the server. + +Treat tunnels, endpoint publication/revocation, access-policy replacement, +agent reboot, exit-node/network routing and controller/user-agent settings as +high-impact mutations. Tools such as `shd_agents_create_tunnel`, +`shd_agents_close_tunnel`, `shd_agents_publish_controller_endpoint`, +`shd_agents_revoke_controller_endpoint`, `shd_agents_reboot_user_agent` and +`shd_agents_force_exit_network_route` require explicit authorization and exact +scope. Preserve audit/readback and never print secrets returned by diagnostics. diff --git a/plugins/shd-mcp-plugin/skills/shd-airbase-audit/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-airbase-audit/SKILL.md new file mode 100644 index 0000000..4007b8e --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-airbase-audit/SKILL.md @@ -0,0 +1,39 @@ +--- +name: shd-airbase-audit +description: Use when the user asks to inspect AirBase tables, validate project data, find duplicate or broken links, or audit AirBase quality without changing records. +--- + +# AirBase audit + +See `references/audit-contract.md` for the bounded read and evidence format. + +## Workflow + +1. Resolve the canonical Base identity. A supplied `project_code` is accepted + as the legacy alias only when the server accepts it; prefer the returned + Base identity for subsequent calls. +2. If no table is named, call `shd_airbase_tables` first and select only the + relevant tables. Do not load every table by default. +3. Call `shd_airbase_schema` for each selected table before reading its + records. Pass the table explicitly when a single-table audit is requested. +4. Call `shd_airbase_records` with bounded reads and the selected table. +5. Compare records with the returned schema and report only evidence visible in + the response. + +## Findings + +Check for duplicate business numbers, missing required values, invalid select +values, unresolved linked records, orphaned references, conflicting DALI or +electrical identifiers, formula/derived-field risks and incomplete source data +when the selected tables expose those fields. + +Report `table → record → field → observed value → reason → priority`. Distinguish +confirmed defects from data that could not be checked because the schema or +records were unavailable. + +## Safety + +This workflow is read-only. Do not call create, update, delete, import, bulk +upsert or generic action tools while auditing. If the user asks to repair +findings, first present the proposed records and then use the dedicated +validation/dry-run contract before any explicit write. diff --git a/plugins/shd-mcp-plugin/skills/shd-airbase-audit/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-airbase-audit/agents/openai.yaml new file mode 100644 index 0000000..46f3b8c --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-airbase-audit/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: AirBase audit + short_description: Read-only schema and data audit +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-airbase-audit/references/audit-contract.md b/plugins/shd-mcp-plugin/skills/shd-airbase-audit/references/audit-contract.md new file mode 100644 index 0000000..7595f3f --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-airbase-audit/references/audit-contract.md @@ -0,0 +1,20 @@ +# AirBase audit contract + +Use this contract for every read-only AirBase audit. + +1. Resolve the canonical Base identity before selecting tables. Treat a + user-provided project code as an alias until the server returns the + authoritative identity. +2. Discover tables only when the request does not name them. Do not load every + table just to find a possible issue. +3. Read the schema before records for every selected table. Keep the table + name explicit in each subsequent request. +4. Bound record reads with the smallest useful limit and preserve the server's + pagination or truncation metadata. +5. Report evidence as `table → record → field → observed value → reason → + priority`. Mark a finding as unverified when the schema or record needed to + prove it was unavailable. + +Never repair an audit finding inside the audit workflow. A repair requires a +separate proposal, an explicit mutation request, the current schema and +records, the server's validation/dry-run contract, and final readback. diff --git a/plugins/shd-mcp-plugin/skills/shd-analytics/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-analytics/SKILL.md new file mode 100644 index 0000000..641c579 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-analytics/SKILL.md @@ -0,0 +1,33 @@ +--- +name: shd-analytics +description: Use when the user asks for SHD project or company analytics, trends, comparisons, metrics, charts, reports, or time summaries. +--- + +# SHD analytics and reports + +## Scope + +1. Resolve the organization or project scope before aggregating values. Use an + exact `project_code` when the user supplied one; otherwise resolve it with + `shd_list_projects`. +2. Use `shd_get_project_context` for a bounded cross-module summary and request + only the sections needed for the question. +3. For task effort use `shd_get_task_time_report`; use + `shd_export_task_time_report` only when the user asks for an export. +4. For proposal analytics use `shd_get_proposal_public_analytics` only for a + resolved proposal or public analytics scope accepted by its schema. +5. If the requested metric does not map to a known tool, call + `shd_capabilities` and report the unavailable capability instead of inventing + an endpoint or field. + +## Reporting rules + +- State the period, scope, currency and timezone returned by SHD. +- Distinguish totals returned by the server from calculations made from rows. +- Preserve null, unavailable and permission-denied sections as separate states. +- Do not infer profitability, completion, approval or trend direction from one + record or from a missing section. +- Present chart-ready data as a compact table with labels, units and source + fields. Use a UI resource only when the server advertises a matching widget. + +See `references/analytics-contract.md` before composing a multi-section report. diff --git a/plugins/shd-mcp-plugin/skills/shd-analytics/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-analytics/agents/openai.yaml new file mode 100644 index 0000000..cd517cf --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-analytics/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD analytics + short_description: Metrics, comparisons and reports +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-analytics/references/analytics-contract.md b/plugins/shd-mcp-plugin/skills/shd-analytics/references/analytics-contract.md new file mode 100644 index 0000000..a44ab7e --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-analytics/references/analytics-contract.md @@ -0,0 +1,9 @@ +# Analytics contract + +- Every metric has an explicit scope, period, unit and source operation. +- A server total is authoritative; a locally calculated total must be marked as + calculated and list the rows used. +- A comparison must use the same period boundaries and unit on both sides. +- Missing data, denied access and a zero value are different outcomes. +- Recommendations follow the evidence and are not written back to SHD unless a + separate mutation is explicitly requested. diff --git a/plugins/shd-mcp-plugin/skills/shd-discussions/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-discussions/SKILL.md new file mode 100644 index 0000000..394520a --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-discussions/SKILL.md @@ -0,0 +1,26 @@ +--- +name: shd-discussions +description: Use when the user asks to find, read, search, create or edit SHD discussion channels, topics, messages or attachments. +--- + +# SHD discussions + +See `references/discussion-contract.md` for the tool map and message safety +rules. + +## Resolve and read + +- Resolve the organization or project context before selecting a channel. +- Use `shd_list_discussion_channels` and `shd_list_discussion_topics` before + reading messages when an ID is not already known. +- Use bounded reads with `shd_list_discussion_messages` or + `shd_search_discussions`; preserve pagination and returned identifiers. +- Treat private channels and messages as server-authorized data. Do not infer + membership or quote content outside the returned scope. + +## Mutations + +Only create or edit a message, or upload an attachment, when the user asks for +that exact operation. Confirm the target topic, preserve the latest message +identity/version when available, and report the server result. Never delete or +rewrite discussion history by guessing a topic from its display name. diff --git a/plugins/shd-mcp-plugin/skills/shd-discussions/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-discussions/agents/openai.yaml new file mode 100644 index 0000000..e82db45 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-discussions/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD discussions" + short_description: "Search and manage discussion threads" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-discussions/references/discussion-contract.md b/plugins/shd-mcp-plugin/skills/shd-discussions/references/discussion-contract.md new file mode 100644 index 0000000..471058b --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-discussions/references/discussion-contract.md @@ -0,0 +1,14 @@ +# Discussion contract + +Use the narrowest tool for the request: + +- discovery: `shd_list_discussion_channels`, + `shd_list_discussion_topics`; +- content: `shd_list_discussion_messages`, `shd_search_discussions`; +- writes: `shd_add_discussion_message`, `shd_update_discussion_message`; +- files: `shd_upload_discussion_attachment`. + +Resolve the canonical channel/topic/message identifiers before a write. Keep +searches bounded, distinguish no results from denied scope, and retain the +server's pagination or conflict fields. A message edit or attachment upload is +a side effect: require explicit user intent and report the final server result. diff --git a/plugins/shd-mcp-plugin/skills/shd-documents/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-documents/SKILL.md new file mode 100644 index 0000000..94fc1c7 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-documents/SKILL.md @@ -0,0 +1,29 @@ +--- +name: shd-documents +description: Use when the user asks to inspect, generate, send, publish, revise or audit SHD documents, templates, comments or public links. +--- + +# SHD documents + +See `references/document-safety.md` before handling publication, PDF or +contract-related document actions. + +## Read and prepare + +- Resolve the project and document identifiers before reading content. +- Read the document, relevant template/configuration, revisions and comments + needed for the requested result; do not treat a filename as authorization. +- Keep document status, approval state, external links and public links + separate in the report. +- Use the document's returned revision or version when a write accepts one. +- In a UI-capable MCP host, pass normalized list records to + `shd_render_documents_widget`; keep the structured document list as the + fallback and use `shd_get_contract` only for an explicitly selected record. + +## Mutations and publication + +Generating a PDF, sending a document, saving a contract, publishing a public +link or changing document state requires an explicit request. Explain the +target and audience before publication, preserve the server's ACL and expiry +rules, and perform readback when the tool provides it. Never expose private +links, bearer values or document content outside the authorized result. diff --git a/plugins/shd-mcp-plugin/skills/shd-documents/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-documents/agents/openai.yaml new file mode 100644 index 0000000..6767f69 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-documents/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD documents" + short_description: "Inspect and safely manage documents" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-documents/references/document-safety.md b/plugins/shd-mcp-plugin/skills/shd-documents/references/document-safety.md new file mode 100644 index 0000000..accf4b1 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-documents/references/document-safety.md @@ -0,0 +1,14 @@ +# Document safety contract + +Useful read tools include `shd_list_contracts`, `shd_get_contract`, +`shd_list_document_revisions`, `shd_list_document_templates`, +`shd_list_document_comments`, `shd_list_document_external_links` and +`shd_list_document_public_links`. PDF inspection uses +`shd_download_document_pdf`; generation uses `shd_generate_document_pdf`. + +Treat these as separate side effects: `shd_send_document`, +`shd_publish_document_public_link`, `shd_save_document_contract` and +`shd_document_contract_workflow`. Before any of them, resolve the document, +read the current state and require the exact requested action. Report recipient, +visibility, expiry, revision and server validation when returned. A public link +is never a harmless preview and must not be created just to test access. diff --git a/plugins/shd-mcp-plugin/skills/shd-entity-resolution/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-entity-resolution/SKILL.md new file mode 100644 index 0000000..b954d0a --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-entity-resolution/SKILL.md @@ -0,0 +1,26 @@ +--- +name: shd-entity-resolution +description: Use when the user asks to find a CRM entity, resolve duplicate names, compare counterparties, or determine whether records refer to the same business object. +--- + +# SHD entity resolution + +## Resolve first + +1. Prefer an exact returned ID, code or external reference over a name. +2. Search with `shd_list_crm`, then read candidates with + `shd_get_crm_entity`. +3. Inspect `shd_list_crm_entity_links` when relationships, projects or deals + are part of the identity decision. +4. Compare only fields returned by SHD. Report candidate records, matching + evidence, conflicting evidence and unresolved fields separately. + +## Safety + +- A similar name is not proof of a duplicate. +- Do not merge, delete, reassign ownership or change a relationship while + resolving identity. +- A write requires an exact target, an explicit user request and the current + server validation contract. Read the record again after a permitted write. + +See `references/entity-resolution.md` for the candidate-report format. diff --git a/plugins/shd-mcp-plugin/skills/shd-entity-resolution/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-entity-resolution/agents/openai.yaml new file mode 100644 index 0000000..bb4289a --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-entity-resolution/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD entity resolution + short_description: Resolve CRM records and duplicates +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-entity-resolution/references/entity-resolution.md b/plugins/shd-mcp-plugin/skills/shd-entity-resolution/references/entity-resolution.md new file mode 100644 index 0000000..0577a30 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-entity-resolution/references/entity-resolution.md @@ -0,0 +1,8 @@ +# Entity-resolution report + +Report each candidate as: + +`candidate → identifiers → matching fields → conflicting fields → related records → confidence → next action` + +Confidence is an explanation of returned evidence, not a server permission or +an authorization to merge records. diff --git a/plugins/shd-mcp-plugin/skills/shd-estimate-management/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-estimate-management/SKILL.md new file mode 100644 index 0000000..a73f299 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-estimate-management/SKILL.md @@ -0,0 +1,28 @@ +--- +name: shd-estimate-management +description: Use when the user asks to inspect, compare, validate, create or update SHD estimates, proposals, terms or document contracts. +--- + +# SHD estimates and proposal documents + +## Read path + +1. Resolve the project, proposal, terms document or contract before acting. +2. Use `shd_list_proposals` and `shd_get_proposal` for proposal registers and + details. +3. Use `shd_list_contracts`, `shd_get_contract`, + `shd_get_pdf_scheme_editor_completeness` and `shd_validate_pdf_scheme_editor_document` for + terms and completeness checks. +4. Inspect template capabilities before proposing a template-driven change. +5. Report line items, versions, completeness, approvals and publication state + only when those fields are returned by the server. + +## Mutations + +- Creating, replacing items, requesting approval, publishing, restoring or + deleting a proposal/document requires an exact explicit request. +- Read the latest version and use the tool's concurrency and idempotency fields + before a write. +- Never publish or request approval as a side effect of an audit or comparison. + +See `references/estimate-safety.md` before a proposal or terms mutation. diff --git a/plugins/shd-mcp-plugin/skills/shd-estimate-management/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-estimate-management/agents/openai.yaml new file mode 100644 index 0000000..98ddf61 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-estimate-management/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD estimates and proposals + short_description: Estimate and document workflows +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-estimate-management/references/estimate-safety.md b/plugins/shd-mcp-plugin/skills/shd-estimate-management/references/estimate-safety.md new file mode 100644 index 0000000..41e0e8b --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-estimate-management/references/estimate-safety.md @@ -0,0 +1,7 @@ +# Estimate and proposal safety + +- Draft, validated, approved and published are separate states. +- A calculated total is not an approval and a generated PDF is not a + publication. +- Compare revisions before restoring or replacing a document. +- Keep the project, counterparty and version identifiers in the final report. diff --git a/plugins/shd-mcp-plugin/skills/shd-event-positions/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-event-positions/SKILL.md new file mode 100644 index 0000000..98093f3 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-event-positions/SKILL.md @@ -0,0 +1,25 @@ +--- +name: shd-event-positions +description: Use when the user asks about SHD events, booking availability, scheduling slots, event types or booking positions. +--- + +# SHD events and scheduling + +## Read path + +1. Start with `shd_get_scheduling_overview` when the scope is broad. +2. Use `shd_list_scheduling_event_types`, + `shd_list_scheduling_availability`, `shd_list_scheduling_slots` and + `shd_list_scheduling_bookings` for the exact requested view. +3. Preserve the returned timezone, slot state, booking state and integration + status. Do not turn an available slot into a confirmed booking. + +## Mutations + +`shd_hold_scheduling_slot`, `shd_create_scheduling_booking`, +`shd_confirm_scheduling_booking`, `shd_reschedule_scheduling_booking` and +`shd_cancel_scheduling_booking` are explicit writes. Read the current slot or +booking first, pass the current concurrency/idempotency values accepted by the +schema and report the final server state. + +See `references/scheduling-safety.md` for the state boundaries. diff --git a/plugins/shd-mcp-plugin/skills/shd-event-positions/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-event-positions/agents/openai.yaml new file mode 100644 index 0000000..c2d0cf6 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-event-positions/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD events and scheduling + short_description: Slots, bookings and event positions +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-event-positions/references/scheduling-safety.md b/plugins/shd-mcp-plugin/skills/shd-event-positions/references/scheduling-safety.md new file mode 100644 index 0000000..1016945 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-event-positions/references/scheduling-safety.md @@ -0,0 +1,7 @@ +# Scheduling state boundaries + +- Availability is not a hold. +- A hold is not a booking. +- A booking is not confirmed until SHD returns confirmation. +- A cancellation or reschedule must identify the exact booking and preserve + the server's conflict response. diff --git a/plugins/shd-mcp-plugin/skills/shd-finance-crm/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-finance-crm/SKILL.md new file mode 100644 index 0000000..bb0c149 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-finance-crm/SKILL.md @@ -0,0 +1,41 @@ +--- +name: shd-finance-crm +description: Use when the user asks for finance or CRM information, document/payment checks, customer records, deals, relationships, or a project business overview in SHD. +--- + +# SHD Finance and CRM + +For a high-level project overview, prefer `shd_get_project_context` and request +only the `finance` or `summary` sections needed. For a detailed request use +the module tools below. + +## Finance + +- Find documents with `shd_list_finance_documents`. +- Read one with `shd_get_finance_document`. +- Read payments with `shd_list_finance_payments` or + `shd_get_finance_payment`. +- Resolve allowed references with `shd_list_finance_refs`. +- In a UI-capable MCP host, pass normalized finance records to + `shd_render_finance_widget`; keep the structured list as the fallback. + +When auditing, compare document type, project/counterparty links, line items, +currency, totals, due dates, payment state and returned validation. Never infer +approval, payment or publication from a document's title or amount. + +## CRM + +- Find entities with `shd_list_crm` and read one with `shd_get_crm_entity`. +- In a UI-capable MCP host, pass normalized CRM records to + `shd_render_crm_widget`; keep the structured list as the fallback. +- Resolve relationships with `shd_list_crm_entity_links` and inspect available + pipelines/stages before a deal change. +- Use `shd_save_crm_entity` only for an explicitly requested create/update after + duplicate and relationship checks. + +Do not approve, publish, delete, merge, move a deal stage, create a payment or +change ownership unless the user explicitly requests that exact operation. +Report IDs, resolved links, changed fields and server validation separately from +recommendations. + +See `references/finance-crm-audit.md` for the finance/CRM audit boundary. diff --git a/plugins/shd-mcp-plugin/skills/shd-finance-crm/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-finance-crm/agents/openai.yaml new file mode 100644 index 0000000..e41e693 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-finance-crm/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD finance and CRM + short_description: Finance and customer workflows +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-finance-crm/references/finance-crm-audit.md b/plugins/shd-mcp-plugin/skills/shd-finance-crm/references/finance-crm-audit.md new file mode 100644 index 0000000..bfeaef8 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-finance-crm/references/finance-crm-audit.md @@ -0,0 +1,6 @@ +# Finance and CRM audit boundary + +Finance documents, payments, allocations, CRM entities, links and stages are +different objects. Report their identifiers and returned state separately. +Use the dedicated `shd-financial-account-audit` workflow for reconciliation, +balances, settlements or audit-log questions. diff --git a/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/SKILL.md new file mode 100644 index 0000000..68ed30e --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/SKILL.md @@ -0,0 +1,30 @@ +--- +name: shd-financial-account-audit +description: Use when the user asks to audit SHD finance accounts, balances, payments, documents, allocations, settlements or finance history. +--- + +# SHD financial audit + +## Read path + +1. Resolve project, legal entity, counterparty and period before reading. +2. Use `shd_list_finance_refs` for allowed categories, accounts and reference + values when the request depends on them. +3. Read documents and payments with `shd_list_finance_documents`, + `shd_get_finance_document`, `shd_list_finance_payments` and + `shd_get_finance_payment`. +4. Use `shd_get_finance_account_balance` and + `shd_list_finance_audit_log` when balances or history are requested. +5. Reconcile only records returned for the same scope and period. Mark missing + links, currency differences and permission gaps explicitly. + +## Settlement safety + +- Allocation, removal of allocation, payment creation, approval, deletion and + document replacement are writes; do not perform them during an audit. +- A document total is not proof of payment, and a payment is not proof of + allocation. +- For an explicitly requested write, read the latest payment/document and use + the server's idempotency and concurrency contract, then read back the result. + +See `references/finance-audit.md` for the reconciliation format. diff --git a/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/agents/openai.yaml new file mode 100644 index 0000000..342ddb5 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD financial audit + short_description: Balances, payments and settlements +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/references/finance-audit.md b/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/references/finance-audit.md new file mode 100644 index 0000000..a2546a9 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-financial-account-audit/references/finance-audit.md @@ -0,0 +1,8 @@ +# Finance audit format + +Report: + +`scope → period → currency → source record → observed amount/state → linked record → discrepancy → priority` + +Do not hide an unresolved counterparty, currency conversion, allocation or +permission boundary behind an aggregate number. diff --git a/plugins/shd-mcp-plugin/skills/shd-gitea/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-gitea/SKILL.md new file mode 100644 index 0000000..3657c7e --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-gitea/SKILL.md @@ -0,0 +1,24 @@ +--- +name: shd-gitea +description: Use when the user asks to inspect or manage SHD-linked Gitea issues, labels, comments or issue attachments. +--- + +# SHD Gitea + +See `references/gitea-contract.md` for repository and issue safety. + +## Read and resolve + +- Resolve the canonical repository and issue identifiers returned by SHD before + acting. +- Read the issue, labels and current state before creating a comment or + changing labels. +- Keep Gitea state separate from SHD project/task state; do not claim that a + label or issue update changed the SHD record unless the server says so. + +## Mutations + +Creating an issue, commenting, changing labels or uploading an attachment +requires an explicit request. Report repository, issue number, changed labels, +comment/attachment result and server validation. Never put credentials or +private attachment URLs in the response or repository. diff --git a/plugins/shd-mcp-plugin/skills/shd-gitea/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-gitea/agents/openai.yaml new file mode 100644 index 0000000..b3f6885 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-gitea/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD Gitea" + short_description: "Review and update linked Gitea issues" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-gitea/references/gitea-contract.md b/plugins/shd-mcp-plugin/skills/shd-gitea/references/gitea-contract.md new file mode 100644 index 0000000..d7d798f --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-gitea/references/gitea-contract.md @@ -0,0 +1,11 @@ +# Gitea contract + +Use `shd_list_gitea_issues` and `shd_list_gitea_labels` for discovery. Writes +are `shd_create_gitea_issue`, `shd_comment_gitea_issue`, +`shd_update_gitea_issue_labels` and `shd_upload_gitea_issue_attachment`. + +Resolve the repository and issue from returned server data, preserve the +current issue state before a label update, and use an idempotency key when the +tool schema offers one. A comment or attachment is an external side effect; +require exact user intent and report readback. Do not assume Gitea permissions +from SHD project permissions alone. diff --git a/plugins/shd-mcp-plugin/skills/shd-inventory/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-inventory/SKILL.md new file mode 100644 index 0000000..ef3a3f8 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-inventory/SKILL.md @@ -0,0 +1,26 @@ +--- +name: shd-inventory +description: Use when the user asks to inspect or change SHD inventory items, assets, stock, procurement, reservations, receipts or stocktakes. +--- + +# SHD inventory + +See `references/inventory-safety.md` before any stock, asset or procurement +mutation. + +## Inspect + +- Resolve the project, location, item, asset or procurement identity from + server results. +- Read current stock, lots, reservations, movements, receipts, maintenance, + assignments and audit data relevant to the question. +- Keep available, reserved, received, written-off and counted quantities + distinct. Do not calculate an authoritative balance from partial pages. + +## Mutations + +Reserve, release, receive, write off or close a stocktake only on an explicit +request. Creating/updating resources and generic inventory actions require the +same read-before-write and conflict/idempotency handling. Report quantities, +location, source document, server validation and readback; never silently +repair a discrepancy by changing stock. diff --git a/plugins/shd-mcp-plugin/skills/shd-inventory/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-inventory/agents/openai.yaml new file mode 100644 index 0000000..6c5e874 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-inventory/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD inventory" + short_description: "Audit stock, assets and procurement" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-inventory/references/inventory-safety.md b/plugins/shd-mcp-plugin/skills/shd-inventory/references/inventory-safety.md new file mode 100644 index 0000000..b9ab671 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-inventory/references/inventory-safety.md @@ -0,0 +1,14 @@ +# Inventory safety contract + +Read tools cover items/assets, procurement, locations, movements, lots, +reservations, receipts, stocktakes, labels, assignments, maintenance and the +inventory audit log. Use the narrowest `shd_list_inventory_*` or +`shd_get_inventory_*` tool and preserve pagination. + +Side-effecting operations include `shd_reserve_inventory_stock`, +`shd_release_inventory_stock`, `shd_receive_inventory_stock`, +`shd_write_off_inventory_stock`, `shd_close_inventory_stocktake`, +`shd_create_inventory_resource`, `shd_update_inventory_resource` and +`shd_inventory_action`. Resolve the exact target and latest quantity first; +require explicit confirmation for irreversible or bulk operations and verify +the resulting movement/audit record. diff --git a/plugins/shd-mcp-plugin/skills/shd-notifications/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-notifications/SKILL.md new file mode 100644 index 0000000..2122ae9 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-notifications/SKILL.md @@ -0,0 +1,25 @@ +--- +name: shd-notifications +description: Use when the user asks to inspect SHD notifications, notification modules, read state or preferences, or mark notifications read. +--- + +# SHD notifications + +See `references/notification-contract.md` for read-state and preference +boundaries. + +## Read + +- Use `shd_list_notifications` with bounded filters and preserve notification + IDs and timestamps. +- Use module, read-state and preference tools only for the authenticated user + or the explicitly authorized scope. +- Distinguish unread, read, muted and unavailable data; do not invent a missing + notification source. + +## State changes + +Marking one or more notifications read or changing preferences changes user +state. Do it only when explicitly requested, use the returned IDs, and report +the number and final state. Never mark a whole feed read merely because it was +displayed or summarized. diff --git a/plugins/shd-mcp-plugin/skills/shd-notifications/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-notifications/agents/openai.yaml new file mode 100644 index 0000000..5399f95 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-notifications/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD notifications" + short_description: "Review alerts and notification state" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-notifications/references/notification-contract.md b/plugins/shd-mcp-plugin/skills/shd-notifications/references/notification-contract.md new file mode 100644 index 0000000..8f73e2f --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-notifications/references/notification-contract.md @@ -0,0 +1,11 @@ +# Notification contract + +Read with `shd_list_notifications`, `shd_list_notification_modules`, +`shd_get_notification_read_state`, `shd_get_notification_preferences` and, +for document-specific items, `shd_list_document_notifications`. + +State-changing tools are `shd_update_notification_preferences`, +`shd_mark_notification_read`, `shd_mark_all_notifications_read` and +`shd_mark_notifications_read`. Require explicit scope for bulk marking, +prefer stable notification IDs, and report server readback. A list response is +not authorization to alter every item in it. diff --git a/plugins/shd-mcp-plugin/skills/shd-organizations-acl/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-organizations-acl/SKILL.md new file mode 100644 index 0000000..f6504a9 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-organizations-acl/SKILL.md @@ -0,0 +1,26 @@ +--- +name: shd-organizations-acl +description: Use when the user asks to inspect or change SHD organizations, members, invitations, roles, access audits or two-factor policies. +--- + +# SHD organizations and ACL + +See `references/organization-acl.md` for identity resolution and mutation +boundaries. + +## Inspect + +- Resolve the canonical organization ID; never use a guessed name as a write + target. +- Read members, invitations, audit entries and the current two-factor policy + before explaining access. +- Report organization, user, role, invitation state and policy as separate + facts. A denied or incomplete list is not proof that a member is absent. + +## Change access + +Inviting, adding, updating or removing a member, changing a two-factor policy, +or creating, archiving or restoring an organization requires explicit intent. +Show the exact organization and user scope before the mutation. Preserve server +authorization, conflict checks and audit readback; never broaden access to +make a failed request succeed. diff --git a/plugins/shd-mcp-plugin/skills/shd-organizations-acl/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-organizations-acl/agents/openai.yaml new file mode 100644 index 0000000..77aa4e1 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-organizations-acl/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD organizations and ACL" + short_description: "Audit members, roles and access policies" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-organizations-acl/references/organization-acl.md b/plugins/shd-mcp-plugin/skills/shd-organizations-acl/references/organization-acl.md new file mode 100644 index 0000000..4809d07 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-organizations-acl/references/organization-acl.md @@ -0,0 +1,13 @@ +# Organization and ACL contract + +Read with `shd_list_organization_members`, `shd_list_organization_invitations`, +`shd_list_organization_audit` and +`shd_get_organization_two_factor_policy`. Resolve users and organizations from +server-returned IDs. + +Mutations include invitation operations, member add/update/remove, +organization create/update/archive/restore and +`shd_update_organization_two_factor_policy`. Every mutation needs explicit +authorization, the latest relevant state, and final server/audit readback when +available. Do not disclose invitation secrets or use an email/name match as a +substitute for a returned user identity. diff --git a/plugins/shd-mcp-plugin/skills/shd-project-files/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-project-files/SKILL.md new file mode 100644 index 0000000..d4ffe30 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-project-files/SKILL.md @@ -0,0 +1,30 @@ +--- +name: shd-project-files +description: Use when the user asks to inspect project files, obtain a file link, share or zip a file, rename or move a path, or delete project files. +--- + +# SHD project files + +## Resolve paths + +1. Call `shd_list_files` with the exact `project_code` and, when supplied, the + path. +2. Resolve the exact file or directory path and distinguish files from + directories before any action. +3. Use `shd_get_file_link` only when the user asks for a temporary file link or + download. + +## Actions + +`shd_create_file_action` supports the explicit actions `share-link`, `zip-link`, +`rename`, `move` and `delete`. Use the exact action matching the request. For +rename, move and delete, confirm the source/destination and replacement scope +from the current listing first. Mutating actions require a stable +`idempotency_key`; delete additionally requires `confirm: true` and the server +version/confirmation fields accepted by its schema. + +Do not expose bearer tokens or raw secret material in prose or logs. Return a +temporary URL only when the user explicitly asks for it. Do not overwrite, +publish, share or delete a path merely because the user asked to inspect it. + +See `references/file-actions.md` for the file-action boundary. diff --git a/plugins/shd-mcp-plugin/skills/shd-project-files/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-project-files/agents/openai.yaml new file mode 100644 index 0000000..06f6af2 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-project-files/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD project files + short_description: Inspect and safely manage files +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-project-files/references/file-actions.md b/plugins/shd-mcp-plugin/skills/shd-project-files/references/file-actions.md new file mode 100644 index 0000000..c335b47 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-project-files/references/file-actions.md @@ -0,0 +1,5 @@ +# File action boundary + +Resolve the exact project path first. Inspection, temporary link retrieval, +share/zip, rename/move and delete are separate intents. Mutating actions need +the server's current version, confirmation and idempotency fields. diff --git a/plugins/shd-mcp-plugin/skills/shd-project-status/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-project-status/SKILL.md new file mode 100644 index 0000000..4abdfa9 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-project-status/SKILL.md @@ -0,0 +1,41 @@ +--- +name: shd-project-status +description: Use when the user asks for a status report, operational summary, risks, blockers, next actions, or a cross-module overview of one SHD project. +--- + +# SHD project status + +## Resolve the project + +- If the user supplied an exact project code, use it directly. +- If the user supplied only a name, call `shd_list_projects` with `q` once, + choose only an unambiguous exact match, and ask for the project code when + multiple projects match. + +## Read the context + +For a full summary call `shd_get_project_context` with the resolved +`project_code`, `include` set to the requested sections, and +`limit_per_section: 100`. The available sections are: + +`summary`, `tasks`, `discussions`, `documents`, `finance`, `terms`, `agents`. + +Request only the sections needed for a narrow question. Do not pretend that a +section is empty when the server reported it unavailable or the user lacks +access. + +## Report + +Separate the result into: + +- confirmed project state; +- current deadlines or status values returned by SHD; +- blockers and risks supported by retrieved records; +- next actions, clearly marked as recommendations; +- unavailable or unresolved sections. + +Do not convert a task deadline into a project deadline, infer a completion date +from activity, or claim deployment success from a status field. This workflow is +read-only unless the user separately requests a specific change. + +See `references/context-sections.md` for section selection and unavailable data. diff --git a/plugins/shd-mcp-plugin/skills/shd-project-status/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-project-status/agents/openai.yaml new file mode 100644 index 0000000..0305dca --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-project-status/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD project status + short_description: Project summaries and risks +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-project-status/references/context-sections.md b/plugins/shd-mcp-plugin/skills/shd-project-status/references/context-sections.md new file mode 100644 index 0000000..116ac4c --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-project-status/references/context-sections.md @@ -0,0 +1,5 @@ +# Project context sections + +Use only the sections requested by the user. Available sections currently +include `summary`, `tasks`, `discussions`, `documents`, `finance`, `terms` and +`agents`; unavailable or denied sections must remain visible in the report. diff --git a/plugins/shd-mcp-plugin/skills/shd-realtime-activity/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-realtime-activity/SKILL.md new file mode 100644 index 0000000..b50d6b8 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-realtime-activity/SKILL.md @@ -0,0 +1,23 @@ +--- +name: shd-realtime-activity +description: Use when the user asks to inspect SHD realtime events, start or terminate a realtime session, or review operational activity and audit history. +--- + +# SHD realtime and activity + +See `references/realtime-contract.md` for session lifecycle and event handling. + +## Read and observe + +- Resolve the authorized project/module scope before reading events or audit + history. +- Use bounded event reads and preserve event IDs, cursors and timestamps. +- Distinguish historical audit records from a live realtime session; do not + claim that an event stream is complete when it was truncated or disconnected. + +## Session lifecycle + +Starting or terminating a realtime session is a stateful operation. Require an +explicit request, retain the returned session ID and scope, avoid duplicate +starts on retry, and terminate only the requested session. Report connection +or terminal state rather than fabricating an event. diff --git a/plugins/shd-mcp-plugin/skills/shd-realtime-activity/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-realtime-activity/agents/openai.yaml new file mode 100644 index 0000000..5368881 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-realtime-activity/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD realtime and activity" + short_description: "Inspect events and manage live sessions" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-realtime-activity/references/realtime-contract.md b/plugins/shd-mcp-plugin/skills/shd-realtime-activity/references/realtime-contract.md new file mode 100644 index 0000000..dc3b920 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-realtime-activity/references/realtime-contract.md @@ -0,0 +1,11 @@ +# Realtime and activity contract + +Use `shd_start_realtime_session`, `shd_get_realtime_events` and +`shd_terminate_realtime_session` for the session lifecycle. Use the relevant +read-only audit/activity tool for historical records, such as +`shd_get_audit_history`, when the request is about what already happened. + +Keep session IDs, scopes, cursors and last-seen timestamps separate. A retry +must not create a second session unless the server explicitly supports an +idempotency key. Termination requires the exact returned session ID and should +be confirmed by terminal readback. diff --git a/plugins/shd-mcp-plugin/skills/shd-routing/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-routing/SKILL.md new file mode 100644 index 0000000..34f3a32 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-routing/SKILL.md @@ -0,0 +1,66 @@ +--- +name: shd-routing +description: Use for any request that asks SHD for data, a project operation, a module workflow, or a change to SHD records. Route the request to the narrowest existing SHD MCP tool and preserve server-side permissions. +--- + +# SHD routing and safety + +SHD MCP is the source of truth for live data, tool schemas, permissions, +organization/project scope, conflicts, idempotency, audit and persisted +readback. Do not invent fields, statuses, identifiers, routes or capabilities. + +## Route the request + +- Project list, project status, deadlines or project context: use the Projects + Core tools and the `shd-active-projects` or `shd-project-status` workflow. +- AirBase schema, records, links or data quality: use AirBase tools and + the `shd-airbase-audit` workflow. +- Tasks, task projects, task status or task dates: use Tasks tools and the + `shd-task-workflow` workflow. +- Project files and paths: use Files tools and the `shd-project-files` workflow. +- Discussions, channels, topics, messages or attachments: use Discussions tools and the `shd-discussions` workflow. +- Documents, templates, revisions, PDFs or publication links: use Documents tools and the `shd-documents` workflow. +- Organizations, members, invitations, roles or two-factor policy: use Organizations tools and the `shd-organizations-acl` workflow. +- Notifications, preferences or read state: use Notifications tools and the `shd-notifications` workflow. +- Inventory, stock, assets, procurement or stocktakes: use Inventory tools and the `shd-inventory` workflow. +- Agents, controllers, diagnostics, tunnels or network access: use Agents tools and the `shd-agents-operations` workflow. +- Controller devices, current states, presence/history or sensor statistics: use the controller-integrations source tools; in a UI-capable host call the matching controller render tool after the data tool and before the final response (`shd_render_controller_devices_widget`, `shd_render_controller_live_state_widget`, `shd_render_controller_history_widget` or `shd_render_controller_statistics_widget`). +- Status-page monitors, history, groups or incidents: use Status Page tools and the `shd-status-page` workflow. +- Realtime sessions, event cursors or historical activity: use Realtime tools and the `shd-realtime-activity` workflow. +- Terms documents, revisions, completeness or contract discussions: use Terms tools and the `shd-terms-contracts` workflow. +- Linked Gitea issues, labels, comments or attachments: use Gitea tools and the `shd-gitea` workflow. +- Finance or CRM records: use the specific module tools and the + `shd-finance-crm` workflow; use project context for a high-level overview. +- Finance reconciliation, balances, settlements or audit history: + `shd-financial-account-audit`. +- Proposals, estimates, quotes or estimate-document contracts: + `shd-estimate-management`. +- Scheduling, event types, slots or bookings: `shd-event-positions`. +- Wiki, notes, pages, revisions or note databases: `shd-wiki-management`. +- Entity matching or possible duplicates: `shd-entity-resolution`. +- Analytics, comparisons, trends or reports: `shd-analytics`. +- A multi-record change: use `shd-safe-bulk-change` before any mutation. + +Use `shd_capabilities` only when the available module or permission boundary is +unclear. If a tool or module is unavailable, report that fact instead of +falling back to an undocumented endpoint. + +## Read-first rule + +For every write, first read the target and its relevant schema or metadata. +Only perform a mutation when the user explicitly requested that mutation. A +request to inspect, summarize, compare, audit or suggest is not permission to +write. + +Preserve the exact server result. Separate confirmed values from inference, +and include affected identifiers, conflicts, validation errors and final +readback when the server returns them. + +## Scope and secrets + +Never ask for or expose access tokens, passwords, cookies, private keys, raw +public-link bearer tokens or integration secrets. Never use a user-provided +project name as an identifier when SHD has not resolved it to a returned +project code or ID. + +See `references/module-map.md` for the current high-level routing map. diff --git a/plugins/shd-mcp-plugin/skills/shd-routing/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-routing/agents/openai.yaml new file mode 100644 index 0000000..d3cfaec --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-routing/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD routing and safety + short_description: Route requests to safe MCP tools +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-routing/references/module-map.md b/plugins/shd-mcp-plugin/skills/shd-routing/references/module-map.md new file mode 100644 index 0000000..311e07a --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-routing/references/module-map.md @@ -0,0 +1,25 @@ +# SHD module map + +- Projects and project context → `shd_list_projects`, `shd_get_project`, + `shd_get_project_context`. +- ProjectBase → `shd_project_db_*`, `shd_project_base_*`. +- Tasks → `shd_list_tasks`, `shd_get_task`, task mutation tools. +- Files → `shd_list_files`, file action tools. +- Discussions → channel, topic, message search and attachment tools. +- Documents → document, template, revision, PDF and publication-link tools. +- Organizations and ACL → members, invitations, audit and two-factor policy + tools. +- Notifications → notification, preference and read-state tools. +- Inventory → stock, asset, procurement, reservation and stocktake tools. +- Agents → controller/user-agent status, diagnostics, tunnels, endpoints and + network-operation tools. +- Status Page → monitor, history, group and incident tools. +- Realtime and activity → session, event-cursor and audit/activity tools. +- Terms and contracts → Terms documents, completeness, revisions and point + discussion tools. +- Gitea → linked issue, label, comment and attachment tools. +- Finance and CRM → module-specific finance/CRM tools. +- Proposals, terms and document contracts → proposal/terms/document tools. +- Scheduling → `shd_get_scheduling_overview` and scheduling tools. +- Wiki and notes → note-space, page, revision and database tools. +- Unknown or permission-sensitive capability → `shd_capabilities` first. diff --git a/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/SKILL.md new file mode 100644 index 0000000..d911844 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/SKILL.md @@ -0,0 +1,30 @@ +--- +name: shd-safe-bulk-change +description: Use before any SHD bulk create, update, import, archive, delete, or other multi-record mutation, and whenever the user asks to plan a mass change. +--- + +# Safe SHD bulk changes + +## Plan first + +1. Confirm the target module, organization/project scope, exact match rule and + intended side effect. +2. Read current schemas and records. Estimate affected, skipped, invalid and + ambiguous rows. +3. Prefer a server validation or dry-run operation. For AirBase use + `shd_airbase_validate_records`, then + `shd_airbase_bulk_create_records` or + `shd_airbase_bulk_upsert_records` with `dry_run: true` when applicable. +4. Present the plan and stop unless the user explicitly authorized execution. + +## Execute safely + +Use stable idempotency keys, expected versions and exact match fields accepted +by the selected tool. Do not silently widen a project, organization, table or +record filter. Do not convert a failed or conflicting row into a new record. + +After execution, report created, updated, skipped, rejected and conflicted +rows, then use the server's persisted readback when available. Destructive +operations require explicit confirmation and remain subject to server ACL. + +See `references/write-preflight.md` for the required preflight record. diff --git a/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/agents/openai.yaml new file mode 100644 index 0000000..6a969e2 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD safe bulk changes + short_description: Plan and validate bulk mutations +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/references/write-preflight.md b/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/references/write-preflight.md new file mode 100644 index 0000000..9dd969d --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-safe-bulk-change/references/write-preflight.md @@ -0,0 +1,7 @@ +# Write preflight + +Before a multi-record write, record: + +`module → scope → match rule → current count → affected count → skipped/ambiguous count → validation result → idempotency key` + +Stop on an ambiguous match, validation failure, conflict or missing readback. diff --git a/plugins/shd-mcp-plugin/skills/shd-status-page/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-status-page/SKILL.md new file mode 100644 index 0000000..cb50a3d --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-status-page/SKILL.md @@ -0,0 +1,22 @@ +--- +name: shd-status-page +description: Use when the user asks to inspect SHD status-page monitors, history, groups or incidents, or to change public status monitoring. +--- + +# SHD status page + +See `references/status-page-safety.md` for monitor and incident boundaries. + +## Read + +- Resolve the status-page scope and monitor IDs before reporting health. +- Read current monitors and history; distinguish current state from historical + observations and from an incident declaration. +- Report unavailable, stale or unauthorized monitors explicitly. + +## Mutations + +Publishing an incident, creating/updating/deleting a group or monitor changes +an externally visible monitoring surface. Require an explicit request, exact +scope and current readback. Do not publish an incident to test the integration +and do not infer an outage from one missing response. diff --git a/plugins/shd-mcp-plugin/skills/shd-status-page/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-status-page/agents/openai.yaml new file mode 100644 index 0000000..6a56e6d --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-status-page/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD status page" + short_description: "Inspect monitors and publish incidents safely" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-status-page/references/status-page-safety.md b/plugins/shd-mcp-plugin/skills/shd-status-page/references/status-page-safety.md new file mode 100644 index 0000000..e9b8482 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-status-page/references/status-page-safety.md @@ -0,0 +1,14 @@ +# Status-page safety contract + +Read with `shd_get_status_page_monitors` and +`shd_get_status_page_monitor_history`. Mutations are +`shd_publish_status_page_incident`, `shd_create_status_page_group`, +`shd_update_status_page_group`, `shd_delete_status_page_group`, +`shd_create_status_page_monitor`, `shd_update_status_page_monitor` and +`shd_delete_status_page_monitor`. + +Before a mutation, resolve the canonical monitor/group IDs, read the latest +state and show the public scope, message, duration or target being changed. +Report server validation and final status. Deletion and incident publication +are consequential actions and must never be inferred from a question asking for +diagnosis. diff --git a/plugins/shd-mcp-plugin/skills/shd-task-workflow/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-task-workflow/SKILL.md new file mode 100644 index 0000000..b641250 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-task-workflow/SKILL.md @@ -0,0 +1,44 @@ +--- +name: shd-task-workflow +description: Use when the user asks to find, inspect, create, update, reschedule, archive, or delete SHD tasks or task-project data. +--- + +# SHD task workflow + +See `references/task-contract.md` for identifier resolution, conflict handling +and mutation readback requirements. + +## Read and resolve + +- Use `shd_list_task_projects` when the request concerns a task project or its + status. +- Use `shd_list_tasks` with `project_code`, `project_id`, `q`, `archived`, + `limit` or `offset` to find tasks. +- Use `shd_get_task` for one resolved task and + `shd_get_task_project_meta` for statuses, tags, sprints, views and custom + fields. +- Read comments or attachments only when they are relevant to the request. +- After `shd_list_tasks` returns, always pass normalized task records to + `shd_render_tasks_widget` before the final response when the host supports + MCP Apps UI. Use the structured task list or plain text only when the render + tool is unavailable or returns an error. + +Do not create a task from an ambiguous project name or duplicate a task that +already matches the request. + +## Mutations + +Only mutate when the user explicitly asks for the exact operation: + +- create with `shd_create_task`; +- update fields with `shd_update_task`; +- change status with `shd_update_task_status`; +- archive with `shd_archive_task`; +- delete with `shd_delete_task` and the server's explicit confirmation contract. + +Before updating, pass the latest `updated_at` when the tool accepts it. Use an +idempotency key when the tool schema accepts one. If the server reports a +conflict, stop and show the current state; do not overwrite it by guessing. + +Report the task ID, changed fields, server validation, conflict state and final +status. Never treat a suggested change as authorization to execute it. diff --git a/plugins/shd-mcp-plugin/skills/shd-task-workflow/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-task-workflow/agents/openai.yaml new file mode 100644 index 0000000..9c630ca --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-task-workflow/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD task workflow + short_description: Read and safely update tasks +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-task-workflow/references/task-contract.md b/plugins/shd-mcp-plugin/skills/shd-task-workflow/references/task-contract.md new file mode 100644 index 0000000..2442a29 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-task-workflow/references/task-contract.md @@ -0,0 +1,20 @@ +# Task workflow contract + +Resolve task context before acting: + +- use a canonical task-project identifier when the server returns one; +- search by explicit project, task ID or bounded query rather than guessing + from a display name; +- read task-project metadata before changing status, sprint, tags or custom + fields; +- retain `updated_at`, version or equivalent conflict data from the latest + read whenever the mutation schema supports it. + +For a write, report the exact operation, task ID, changed fields, idempotency +key when used, server validation, conflict result and readback state. A +conflict or ambiguous match is a stop condition: show the current state and +ask for a narrower target instead of overwriting it. + +Delete and archive operations require their server-side confirmation contract. +Never treat a proposed task change or a natural-language suggestion as write +authorization. diff --git a/plugins/shd-mcp-plugin/skills/shd-terms-contracts/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-terms-contracts/SKILL.md new file mode 100644 index 0000000..0638c1e --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-terms-contracts/SKILL.md @@ -0,0 +1,24 @@ +--- +name: shd-terms-contracts +description: Use when the user asks to inspect, compare, discuss, revise or restore SHD Terms documents and contract points. +--- + +# SHD Terms and contracts + +See `references/terms-contract.md` for revision, completeness and discussion +rules. + +## Inspect + +- Resolve the canonical Terms document and project context. +- Read completeness, current revision and relevant history before comparing or + summarizing contractual content. +- Keep document text, comments, point discussions and completeness findings + distinct; identify unavailable sections instead of filling them in. + +## Changes + +Restoring a revision or adding a contract comment/discussion point requires an +explicit request and the exact document/point scope. Preserve the latest +revision or conflict token where available, report the created comment or +restored revision, and never present a draft as an approved contract. diff --git a/plugins/shd-mcp-plugin/skills/shd-terms-contracts/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-terms-contracts/agents/openai.yaml new file mode 100644 index 0000000..8211180 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-terms-contracts/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "SHD Terms and contracts" + short_description: "Review revisions and contract completeness" +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-terms-contracts/references/terms-contract.md b/plugins/shd-mcp-plugin/skills/shd-terms-contracts/references/terms-contract.md new file mode 100644 index 0000000..8c61162 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-terms-contracts/references/terms-contract.md @@ -0,0 +1,11 @@ +# Terms and contract contract + +Read with `shd_list_terms_documents`, `shd_get_terms_document`, +`shd_get_terms_document_completeness`, `shd_list_terms_document_revisions` and +`shd_get_terms_document_revision`. Use `shd_terms_description_comment` and +`shd_terms_point_discussion` only for explicit comment/discussion requests. + +`shd_restore_terms_document_revision` is a mutation: resolve the document and +revision, read current state, require confirmation and report readback. Never +restore merely because an older revision looks cleaner, and never confuse +completeness analysis with legal approval. diff --git a/plugins/shd-mcp-plugin/skills/shd-wiki-management/SKILL.md b/plugins/shd-mcp-plugin/skills/shd-wiki-management/SKILL.md new file mode 100644 index 0000000..bbebed1 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-wiki-management/SKILL.md @@ -0,0 +1,26 @@ +--- +name: shd-wiki-management +description: Use when the user asks to search, read, compare, create or update SHD Wiki, notes, pages, collections, databases, revisions or attachments. +--- + +# SHD Wiki and notes + +## Read path + +1. Resolve the note space and page tree with `shd_list_note_spaces`, + `shd_get_note_space_tree` and `shd_list_note_pages`. +2. Search with `shd_search_note_blocks`, then read the selected page with + `shd_read_note_document` or `shd_get_note_page`. +3. Use revision and operation tools to compare versions before proposing a + replacement or restore. +4. Keep attachments, backlinks, comments, database records and page content as + separate objects in the report. + +## Mutations + +Creating, replacing, moving, archiving, deleting, importing, granting members +or changing review state requires an exact explicit request. Read the current +object first, preserve the latest revision/version and use the dedicated +operation contract. Read back the persisted result after a successful write. + +See `references/wiki-safety.md` for content and revision boundaries. diff --git a/plugins/shd-mcp-plugin/skills/shd-wiki-management/agents/openai.yaml b/plugins/shd-mcp-plugin/skills/shd-wiki-management/agents/openai.yaml new file mode 100644 index 0000000..08872fa --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-wiki-management/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: SHD Wiki and notes + short_description: Pages, revisions and note databases +policy: + allow_implicit_invocation: false diff --git a/plugins/shd-mcp-plugin/skills/shd-wiki-management/references/wiki-safety.md b/plugins/shd-mcp-plugin/skills/shd-wiki-management/references/wiki-safety.md new file mode 100644 index 0000000..e210295 --- /dev/null +++ b/plugins/shd-mcp-plugin/skills/shd-wiki-management/references/wiki-safety.md @@ -0,0 +1,7 @@ +# Wiki safety + +- A search result is not the full page. +- A revision diff is not a write proposal. +- A replacement must preserve the selected page and revision identifiers. +- Deleting a page, collection, database or attachment requires an exact target + and the server's explicit confirmation fields. diff --git a/plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json b/plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json new file mode 100644 index 0000000..c7ffeb4 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json @@ -0,0 +1,1099 @@ +{ + "version": 1, + "engine": "shd-universal-v1", + "copy": { + "discussions": { + "ru": [ + "Обсуждения", + "Темы обсуждений с поиском и просмотром сообщений.", + "Тема", + "Обновлено", + "Сообщение" + ], + "en": [ + "Discussions", + "Discussion topics with search and message details.", + "Topic", + "Updated", + "Message" + ] + }, + "notifications": { + "ru": [ + "Уведомления", + "Последние уведомления и события модулей.", + "Уведомление", + "Время", + "Источник" + ], + "en": [ + "Notifications", + "Recent notifications and module events.", + "Notification", + "Time", + "Source" + ] + }, + "scheduling": { + "ru": [ + "Расписание", + "Ближайшие события и записи из расписания.", + "Событие", + "Начало", + "Участник" + ], + "en": [ + "Scheduling", + "Upcoming events and bookings from scheduling.", + "Event", + "Starts", + "Guest" + ] + }, + "inventory": { + "ru": [ + "Склад", + "Складские позиции, остатки и статусы.", + "Позиция", + "Обновлено", + "Количество" + ], + "en": [ + "Inventory", + "Stock items, quantities and statuses.", + "Item", + "Updated", + "Quantity" + ] + }, + "agents": { + "ru": [ + "Агенты и контроллеры", + "Доступные агенты, контроллеры и их текущее состояние.", + "Агент или контроллер", + "Проверено", + "Тип" + ], + "en": [ + "Agents & Controllers", + "Available agents, controllers and their current state.", + "Agent or controller", + "Checked", + "Type" + ] + }, + "status-page": { + "ru": [ + "Status Page", + "Состояние доступности сервисов и мониторов.", + "Монитор", + "Проверено", + "Доступность" + ], + "en": [ + "Status Page", + "Service availability and monitor state.", + "Monitor", + "Checked", + "Availability" + ] + }, + "airbase": { + "ru": [ + "AirBase", + "Каталог таблиц AirBase с просмотром схемы.", + "Таблица", + "Обновлено", + "Записей" + ], + "en": [ + "AirBase", + "AirBase table catalog with schema details.", + "Table", + "Updated", + "Records" + ] + }, + "notes": { + "ru": [ + "Заметки", + "Страницы Notes с поиском и просмотром содержимого.", + "Страница", + "Обновлено", + "Пространство" + ], + "en": [ + "Notes", + "Notes pages with search and content details.", + "Page", + "Updated", + "Space" + ] + }, + "project-overview": { + "ru": [ + "Обзор проекта", + "Сводные данные по разделам выбранного проекта.", + "Раздел", + "Обновлено", + "Элементы" + ], + "en": [ + "Project overview", + "Cross-module records for the selected project.", + "Section", + "Updated", + "Items" + ] + }, + "files": { + "ru": [ + "Файлы", + "Файлы выбранного проекта с поиском и безопасным просмотром ссылок.", + "Файл", + "Обновлено", + "Размер или тип" + ], + "en": [ + "Files", + "Files from the selected project with search and safe links.", + "File", + "Updated", + "Size or type" + ] + }, + "proposals": { + "ru": [ + "Предложения", + "Предложения и сметы с поиском, статусами и деталями.", + "Предложение", + "Обновлено", + "Сумма или номер" + ], + "en": [ + "Proposals", + "Proposals with search, statuses and details.", + "Proposal", + "Updated", + "Amount or number" + ] + }, + "terms": { + "ru": [ + "Terms / ТЗ", + "Документы технического задания и их ревизии.", + "Документ", + "Обновлено", + "Страницы или ревизия" + ], + "en": [ + "Terms documents", + "Terms of Reference documents and revisions.", + "Document", + "Updated", + "Pages or revision" + ] + }, + "activity": { + "ru": [ + "Активность", + "Авторизованные realtime-события SHD по выбранному scope.", + "Событие", + "Время", + "Модуль или тип" + ], + "en": [ + "Activity", + "Authenticated SHD realtime events for the selected scope.", + "Event", + "Time", + "Module or type" + ] + }, + "organizations": { + "ru": [ + "Организации и доступ", + "Организации, роли и безопасный просмотр участников.", + "Организация", + "Обновлено", + "Роль или тип" + ], + "en": [ + "Organizations & access", + "Organizations, roles and safe membership details.", + "Organization", + "Updated", + "Role or type" + ] + }, + "gitea": { + "ru": [ + "Gitea Issues", + "Открытые задачи разрешённого site или project репозитория.", + "Issue", + "Обновлено", + "Состояние" + ], + "en": [ + "Gitea Issues", + "Open issues for an allowed site or project repository.", + "Issue", + "Updated", + "State" + ] + }, + "schemes": { + "ru": [ + "Схемы", + "Документы PDF Scheme Editor и их текущее состояние.", + "Схема", + "Обновлено", + "Ревизия или страницы" + ], + "en": [ + "Schemes", + "PDF Scheme Editor documents and their current state.", + "Scheme", + "Updated", + "Revision or pages" + ] + }, + "task-kanban": { + "ru": [ + "Задачи Kanban", + "Задачи по колонкам статусов с поиском и деталями.", + "Задача", + "Срок", + "Исполнитель" + ], + "en": [ + "Task Kanban", + "Tasks grouped by workflow status with search and details.", + "Task", + "Due", + "Assignee" + ] + }, + "team-workload": { + "ru": [ + "Загрузка команды", + "Плановая нагрузка, ёмкость и утилизация команды.", + "Участник", + "Период", + "Часы или процент" + ], + "en": [ + "Team workload", + "Planned workload, capacity and team utilization.", + "Member", + "Period", + "Hours or percent" + ] + }, + "project-timeline": { + "ru": [ + "Timeline проекта", + "Задачи проекта на временной ленте.", + "Задача", + "Срок", + "Статус" + ], + "en": [ + "Project timeline", + "Project tasks on a chronological timeline.", + "Task", + "Due", + "Status" + ] + }, + "crm-funnel": { + "ru": [ + "CRM-воронка", + "Сделки CRM, сгруппированные по стадиям.", + "Стадия", + "Сделки", + "Сумма" + ], + "en": [ + "CRM funnel", + "CRM deals grouped by pipeline stage.", + "Stage", + "Deals", + "Amount" + ] + }, + "finance-dashboard": { + "ru": [ + "Финансовый дашборд", + "Суммы, статусы и документы финансового модуля.", + "Документ", + "Обновлено", + "Сумма" + ], + "en": [ + "Finance dashboard", + "Finance amounts, statuses and documents.", + "Document", + "Updated", + "Amount" + ] + }, + "proposal-approvals": { + "ru": [ + "Согласования предложений", + "Очередь предложений и текущие approval-статусы.", + "Предложение", + "Обновлено", + "Approval" + ], + "en": [ + "Proposal approvals", + "Proposal queue and current approval states.", + "Proposal", + "Updated", + "Approval" + ] + }, + "documents-completeness": { + "ru": [ + "Полнота документов", + "Score, ревизии и состояние Terms-документов.", + "Документ", + "Обновлено", + "Полнота" + ], + "en": [ + "Documents completeness", + "Scores, revisions and Terms document state.", + "Document", + "Updated", + "Completeness" + ] + }, + "agents-health": { + "ru": [ + "Здоровье агентов", + "Состояние агентов и контроллеров.", + "Агент или контроллер", + "Проверено", + "Health" + ], + "en": [ + "Agents health", + "Agent and controller health state.", + "Agent or controller", + "Checked", + "Health" + ] + }, + "schedule-calendar": { + "ru": [ + "Календарь расписания", + "Ближайшие записи, сгруппированные по дням.", + "Событие", + "День", + "Участник" + ], + "en": [ + "Schedule calendar", + "Upcoming bookings grouped by day.", + "Event", + "Day", + "Guest" + ] + }, + "acl-matrix": { + "ru": [ + "ACL-матрица", + "Участники организации, роли и permissions.", + "Участник", + "Роль", + "Права" + ], + "en": [ + "ACL matrix", + "Organization members, roles and permissions.", + "Member", + "Role", + "Permissions" + ] + }, + "inventory-warnings": { + "ru": [ + "Предупреждения склада", + "Низкие остатки и критические складские позиции.", + "Позиция", + "Обновлено", + "Остаток" + ], + "en": [ + "Inventory warnings", + "Low-stock and critical inventory items.", + "Item", + "Updated", + "Stock" + ] + }, + "gitea-board": { + "ru": [ + "Gitea-доска", + "Issues по состояниям разрешённого репозитория.", + "Issue", + "Состояние", + "Ссылка" + ], + "en": [ + "Gitea board", + "Issues grouped by state for an allowed repository.", + "Issue", + "State", + "Link" + ] + }, + "schemes-progress": { + "ru": [ + "Прогресс схем", + "Документы схем, ревизии, страницы и completeness.", + "Схема", + "Обновлено", + "Прогресс" + ], + "en": [ + "Schemes progress", + "Scheme documents, revisions, pages and completeness.", + "Scheme", + "Updated", + "Progress" + ] + }, + "notes-tree": { + "ru": [ + "Дерево Notes", + "Пространства Notes и их иерархия.", + "Узел", + "Обновлено", + "Тип" + ], + "en": [ + "Notes tree", + "Notes spaces and their hierarchy.", + "Node", + "Updated", + "Type" + ] + }, + "airbase-preview": { + "ru": [ + "AirBase preview", + "Предпросмотр записей выбранной таблицы AirBase.", + "Запись", + "Обновлено", + "Поля" + ], + "en": [ + "AirBase preview", + "Preview of records from the selected AirBase table.", + "Record", + "Updated", + "Fields" + ] + }, + "documents": { + "ru": [ + "Документы", + "Реестр документов с поиском и просмотром деталей.", + "Документ", + "Обновлено", + "Сумма" + ], + "en": [ + "Documents", + "Document register with search and details.", + "Document", + "Updated", + "Amount" + ] + }, + "tasks": { + "ru": [ + "Задачи", + "Реестр задач с поиском и просмотром деталей.", + "Задача", + "Срок", + "Приоритет" + ], + "en": [ + "Tasks", + "Task register with search and details.", + "Task", + "Due date", + "Priority" + ] + }, + "finance": { + "ru": [ + "Финансы", + "Реестр финансовых документов с поиском и просмотром деталей.", + "Документ", + "Срок", + "Сумма" + ], + "en": [ + "Finance", + "Finance document register with search and details.", + "Document", + "Due date", + "Amount" + ] + }, + "crm": { + "ru": [ + "CRM", + "Реестр CRM-сущностей с поиском и просмотром деталей.", + "Сущность", + "Обновлено", + "Стадия" + ], + "en": [ + "CRM", + "CRM entity register with search and details.", + "Entity", + "Updated", + "Stage" + ] + }, + "pdf-scheme-editor": { + "ru": [ + "PDF Scheme Editor", + "Документы схем и их текущее состояние.", + "Документ", + "Обновлено", + "Ревизия или страницы" + ], + "en": [ + "PDF Scheme Editor", + "Scheme documents and their current state.", + "Document", + "Updated", + "Revision or pages" + ] + } + }, + "defaults": { + "discussions": { + "listTool": "shd_list_discussion_topics", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_list_discussion_messages", + "detailKey": "topic_id", + "detailArgs": { + "limit": 50 + }, + "paths": [ + "data" + ], + "sort": "desc" + }, + "notifications": { + "listTool": "shd_list_notifications", + "listArgs": { + "limit": 50, + "offset": 0 + }, + "paths": [ + "data" + ], + "sort": "desc" + }, + "scheduling": { + "listTool": "shd_get_scheduling_overview", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_list_scheduling_bookings", + "detailKey": "booking_id", + "detailArgs": { + "limit": 1 + }, + "paths": [ + "data.bookings", + "data.upcoming_bookings", + "data" + ], + "sort": "asc" + }, + "inventory": { + "listTool": "shd_list_inventory_items", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_inventory_item", + "detailKey": "item_id", + "paths": [ + "data" + ], + "sort": "desc" + }, + "agents": { + "listTool": "shd_agents_overview", + "listArgs": {}, + "paths": [ + "data.agents", + "data.user_agents", + "data" + ], + "sort": "asc" + }, + "status-page": { + "listTool": "shd_get_status_page_monitors", + "listArgs": { + "environment": "dev" + }, + "readTool": "shd_get_status_page_monitor_history", + "detailKey": "monitor_id", + "detailArgs": { + "limit": 20 + }, + "paths": [ + "data" + ], + "nested": "monitors", + "sort": "asc" + }, + "airbase": { + "listTool": "shd_airbase_tables", + "listArgs": {}, + "readTool": "shd_airbase_schema", + "detailKey": "table", + "paths": [ + "data.tables", + "data" + ], + "contextRequired": true, + "sort": "asc" + }, + "notes": { + "listTool": "shd_list_note_pages", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_note_page", + "detailKey": "page_id", + "paths": [ + "data" + ], + "sort": "desc" + }, + "project-overview": { + "listTool": "shd_get_project_context", + "listArgs": { + "include": [ + "summary", + "tasks", + "discussions", + "documents", + "finance", + "terms", + "agents" + ], + "limit_per_section": 100 + }, + "paths": [ + "data.sections.tasks", + "data.sections.discussions", + "data.sections.documents", + "data.sections.finance", + "data.sections.terms" + ], + "combinePaths": true, + "contextRequired": true, + "sort": "desc" + }, + "files": { + "listTool": "shd_list_files", + "readTool": "shd_get_file_link", + "detailKey": "path", + "detailArgs": { + "download": false + }, + "detailSourceArgs": [ + "project_code" + ], + "paths": [ + "data" + ], + "contextRequired": true, + "sort": "desc" + }, + "proposals": { + "listTool": "shd_list_proposals", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_proposal", + "detailKey": "proposal_id", + "detailSourceArgs": [], + "paths": [ + "data" + ], + "sort": "desc" + }, + "terms": { + "listTool": "shd_list_terms_documents", + "readTool": "shd_get_terms_document", + "detailKey": "document_id", + "detailSourceArgs": [ + "project_code" + ], + "paths": [ + "data" + ], + "contextRequired": true, + "sort": "desc" + }, + "activity": { + "listTool": "shd_get_realtime_events", + "listArgs": { + "module": "notifications", + "limit": 100 + }, + "paths": [ + "data.events", + "events", + "data" + ], + "sort": "desc" + }, + "organizations": { + "listTool": "shd_admin_list_organizations", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_admin_get_organization_members", + "detailKey": "organization_id", + "detailSourceArgs": [], + "paths": [ + "data" + ], + "sort": "asc" + }, + "gitea": { + "listTool": "shd_list_gitea_issues", + "listArgs": { + "target": "site", + "limit": 100 + }, + "paths": [ + "data" + ], + "sort": "desc" + }, + "schemes": { + "listTool": "shd_list_pdf_scheme_editor_documents", + "readTool": "shd_get_pdf_scheme_editor_document", + "detailKey": "document", + "detailSourceArgs": [ + "project" + ], + "paths": [ + "data" + ], + "contextRequired": true, + "sort": "desc" + }, + "task-kanban": { + "listTool": "shd_list_tasks", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_task", + "detailKey": "task_id", + "detailSourceArgs": [], + "paths": [ + "data" + ], + "view": "kanban", + "groupBy": "status", + "sort": "asc" + }, + "team-workload": { + "listTool": "shd_get_task_workload", + "listArgs": { + "period": "month" + }, + "paths": [ + "data.rows", + "data.workload", + "data.assignees", + "data" + ], + "view": "workload", + "sort": "desc" + }, + "project-timeline": { + "listTool": "shd_list_tasks", + "listArgs": { + "limit": 100 + }, + "paths": [ + "data" + ], + "view": "timeline", + "sort": "asc" + }, + "crm-funnel": { + "listTool": "shd_list_crm", + "listArgs": { + "section": "deals", + "limit": 100 + }, + "readTool": "shd_get_crm_entity", + "detailKey": "entity_id", + "detailSourceArgs": [ + "section" + ], + "paths": [ + "data" + ], + "view": "funnel", + "groupBy": "stage", + "sort": "desc" + }, + "finance-dashboard": { + "listTool": "shd_list_finance_documents", + "listArgs": { + "limit": 100 + }, + "paths": [ + "data" + ], + "view": "dashboard", + "sort": "desc" + }, + "proposal-approvals": { + "listTool": "shd_list_proposals", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_proposal_approval", + "detailKey": "proposal_id", + "detailSourceArgs": [], + "paths": [ + "data" + ], + "view": "approval", + "sort": "desc" + }, + "documents-completeness": { + "listTool": "shd_list_pdf_scheme_editor_documents", + "readTool": "shd_get_pdf_scheme_editor_document_completeness", + "detailKey": "document_id", + "detailSourceArgs": [ + "project_code" + ], + "paths": [ + "data" + ], + "view": "completeness", + "contextRequired": true, + "contextKeys": [ + "project_code" + ], + "sort": "desc" + }, + "agents-health": { + "listTool": "shd_agents_overview", + "paths": [ + "data.agents", + "data.user_agents", + "data" + ], + "view": "health", + "sort": "asc" + }, + "schedule-calendar": { + "listTool": "shd_get_scheduling_overview", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_list_scheduling_bookings", + "detailKey": "booking_id", + "detailArgs": { + "limit": 1 + }, + "paths": [ + "data.bookings", + "data.upcoming_bookings", + "data" + ], + "view": "calendar", + "sort": "asc" + }, + "acl-matrix": { + "listTool": "shd_list_organization_members", + "listArgs": { + "limit": 100 + }, + "paths": [ + "data" + ], + "view": "matrix", + "contextRequired": true, + "contextKeys": [ + "organization_id" + ], + "contextAll": true, + "sort": "asc" + }, + "inventory-warnings": { + "listTool": "shd_list_inventory_items", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_inventory_item", + "detailKey": "item_id", + "detailSourceArgs": [], + "paths": [ + "data" + ], + "view": "warnings", + "sort": "asc" + }, + "gitea-board": { + "listTool": "shd_list_gitea_issues", + "listArgs": { + "target": "site", + "limit": 100 + }, + "paths": [ + "data" + ], + "view": "kanban", + "groupBy": "state", + "sort": "asc" + }, + "schemes-progress": { + "listTool": "shd_list_pdf_scheme_editor_documents", + "readTool": "shd_get_pdf_scheme_editor_document", + "detailKey": "document", + "detailSourceArgs": [ + "project" + ], + "paths": [ + "data" + ], + "view": "progress", + "contextRequired": true, + "contextKeys": [ + "project" + ], + "sort": "desc" + }, + "notes-tree": { + "listTool": "shd_list_note_spaces", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_note_space_tree", + "detailKey": "space_id", + "detailSourceArgs": [], + "paths": [ + "data" + ], + "view": "tree", + "sort": "asc" + }, + "airbase-preview": { + "listTool": "shd_airbase_records", + "listArgs": { + "limit": 100, + "include_fields": true, + "include_view_meta": true + }, + "paths": [ + "data.records", + "data" + ], + "view": "preview", + "contextRequired": true, + "contextSets": [ + [ + "base", + "project_code" + ], + [ + "table" + ] + ], + "sort": "asc" + }, + "documents": { + "listTool": "shd_list_contracts", + "listArgs": { + "archived": false, + "limit": 100 + }, + "readTool": "shd_get_contract", + "detailKey": "contract_id", + "paths": [ + "data" + ], + "sort": "desc" + }, + "tasks": { + "listTool": "shd_list_tasks", + "listArgs": { + "archived": false, + "limit": 100 + }, + "readTool": "shd_get_task", + "detailKey": "task_id", + "paths": [ + "data" + ], + "sort": "asc" + }, + "finance": { + "listTool": "shd_list_finance_documents", + "listArgs": { + "archived": false, + "limit": 100 + }, + "readTool": "shd_get_finance_document", + "detailKey": "document_id", + "paths": [ + "data" + ], + "sort": "desc" + }, + "crm": { + "listTool": "shd_list_crm", + "listArgs": { + "limit": 100 + }, + "readTool": "shd_get_crm_entity", + "detailKey": "entity_id", + "detailSourceArgs": [ + "section" + ], + "paths": [ + "data" + ], + "sort": "desc" + }, + "pdf-scheme-editor": { + "listTool": "shd_list_pdf_scheme_editor_documents", + "readTool": "shd_get_pdf_scheme_editor_document", + "detailKey": "document", + "detailSourceArgs": [ + "project" + ], + "paths": [ + "data" + ], + "contextRequired": true, + "sort": "desc" + } + } +} diff --git a/plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html b/plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html new file mode 100644 index 0000000..3a7b58b --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html @@ -0,0 +1,109 @@ + + + + + + SHD MCP + + + + +
+
SHD MCP

SHD

+
+
+
+ +
+ + + + diff --git a/plugins/shd-mcp-plugin/widgets/_shared/widget-theme.css b/plugins/shd-mcp-plugin/widgets/_shared/widget-theme.css new file mode 100644 index 0000000..6129164 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/_shared/widget-theme.css @@ -0,0 +1,1236 @@ +:root { + color-scheme: light dark; + --shd-bg: #f3f6f9; + --shd-surface: #ffffff; + --shd-surface-raised: #ffffff; + --shd-surface-subtle: #eef2f6; + --shd-line: #d6dee7; + --shd-line-strong: #b9c5d1; + --shd-ink: #17202a; + --shd-muted: #66717e; + --shd-accent: #496581; + --shd-accent-strong: #35516d; + --shd-accent-soft: rgba(73, 101, 129, .12); + --shd-on-accent: #ffffff; + --shd-good: #19734b; + --shd-good-soft: #e2f3eb; + --shd-warning: #946000; + --shd-warning-soft: #fff2cf; + --shd-danger: #b42318; + --shd-danger-soft: #ffebe9; + --shd-shadow: 0 1px 2px rgba(15, 23, 42, .05), 0 12px 30px rgba(15, 23, 42, .08); + --shd-shadow-soft: 0 1px 2px rgba(15, 23, 42, .05); + --shd-radius-sm: 10px; + --shd-radius-md: 14px; + --shd-radius-lg: 20px; + --shd-space-1: 4px; + --shd-space-2: 8px; + --shd-space-3: 12px; + --shd-space-4: 16px; + --shd-space-5: 20px; + --shd-space-6: 24px; + --shd-space-7: 32px; + + /* Compatibility aliases for existing widget renderers and write actions. */ + --bg: var(--shd-bg); + --surface: var(--shd-surface); + --panel: var(--shd-surface); + --hover: var(--shd-surface-subtle); + --surface-hover: var(--shd-surface-subtle); + --line: var(--shd-line); + --border: var(--shd-line); + --ink: var(--shd-ink); + --text: var(--shd-ink); + --muted: var(--shd-muted); + --accent: var(--shd-accent); + --soft: var(--shd-accent-soft); + --accent-soft: var(--shd-accent-soft); + --good: var(--shd-good); + --success: var(--shd-good); + --success-soft: var(--shd-good-soft); + --warning: var(--shd-warning); + --warning-soft: var(--shd-warning-soft); + --bad: var(--shd-danger); + --danger: var(--shd-danger); + --danger-soft: var(--shd-danger-soft); + --button: var(--shd-on-accent); + --shadow: var(--shd-shadow); +} + +@media (prefers-color-scheme: dark) { + :root { + --shd-bg: #15181c; + --shd-surface: #1e2328; + --shd-surface-raised: #262c32; + --shd-surface-subtle: #2d343b; + --shd-line: #3d464f; + --shd-line-strong: #56616c; + --shd-ink: #f1f4f7; + --shd-muted: #a5adb6; + --shd-accent: #d8b56a; + --shd-accent-strong: #edc97d; + --shd-accent-soft: rgba(216, 181, 106, .16); + --shd-on-accent: #211b10; + --shd-good: #83d2a4; + --shd-good-soft: rgba(71, 164, 111, .22); + --shd-warning: #f2c66d; + --shd-warning-soft: rgba(196, 139, 40, .22); + --shd-danger: #ff9a93; + --shd-danger-soft: rgba(196, 61, 50, .22); + --shd-shadow: 0 1px 2px rgba(0, 0, 0, .22), 0 16px 34px rgba(0, 0, 0, .24); + --shd-shadow-soft: 0 1px 2px rgba(0, 0, 0, .22); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + min-width: 0; + background: var(--shd-bg); +} + +body { + min-width: 0; + margin: 0; + background: var(--shd-bg); + color: var(--shd-ink); + font: 14px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +button, +input, +select, +textarea { + font: inherit; +} + +button { + color: inherit; +} + +.shell, +.app { + width: 100%; + max-width: 1120px; + margin: 0 auto; + padding: clamp(14px, 2.5vw, 28px); +} + +.card, +.app { + min-width: 0; + padding: clamp(18px, 2.6vw, 30px); + border: 1px solid var(--shd-line); + border-radius: var(--shd-radius-lg); + background: var(--shd-surface); + box-shadow: var(--shd-shadow); +} + +.card > * + *, +.app > * + *, +.shell > * + *, +.visual > * + * { + margin-block-start: var(--shd-space-5); +} + +.card > .eyebrow + h1, +.card > h1 + .lede, +.app > .header + .toolbar { + margin-block-start: var(--shd-space-1); +} + +.card > .summary:empty, +.card > .status:empty, +.app > .summary:empty, +.app > .status:empty { + display: none; +} + +.eyebrow { + color: var(--shd-muted); + font-size: 11px; + font-weight: 750; + letter-spacing: .12em; + text-transform: uppercase; +} + +h1, +h2, +h3, +p { + overflow-wrap: anywhere; +} + +h1 { + margin: 0; + font-size: clamp(22px, 2.5vw, 30px); + line-height: 1.15; + letter-spacing: -.02em; +} + +.lede, +.subtitle { + margin: 0; + color: var(--shd-muted); +} + +.header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--shd-space-5); +} + +.toolbar { + display: flex; + align-items: center; + gap: var(--shd-space-2); + flex-wrap: wrap; +} + +.toolbar > .search { + flex: 1 1 220px; +} + +.search, +.toolbar input, +.toolbar select, +.setting input, +.setting select, +.shd-widget-actions-select, +.shd-widget-actions-input { + min-width: 0; + border: 1px solid var(--shd-line-strong); + border-radius: var(--shd-radius-sm); + background: var(--shd-bg); + color: var(--shd-ink); + outline: 0; +} + +.search, +.toolbar input, +.toolbar select { + padding: 10px 12px; +} + +.toolbar select { + min-width: 150px; +} + +.search:focus, +.toolbar input:focus, +.toolbar select:focus, +.setting input:focus, +.setting select:focus, +.shd-widget-actions-select:focus, +.shd-widget-actions-input:focus { + border-color: var(--shd-accent); + box-shadow: 0 0 0 3px var(--shd-accent-soft); +} + +button { + cursor: pointer; +} + +button:focus-visible, +[role="button"]:focus-visible { + outline: 3px solid var(--shd-accent-soft); + outline-offset: 2px; +} + +.button, +.refresh, +.toolbar button, +.actions button, +.inline-action { + border: 1px solid var(--shd-line-strong); + border-radius: var(--shd-radius-sm); + padding: 9px 13px; + background: var(--shd-surface-raised); + color: var(--shd-ink); + font-weight: 650; + white-space: nowrap; +} + +.button:hover, +.toolbar button:hover, +.actions button:hover, +.inline-action:hover { + border-color: var(--shd-accent); + background: var(--shd-surface-subtle); +} + +.refresh, +.toolbar .primary, +.actions button, +.inline-action, +.app #refresh { + border-color: var(--shd-accent); + background: var(--shd-accent); + color: var(--shd-on-accent); +} + +.refresh:hover, +.toolbar .primary:hover, +.actions button:hover, +.inline-action:hover, +.app #refresh:hover { + border-color: var(--shd-accent-strong); + background: var(--shd-accent-strong); +} + +.refresh:disabled, +.toolbar button:disabled, +.actions button:disabled, +.button:disabled, +.inline-action:disabled { + cursor: wait; + opacity: .58; +} + +.status { + min-height: 0; + color: var(--shd-muted); +} + +.status.error, +.error { + color: var(--shd-danger); +} + +.summary { + display: flex; + flex-wrap: wrap; + gap: var(--shd-space-2); +} + +.summary span, +.summary-item, +.pill { + display: inline-flex; + align-items: center; + max-width: 100%; + min-height: 26px; + padding: 4px 9px; + overflow: hidden; + border: 1px solid transparent; + border-radius: 999px; + background: var(--shd-surface-subtle); + color: var(--shd-muted); + font-size: 12px; + line-height: 1.25; + text-overflow: ellipsis; + white-space: nowrap; +} + +.summary strong, +.summary-item strong { + margin-inline-end: 4px; + color: var(--shd-ink); + font-weight: 700; +} + +.pill.good, +.pill.up, +.pill.ok, +.pill.active, +.status-done { + border-color: var(--shd-line); + background: var(--shd-good-soft); + color: var(--shd-good); +} + +.pill.bad, +.pill.down, +.pill.error, +.pill.overdue, +.status-danger { + border-color: var(--shd-line); + background: var(--shd-danger-soft); + color: var(--shd-danger); +} + +.status-progress { + background: var(--shd-accent-soft); + color: var(--shd-accent-strong); +} + +.status-neutral { + background: var(--shd-surface-subtle); + color: var(--shd-muted); +} + +.status-warning { + background: var(--shd-warning-soft); + color: var(--shd-warning); +} + +.table-wrap, +.table, +.visual, +.matrix-wrap { + min-width: 0; + overflow: auto; + border: 1px solid var(--shd-line-strong); + border-radius: var(--shd-radius-md); + background: var(--shd-surface-raised); + box-shadow: var(--shd-shadow-soft); +} + +.table-wrap, +.visual { + max-height: 520px; +} + +.visual { + padding: var(--shd-space-3); +} + +table { + width: 100%; + min-width: 560px; + border-collapse: separate; + border-spacing: 0; +} + +th, +td { + padding: 13px 15px; + border-bottom: 1px solid var(--shd-line); + text-align: left; + vertical-align: top; +} + +th { + position: sticky; + top: 0; + z-index: 1; + background: var(--shd-surface-subtle); + color: var(--shd-muted); + font-size: 12px; + font-weight: 700; + letter-spacing: .01em; +} + +tbody tr:last-child td, +tr:last-child td { + border-bottom: 0; +} + +tbody tr:hover, +.record:hover, +.record:focus, +.project-row:hover { + background: var(--shd-accent-soft); +} + +.record { + cursor: pointer; + outline: 0; +} + +.title, +.project-title, +.visual-title { + font-weight: 700; +} + +.sub, +.project-code, +.visual-date, +.visual-value, +.muted, +.loading { + color: var(--shd-muted); +} + +.sub, +.project-code { + display: block; + margin-top: var(--shd-space-1); + font-size: 12px; +} + +.empty { + padding: var(--shd-space-6) var(--shd-space-4); + color: var(--shd-muted); + text-align: center; +} + +.visual[hidden], +.details[hidden], +.error[hidden] { + display: none; +} + +.visual-card { + display: block; + width: 100%; + min-width: 0; + padding: 14px 15px; + border: 1px solid var(--shd-line); + border-radius: var(--shd-radius-sm); + background: var(--shd-surface-raised); + color: inherit; + text-align: left; + cursor: pointer; +} + +.visual-card:hover, +.visual-card:focus { + border-color: var(--shd-accent); + background: var(--shd-surface-subtle); + outline: 0; +} + +.visual-title { + display: block; + overflow-wrap: anywhere; +} + +.visual-date, +.visual-value { + display: block; + margin-top: var(--shd-space-1); + font-size: 12px; +} + +.visual-line { + display: flex; + align-items: center; + gap: var(--shd-space-2); + flex-wrap: wrap; + margin-top: var(--shd-space-2); +} + +.grid, +.metric-grid, +.preview-grid, +.calendar, +.kanban { + display: grid; + min-width: 0; + gap: var(--shd-space-4); +} + +#content { + display: grid; + min-width: 0; + gap: var(--shd-space-5); +} + +.grid, +.preview-grid { + grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); +} + +.metric-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.panel, +.metric, +.progress-card, +.preview-card, +.kanban-col, +.calendar-day, +.detail { + min-width: 0; + border: 1px solid var(--shd-line); + border-radius: var(--shd-radius-md); + background: var(--shd-surface-raised); + box-shadow: var(--shd-shadow-soft); +} + +.panel { + padding: var(--shd-space-5); +} + +.panel h2, +.panel h3 { + margin: 0 0 var(--shd-space-3); + color: var(--shd-ink); + font-size: 16px; + line-height: 1.25; +} + +.panel p { + margin: 0; + color: var(--shd-muted); +} + +.metric { + padding: var(--shd-space-4); +} + +.metric-label { + display: block; + color: var(--shd-muted); + font-size: 12px; + font-weight: 600; +} + +.metric-value { + display: block; + margin-top: var(--shd-space-2); + color: var(--shd-ink); + font-size: clamp(22px, 3vw, 30px); + font-weight: 750; + letter-spacing: -.02em; + overflow-wrap: anywhere; +} + +.metric p { + margin: var(--shd-space-2) 0 0; + color: var(--shd-ink); + font-size: clamp(20px, 2.7vw, 28px); + font-weight: 750; + letter-spacing: -.02em; +} + +.kanban { + grid-auto-flow: column; + grid-auto-columns: minmax(210px, 1fr); + align-items: start; + overflow-x: auto; +} + +.kanban-col { + min-width: 210px; + padding: var(--shd-space-3); +} + +.kanban-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--shd-space-2); + margin-bottom: var(--shd-space-3); + font-weight: 700; +} + +.kanban-list, +.source-list, +.tree { + display: grid; + gap: var(--shd-space-2); +} + +.metric-grid + .kanban-list { + margin-top: var(--shd-space-1); +} + +.timeline { + display: grid; + gap: 0; +} + +.timeline-item { + display: grid; + position: relative; + grid-template-columns: 18px minmax(0, 1fr); + gap: var(--shd-space-3); + padding-bottom: var(--shd-space-4); +} + +.timeline-rail { + position: absolute; + top: 16px; + bottom: 0; + left: 8px; + width: 2px; + background: var(--shd-line); +} + +.timeline-item:last-child .timeline-rail { + display: none; +} + +.timeline-dot { + z-index: 1; + width: 16px; + height: 16px; + border: 3px solid var(--shd-accent-soft); + border-radius: 50%; + background: var(--shd-accent); +} + +.timeline-content { + min-width: 0; +} + +.timeline-meta { + margin-bottom: var(--shd-space-2); + color: var(--shd-muted); + font-size: 12px; +} + +.funnel { + display: grid; + gap: var(--shd-space-3); +} + +.funnel-row { + display: grid; + grid-template-columns: minmax(100px, 1fr) minmax(120px, 2fr) auto; + align-items: center; + gap: var(--shd-space-3); +} + +progress { + width: 100%; + height: 10px; + overflow: hidden; + border: 0; + border-radius: 999px; + background: var(--shd-surface-subtle); + accent-color: var(--shd-accent); +} + +progress::-webkit-progress-bar { + border-radius: 999px; + background: var(--shd-surface-subtle); +} + +progress::-webkit-progress-value { + border-radius: 999px; + background: var(--shd-accent); +} + +progress::-moz-progress-bar { + border-radius: 999px; + background: var(--shd-accent); +} + +.progress-card { + display: grid; + gap: var(--shd-space-2); + padding: var(--shd-space-4); +} + +.preview-card { + padding: var(--shd-space-4); +} + +.preview-fields { + display: grid; + gap: var(--shd-space-2); + margin-top: var(--shd-space-3); +} + +.preview-field { + overflow-wrap: anywhere; + font-size: 12px; +} + +.preview-field strong { + color: var(--shd-muted); + font-weight: 600; +} + +.calendar { + grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); +} + +.calendar-day { + display: grid; + align-content: start; + gap: var(--shd-space-3); + padding: var(--shd-space-3); +} + +.calendar-head { + font-weight: 700; +} + +.matrix-wrap { + background: var(--shd-surface-raised); +} + +.matrix-table { + min-width: 600px; +} + +.matrix-table th, +.matrix-table td { + padding: 12px 14px; +} + +.tree-children { + display: grid; + gap: var(--shd-space-2); + margin-left: var(--shd-space-3); + padding-left: var(--shd-space-4); + border-left: 1px solid var(--shd-line); +} + +.details { + min-width: 0; + padding: var(--shd-space-5); + border: 1px solid var(--shd-line-strong); + border-radius: var(--shd-radius-md); + background: var(--shd-surface-subtle); + box-shadow: var(--shd-shadow-soft); +} + +.detail-head, +.details-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--shd-space-4); +} + +.details h2, +.details-title { + margin: 0; + font-size: 18px; + line-height: 1.25; +} + +.details-grid, +.grid { + margin-top: var(--shd-space-4); +} + +.detail { + padding: var(--shd-space-3); +} + +.label, +.detail-label { + display: block; + margin-bottom: var(--shd-space-1); + color: var(--shd-muted); + font-size: 11px; +} + +.value, +.detail-value { + display: block; + overflow-wrap: anywhere; + white-space: pre-wrap; +} + +.close { + border: 0; + background: transparent; + color: var(--shd-muted); + font-size: 20px; +} + +.chart { + min-height: 176px; + margin: var(--shd-space-3) 0 0; + padding: var(--shd-space-2); + overflow: hidden; + border: 1px solid var(--shd-line); + border-radius: var(--shd-radius-sm); + background: var(--shd-surface-subtle); +} + +.chart svg { + display: block; + width: 100%; + height: 160px; +} + +.chart-empty { + display: grid; + height: 156px; + place-items: center; + color: var(--shd-muted); + font-size: 12px; +} + +.axis { + fill: var(--shd-muted); + font-size: 10px; +} + +.chart-grid-line { + stroke: var(--shd-line); + stroke-dasharray: 3 4; + stroke-width: 1; +} + +.chart-axis-line { + stroke: var(--shd-line-strong); + stroke-width: 1; +} + +.chart-x-label, +.chart-y-label { + fill: var(--shd-muted); + font-size: 10px; + font-variant-numeric: tabular-nums; +} + +.chart-line { + fill: none; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.4; + vector-effect: non-scaling-stroke; +} + +.line-a, +.line-b { + fill: none; +} + +.line-a { + stroke: #5e91c0; +} + +.line-b { + stroke: #d48655; +} + +.line-c { + stroke: #73b987; +} + +.line-d { + stroke: #b58bd9; +} + +.line-e { + stroke: #d9ad5c; +} + +.line-f { + stroke: #63b9c9; +} + +.comparison-chart { + min-height: 260px; +} + +.comparison-chart svg { + height: 232px; +} + +.chart-legend { + display: grid; + gap: var(--shd-space-2); + margin-top: var(--shd-space-3); +} + +.chart-legend-row { + display: grid; + grid-template-columns: auto minmax(150px, 1fr) repeat(4, auto); + align-items: center; + gap: var(--shd-space-3); + min-width: 0; + padding: 10px 12px; + border: 1px solid var(--shd-line); + border-radius: var(--shd-radius-sm); + background: var(--shd-surface-subtle); +} + +.chart-swatch { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.chart-swatch.line-a { + background: #5e91c0; +} + +.chart-swatch.line-b { + background: #d48655; +} + +.chart-swatch.line-c { + background: #73b987; +} + +.chart-swatch.line-d { + background: #b58bd9; +} + +.chart-swatch.line-e { + background: #d9ad5c; +} + +.chart-swatch.line-f { + background: #63b9c9; +} + +.chart-legend-name, +.chart-legend-stat { + min-width: 0; + overflow-wrap: anywhere; +} + +.chart-legend-name { + color: var(--shd-ink); + font-weight: 700; +} + +.chart-legend-meta { + display: block; + margin-top: var(--shd-space-1); + color: var(--shd-muted); + font-size: 11px; + font-weight: 400; +} + +.chart-legend-stat { + color: var(--shd-ink); + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.chart-legend-stat-label { + margin-right: var(--shd-space-1); + color: var(--shd-muted); + font-size: 11px; +} + +.source-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--shd-space-3); + min-width: 0; + padding: 10px 12px; + border: 1px solid var(--shd-line); + border-radius: var(--shd-radius-sm); + background: var(--shd-surface-subtle); +} + +.source-row > * { + min-width: 0; + overflow-wrap: anywhere; +} + +.setting { + display: grid; + grid-template-columns: minmax(130px, 1fr) minmax(150px, 1fr); + align-items: center; + gap: var(--shd-space-3); + padding: var(--shd-space-3) 0; + border-bottom: 1px solid var(--shd-line); +} + +.setting:last-child { + border-bottom: 0; +} + +.setting label { + font-weight: 700; +} + +.setting small { + display: block; + color: var(--shd-muted); + font-weight: 400; +} + +.setting input, +.setting select { + width: 100%; + padding: 9px 10px; +} + +.setting input:disabled, +.setting select:disabled { + opacity: .6; +} + +.actions { + display: flex; + justify-content: flex-end; + gap: var(--shd-space-2); + margin-top: var(--shd-space-4); +} + +.inline-action { + padding: 6px 9px; + font-size: 12px; +} + +/* Active-projects keeps its own data renderer but shares this visual contract. */ +.app { + max-width: 1040px; +} + +.app .header h1 { + font-size: clamp(21px, 2.4vw, 28px); +} + +.app .subtitle { + margin-top: var(--shd-space-1); + font-size: 13px; +} + +.app .table { + overflow: hidden; +} + +.app .table-head, +.app .project-row { + display: grid; + grid-template-columns: minmax(0, 1.7fr) minmax(130px, .8fr) minmax(110px, .7fr); + align-items: center; + gap: var(--shd-space-3); +} + +.app .table-head { + padding: 11px 16px; + background: var(--shd-surface-subtle); + color: var(--shd-muted); + font-size: 11px; + font-weight: 700; + letter-spacing: .04em; + text-transform: uppercase; +} + +.app .project-row { + width: 100%; + padding: 14px 16px; + border: 0; + border-top: 1px solid var(--shd-line); + background: var(--shd-surface-raised); + text-align: left; +} + +.app .project-name, +.app .project-title { + min-width: 0; +} + +.app .project-title { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.app .deadline { + color: var(--shd-ink); + font-size: 13px; +} + +.app .deadline.missing { + color: var(--shd-muted); +} + +.app .deadline.overdue { + color: var(--shd-danger); + font-weight: 700; +} + +.app .status { + display: inline-flex; + width: fit-content; + max-width: 100%; + padding: 5px 9px; + overflow: hidden; + border-radius: 999px; + font-size: 12px; + font-weight: 650; + text-overflow: ellipsis; + white-space: nowrap; +} + +.app .details { + background: var(--shd-surface-subtle); +} + +@media (max-width: 720px) { + .shell, + .app { + padding: 12px; + } + + .card, + .app { + padding: 16px; + border-radius: var(--shd-radius-md); + } + + .header { + display: block; + } + + .header .button, + .header #refresh { + margin-top: var(--shd-space-3); + } + + .toolbar > * { + flex: 1 1 180px; + } + + .toolbar .refresh, + .toolbar button, + .toolbar select { + flex-grow: 0; + } + + .grid, + .preview-grid { + grid-template-columns: 1fr; + } + + .metric-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .chart-legend-row { + grid-template-columns: auto minmax(0, 1fr); + } + + .chart-legend-stat { + grid-column: 2; + } + + .setting { + grid-template-columns: 1fr; + } + + .funnel-row { + grid-template-columns: 1fr auto; + } + + .funnel-row progress { + grid-column: 1 / -1; + grid-row: 2; + } + + .app .table-head, + .app .project-row { + grid-template-columns: minmax(0, 1.4fr) minmax(100px, .8fr) minmax(90px, .7fr); + gap: var(--shd-space-2); + padding-right: 10px; + padding-left: 10px; + } + + .details-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 440px) { + .metric-grid, + .details-grid { + grid-template-columns: 1fr; + } + + .detail-head, + .details-header { + display: block; + } + + .close { + margin-top: var(--shd-space-2); + } +} diff --git a/plugins/shd-mcp-plugin/widgets/_shared/write-actions.css b/plugins/shd-mcp-plugin/widgets/_shared/write-actions.css new file mode 100644 index 0000000..0f96251 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/_shared/write-actions.css @@ -0,0 +1,142 @@ +.shd-widget-actions { + min-width: 0; + padding: var(--shd-space-5, 20px); + border: 1px solid var(--shd-line-strong, var(--line, rgba(31, 41, 55, .16))); + border-radius: var(--shd-radius-md, 14px); + background: var(--shd-surface-raised, var(--surface, #e8edf2)); + color: var(--shd-ink, var(--ink, #1d1f25)); + box-shadow: var(--shd-shadow-soft, none); +} + +.shd-widget-actions[hidden] { + display: none; +} + +.shd-widget-actions-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--shd-space-3, 12px); + margin-bottom: var(--shd-space-3, 12px); +} + +.shd-widget-actions-selection { + overflow: hidden; + color: var(--shd-muted, var(--muted, #616670)); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.shd-widget-actions-select, +.shd-widget-actions-input { + width: 100%; + padding: 9px 10px; + border: 1px solid var(--shd-line-strong, var(--line, rgba(31, 41, 55, .16))); + border-radius: var(--shd-radius-sm, 10px); + background: var(--shd-bg, var(--bg, #f8fafc)); + color: var(--shd-ink, var(--ink, #1d1f25)); +} + +.shd-widget-actions-select:focus, +.shd-widget-actions-input:focus { + border-color: var(--shd-accent, var(--accent, #535963)); + outline: 0; + box-shadow: 0 0 0 3px var(--shd-accent-soft, var(--soft, rgba(75, 85, 99, .12))); +} + +.shd-widget-actions-form { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: var(--shd-space-3, 12px); + margin-top: var(--shd-space-3, 12px); +} + +.shd-widget-actions-field { + display: grid; + gap: var(--shd-space-1, 4px); + color: var(--shd-muted, var(--muted, #616670)); + font-size: 11px; +} + +.shd-widget-actions-field:has(textarea) { + grid-column: span 2; +} + +.shd-widget-actions-field input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--shd-accent, var(--accent, #535963)); +} + +.shd-widget-actions-confirm { + display: flex; + grid-column: 1 / -1; + align-items: center; + gap: var(--shd-space-2, 8px); + color: var(--shd-ink, var(--ink, #1d1f25)); + font-size: 12px; +} + +.shd-widget-actions-warning, +.shd-widget-actions-hint { + grid-column: 1 / -1; + color: var(--shd-muted, var(--muted, #616670)); + font-size: 12px; +} + +.shd-widget-actions-danger { + color: var(--shd-danger, var(--bad, #b42318)); +} + +.shd-widget-actions-status { + min-height: 18px; + margin-top: var(--shd-space-3, 12px); + color: var(--shd-muted, var(--muted, #616670)); + font-size: 12px; +} + +.shd-widget-actions-status-error { + color: var(--shd-danger, var(--bad, #b42318)); +} + +.shd-widget-actions-buttons { + display: flex; + justify-content: flex-end; + gap: var(--shd-space-2, 8px); + margin-top: var(--shd-space-4, 16px); +} + +.shd-widget-actions-buttons button { + padding: 9px 12px; + border: 1px solid var(--shd-line-strong, var(--line, rgba(31, 41, 55, .16))); + border-radius: var(--shd-radius-sm, 10px); + background: var(--shd-surface-subtle, var(--bg, #f8fafc)); + color: var(--shd-ink, var(--ink, #1d1f25)); + cursor: pointer; +} + +.shd-widget-actions-buttons button:hover { + border-color: var(--shd-accent, var(--accent, #535963)); + background: var(--shd-surface, var(--hover, #e1e8ef)); +} + +.shd-widget-actions-buttons button:disabled { + cursor: wait; + opacity: .62; +} + +@media (max-width: 600px) { + .shd-widget-actions-field:has(textarea) { + grid-column: auto; + } + + .shd-widget-actions-header { + display: block; + } + + .shd-widget-actions-selection { + display: block; + margin-top: var(--shd-space-1, 4px); + } +} diff --git a/plugins/shd-mcp-plugin/widgets/_shared/write-actions.js b/plugins/shd-mcp-plugin/widgets/_shared/write-actions.js new file mode 100644 index 0000000..df7cce1 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/_shared/write-actions.js @@ -0,0 +1,454 @@ +(function () { + 'use strict'; + + if (window.__SHD_WIDGET_WRITE_ACTIONS__) return; + window.__SHD_WIDGET_WRITE_ACTIONS__ = true; + + var copy = { + ru: { + actions: 'Действия', + chooseAction: 'Выберите действие', + selectRecord: 'Сначала выберите запись.', + submit: 'Сохранить', + cancel: 'Отмена', + confirm: 'Подтверждаю действие', + warning: 'Проверьте данные перед записью.', + destructive: 'Операция может изменить или удалить данные.', + required: 'Заполните обязательное поле.', + invalidJson: 'Введите корректный JSON.', + success: 'Изменения сохранены.', + failure: 'Не удалось сохранить изменения.', + loading: 'Сохраняю…', + noActions: 'Для этого виджета нет доступных действий.', + refreshed: 'Список обновлён.', + fieldValue: 'Значение', + }, + en: { + actions: 'Actions', + chooseAction: 'Choose an action', + selectRecord: 'Select a record first.', + submit: 'Save', + cancel: 'Cancel', + confirm: 'I confirm this action', + warning: 'Review the values before writing.', + destructive: 'This operation may change or remove data.', + required: 'Fill in the required field.', + invalidJson: 'Enter valid JSON.', + success: 'Changes saved.', + failure: 'Could not save changes.', + loading: 'Saving…', + noActions: 'No actions are available for this widget.', + refreshed: 'List refreshed.', + fieldValue: 'Value', + }, + }; + + var state = { + widget: {}, + records: [], + selected: null, + action: null, + inputs: {}, + busy: false, + lang: String(document.documentElement.lang || 'ru').toLowerCase().indexOf('ru') === 0 ? 'ru' : 'en', + }; + var pending = new Map(); + var nextRequestId = 1; + var elements = {}; + + function t(key) { + return (copy[state.lang] && copy[state.lang][key]) || copy.en[key] || key; + } + + function object(value) { + return Boolean(value && typeof value === 'object' && !Array.isArray(value)); + } + + function normalize(value) { + if (!value) return null; + if (value.structuredContent) return normalize(value.structuredContent); + if (value.toolOutput) return normalize(value.toolOutput); + if (value.toolInput) return normalize(value.toolInput); + if (value.arguments && !value.data && !value.items && !value.projects) return normalize(value.arguments); + return value; + } + + function widgetFrom(value) { + var payload = normalize(value) || {}; + var meta = object(payload.meta) ? payload.meta : {}; + return object(meta.widget) ? meta.widget : {}; + } + + function recordsFrom(value) { + var payload = normalize(value) || {}; + var data = Array.isArray(payload.items) ? payload.items : payload.projects; + if (!Array.isArray(data)) data = payload.data; + if (data && !Array.isArray(data) && Array.isArray(data.data)) data = data.data; + return Array.isArray(data) ? data.filter(function (item) { return object(item); }) : []; + } + + function readPath(value, path) { + if (!value || path === undefined || path === null) return undefined; + var parts = String(path).split('.'); + var current = value; + for (var index = 0; index < parts.length; index += 1) { + if (current === undefined || current === null) return undefined; + current = current[parts[index]]; + } + return current; + } + + function firstValue(paths) { + var list = Array.isArray(paths) ? paths : [paths]; + var sources = [state.selected, state.widget.sourceArgs, state.widget.listArgs]; + for (var sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) { + var sourceObject = sources[sourceIndex]; + if (!sourceObject) continue; + for (var pathIndex = 0; pathIndex < list.length; pathIndex += 1) { + var value = readPath(sourceObject, list[pathIndex]); + if (value !== undefined && value !== null && value !== '') return value; + } + } + return undefined; + } + + function text(value) { + if (value === undefined || value === null) return ''; + if (typeof value === 'object') return JSON.stringify(value); + return String(value); + } + + function label(value) { + if (object(value)) return value[state.lang] || value.ru || value.en || t('fieldValue'); + return String(value || t('fieldValue')); + } + + function actionLabel(action) { + return label(action && action.label) || action.id || action.tool || t('actions'); + } + + function hasTargets(action) { + return Boolean(action && ((Array.isArray(action.targets) && action.targets.length) || action.target)); + } + + function actionList() { + return Array.isArray(state.widget.actions) ? state.widget.actions.filter(function (action) { + return action && action.tool; + }) : []; + } + + function request(method, params) { + var openai = typeof window !== 'undefined' ? window.openai : undefined; + if (method === 'tools/call' && openai && typeof openai.callTool === 'function') { + return openai.callTool(params.name, params.arguments || {}); + } + var id = 'actions-' + nextRequestId++; + window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params || {} }, '*'); + return new Promise(function (resolve, reject) { + var timeout = window.setTimeout(function () { + pending.delete(id); + reject(new Error(t('failure'))); + }, 15000); + pending.set(id, { resolve: resolve, reject: reject, timeout: timeout }); + }); + } + + function unwrap(value) { + var payload = normalize(value); + if (value && value.isError) throw new Error(payload && payload.error ? text(payload.error) : t('failure')); + if (payload && payload.error) throw new Error(text(payload.error)); + return payload || value; + } + + function make(tag, value, className) { + var node = document.createElement(tag); + if (className) node.className = className; + if (value !== undefined) node.textContent = value; + return node; + } + + function ensurePanel() { + if (elements.panel) return; + var root = document.querySelector('main') || document.body; + var panel = make('section', undefined, 'shd-widget-actions'); + panel.hidden = true; + panel.setAttribute('aria-live', 'polite'); + var header = make('div', undefined, 'shd-widget-actions-header'); + header.append(make('strong', t('actions')), make('span', '', 'shd-widget-actions-selection')); + var select = document.createElement('select'); + select.className = 'shd-widget-actions-select'; + select.setAttribute('aria-label', t('actions')); + var form = make('form', undefined, 'shd-widget-actions-form'); + var status = make('div', '', 'shd-widget-actions-status'); + var buttons = make('div', undefined, 'shd-widget-actions-buttons'); + var submit = make('button', t('submit'), 'shd-widget-actions-submit'); + submit.type = 'submit'; + var cancel = make('button', t('cancel'), 'shd-widget-actions-cancel'); + cancel.type = 'button'; + buttons.append(submit, cancel); + panel.append(header, select, form, status, buttons); + root.append(panel); + elements = { panel: panel, selection: header.lastChild, select: select, form: form, status: status, submit: submit, cancel: cancel }; + select.addEventListener('change', function () { + var selected = actionList().find(function (action) { return action.id === select.value; }); + state.action = selected || null; + renderForm(); + }); + cancel.addEventListener('click', function () { + state.action = null; + select.value = ''; + renderForm(); + }); + form.addEventListener('submit', function (event) { + event.preventDefault(); + submitAction(); + }); + } + + function inputValue(fieldConfig) { + var value = firstValue(fieldConfig.source || []); + if (value === undefined && fieldConfig.default !== undefined) value = fieldConfig.default; + return value; + } + + function createInput(fieldConfig) { + var type = fieldConfig.type || 'text'; + var input; + if (type === 'textarea' || type === 'json') { + input = document.createElement('textarea'); + input.rows = type === 'json' ? 4 : 3; + if (type === 'json') input.placeholder = '{ }'; + } else if (type === 'select') { + input = document.createElement('select'); + (fieldConfig.options || []).forEach(function (option) { + var item = document.createElement('option'); + item.value = String(option[0]); + item.textContent = state.lang === 'ru' ? String(option[1]) : String(option[2] || option[1]); + input.append(item); + }); + } else { + input = document.createElement('input'); + input.type = type === 'number' || type === 'email' ? type : 'text'; + } + input.className = 'shd-widget-actions-input'; + input.name = fieldConfig.name; + var value = inputValue(fieldConfig); + if (type === 'checkbox') { + input.type = 'checkbox'; + input.checked = value === true || value === 'true' || value === 1 || value === '1'; + } else if (value !== undefined && value !== null) { + input.value = type === 'json' && typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value); + } + if (fieldConfig.required) input.required = true; + return input; + } + + function renderForm() { + ensurePanel(); + var selectedActionId = state.action ? state.action.id : ''; + elements.select.replaceChildren(); + var placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = t('chooseAction'); + elements.select.append(placeholder); + actionList().forEach(function (action) { + var option = document.createElement('option'); + option.value = action.id; + option.textContent = actionLabel(action); + elements.select.append(option); + }); + elements.select.value = selectedActionId; + elements.select.hidden = actionList().length === 0; + elements.form.replaceChildren(); + elements.status.textContent = ''; + elements.submit.disabled = state.busy; + var action = state.action; + if (!action) { + elements.selection.textContent = state.selected ? text(state.selected.title || state.selected.name || state.selected.id || '') : ''; + elements.submit.hidden = true; + elements.cancel.hidden = true; + if (actionList().length && !state.selected && actionList().every(hasTargets)) { + elements.form.append(make('div', t('selectRecord'), 'shd-widget-actions-hint')); + } + return; + } + elements.selection.textContent = state.selected ? text(state.selected.title || state.selected.name || state.selected.id || '') : ''; + elements.submit.hidden = false; + elements.cancel.hidden = false; + if (hasTargets(action) && !state.selected) elements.form.append(make('div', t('selectRecord'), 'shd-widget-actions-hint')); + (action.fields || []).forEach(function (fieldConfig) { + if (!fieldConfig || !fieldConfig.name || fieldConfig.generated || fieldConfig.type === 'hidden') return; + var wrapper = make('label', undefined, 'shd-widget-actions-field'); + wrapper.append(make('span', label(fieldConfig.label))); + wrapper.append(createInput(fieldConfig)); + elements.form.append(wrapper); + }); + if (action.confirm) { + var confirmLabel = make('label', undefined, 'shd-widget-actions-confirm'); + var confirm = document.createElement('input'); + confirm.type = 'checkbox'; + confirm.name = '__confirm'; + confirm.required = true; + confirmLabel.append(confirm, make('span', t('confirm'))); + elements.form.append(confirmLabel); + } + var notice = make('div', action.destructive ? t('destructive') : t('warning'), action.destructive ? 'shd-widget-actions-warning shd-widget-actions-danger' : 'shd-widget-actions-warning'); + elements.form.append(notice); + } + + function coerce(value, type) { + if (type === 'number') { + var number = Number(value); + return Number.isFinite(number) ? number : value; + } + if (type === 'checkbox') return Boolean(value); + if (type === 'json') { + try { return JSON.parse(value); } catch (error) { throw new Error(t('invalidJson')); } + } + return value; + } + + function targetType(name) { + return /^(task|document|contract|booking|item|monitor|proposal|approval|page|notification|topic|membership|organization|space|status|project)_?id$/.test(name) || name === 'issue_number' || name === 'number' ? 'number' : 'text'; + } + + function targetList(action) { + if (Array.isArray(action.targets)) return action.targets; + return action.target ? [action.target] : []; + } + + function collectArguments(action) { + var args = Object.assign({}, action.fixed || {}); + (action.context || []).forEach(function (name) { + var value = firstValue([name]); + if (value !== undefined && value !== null && value !== '') args[name] = value; + }); + targetList(action).forEach(function (targetConfig) { + var value = firstValue(targetConfig.source || targetConfig.name); + if (value === undefined || value === null || value === '') throw new Error(t('selectRecord')); + args[targetConfig.name] = coerce(value, targetConfig.type || targetType(targetConfig.name)); + }); + (action.fields || []).forEach(function (fieldConfig) { + if (!fieldConfig || !fieldConfig.name || fieldConfig.generated || fieldConfig.type === 'hidden') return; + var input = Array.prototype.find.call(elements.form.querySelectorAll('[name]'), function (node) { + return node.getAttribute('name') === fieldConfig.name; + }); + if (!input) return; + var raw = fieldConfig.type === 'checkbox' ? input.checked : input.value.trim(); + if (raw === '' && !input.checked && !fieldConfig.required) return; + if (raw === '' && fieldConfig.required) throw new Error(t('required')); + args[fieldConfig.name] = coerce(raw, fieldConfig.type || 'text'); + }); + if (action.idempotencyKey) args.idempotency_key = 'shd-widget-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10); + if (action.confirmArg) args.confirm = true; + return args; + } + + function setStatus(message, error) { + elements.status.textContent = message || ''; + elements.status.className = 'shd-widget-actions-status' + (error ? ' shd-widget-actions-status-error' : ''); + } + + function submitAction() { + if (state.busy || !state.action) return; + var action = state.action; + var args; + try { args = collectArguments(action); } catch (error) { setStatus(error.message || t('failure'), true); return; } + state.busy = true; + elements.submit.disabled = true; + elements.cancel.disabled = true; + setStatus(t('loading'), false); + request('tools/call', { name: action.tool, arguments: args }).then(function (result) { + unwrap(result); + setStatus(t('success'), false); + var refresh = document.getElementById('refresh'); + if (refresh && typeof refresh.click === 'function') window.setTimeout(function () { refresh.click(); }, 250); + }).catch(function (error) { + setStatus(error && error.message ? error.message : t('failure'), true); + }).finally(function () { + state.busy = false; + elements.submit.disabled = false; + elements.cancel.disabled = false; + }); + } + + function recordFromNode(node) { + var data = node && node.dataset ? node.dataset : {}; + var recordId = String(data.shdRecordId || '').toLowerCase(); + if (recordId) { + var exact = state.records.find(function (item) { + return text(item.id || item.code || item.key || item.number).toLowerCase() === recordId; + }); + if (exact) return exact; + } + var recordTitle = String(data.shdRecordTitle || '').toLowerCase(); + if (recordTitle) { + var titled = state.records.find(function (item) { + return text(item.title || item.name || item.display_name || item.number).toLowerCase() === recordTitle; + }); + if (titled) return titled; + } + var ariaLabel = node && node.getAttribute ? String(node.getAttribute('aria-label') || '').toLowerCase() : ''; + return state.records.find(function (item) { + var title = text(item.title || item.name || item.display_name || item.number).toLowerCase(); + return title && title === ariaLabel; + }) || null; + } + + function update(value) { + var payload = normalize(value) || {}; + var widget = widgetFrom(payload); + if (Object.keys(widget).length) state.widget = widget; + var records = recordsFrom(payload); + if (records.length || Array.isArray(payload.data) || Array.isArray(payload.items) || Array.isArray(payload.projects)) { + state.records = records; + if (state.selected && !state.records.some(function (item) { + var selectedId = text(state.selected.id || state.selected.code || state.selected.key || state.selected.number).toLowerCase(); + var itemId = text(item.id || item.code || item.key || item.number).toLowerCase(); + return selectedId && itemId && selectedId === itemId; + })) { + state.selected = null; + state.action = null; + } + } + ensurePanel(); + var actions = actionList(); + elements.panel.hidden = actions.length === 0; + if (actions.length && !state.action) renderForm(); + else if (actions.length) renderForm(); + } + + document.addEventListener('click', function (event) { + var node = event.target && event.target.closest ? event.target.closest('.record,.project-row,.visual-card,.progress-card,.preview-card,.calendar-card,.timeline-card,.kanban-card') : null; + if (!node || (elements.panel && elements.panel.contains(node))) return; + var item = recordFromNode(node); + if (!item) return; + state.selected = item; + ensurePanel(); + renderForm(); + }, true); + + window.addEventListener('shd-widget-data', function (event) { + update(event && event.detail); + }); + + window.__SHD_WIDGET_ACTIONS_UPDATE__ = update; + + window.addEventListener('message', function (event) { + if (event.source !== window.parent) return; + var message = event.data; + if (!message || message.jsonrpc !== '2.0') return; + if (message.id !== undefined && pending.has(message.id)) { + var requestState = pending.get(message.id); + pending.delete(message.id); + window.clearTimeout(requestState.timeout); + if (message.error) requestState.reject(new Error(message.error.message || t('failure'))); + else requestState.resolve(message.result); + return; + } + }, { passive: true }); + + ensurePanel(); + var initial = typeof window !== 'undefined' && (window.__SHD_WIDGET_STATE__ || (window.openai && (window.openai.toolOutput || window.openai.toolInput))); + if (initial) update(initial); +}()); diff --git a/plugins/shd-mcp-plugin/widgets/acl-matrix/widget.json b/plugins/shd-mcp-plugin/widgets/acl-matrix/widget.json new file mode 100644 index 0000000..fca5dc1 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/acl-matrix/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-acl-matrix", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/acl-matrix/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/active-projects/source.html b/plugins/shd-mcp-plugin/widgets/active-projects/source.html new file mode 100644 index 0000000..360f1f6 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/active-projects/source.html @@ -0,0 +1,1812 @@ + + + + + + + + + +
+
+
+

Активные проекты

+

Сроки завершения и текущие статусы

+
+ +
+
+ +
+
+
+
+ Проект + Дата завершения + Статус +
+
+ + +
+ +
+ + + + diff --git a/plugins/shd-mcp-plugin/widgets/active-projects/widget.json b/plugins/shd-mcp-plugin/widgets/active-projects/widget.json new file mode 100644 index 0000000..c6692ea --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/active-projects/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-active-projects", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/active-projects/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/activity/widget.json b/plugins/shd-mcp-plugin/widgets/activity/widget.json new file mode 100644 index 0000000..6ee4e2f --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/activity/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-activity", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/activity/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/agents-health/widget.json b/plugins/shd-mcp-plugin/widgets/agents-health/widget.json new file mode 100644 index 0000000..cf18d53 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/agents-health/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-agents-health", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/agents-health/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/agents/widget.json b/plugins/shd-mcp-plugin/widgets/agents/widget.json new file mode 100644 index 0000000..c47e6de --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/agents/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-agents", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/agents/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/airbase-preview/widget.json b/plugins/shd-mcp-plugin/widgets/airbase-preview/widget.json new file mode 100644 index 0000000..07f4cf0 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/airbase-preview/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-airbase-preview", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/airbase-preview/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/airbase/widget.json b/plugins/shd-mcp-plugin/widgets/airbase/widget.json new file mode 100644 index 0000000..46bff01 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/airbase/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-airbase", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/airbase/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/controller-observability/source.html b/plugins/shd-mcp-plugin/widgets/controller-observability/source.html new file mode 100644 index 0000000..f6dbc13 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/controller-observability/source.html @@ -0,0 +1,1493 @@ + + + + + + SHD controller observability + + + +
+
SHD MCP · controller observability
+

Контроллеры

+
+
+
+ + + diff --git a/plugins/shd-mcp-plugin/widgets/controller-observability/widget.json b/plugins/shd-mcp-plugin/widgets/controller-observability/widget.json new file mode 100644 index 0000000..4f5b937 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/controller-observability/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-controller-observability", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/controller-observability/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/crm-funnel/widget.json b/plugins/shd-mcp-plugin/widgets/crm-funnel/widget.json new file mode 100644 index 0000000..708f917 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/crm-funnel/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-crm-funnel", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/crm-funnel/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/crm/widget.json b/plugins/shd-mcp-plugin/widgets/crm/widget.json new file mode 100644 index 0000000..9f8f763 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/crm/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-crm", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "uri": "ui://shd/crm/v1.html", + "mediaType": "text/html;profile=mcp-app" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/discussions/widget.json b/plugins/shd-mcp-plugin/widgets/discussions/widget.json new file mode 100644 index 0000000..1ddcf5b --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/discussions/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-discussions", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/discussions/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/documents-completeness/widget.json b/plugins/shd-mcp-plugin/widgets/documents-completeness/widget.json new file mode 100644 index 0000000..e1156ff --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/documents-completeness/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-documents-completeness", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/documents-completeness/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/documents/widget.json b/plugins/shd-mcp-plugin/widgets/documents/widget.json new file mode 100644 index 0000000..ae27d7d --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/documents/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-documents", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "uri": "ui://shd/documents/v1.html", + "mediaType": "text/html;profile=mcp-app" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/files/widget.json b/plugins/shd-mcp-plugin/widgets/files/widget.json new file mode 100644 index 0000000..75db914 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/files/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-files", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/files/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/finance-dashboard/widget.json b/plugins/shd-mcp-plugin/widgets/finance-dashboard/widget.json new file mode 100644 index 0000000..a8b576c --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/finance-dashboard/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-finance-dashboard", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/finance-dashboard/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/finance/widget.json b/plugins/shd-mcp-plugin/widgets/finance/widget.json new file mode 100644 index 0000000..915a354 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/finance/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-finance", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "uri": "ui://shd/finance/v1.html", + "mediaType": "text/html;profile=mcp-app" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/gitea-board/widget.json b/plugins/shd-mcp-plugin/widgets/gitea-board/widget.json new file mode 100644 index 0000000..95a2f8e --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/gitea-board/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-gitea-board", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/gitea-board/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/gitea/widget.json b/plugins/shd-mcp-plugin/widgets/gitea/widget.json new file mode 100644 index 0000000..42d1fa2 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/gitea/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-gitea", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/gitea/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/inventory-warnings/widget.json b/plugins/shd-mcp-plugin/widgets/inventory-warnings/widget.json new file mode 100644 index 0000000..7ca5e75 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/inventory-warnings/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-inventory-warnings", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/inventory-warnings/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/inventory/widget.json b/plugins/shd-mcp-plugin/widgets/inventory/widget.json new file mode 100644 index 0000000..865b811 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/inventory/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-inventory", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/inventory/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/notes-tree/widget.json b/plugins/shd-mcp-plugin/widgets/notes-tree/widget.json new file mode 100644 index 0000000..30f643a --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/notes-tree/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-notes-tree", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/notes-tree/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/notes/widget.json b/plugins/shd-mcp-plugin/widgets/notes/widget.json new file mode 100644 index 0000000..be679f2 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/notes/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-notes", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/notes/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/notifications/widget.json b/plugins/shd-mcp-plugin/widgets/notifications/widget.json new file mode 100644 index 0000000..6604c9e --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/notifications/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-notifications", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/notifications/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/organizations/widget.json b/plugins/shd-mcp-plugin/widgets/organizations/widget.json new file mode 100644 index 0000000..6631872 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/organizations/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-organizations", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/organizations/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/pdf-scheme-editor-progress/widget.json b/plugins/shd-mcp-plugin/widgets/pdf-scheme-editor-progress/widget.json new file mode 100644 index 0000000..c0f6fe4 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/pdf-scheme-editor-progress/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-pdf-scheme-editor-progress", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/pdf-scheme-editor-progress/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/pdf-scheme-editor/widget.json b/plugins/shd-mcp-plugin/widgets/pdf-scheme-editor/widget.json new file mode 100644 index 0000000..a5d1a45 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/pdf-scheme-editor/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-pdf-scheme-editor", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/pdf-scheme-editor/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/project-overview/widget.json b/plugins/shd-mcp-plugin/widgets/project-overview/widget.json new file mode 100644 index 0000000..8578006 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/project-overview/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-project-overview", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/project-overview/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/project-timeline/widget.json b/plugins/shd-mcp-plugin/widgets/project-timeline/widget.json new file mode 100644 index 0000000..8f86645 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/project-timeline/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-project-timeline", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/project-timeline/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/proposal-approvals/widget.json b/plugins/shd-mcp-plugin/widgets/proposal-approvals/widget.json new file mode 100644 index 0000000..9f73756 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/proposal-approvals/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-proposal-approvals", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/proposal-approvals/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/proposals/widget.json b/plugins/shd-mcp-plugin/widgets/proposals/widget.json new file mode 100644 index 0000000..5db474e --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/proposals/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-proposals", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/proposals/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/schedule-calendar/widget.json b/plugins/shd-mcp-plugin/widgets/schedule-calendar/widget.json new file mode 100644 index 0000000..c019e4d --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/schedule-calendar/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-schedule-calendar", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/schedule-calendar/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/scheduling/widget.json b/plugins/shd-mcp-plugin/widgets/scheduling/widget.json new file mode 100644 index 0000000..6878458 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/scheduling/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-scheduling", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/scheduling/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/status-page/widget.json b/plugins/shd-mcp-plugin/widgets/status-page/widget.json new file mode 100644 index 0000000..9aeb302 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/status-page/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-status-page", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/status-page/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/task-kanban/widget.json b/plugins/shd-mcp-plugin/widgets/task-kanban/widget.json new file mode 100644 index 0000000..8150139 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/task-kanban/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-task-kanban", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/task-kanban/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/tasks/widget.json b/plugins/shd-mcp-plugin/widgets/tasks/widget.json new file mode 100644 index 0000000..b5e2ea6 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/tasks/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-tasks", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "uri": "ui://shd/tasks/v1.html", + "mediaType": "text/html;profile=mcp-app" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/plugins/shd-mcp-plugin/widgets/team-workload/widget.json b/plugins/shd-mcp-plugin/widgets/team-workload/widget.json new file mode 100644 index 0000000..1b9c638 --- /dev/null +++ b/plugins/shd-mcp-plugin/widgets/team-workload/widget.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "name": "shd-team-workload", + "version": "1.0.0", + "resources": [ + { + "file": "index.html", + "mediaType": "text/html;profile=mcp-app", + "uri": "ui://shd/team-workload/v1.html" + } + ], + "license": "../../../../../LICENSE", + "source": { + "repository": "https://github.com/bulava92/shd-mcp-plugin" + } +} diff --git a/scripts/generate-widget-artifacts.mjs b/scripts/generate-widget-artifacts.mjs new file mode 100644 index 0000000..ceb9f6f --- /dev/null +++ b/scripts/generate-widget-artifacts.mjs @@ -0,0 +1,342 @@ +import { createHash } from 'node:crypto'; +import { gzipSync } from 'node:zlib'; +import { + existsSync, + mkdirSync, + readFileSync, + readdirSync, + writeFileSync, +} from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { runInNewContext } from 'node:vm'; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const widgetsRoot = join(repositoryRoot, 'plugins', 'shd-mcp-plugin', 'widgets'); +const templatePath = join(widgetsRoot, '_shared', 'universal-widget.html'); +const configPath = join(widgetsRoot, '_shared', 'universal-widget-config.json'); +const themePath = join(widgetsRoot, '_shared', 'widget-theme.css'); +const actionsCssPath = join(widgetsRoot, '_shared', 'write-actions.css'); +const actionsJsPath = join(widgetsRoot, '_shared', 'write-actions.js'); +const bootstrapPath = join(widgetsRoot, 'airbase-preview', 'v1', 'index.html'); +const moduleToken = '__SHD_WIDGET_MODULE__'; +const configToken = '__SHD_WIDGET_CONFIG__'; +const themeToken = '__SHD_WIDGET_THEME_CSS__'; +const actionsCssToken = '__SHD_WIDGET_ACTIONS_CSS__'; +const actionsJsToken = '__SHD_WIDGET_ACTIONS_JS__'; +const engineName = 'shd-universal-v1'; +const checkOnly = process.argv.includes('--check'); +const extractConfigOnly = process.argv.includes('--extract-config'); +const migrateTemplate = process.argv.includes('--migrate-template'); +const excludedWidgets = new Set(['active-projects', 'controller-observability']); +const themedSpecializedWidgets = new Set(['active-projects', 'controller-observability']); +const runtimeModuleByArtifact = new Map([ + ['pdf-scheme-editor-progress', 'schemes-progress'], +]); + +function fail(message) { + throw new Error(`[widget-artifacts] ${message}`); +} + +function readWidgetTheme() { + if (!existsSync(themePath)) fail(`widget theme is missing: ${themePath}`); + const theme = readFileSync(themePath, 'utf8').trim(); + if (!theme) fail(`widget theme is empty: ${themePath}`); + return theme; +} + +function readActionsCss() { + if (!existsSync(actionsCssPath)) fail(`widget actions CSS is missing: ${actionsCssPath}`); + const css = readFileSync(actionsCssPath, 'utf8').trim(); + if (!css) fail(`widget actions CSS is empty: ${actionsCssPath}`); + return css; +} + +function readActionsJs() { + if (!existsSync(actionsJsPath)) fail(`widget actions JavaScript is missing: ${actionsJsPath}`); + const source = readFileSync(actionsJsPath, 'utf8').trim(); + if (!source) fail(`widget actions JavaScript is empty: ${actionsJsPath}`); + return source; +} + +function ensureCanonicalTemplate() { + if (existsSync(templatePath)) return; + if (checkOnly) fail(`canonical template is missing: ${templatePath}`); + if (!existsSync(bootstrapPath)) fail(`bootstrap widget is missing: ${bootstrapPath}`); + + const source = readFileSync(bootstrapPath, 'utf8'); + const template = source + .replace( + 'data-widget-module="airbase-preview"', + `data-shd-widget-engine="${engineName}" data-widget-module="${moduleToken}"`, + ) + .replace( + "var DEFAULT_MODULE='airbase-preview'", + `var DEFAULT_MODULE='${moduleToken}'`, + ); + if (template === source || !template.includes(moduleToken)) { + fail('could not bootstrap the universal template from airbase-preview'); + } + writeFileSync(templatePath, template); +} + +function isRecord(value) { + return value && typeof value === 'object' && !Array.isArray(value); +} + +function extractWidgetConfig(template) { + const start = template.indexOf(' var M='); + const end = template.indexOf(' var pending=', start); + if (start < 0 || end < 0 || end <= start) { + fail('could not locate the legacy M/S configuration block in the canonical template'); + } + + const block = template.slice(start, end); + let extracted; + try { + extracted = runInNewContext(`(() => { ${block}; return { copy: M, defaults: S }; })()`); + } catch (error) { + fail(`could not evaluate the legacy M/S configuration block: ${error instanceof Error ? error.message : String(error)}`); + } + if (!isRecord(extracted) || !isRecord(extracted.copy) || !isRecord(extracted.defaults)) { + fail('legacy M/S configuration block did not produce copy/defaults objects'); + } + return extracted; +} + +function readWidgetConfig() { + if (!existsSync(configPath)) return null; + let config; + try { + config = JSON.parse(readFileSync(configPath, 'utf8')); + } catch (error) { + fail(`widget config is not valid JSON: ${error instanceof Error ? error.message : String(error)}`); + } + if (!isRecord(config)) fail('widget config must be a JSON object'); + if (config.engine !== engineName) fail(`widget config engine must be ${engineName}`); + if (!isRecord(config.copy) || !isRecord(config.defaults)) { + fail('widget config must contain copy and defaults objects'); + } + return config; +} + +function ensureWidgetConfig(template) { + const existing = readWidgetConfig(); + if (existing) return existing; + if (checkOnly) fail(`widget config is missing: ${configPath}`); + + const extracted = extractWidgetConfig(template); + const config = { + version: 1, + engine: engineName, + copy: extracted.copy, + defaults: extracted.defaults, + }; + writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`); + return config; +} + +function migrateCanonicalTemplate(template) { + if (template.includes(configToken)) return template; + if (!migrateTemplate) { + fail(`canonical template must contain ${configToken}; run with --migrate-template once`); + } + + const start = template.indexOf(' var M='); + const end = template.indexOf(' var pending=', start); + if (start < 0 || end < 0 || end <= start) { + fail('could not locate the legacy M/S configuration block for migration'); + } + const replacement = [ + ' var MODULE_CONFIG=__SHD_WIDGET_CONFIG__;', + ' var M=MODULE_CONFIG.copy;', + ' var S=MODULE_CONFIG.defaults;', + ].join('\n') + '\n'; + const migrated = `${template.slice(0, start)}${replacement}${template.slice(end)}`; + writeFileSync(templatePath, migrated); + return migrated; +} + +function readUniversalWidgets() { + return readdirSync(widgetsRoot, { withFileTypes: true }) + .filter((entry) => entry.isDirectory() && !excludedWidgets.has(entry.name) && entry.name !== '_shared') + .map((entry) => entry.name) + .filter((name) => existsSync(join(widgetsRoot, name, 'widget.json'))) + .sort(); +} + +function expectedArtifact(path, content) { + if (checkOnly) { + const current = existsSync(path) ? readFileSync(path, 'utf8') : null; + if (current !== content) fail(`generated file is stale: ${path}`); + return; + } + if (!existsSync(path) || readFileSync(path, 'utf8') !== content) writeFileSync(path, content); +} + +function injectActionsJs(html, actionsJs) { + let rendered = html.replaceAll(actionsJsToken, actionsJs); + const legacyPattern = /[\s\S]*?<\/script>/i; + if (legacyPattern.test(rendered)) { + rendered = rendered.replace( + legacyPattern, + ``, + ); + } + return rendered; +} + +function renderHtml(template, module, config, theme, actionsCss, actionsJs) { + const serializedConfig = JSON.stringify({ copy: config.copy, defaults: config.defaults }) + .replaceAll('<', '\\u003c'); + const html = injectActionsJs(template + .replaceAll(moduleToken, module) + .replaceAll(configToken, serializedConfig) + .replaceAll(themeToken, theme) + .replaceAll(actionsCssToken, actionsCss), actionsJs); + if (html.includes(moduleToken)) fail(`unresolved module token for ${module}`); + if (html.includes(configToken)) fail(`unresolved config token for ${module}`); + if (html.includes(themeToken)) fail(`unresolved theme token for ${module}`); + if (html.includes(actionsCssToken)) fail(`unresolved actions CSS token for ${module}`); + if (html.includes(actionsJsToken)) fail(`unresolved actions JavaScript token for ${module}`); + if (!html.includes(`data-shd-widget-engine="${engineName}"`)) fail(`engine marker is missing for ${module}`); + if (!html.includes(`data-widget-module="${module}"`)) fail(`module marker is missing for ${module}`); + if (!html.includes(`var DEFAULT_MODULE='${module}'`)) fail(`default module is missing for ${module}`); + return html; +} + +function renderSpecializedHtml(source, module, theme, actionsCss, actionsJs) { + const stylePattern = /]*>[\s\S]*?<\/style>/i; + if (!stylePattern.test(source)) fail(`specialized widget has no inline style block: ${module}`); + + let html = source.replace( + stylePattern, + ``, + ); + const actionsPattern = /[\s\S]*?<\/style>/i; + if (actionsPattern.test(html)) { + html = html.replace( + actionsPattern, + ``, + ); + } + html = injectActionsJs(html, actionsJs); + if (!html.includes(`data-shd-widget-engine="${engineName}"`)) { + const mainPattern = /]*>/i; + const main = html.match(mainPattern)?.[0]; + if (!main) fail(`specialized widget has no main element: ${module}`); + html = html.replace(mainPattern, main.replace(/>$/, ` data-shd-widget-engine="${engineName}" data-widget-module="${module}">`)); + } else { + html = html.replace(/data-widget-module="[^"]*"/, `data-widget-module="${module}"`); + } + if (!html.includes(`data-shd-widget-engine="${engineName}"`)) fail(`engine marker is missing for ${module}`); + if (!html.includes(`data-widget-module="${module}"`)) fail(`module marker is missing for ${module}`); + return html; +} + +function manifestFor(widgetPath, html, module, kind = 'universal') { + const manifestPath = join(dirname(widgetPath), 'widget.json'); + if (!existsSync(manifestPath)) fail(`manifest is missing: ${manifestPath}`); + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); + const resource = Array.isArray(manifest.resources) + ? manifest.resources.find((item) => item?.file === 'index.html') + : null; + if (!resource) fail(`index.html resource is missing from ${manifestPath}`); + + const bytes = Buffer.byteLength(html, 'utf8'); + const digest = createHash('sha256').update(html, 'utf8').digest('hex'); + resource.bytes = bytes; + resource.gzipBytes = gzipSync(Buffer.from(html, 'utf8'), { mtime: 0 }).length; + resource.sha256 = digest; + const source = kind === 'universal' + ? { + ...(manifest.source || {}), + path: 'plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html', + config: 'plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json', + theme: 'plugins/shd-mcp-plugin/widgets/_shared/widget-theme.css', + actions: 'plugins/shd-mcp-plugin/widgets/_shared/write-actions.css', + generatedBy: 'scripts/generate-widget-artifacts.mjs', + engine: engineName, + } + : { + ...(manifest.source || {}), + path: `plugins/shd-mcp-plugin/widgets/${module}/source.html`, + theme: 'plugins/shd-mcp-plugin/widgets/_shared/widget-theme.css', + ...(html.includes('data-shd-widget-write-actions="js"') + ? { actions: 'plugins/shd-mcp-plugin/widgets/_shared/write-actions.css' } + : {}), + generatedBy: 'scripts/generate-widget-artifacts.mjs', + engine: engineName, + }; + manifest.source = source; + if (manifest.source.module && manifest.source.module !== module) delete manifest.source.module; + return { manifest, resource, digest }; +} + +function upstreamFor(resource, kind = 'universal', manifest = {}) { + const heading = `# ${manifest.name || 'SHD widget'}`; + const code = String.fromCharCode(96); + const sourceDescription = kind === 'universal' + ? `The standalone HTML is generated from ${code}widgets/_shared/universal-widget.html${code}, ${code}widgets/_shared/universal-widget-config.json${code} and ${code}widgets/_shared/widget-theme.css${code} by ${code}scripts/generate-widget-artifacts.mjs${code} using the ${code}${engineName}${code} engine.` + : `The specialized renderer remains in its module directory, while its shared visual layer is injected from ${code}widgets/_shared/widget-theme.css${code} by ${code}scripts/generate-widget-artifacts.mjs${code}.`; + return `${heading}\n\nThis artifact is served by the SHD MCP backend as ${resource.uri}.\n\n${sourceDescription} It uses the MCP Apps bridge first and keeps ${code}window.openai${code} as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.\n`; +} + +function main() { + ensureCanonicalTemplate(); + let template = readFileSync(templatePath, 'utf8'); + const theme = readWidgetTheme(); + const actionsCss = readActionsCss(); + const actionsJs = readActionsJs(); + const configExisted = existsSync(configPath); + const config = ensureWidgetConfig(template); + if (extractConfigOnly) { + console.log(`${configExisted ? 'checked' : 'extracted'} widget config: ${configPath}`); + return; + } + template = migrateCanonicalTemplate(template); + if (!template.includes(moduleToken)) fail(`canonical template must contain ${moduleToken}`); + if (!template.includes(configToken)) fail(`canonical template must contain ${configToken}`); + + const widgets = readUniversalWidgets(); + if (widgets.length === 0) fail('no universal widget artifacts found'); + let generated = 0; + for (const artifactName of widgets) { + const module = runtimeModuleByArtifact.get(artifactName) || artifactName; + const widgetPath = join(widgetsRoot, artifactName, 'v1'); + mkdirSync(widgetPath, { recursive: true }); + const html = renderHtml(template, module, config, theme, actionsCss, actionsJs); + const { manifest, resource, digest } = manifestFor(widgetPath, html, module, 'universal'); + const manifestText = `${JSON.stringify(manifest, null, 2)}\n`; + expectedArtifact(join(widgetPath, 'index.html'), html); + expectedArtifact(join(widgetPath, 'manifest.json'), manifestText); + expectedArtifact(join(widgetPath, 'SHA256SUMS'), `${digest} index.html\n`); + expectedArtifact(join(widgetPath, 'UPSTREAM.md'), upstreamFor(resource, 'universal', manifest)); + generated += 1; + } + + let specialized = 0; + for (const artifactName of [...themedSpecializedWidgets].sort()) { + const widgetPath = join(widgetsRoot, artifactName, 'v1'); + mkdirSync(widgetPath, { recursive: true }); + const sourcePath = join(widgetsRoot, artifactName, 'source.html'); + const source = readFileSync(sourcePath, 'utf8'); + const html = renderSpecializedHtml(source, artifactName, theme, actionsCss, actionsJs); + const { manifest, resource, digest } = manifestFor(widgetPath, html, artifactName, 'specialized'); + const manifestText = `${JSON.stringify(manifest, null, 2)}\n`; + expectedArtifact(join(widgetPath, 'index.html'), html); + expectedArtifact(join(widgetPath, 'manifest.json'), manifestText); + expectedArtifact(join(widgetPath, 'SHA256SUMS'), `${digest} index.html\n`); + expectedArtifact(join(widgetPath, 'UPSTREAM.md'), upstreamFor(resource, 'specialized', manifest)); + specialized += 1; + } + + console.log(`${checkOnly ? 'checked' : 'generated'} ${generated} universal and ${specialized} specialized widget artifacts; engine=${engineName}`); +} + +try { + main(); +} catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; +}