chore: keep widget sources only
This commit is contained in:
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"shd": {
|
||||
"type": "http",
|
||||
"url": "https://shd.xyz.su/mcp",
|
||||
"oauth_resource": "https://shd.xyz.su/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
Binary file not shown.
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: Active SHD projects
|
||||
short_description: Project deadlines and statuses
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD agents operations"
|
||||
short_description: "Diagnose agents and guarded runtime actions"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: AirBase audit
|
||||
short_description: Read-only schema and data audit
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD analytics
|
||||
short_description: Metrics, comparisons and reports
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD discussions"
|
||||
short_description: "Search and manage discussion threads"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD documents"
|
||||
short_description: "Inspect and safely manage documents"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD entity resolution
|
||||
short_description: Resolve CRM records and duplicates
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD estimates and proposals
|
||||
short_description: Estimate and document workflows
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD events and scheduling
|
||||
short_description: Slots, bookings and event positions
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD finance and CRM
|
||||
short_description: Finance and customer workflows
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD financial audit
|
||||
short_description: Balances, payments and settlements
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD Gitea"
|
||||
short_description: "Review and update linked Gitea issues"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD inventory"
|
||||
short_description: "Audit stock, assets and procurement"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD notifications"
|
||||
short_description: "Review alerts and notification state"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD organizations and ACL"
|
||||
short_description: "Audit members, roles and access policies"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD project files
|
||||
short_description: Inspect and safely manage files
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD project status
|
||||
short_description: Project summaries and risks
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD realtime and activity"
|
||||
short_description: "Inspect events and manage live sessions"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD routing and safety
|
||||
short_description: Route requests to safe MCP tools
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD safe bulk changes
|
||||
short_description: Plan and validate bulk mutations
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD status page"
|
||||
short_description: "Inspect monitors and publish incidents safely"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD task workflow
|
||||
short_description: Read and safely update tasks
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: "SHD Terms and contracts"
|
||||
short_description: "Review revisions and contract completeness"
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
interface:
|
||||
display_name: SHD Wiki and notes
|
||||
short_description: Pages, revisions and note databases
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
@@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>SHD MCP</title>
|
||||
<style data-shd-widget-theme="shared">
|
||||
__SHD_WIDGET_THEME_CSS__
|
||||
</style>
|
||||
<style data-shd-widget-write-actions="css">
|
||||
__SHD_WIDGET_ACTIONS_CSS__
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell" data-shd-widget-engine="shd-universal-v1" data-widget-module="__SHD_WIDGET_MODULE__"><section class="card">
|
||||
<div class="eyebrow">SHD MCP</div><h1 id="title">SHD</h1><p class="lede" id="desc"></p><div class="summary" id="summary"></div>
|
||||
<div class="toolbar"><input class="search" id="search" type="search" autocomplete="off"><button class="refresh" id="refresh" type="button"></button></div>
|
||||
<div class="status" id="status" role="status" aria-live="polite"></div>
|
||||
<div class="table-wrap"><table><thead><tr><th id="head-title"></th><th id="head-status"></th><th id="head-date"></th><th id="head-value"></th></tr></thead><tbody id="rows"></tbody></table><div class="empty" id="empty" hidden></div></div><div class="visual" id="visual" hidden></div>
|
||||
<section class="details" id="details" hidden aria-live="polite"><div class="detail-head"><div><h2 id="detail-title"></h2><div class="sub" id="detail-sub"></div></div><button class="close" id="close" type="button">×</button></div><div class="grid" id="grid"></div></section>
|
||||
</section></main>
|
||||
<script>
|
||||
(function(){
|
||||
'use strict';
|
||||
var DEFAULT_MODULE='__SHD_WIDGET_MODULE__';
|
||||
var C={ru:{refresh:'Обновить',search:'Поиск по названию, статусу или ID',loading:'Загрузка…',empty:'Нет данных для отображения.',records:'записей',status:'Статус',date:'Дата',value:'Детали',id:'ID',details:'Детали',close:'Закрыть',load:'Не удалось загрузить данные.',detail:'Не удалось загрузить детали.',context:'Для обновления нужен контекст источника',total:'Всего',amount:'Сумма',planned:'План',capacity:'Ёмкость',utilization:'Загрузка',overload:'Перегрузка',warnings:'Предупреждения',complete:'Готово',pending:'Ожидают',approved:'Согласовано',rejected:'Отклонено',progress:'Прогресс',score:'Оценка',member:'Участник',role:'Роль',permissions:'Разрешения',preview:'Предпросмотр',noWarnings:'Предупреждений нет.',health:'Состояние',yes:'Да',no:'Нет'},en:{refresh:'Refresh',search:'Search by name, status or ID',loading:'Loading…',empty:'No data to display.',records:'records',status:'Status',date:'Date',value:'Details',id:'ID',details:'Details',close:'Close',load:'Could not load data.',detail:'Could not load details.',context:'A source context is required to refresh.',total:'Total',amount:'Amount',planned:'Planned',capacity:'Capacity',utilization:'Utilization',overload:'Overload',warnings:'Warnings',complete:'Complete',pending:'Pending',approved:'Approved',rejected:'Rejected',progress:'Progress',score:'Score',member:'Member',role:'Role',permissions:'Permissions',preview:'Preview',noWarnings:'No warnings.',health:'Health',yes:'Yes',no:'No'}};
|
||||
var FIELD_LABELS={ru:{id:'ID',key:'Ключ',project_id:'ID проекта',task_project_id:'ID проекта задачи',linked_project_ids:'Связанные проекты',project_code:'Код проекта',base_reference:'Ссылка на базу',base_public_id:'Публичный ID базы',table_public_id:'Публичный ID таблицы',record_public_id:'Публичный ID записи',title:'Название',name:'Название',description:'Описание',status:'Статус',status_id:'ID статуса',is_done:'Завершено',is_complete:'Завершено',section:'Раздел',sort_order:'Порядок сортировки',created_at:'Создано',updated_at:'Обновлено',due_date:'Срок',deadline:'Дедлайн',starts_at:'Начало',ends_at:'Окончание',url:'Ссылка',diagnostic:'Диагностика',message:'Сообщение',type:'Тип',state:'Состояние',phase:'Этап',health:'Состояние',email:'Электронная почта',username:'Имя пользователя',role:'Роль',permissions:'Разрешения',source:'Источник',module:'Модуль',table:'Таблица',fields:'Поля',value:'Значение',stage:'Стадия',approval_status:'Статус согласования',approval_state:'Состояние согласования',priority:'Приоритет',assignee:'Исполнитель',owner:'Владелец',type:'Тип',category:'Категория',permissions:'Разрешения',permission:'Разрешение',read:'Чтение',write:'Запись',manage:'Управление',admin:'Администратор',view:'Просмотр',create:'Создание',update:'Изменение',delete:'Удаление',error:'Ошибка',warning:'Предупреждение',message:'Сообщение',path:'Путь',link:'Ссылка',amount:'Сумма',total:'Итого',quantity:'Количество',available_quantity:'Доступное количество',stock:'Остаток',planned:'План',planned_hours:'Плановые часы',capacity:'Ёмкость',capacity_hours:'Часы ёмкости',utilization:'Загрузка',utilization_percent:'Загрузка, %',overload:'Перегрузка',overload_hours:'Часы перегрузки',score:'Оценка',progress:'Прогресс',completeness_score:'Оценка полноты',completion_percent:'Полнота, %',availability_percent:'Доступность, %',record_count:'Количество записей',revision:'Ревизия',pages:'Страницы',page_count:'Количество страниц',date:'Дата',is_archived:'В архиве',low_stock:'Мало на складе',critical:'Критический',meta:'Метаданные'},en:{id:'ID',key:'Key',project_id:'Project ID',task_project_id:'Task project ID',linked_project_ids:'Linked projects',project_code:'Project code',base_reference:'Base reference',base_public_id:'Public base ID',table_public_id:'Public table ID',record_public_id:'Public record ID',title:'Title',name:'Name',description:'Description',status:'Status',status_id:'Status ID',is_done:'Completed',is_complete:'Completed',section:'Section',sort_order:'Sort order',created_at:'Created',updated_at:'Updated',due_date:'Due date',deadline:'Deadline',starts_at:'Starts',ends_at:'Ends',url:'URL',diagnostic:'Diagnostics',message:'Message',type:'Type',state:'State',phase:'Phase',health:'Health',email:'Email',username:'Username',role:'Role',permissions:'Permissions',source:'Source',module:'Module',table:'Table',fields:'Fields',value:'Value',stage:'Stage',approval_status:'Approval status',approval_state:'Approval state',priority:'Priority',assignee:'Assignee',owner:'Owner',type:'Type',category:'Category',permissions:'Permissions',permission:'Permission',read:'Read',write:'Write',manage:'Manage',admin:'Admin',view:'View',create:'Create',update:'Update',delete:'Delete',error:'Error',warning:'Warning',message:'Message',path:'Path',link:'Link',amount:'Amount',total:'Total',quantity:'Quantity',available_quantity:'Available quantity',stock:'Stock',planned:'Planned',planned_hours:'Planned hours',capacity:'Capacity',capacity_hours:'Capacity hours',utilization:'Utilization',utilization_percent:'Utilization, %',overload:'Overload',overload_hours:'Overload hours',score:'Score',progress:'Progress',completeness_score:'Completeness score',completion_percent:'Completeness, %',availability_percent:'Availability, %',record_count:'Record count',revision:'Revision',pages:'Pages',page_count:'Page count',date:'Date',is_archived:'Archived',low_stock:'Low stock',critical:'Critical',meta:'Metadata'}};
|
||||
var VALUE_LABELS={ru:{Tasks_Status_Todo:'К выполнению',Tasks_Status_Done:'Завершено',Tasks_Status_In_Progress:'В работе',Tasks_Status_Review:'На проверке',Modules_Done:'Завершено',todo:'К выполнению',to_do:'К выполнению','to do':'К выполнению',in_progress:'В работе','in progress':'В работе','in-progress':'В работе',in_progress_status:'В работе',done:'Завершено',completed:'Завершено',complete:'Завершено',closed:'Закрыто',active:'Активно',pending:'Ожидает',review:'На проверке',approved:'Согласовано',rejected:'Отклонено',archived:'В архиве',not_set:'Не задано'},en:{Tasks_Status_Todo:'To do',Tasks_Status_Done:'Done',Tasks_Status_In_Progress:'In progress',Tasks_Status_Review:'In review',Modules_Done:'Done',todo:'To do',to_do:'To do','to do':'To do',in_progress:'In progress','in progress':'In progress','in-progress':'In progress',in_progress_status:'In progress',done:'Done',completed:'Completed',complete:'Complete',closed:'Closed',active:'Active',pending:'Pending',review:'In review',approved:'Approved',rejected:'Rejected',archived:'Archived',not_set:'Not set'}};
|
||||
var MODULE_CONFIG=__SHD_WIDGET_CONFIG__;
|
||||
var M=MODULE_CONFIG.copy;
|
||||
var S=MODULE_CONFIG.defaults;
|
||||
var pending=new Map(),nextId=1,rows=[],state={module:DEFAULT_MODULE,config:Object.assign({},S[DEFAULT_MODULE]),lang:'ru',timezone:'',busy:false};
|
||||
var e={title:document.getElementById('title'),desc:document.getElementById('desc'),search:document.getElementById('search'),refresh:document.getElementById('refresh'),status:document.getElementById('status'),summary:document.getElementById('summary'),tableWrap:document.querySelector('.table-wrap'),visual:document.getElementById('visual'),rows:document.getElementById('rows'),empty:document.getElementById('empty'),details:document.getElementById('details'),detailTitle:document.getElementById('detail-title'),detailSub:document.getElementById('detail-sub'),grid:document.getElementById('grid'),close:document.getElementById('close'),ht:document.getElementById('head-title'),hs:document.getElementById('head-status'),hd:document.getElementById('head-date'),hv:document.getElementById('head-value')};
|
||||
function lang(){var x=typeof window!=='undefined'&&window.openai&&window.openai.locale;x=String(x||document.documentElement.lang||'ru').toLowerCase();return x.indexOf('en')===0?'en':'ru'}
|
||||
function t(k){return(C[state.lang]||C.ru)[k]||k}
|
||||
function obj(x){return x&&typeof x==='object'&&!Array.isArray(x)}
|
||||
function merge(a,b){return Object.assign({},a||{},b||{})}
|
||||
function payload(x){if(!x)return null;if(Object.prototype.hasOwnProperty.call(x,'structuredContent'))return x.structuredContent;if(Object.prototype.hasOwnProperty.call(x,'toolOutput'))return x.toolOutput;return x}
|
||||
function unwrap(x){if(x&&x.isError)throw Error(t('load'));var p=payload(x)||{};if(p.error)throw Error(String(p.error.message||p.error.detail||p.error));if(p.ok===false&&p.message)throw Error(String(p.message));return p}
|
||||
function applyConfig(p){p=payload(p)||{};var m=obj(p.meta)?p.meta:{},w=obj(m.widget)?m.widget:{},mod=String(w.module||m.module||state.module||DEFAULT_MODULE);state.module=M[mod]?mod:DEFAULT_MODULE;state.config=merge(S[state.module],w);if(!obj(state.config.sourceArgs)&&obj(m.sourceArgs))state.config.sourceArgs=m.sourceArgs;state.lang=lang();labels()}
|
||||
function labels(){var m=(M[state.module]||M.discussions)[state.lang]||M.discussions.ru;document.documentElement.lang=state.lang;e.title.textContent=m[0];e.desc.textContent=m[1];e.search.placeholder=t('search');e.refresh.textContent=t('refresh');e.empty.textContent=t('empty');e.close.setAttribute('aria-label',t('close'));e.ht.textContent=m[2];e.hs.textContent=t('status');e.hd.textContent=m[3];e.hv.textContent=m[4]}
|
||||
function req(method,params){var o=typeof window!=='undefined'&&window.openai;if(method==='tools/call'&&o&&typeof o.callTool==='function')return Promise.resolve(o.callTool(params.name,params.arguments||{}));var id='widget-'+nextId++;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(Error(t('load')))},12000);pending.set(id,{resolve:resolve,reject:reject,timeout:timeout})})}
|
||||
function notify(method,params){window.parent.postMessage({jsonrpc:'2.0',method:method,params:params||{}},'*')}
|
||||
function path(x,p){return String(p||'').split('.').reduce(function(v,k){return v&&typeof v==='object'?v[k]:undefined},x)}
|
||||
function findArray(x,d){if(Array.isArray(x))return x;if(!obj(x)||d>2)return null;var keys=['items','records','bookings','upcoming_bookings','topics','notifications','pages','spaces','controllers','agents','user_agents','tables','monitors','data'];for(var i=0;i<keys.length;i++)if(Array.isArray(x[keys[i]]))return x[keys[i]];for(var k in x)if(Object.prototype.hasOwnProperty.call(x,k)){var a=findArray(x[k],d+1);if(a)return a}return null}
|
||||
function flatten(a){var n=state.config.nested||state.config.nestedCollection;if(!n)return a;var out=[];a.forEach(function(parent){if(parent&&Array.isArray(parent[n]))parent[n].forEach(function(child){var x=Object.assign({},child);if(parent.name&&!x.group_name)x.group_name=parent.name;out.push(x)});else out.push(parent)});return out}
|
||||
function list(x){var p=payload(x)||{};if(Array.isArray(p.items))return p.items;var ps=Array.isArray(state.config.paths)?state.config.paths:(Array.isArray(state.config.arrayPaths)?state.config.arrayPaths:[]),combined=[];for(var i=0;i<ps.length;i++){var c=path(p,ps[i]),a=Array.isArray(c)?c:findArray(c,0);if(a){if(state.config.combinePaths)combined=combined.concat(a);else return flatten(a)}}if(state.config.combinePaths&&combined.length)return flatten(combined);var d=p.data===undefined?p:p.data;return flatten(Array.isArray(d)?d:(findArray(d,0)||[]))}
|
||||
function str(x){if(x===null||x===undefined||x==='')return'';if(typeof x==='object'){try{return JSON.stringify(x)}catch(err){return''}}return String(x)}
|
||||
function id(x,i){var keys=['id','key','code','uuid','page_id','topic_id','booking_id','item_id','monitor_id','table'];for(var n=0;n<keys.length;n++)if(x&&x[keys[n]]!==undefined&&x[keys[n]]!==null&&x[keys[n]]!=='')return str(x[keys[n]]);return String(i+1)}
|
||||
function status(x){var v=x&&x.status;if(v&&typeof v==='object')v=v.name||v.label||v.code||v.id;return str(v||(x&&(x.state||x.phase||x.health))||'')}
|
||||
function title(x,i){return str(x&&(x.title||x.name||x.subject||x.display_name||x.label||x.table_name||x.table||x.key))||(t('records')+' '+(i+1))}
|
||||
function date(x){return str(x&&(x.starts_at||x.start_at||x.due_date||x.checked_at||x.updated_at||x.created_at||x.sent_at||x.published_at||x.date))}
|
||||
function value(x){if(!x)return'';if(x.availability_percent!==undefined&&x.availability_percent!==null)return str(x.availability_percent)+'%';if(x.quantity!==undefined&&x.quantity!==null)return str(x.quantity);if(x.record_count!==undefined&&x.record_count!==null)return str(x.record_count);return str(x.value!==undefined?x.value:(x.guest_name||x.space_name||x.group_name||x.controller_type||x.type||x.source||x.module||''))}
|
||||
function sub(x){return str(x&&(x.subtitle||x.project_code||x.space_name||x.channel_name||x.group_name||x.code||x.email))}
|
||||
function normalize(x,i){var y=obj(x)?Object.assign({},x):{value:x};y.id=id(x,i);y.title=title(x,i);y.subtitle=sub(x);y.status=status(x);y.date=date(x);y.value=value(x);return y}
|
||||
function dateOnlyText(raw){var parts=raw.split('-').map(Number);if(parts.length!==3||parts.some(function(part){return!Number.isFinite(part)}))return null;var date=new Date(Date.UTC(parts[0],parts[1]-1,parts[2],12));try{return new Intl.DateTimeFormat(state.lang,{dateStyle:'medium',timeZone:'UTC'}).format(date)}catch(err){return null}}
|
||||
function dateText(x){if(x===null||x===undefined||x==='')return'—';var n;if(x instanceof Date)n=x.getTime();else if(typeof x==='number')n=Math.abs(x)<100000000000?x*1000:x;else{var raw=String(x).trim();if(/^\d{4}-\d{2}-\d{2}$/.test(raw)){var only=dateOnlyText(raw);if(only)return only}var numeric=Number(raw);n=raw!==''&&Number.isFinite(numeric)?(Math.abs(numeric)<100000000000?numeric*1000:numeric):Date.parse(raw);if(Number.isNaN(n)&&raw)n=Date.parse(raw.replace(/([+-]\d{2}):(\d{2})$/,'$1$2'))}if(!Number.isFinite(n))return String(x);var options={dateStyle:'medium',timeStyle:'medium'};if(state.timezone)options.timeZone=state.timezone;try{return new Intl.DateTimeFormat(state.lang,options).format(new Date(n))}catch(err){return String(x)}}
|
||||
function make(tag,v,cls){var n=document.createElement(tag);if(cls)n.className=cls;if(v!==undefined)n.textContent=v;return n}
|
||||
function fieldLabel(key){var raw=String(key||'');var labels=FIELD_LABELS[state.lang]||FIELD_LABELS.ru;return labels[raw]||raw.replace(/[_-]+/g,' ')}
|
||||
function dateKey(key){return['date','time','timestamp','source_timestamp','from','to','start','end','bucket_start','created_at','updated_at','due_date','deadline','starts_at','ends_at','checked_at','sent_at','published_at'].indexOf(String(key||''))!==-1}
|
||||
function detailText(x,key){if(x===null||x===undefined||x==='')return'—';if(typeof x==='boolean')return x?t('yes'):t('no');if(Array.isArray(x))return'['+x.map(function(v){return detailText(v,key)}).join(', ')+']';if(obj(x))return'{'+Object.keys(x).map(function(k){return fieldLabel(k)+': '+detailText(x[k],k)}).join(', ')+'}';var raw=String(x),labels=VALUE_LABELS[state.lang]||VALUE_LABELS.ru;if(dateKey(key)&&!Number.isNaN(Date.parse(raw)))return dateText(raw);return labels[raw]||labels[raw.toLowerCase()]||raw}
|
||||
function renderSummary(){e.summary.replaceChildren();var n=make('span');n.append(make('strong',String(rows.length)),' '+t('records'));e.summary.append(n);var c={};rows.forEach(function(x){if(x.status)c[x.status]=(c[x.status]||0)+1});Object.keys(c).slice(0,3).forEach(function(k){var n=make('span');n.append(make('strong',String(c[k])),' '+detailText(k,'status'));e.summary.append(n)})}
|
||||
function field(x,keys){for(var i=0;i<keys.length;i++){var v=x&&x[keys[i]];if(v!==undefined&&v!==null&&v!=='')return v}return''}
|
||||
function numeric(x,keys){var v=field(x,keys);if(v===null||v===undefined||v==='')return null;if(typeof v==='number')return Number.isFinite(v)?v:null;var n=Number(String(v).replace(/,/g,'.').replace(/[^0-9.-]/g,''));return Number.isFinite(n)?n:null}
|
||||
function groupValue(x,key){var v=key?field(x,[key]):status(x);if(obj(v))v=v.name||v.label||v.code||v.id;return str(v)||'—'}
|
||||
function recordAttrs(node,x){if(!node||!x)return node;var recordId=str(x.id);if(recordId)node.dataset.shdRecordId=recordId;var recordTitle=str(x.title);if(recordTitle)node.dataset.shdRecordTitle=recordTitle;return node}
|
||||
function interactiveRecord(node,x){recordAttrs(node,x);if(node.tagName!=='BUTTON'&&node.tagName!=='A'){node.classList.add('record');node.tabIndex=0;node.setAttribute('role','button');node.onkeydown=function(ev){if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();details(x)}}}return node}
|
||||
function visualCard(x,extra){var b=make('button',undefined,'visual-card'+(extra?' '+extra:''));b.type='button';recordAttrs(b,x);b.append(make('span',x.title||'SHD','visual-title'));if(x.subtitle)b.append(make('span',x.subtitle,'sub'));var line=make('span',undefined,'visual-line');line.append(make('span',detailText(x.status,'status'),'pill'));if(x.date)line.append(make('span',dateText(x.date),'visual-date'));b.append(line);var v=field(x,['total_amount','amount','planned_hours','utilization_percent','score','quantity','revision','pages','value']);if(v!==''&&v!==null)b.append(make('span',detailText(v),'visual-value'));b.onclick=function(){details(x)};return b}
|
||||
function visualEmpty(){e.visual.replaceChildren(make('div',t('empty'),'empty'))}
|
||||
function renderKanban(a){if(!a.length){visualEmpty();return}var groups={};a.forEach(function(x){var g=groupValue(x,state.config.groupBy||'status');(groups[g]||(groups[g]=[])).push(x)});var board=make('div',undefined,'kanban');Object.keys(groups).forEach(function(g){var col=make('section',undefined,'kanban-col');var head=make('div',undefined,'kanban-head');head.append(make('span',detailText(g,state.config.groupBy||'status')),make('span',String(groups[g].length),'pill'));col.append(head);var list=make('div',undefined,'kanban-list');groups[g].forEach(function(x){list.append(visualCard(x))});col.append(list);board.append(col)});e.visual.append(board)}
|
||||
function renderTimeline(a){if(!a.length){visualEmpty();return}var wrap=make('div',undefined,'timeline');a.slice().sort(function(x,y){return String(x.date||'').localeCompare(String(y.date||''))}).forEach(function(x){var item=make('article',undefined,'timeline-item'),rail=make('span',undefined,'timeline-rail'),dot=make('span',undefined,'timeline-dot'),content=make('div',undefined,'timeline-content');content.append(make('div',dateText(x.date),'timeline-meta'),visualCard(x,'timeline-card'));item.append(rail,dot,content);wrap.append(item)});e.visual.append(wrap)}
|
||||
function renderFunnel(a){if(!a.length){visualEmpty();return}var groups={},amounts={};a.forEach(function(x){var g=groupValue(x,state.config.groupBy||'stage');groups[g]=(groups[g]||0)+1;amounts[g]=(amounts[g]||0)+(numeric(x,['total_amount','amount','value','sum'])||0)});var max=Object.keys(groups).reduce(function(current,g){return Math.max(current,groups[g])},0),wrap=make('div',undefined,'funnel');Object.keys(groups).forEach(function(g){var row=make('div',undefined,'funnel-row'),bar=document.createElement('progress');bar.className='funnel-fill';bar.max=max||1;bar.value=groups[g];row.append(make('span',detailText(g,state.config.groupBy||'stage')),bar,make('span',String(groups[g])+(amounts[g]?' · '+String(amounts[g]):'')));wrap.append(row)});e.visual.append(wrap)}
|
||||
function metric(label,value){var d=make('div',undefined,'metric');d.append(make('span',label,'metric-label'),make('span',value===null||value===undefined?'—':str(value),'metric-value'));return d}
|
||||
function renderDashboard(a,mode){var grid=make('div',undefined,'metric-grid'),total=a.length,amount=0,planned=0,capacity=0,util=0,overload=0;a.forEach(function(x){amount+=numeric(x,['total_amount','amount','total','sum','value'])||0;planned+=numeric(x,['planned_hours','planned','hours'])||0;capacity+=numeric(x,['capacity_hours','capacity'])||0;util+=numeric(x,['utilization_percent','utilization'])||0;overload+=numeric(x,['overload_hours','overload'])||0});grid.append(metric(t('total'),total));if(mode==='workload'){grid.append(metric(t('planned'),planned||null),metric(t('capacity'),capacity||null),metric(t('utilization'),a.length?Math.round(util/a.length)+'%':null),metric(t('overload'),overload||0))}else{grid.append(metric(t('amount'),amount||null),metric(t('status'),Object.keys(a.reduce(function(m,x){m[groupValue(x,'status')]=1;return m},{})).length))}e.visual.append(grid);if(!a.length){visualEmpty();return}var list=make('div',undefined,'kanban-list');a.slice(0,60).forEach(function(x){list.append(visualCard(x))});e.visual.append(list)}
|
||||
function approvalValue(x){return groupValue(x,'approval_status')!=='—'?groupValue(x,'approval_status'):groupValue(x,'approval_state')!=='—'?groupValue(x,'approval_state'):groupValue(x,'status')}
|
||||
function renderStatusSummary(a,keys){if(!a.length){visualEmpty();return}var counts={};a.forEach(function(x){var g=groupValue(x,keys[0]);if(g==='—')g=groupValue(x,keys[1]);if(g==='—')g=groupValue(x,'status');counts[g]=(counts[g]||0)+1});var grid=make('div',undefined,'metric-grid');Object.keys(counts).slice(0,6).forEach(function(g){grid.append(metric(detailText(g,keys[0]),counts[g]))});e.visual.append(grid);var list=make('div',undefined,'kanban-list');a.slice(0,60).forEach(function(x){list.append(visualCard(x))});e.visual.append(list)}
|
||||
function progressValue(x){var n=numeric(x,['score','completeness_score','completion_percent','progress','availability_percent']);if(n!==null)return Math.max(0,Math.min(100,n));if(x.complete===true||x.is_complete===true||String(status(x)).toLowerCase()==='complete')return 100;return 0}
|
||||
function renderProgress(a){if(!a.length){visualEmpty();return}var grid=make('div',undefined,'preview-grid');a.forEach(function(x){var card=interactiveRecord(make('article',undefined,'progress-card'),x);card.append(make('strong',x.title||'SHD','visual-title'));var meta=make('span',x.subtitle||dateText(x.date),'visual-date');card.append(meta);var p=document.createElement('progress');p.className='progress';p.max=100;p.value=progressValue(x);card.append(p,make('span',t('progress')+': '+String(p.value)+'%','visual-value'));card.onclick=function(){details(x)};grid.append(card)});e.visual.append(grid)}
|
||||
function isWarning(x){var s=String(status(x)).toLowerCase();if(x.low_stock||x.is_low_stock||x.critical||x.warning)return true;if(/low|critical|out|error|overdue|maintenance/.test(s))return true;var q=numeric(x,['quantity','available_quantity','stock']);var min=numeric(x,['min_quantity','minimum_quantity','reorder_level']);return q!==null&&min!==null&&q<=min}
|
||||
function renderWarnings(a){var warnings=a.filter(isWarning),grid=make('div',undefined,'metric-grid');grid.append(metric(t('warnings'),warnings.length),metric(t('total'),a.length));e.visual.append(grid);if(!warnings.length){e.visual.append(make('div',t('noWarnings'),'empty'));return}var list=make('div',undefined,'kanban-list');warnings.forEach(function(x){list.append(visualCard(x,'warning'))});e.visual.append(list)}
|
||||
function renderCalendar(a){if(!a.length){visualEmpty();return}var days={};a.forEach(function(x){var d=x.date?String(x.date).slice(0,10):'—';(days[d]||(days[d]=[])).push(x)});var cal=make('div',undefined,'calendar');Object.keys(days).forEach(function(d){var day=make('section',undefined,'calendar-day'),label=/^\d{4}-\d{2}-\d{2}$/.test(d)?dateOnlyText(d):d;day.append(make('div',label||d,'calendar-head'));days[d].forEach(function(x){day.append(visualCard(x))});cal.append(day)});e.visual.append(cal)}
|
||||
function memberName(x){var v=field(x,['member_name','username','email','name','display_name','user']);if(obj(v))return str(v.name||v.username||v.email||v.id);return str(v)||x.id}
|
||||
function permissionText(x){var p=x.permissions||x.permission||x.scopes||'';if(obj(p))return Object.keys(p).filter(function(k){return p[k]===true||p[k]===1||typeof p[k]==='string'}).map(function(k){return fieldLabel(k)+(typeof p[k]==='string'?'='+detailText(p[k],k):'')}).join(', ')||'—';if(Array.isArray(p))return p.map(function(k){return detailText(k,'permissions')}).join(', ')||'—';return detailText(p,'permissions')||'—'}
|
||||
function renderMatrix(a){if(!a.length){visualEmpty();return}var wrap=make('div',undefined,'matrix-wrap'),table=make('table',undefined,'matrix-table'),head=make('tr');[t('member'),t('role'),t('status'),t('permissions')].forEach(function(x){head.append(make('th',x))});table.append(make('thead').appendChild(head).parentNode);var body=make('tbody');a.forEach(function(x){var row=interactiveRecord(make('tr'),x);row.append(make('td',memberName(x)),make('td',detailText(field(x,['role','organization_role','type']),'role')||'—'),make('td',detailText(status(x),'status')||'—'),make('td',permissionText(x)));row.onclick=function(){details(x)};body.append(row)});table.append(body);wrap.append(table);e.visual.append(wrap)}
|
||||
function treeId(x,i){return str(field(x,['id','space_id','page_id','collection_id','key']))||String(i)}
|
||||
function treeParent(x){return str(field(x,['parent_id','parentId','parent_space_id','parent_collection_id']))}
|
||||
function renderTree(a){if(!a.length){visualEmpty();return}var by={};a.forEach(function(x,i){var p=treeParent(x);(by[p]||(by[p]=[])).push({x:x,i:i})});var tree=make('div',undefined,'tree');function branch(parent,host,seen){(by[parent]||[]).forEach(function(entry){var id=treeId(entry.x,entry.i);if(seen[id])return;var wrap=make('div',undefined,'tree-node');wrap.append(visualCard(entry.x));host.append(wrap);var child=make('div',undefined,'tree-children');branch(id,child,Object.assign({},seen,(function(){var o={};o[id]=true;return o})()));if(child.childNodes.length)wrap.append(child)})}branch('',tree,{});if(!tree.childNodes.length)a.forEach(function(x){tree.append(visualCard(x))});e.visual.append(tree)}
|
||||
function renderPreview(a){if(!a.length){visualEmpty();return}var grid=make('div',undefined,'preview-grid');a.forEach(function(x){var card=interactiveRecord(make('article',undefined,'preview-card'),x);card.append(make('strong',x.title||'SHD','visual-title'));var fields=make('div',undefined,'preview-fields');Object.keys(x).filter(function(k){return['id','title','subtitle','status','date','value','meta'].indexOf(k)===-1}).slice(0,10).forEach(function(k){var cell=make('div',undefined,'preview-field');cell.append(make('strong',fieldLabel(k)+': '),make('span',detailText(x[k],k)));fields.append(cell)});card.append(fields);card.onclick=function(){details(x)};grid.append(card)});e.visual.append(grid)}
|
||||
function renderVisual(a){e.visual.replaceChildren();var v=state.config.view||'table';if(v==='kanban')renderKanban(a);else if(v==='workload'||v==='dashboard')renderDashboard(a,v);else if(v==='timeline')renderTimeline(a);else if(v==='funnel')renderFunnel(a);else if(v==='approval')renderStatusSummary(a,['approval_status','approval_state']);else if(v==='completeness'||v==='progress')renderProgress(a);else if(v==='health')renderStatusSummary(a,['health','status']);else if(v==='calendar')renderCalendar(a);else if(v==='matrix')renderMatrix(a);else if(v==='warnings')renderWarnings(a);else if(v==='tree')renderTree(a);else if(v==='preview')renderPreview(a);else renderDashboard(a,'dashboard')}
|
||||
function renderTable(a){e.rows.replaceChildren();e.empty.hidden=a.length!==0;a.forEach(function(x){var r=document.createElement('tr');r.className='record';recordAttrs(r,x);r.tabIndex=0;r.setAttribute('role','button');r.setAttribute('aria-label',x.title);var n=make('td');n.append(make('span',x.title,'title'));if(x.subtitle)n.append(make('span',x.subtitle,'sub'));var s=make('span',detailText(x.status,'status'),'pill');if(x.status)s.classList.add(String(x.status).toLowerCase().replace(/[^a-z0-9_-]+/g,'-'));var sc=make('td');sc.append(s);r.append(n,sc,make('td',dateText(x.date)),make('td',detailText(x.value,'value')));r.onclick=function(){details(x)};r.onkeydown=function(ev){if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();details(x)}};e.rows.append(r)});renderSummary()}
|
||||
function renderRows(){var q=e.search.value.trim().toLowerCase(),a=rows.filter(function(x){return!q||[x.title,x.subtitle,x.status,x.id,x.value].map(str).join(' ').toLowerCase().indexOf(q)!==-1});if(state.config.view&&state.config.view!=='table'){e.tableWrap.hidden=true;e.visual.hidden=false;e.empty.hidden=true;renderVisual(a);renderSummary();return}e.tableWrap.hidden=false;e.visual.hidden=true;renderTable(a)}
|
||||
function setTimezone(p){var source=payload(p)||{},data=source.data!==undefined?source.data:source,timezone=data&&data.timezone||source.timezone;if(typeof timezone==='string'&&timezone)state.timezone=timezone}
|
||||
function render(x){var p=unwrap(x);applyConfig(p);setTimezone(p);rows=list(p).map(normalize);var dir=state.config.sort==='asc'?1:-1;rows.sort(function(a,b){var x=Date.parse(a.date),y=Date.parse(b.date);if(!Number.isNaN(x)&&!Number.isNaN(y)&&x!==y)return(x-y)*dir;return a.title.localeCompare(b.title,state.lang)*dir});window.__SHD_WIDGET_STATE__=p;renderRows();window.dispatchEvent(new CustomEvent('shd-widget-data',{detail:p}))}
|
||||
function setStatus(x,bad){e.status.textContent=x||'';e.status.className=bad?'status error':'status'}
|
||||
function add(k,v,key){var d=make('div',undefined,'detail');d.append(make('span',k,'label'),make('span',detailText(v,key),'value'));e.grid.append(d)}
|
||||
function show(x){e.detailTitle.textContent=x.title||'SHD';e.detailSub.textContent=x.subtitle||x.id||'';e.grid.replaceChildren();add(t('id'),x.id,'id');if(x.status)add(t('status'),x.status,'status');if(x.date)add(t('date'),dateText(x.date),'date');if(x.value)add(t('value'),x.value,'value');Object.keys(x).filter(function(k){return['id','title','subtitle','status','date','value','meta'].indexOf(k)===-1}).slice(0,8).forEach(function(k){add(fieldLabel(k),x[k],k)});e.details.hidden=false}
|
||||
function details(x){show(x);if(!state.config.readTool)return;var k=state.config.detailKey;if(!k)return;var v=x[k]===undefined?x.id:x[k];if(v===undefined||v===null||v==='')return;if(state.module==='status-page'&&!/^\d+$/.test(String(v)))return;var a={};if(Array.isArray(state.config.detailSourceArgs)){var ctx=merge(state.config.listArgs,state.config.sourceArgs);state.config.detailSourceArgs.forEach(function(name){if(ctx[name]!==undefined)a[name]=ctx[name]})}else a=merge(state.config.sourceArgs,state.config.detailArgs);a=merge(a,state.config.detailArgs);a[k]=k==='table'?String(v):(Number(v)||v);req('tools/call',{name:state.config.readTool,arguments:a}).then(function(result){var p=unwrap(result),d=p.data===undefined?p:p.data;show(x);if(Array.isArray(d)){add(t('records'),d.length);d.slice(0,6).forEach(function(q,i){add(String(i+1),q&&(q.message||q.body||q.content||q.title||q))})}else if(obj(d))Object.keys(d).slice(0,12).forEach(function(q){add(fieldLabel(q),d[q],q)});else add(t('details'),d)}).catch(function(err){setStatus(err.message||t('detail'),true)})}
|
||||
function contextReady(a){if(!state.config.contextRequired)return true;if(Array.isArray(state.config.contextSets)&&state.config.contextSets.length)return state.config.contextSets.every(function(set){return set.some(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''})});var keys=Array.isArray(state.config.contextKeys)&&state.config.contextKeys.length?state.config.contextKeys:['base','project_code','project'];var ready=state.config.contextAll?keys.every(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''}):keys.some(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''});return ready}
|
||||
function refresh(){if(state.busy)return;var a=merge(state.config.listArgs,state.config.sourceArgs);if(!contextReady(a)){setStatus(t('context'),true);return}if(!state.config.listTool)return;state.busy=true;e.refresh.disabled=true;setStatus(t('loading'),false);req('tools/call',{name:state.config.listTool,arguments:a}).then(function(x){render(x);setStatus('',false)}).catch(function(err){setStatus(err.message||t('load'),true)}).finally(function(){state.busy=false;e.refresh.disabled=false})}
|
||||
function init(){return req('ui/initialize',{protocolVersion:'2026-01-26',appInfo:{name:'SHD '+(M[state.module]||M.discussions).ru[0],title:'SHD MCP',version:'1.0.0',websiteUrl:'https://shd.xyz.su'},appCapabilities:{}}).then(function(){notify('ui/notifications/initialized',{})}).catch(function(){})}
|
||||
window.addEventListener('message',function(ev){if(ev.source!==window.parent)return;var m=ev.data;if(!m||m.jsonrpc!=='2.0')return;if(m.id!==undefined&&pending.has(m.id)){var q=pending.get(m.id);pending.delete(m.id);window.clearTimeout(q.timeout);if(m.error)q.reject(Error(m.error.message||t('load')));else q.resolve(m.result);return}if(m.method==='ui/notifications/tool-input'&&!rows.length){try{render(m.params||{})}catch(err){setStatus(err.message||t('load'),true)}}if(m.method==='ui/notifications/tool-result'){try{render(m.params&&m.params.structuredContent)}catch(err){setStatus(err.message||t('load'),true)}}},{passive:true});
|
||||
e.search.oninput=renderRows;e.refresh.onclick=refresh;e.close.onclick=function(){e.details.hidden=true};state.lang=lang();labels();void init();var out=typeof window!=='undefined'&&window.openai?window.openai.toolOutput:null;if(out){try{render(out)}catch(err){setStatus(err.message||t('load'),true)}}else{var input=typeof window!=='undefined'&&window.openai?window.openai.toolInput:null;if(input){try{render(input)}catch(err){setStatus(err.message||t('load'),true)}}else refresh()}
|
||||
}());
|
||||
</script>
|
||||
<script data-shd-widget-write-actions="js">
|
||||
__SHD_WIDGET_ACTIONS_JS__
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}());
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user