feat: complete SHD MCP plugin workflows and widget
Validate SHD MCP plugin / validate (push) Has been cancelled
Validate SHD MCP plugin / validate (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
name: Validate SHD MCP plugin
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
- name: Validate package contracts
|
||||||
|
run: python -m unittest discover -s tests -v
|
||||||
|
- name: Verify widget checksum file
|
||||||
|
working-directory: plugins/shd-mcp-plugin/widgets/active-projects/v1
|
||||||
|
run: sha256sum --check SHA256SUMS
|
||||||
@@ -5,3 +5,5 @@
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
*.log
|
*.log
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# SHD MCP in ChatGPT
|
||||||
|
|
||||||
|
This file describes the user-side connection. The repository cannot create an
|
||||||
|
app inside another person’s ChatGPT account and never stores OAuth tokens.
|
||||||
|
|
||||||
|
## Custom app
|
||||||
|
|
||||||
|
1. Open ChatGPT settings and enable Developer mode for Apps/Connectors.
|
||||||
|
2. Create a custom app with this MCP endpoint:
|
||||||
|
|
||||||
|
`https://shd.xyz.su/mcp`
|
||||||
|
|
||||||
|
3. Complete the SHD OAuth sign-in and consent screen.
|
||||||
|
4. Start a new chat or refresh the app connection.
|
||||||
|
5. Test with: `Покажи активные проекты с датой завершения и статусом.`
|
||||||
|
|
||||||
|
The expected flow is:
|
||||||
|
|
||||||
|
1. ChatGPT calls `shd_list_projects` with `archived: false`.
|
||||||
|
2. ChatGPT may call `shd_render_projects_widget` with the returned project
|
||||||
|
array.
|
||||||
|
3. The widget renders the sorted register and can call `shd_get_project` for
|
||||||
|
a selected row.
|
||||||
|
|
||||||
|
The MCP tools remain usable without the widget. A failed OAuth connection is
|
||||||
|
an account/endpoint configuration problem, not a missing token in this repo.
|
||||||
|
|
||||||
|
## Public directory
|
||||||
|
|
||||||
|
Directory submission is a separate optional release step. It is needed for
|
||||||
|
public discovery and reviewed installation, not for internal use or a custom
|
||||||
|
app added manually in ChatGPT. Before submission, the owner must provide the
|
||||||
|
production HTTPS endpoint, OAuth metadata, privacy/support URLs, accurate app
|
||||||
|
metadata, test prompts and any review credentials requested by OpenAI.
|
||||||
|
|
||||||
|
Use the official [submission guide](https://developers.openai.com/plugins/deploy/submission)
|
||||||
|
and [plugin guidelines](https://developers.openai.com/plugins/app-guidelines).
|
||||||
|
|
||||||
|
## License choice
|
||||||
|
|
||||||
|
This repository currently uses the proprietary SHD MCP Plugin License. Public
|
||||||
|
visibility means the source can be viewed; it does not grant permission to
|
||||||
|
redistribute, resell, modify or use it outside an authorized SHD account.
|
||||||
|
|
||||||
|
MIT would grant anyone permission to use, copy, modify, publish, sublicense and
|
||||||
|
sell the plugin, including forks, while retaining only the copyright notice and
|
||||||
|
license text. Switching to MIT is a product/legal decision and is not done
|
||||||
|
implicitly by publishing the repository.
|
||||||
@@ -10,9 +10,13 @@ The package contains:
|
|||||||
|
|
||||||
- `.codex-plugin/plugin.json` — plugin metadata;
|
- `.codex-plugin/plugin.json` — plugin metadata;
|
||||||
- `.mcp.json` — the official SHD Streamable HTTP MCP endpoint;
|
- `.mcp.json` — the official SHD Streamable HTTP MCP endpoint;
|
||||||
- `skills/` — routing, project, ProjectBase, task, file, finance and CRM
|
- `skills/` — routing, project, ProjectBase, task, file, finance, CRM,
|
||||||
workflows;
|
analytics, estimates, scheduling, entity resolution, Wiki, discussions,
|
||||||
|
documents, organizations/ACL, notifications, inventory, agents, status-page,
|
||||||
|
realtime/activity, Terms/contracts and Gitea workflows;
|
||||||
- `assets/` — plugin branding;
|
- `assets/` — plugin branding;
|
||||||
|
- `widgets/` — versioned MCP Apps resources with manifest, checksum and
|
||||||
|
provenance metadata;
|
||||||
- `.agents/plugins/marketplace.json` — a ready local marketplace entry.
|
- `.agents/plugins/marketplace.json` — a ready local marketplace entry.
|
||||||
|
|
||||||
It does not contain the SHD Laravel application, database code or credentials.
|
It does not contain the SHD Laravel application, database code or credentials.
|
||||||
@@ -36,6 +40,36 @@ git clone https://git.xyz.su/shd/shd-mcp-plugin.git
|
|||||||
|
|
||||||
After installation, start a new Codex thread so the plugin Skills are loaded.
|
After installation, start a new Codex thread so the plugin Skills are loaded.
|
||||||
|
|
||||||
|
## ChatGPT custom app
|
||||||
|
|
||||||
|
ChatGPT uses the same SHD MCP server connection, but it is added separately in
|
||||||
|
the ChatGPT account: open Settings → Apps/Connectors → Developer mode, create
|
||||||
|
a custom app and set the MCP URL to `https://shd.xyz.su/mcp`. Complete the SHD
|
||||||
|
OAuth flow and refresh the app after server metadata changes.
|
||||||
|
|
||||||
|
The active-projects result can render an inline MCP Apps widget. It is a small
|
||||||
|
read-only dashboard with:
|
||||||
|
|
||||||
|
- `Проект → Дата завершения → Статус`;
|
||||||
|
- sorting by the nearest deadline;
|
||||||
|
- local search by project name/code;
|
||||||
|
- a refresh action and a read-only project-details panel.
|
||||||
|
|
||||||
|
If the host does not support MCP Apps UI, the data tool and the plain Markdown
|
||||||
|
table remain fully usable.
|
||||||
|
|
||||||
|
The backend serves the checked-in widget artifact at
|
||||||
|
`ui://shd/active-projects/v1.html`; its manifest and `SHA256SUMS` are kept next
|
||||||
|
to the HTML resource under `plugins/shd-mcp-plugin/widgets/active-projects/v1/`.
|
||||||
|
|
||||||
|
The official OpenAI directory is optional. It is useful for public discovery,
|
||||||
|
one-click installation and review of a public app; it is not required for an
|
||||||
|
internal team or a manually added ChatGPT custom app. See the official
|
||||||
|
[MCP server guide](https://developers.openai.com/plugins/build/mcp-server),
|
||||||
|
[UI guide](https://developers.openai.com/plugins/build/chatgpt-ui), and
|
||||||
|
[submission guide](https://developers.openai.com/plugins/deploy/submission)
|
||||||
|
when public listing is the goal.
|
||||||
|
|
||||||
## Connect SHD
|
## Connect SHD
|
||||||
|
|
||||||
Installation and authorization are separate steps:
|
Installation and authorization are separate steps:
|
||||||
@@ -63,9 +97,24 @@ repository packages the workflows; it does not grant access to the server.
|
|||||||
the project is archived; verify permissions in SHD.
|
the project is archived; verify permissions in SHD.
|
||||||
- **A write is rejected:** the server ACL, required role, version or conflict
|
- **A write is rejected:** the server ACL, required role, version or conflict
|
||||||
check rejected it. Do not bypass the error with a different token.
|
check rejected it. Do not bypass the error with a different token.
|
||||||
|
- **A module is not covered by a dedicated skill:** use `shd-routing`; the MCP
|
||||||
|
server still exposes the current authenticated tool catalog, while skills
|
||||||
|
provide focused workflow and safety guidance for common module operations.
|
||||||
- **Another SHD installation is required:** use its approved MCP URL and its
|
- **Another SHD installation is required:** use its approved MCP URL and its
|
||||||
OAuth resource in the client's secure connection configuration.
|
OAuth resource in the client's secure connection configuration.
|
||||||
|
|
||||||
|
## Local validation
|
||||||
|
|
||||||
|
From the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m unittest discover -s tests -v
|
||||||
|
sha256sum --check plugins/shd-mcp-plugin/widgets/active-projects/v1/SHA256SUMS
|
||||||
|
```
|
||||||
|
|
||||||
|
The full release boundary, including backend deployment and authenticated
|
||||||
|
ChatGPT/browser checks, is documented in [RELEASE.md](RELEASE.md).
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Keep changes inside the plugin package. Do not copy backend implementation into
|
Keep changes inside the plugin package. Do not copy backend implementation into
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
# SHD MCP plugin release checklist
|
||||||
|
|
||||||
|
This repository packages the client-side plugin. The authenticated MCP server
|
||||||
|
and its OAuth deployment are separate release surfaces.
|
||||||
|
|
||||||
|
## Local preflight
|
||||||
|
|
||||||
|
Run from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m unittest discover -s tests -v
|
||||||
|
sha256sum --check plugins/shd-mcp-plugin/widgets/active-projects/v1/SHA256SUMS
|
||||||
|
```
|
||||||
|
|
||||||
|
The Codex plugin validator is an additional environment check when available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 /home/vscode/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py \
|
||||||
|
plugins/shd-mcp-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Widget changes
|
||||||
|
|
||||||
|
The URI `ui://shd/active-projects/v1.html` is a cache key. For a breaking HTML,
|
||||||
|
JavaScript or CSS change, create `v2/`, update the manifest and backend resource
|
||||||
|
registration together, then regenerate `SHA256SUMS`. Keep the widget useful
|
||||||
|
without ChatGPT-specific APIs: the MCP Apps bridge is the baseline and
|
||||||
|
`window.openai` is only a compatibility extension.
|
||||||
|
|
||||||
|
## Publish and deploy gates
|
||||||
|
|
||||||
|
1. Commit and push the same reviewed revision to the GitHub and Gitea mirrors.
|
||||||
|
2. Deploy the backend and ensure the widget artifact is available at the path
|
||||||
|
used by `SHD_MCP_WIDGET_ROOT`, or at the repository path documented by the
|
||||||
|
backend loader.
|
||||||
|
3. Run the backend MCP verification and a real authenticated OAuth/tool call.
|
||||||
|
4. Reconnect the custom ChatGPT app and test the active-projects flow, including
|
||||||
|
the plain structured fallback and widget actions.
|
||||||
|
5. Record the exact revision and any runtime/browser checks. Static tests do
|
||||||
|
not prove OAuth, ACLs, production hosting or ChatGPT rendering.
|
||||||
|
|
||||||
|
Do not place tokens, cookies, client secrets or private host credentials in this
|
||||||
|
repository, its marketplace file or its documentation.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "shd-mcp-plugin",
|
"name": "shd-mcp-plugin",
|
||||||
"version": "0.1.0",
|
"version": "0.4.0",
|
||||||
"description": "Ready-to-install SHD workflows backed by the authenticated SHD MCP server.",
|
"description": "Ready-to-install SHD workflows backed by the authenticated SHD MCP server.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "SHD",
|
"name": "SHD",
|
||||||
@@ -16,13 +16,19 @@
|
|||||||
"tasks",
|
"tasks",
|
||||||
"finance",
|
"finance",
|
||||||
"CRM",
|
"CRM",
|
||||||
|
"documents",
|
||||||
|
"inventory",
|
||||||
|
"organizations",
|
||||||
|
"notifications",
|
||||||
|
"realtime",
|
||||||
|
"Gitea",
|
||||||
"MCP"
|
"MCP"
|
||||||
],
|
],
|
||||||
"skills": "./skills/",
|
"skills": "./skills/",
|
||||||
"interface": {
|
"interface": {
|
||||||
"displayName": "SHD MCP",
|
"displayName": "SHD MCP",
|
||||||
"shortDescription": "SHD projects and operations",
|
"shortDescription": "SHD projects and operations",
|
||||||
"longDescription": "Installable SHD MCP workflows for project status, ProjectBase audits, tasks, files, finance, CRM and safe operational workflows.",
|
"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",
|
"developerName": "SHD",
|
||||||
"category": "Productivity",
|
"category": "Productivity",
|
||||||
"capabilities": [
|
"capabilities": [
|
||||||
|
|||||||
@@ -15,8 +15,34 @@ modules. Skills do not grant permissions or bypass server-side ACLs.
|
|||||||
- guarded task workflows;
|
- guarded task workflows;
|
||||||
- project file inspection and actions;
|
- project file inspection and actions;
|
||||||
- finance and CRM read/audit workflows;
|
- 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.
|
- safe bulk-change planning and validation rules.
|
||||||
|
|
||||||
|
When the connected MCP host supports MCP Apps UI, the active-projects workflow
|
||||||
|
also has an inline widget: it sorts by the nearest completion date, filters by
|
||||||
|
name/code, shows status pills and loads one project’s read-only details. The
|
||||||
|
same tools still return normal structured data for hosts that do not render
|
||||||
|
widgets.
|
||||||
|
|
||||||
|
The active-projects widget is versioned in `widgets/active-projects/v1/` with a
|
||||||
|
manifest, SHA256 checksum and source/provenance note. The SHD MCP backend reads
|
||||||
|
that artifact for the `ui://shd/active-projects/v1.html` resource.
|
||||||
|
|
||||||
## MCP connection
|
## MCP connection
|
||||||
|
|
||||||
The package points to `https://shd.xyz.su/mcp` and requests OAuth for that MCP
|
The package points to `https://shd.xyz.su/mcp` and requests OAuth for that MCP
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ description: Use when the user asks for all active SHD projects, project deadlin
|
|||||||
|
|
||||||
`Проект | Дата завершения | Статус`
|
`Проект | Дата завершения | Статус`
|
||||||
|
|
||||||
6. Preserve the server's project name/code and status. If a deadline is a
|
6. 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`. The widget is optional; the plain table
|
||||||
|
remains the fallback for hosts without MCP Apps UI.
|
||||||
|
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
|
datetime, format it with its returned timezone when available; otherwise
|
||||||
keep the value unmodified and state that timezone data was unavailable.
|
keep the value unmodified and state that timezone data was unavailable.
|
||||||
|
|
||||||
@@ -31,3 +35,5 @@ description: Use when the user asks for all active SHD projects, project deadlin
|
|||||||
- If the server indicates more records than the tool can return, report that
|
- 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.
|
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.
|
- 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,5 @@
|
|||||||
|
# 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 and must keep the plain structured table as fallback.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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,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,26 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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_terms_documents`, `shd_get_terms_document`,
|
||||||
|
`shd_get_terms_document_completeness` and `shd_validate_terms_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.
|
||||||
@@ -33,3 +33,5 @@ Do not approve, publish, delete, merge, move a deal stage, create a payment or
|
|||||||
change ownership unless the user explicitly requests that exact operation.
|
change ownership unless the user explicitly requests that exact operation.
|
||||||
Report IDs, resolved links, changed fields and server validation separately from
|
Report IDs, resolved links, changed fields and server validation separately from
|
||||||
recommendations.
|
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.
|
||||||
@@ -5,6 +5,8 @@ description: Use when the user asks to inspect ProjectBase tables, validate proj
|
|||||||
|
|
||||||
# ProjectBase audit
|
# ProjectBase audit
|
||||||
|
|
||||||
|
See `references/audit-contract.md` for the bounded read and evidence format.
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
1. Resolve the canonical Base identity. A supplied `project_code` is accepted
|
1. Resolve the canonical Base identity. A supplied `project_code` is accepted
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
interface:
|
||||||
|
display_name: ProjectBase audit
|
||||||
|
short_description: Read-only schema and data audit
|
||||||
|
policy:
|
||||||
|
allow_implicit_invocation: false
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# ProjectBase audit contract
|
||||||
|
|
||||||
|
Use this contract for every read-only ProjectBase 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.
|
||||||
@@ -26,3 +26,5 @@ version/confirmation fields accepted by its schema.
|
|||||||
Do not expose bearer tokens or raw secret material in prose or logs. Return a
|
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,
|
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.
|
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.
|
||||||
@@ -37,3 +37,5 @@ Separate the result into:
|
|||||||
Do not convert a task deadline into a project deadline, infer a completion date
|
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
|
from activity, or claim deployment success from a status field. This workflow is
|
||||||
read-only unless the user separately requests a specific change.
|
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.
|
||||||
@@ -18,8 +18,26 @@ readback. Do not invent fields, statuses, identifiers, routes or capabilities.
|
|||||||
- Tasks, task projects, task status or task dates: use Tasks tools and the
|
- Tasks, task projects, task status or task dates: use Tasks tools and the
|
||||||
`shd-task-workflow` workflow.
|
`shd-task-workflow` workflow.
|
||||||
- Project files and paths: use Files tools and the `shd-project-files` 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.
|
||||||
|
- 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
|
- Finance or CRM records: use the specific module tools and the
|
||||||
`shd-finance-crm` workflow; use project context for a high-level overview.
|
`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.
|
- A multi-record change: use `shd-safe-bulk-change` before any mutation.
|
||||||
|
|
||||||
Use `shd_capabilities` only when the available module or permission boundary is
|
Use `shd_capabilities` only when the available module or permission boundary is
|
||||||
@@ -43,3 +61,5 @@ Never ask for or expose access tokens, passwords, cookies, private keys, raw
|
|||||||
public-link bearer tokens or integration secrets. Never use a user-provided
|
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 name as an identifier when SHD has not resolved it to a returned
|
||||||
project code or ID.
|
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.
|
||||||
@@ -26,3 +26,5 @@ record filter. Do not convert a failed or conflicting row into a new record.
|
|||||||
After execution, report created, updated, skipped, rejected and conflicted
|
After execution, report created, updated, skipped, rejected and conflicted
|
||||||
rows, then use the server's persisted readback when available. Destructive
|
rows, then use the server's persisted readback when available. Destructive
|
||||||
operations require explicit confirmation and remain subject to server ACL.
|
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.
|
||||||
@@ -5,6 +5,9 @@ description: Use when the user asks to find, inspect, create, update, reschedule
|
|||||||
|
|
||||||
# SHD task workflow
|
# SHD task workflow
|
||||||
|
|
||||||
|
See `references/task-contract.md` for identifier resolution, conflict handling
|
||||||
|
and mutation readback requirements.
|
||||||
|
|
||||||
## Read and resolve
|
## Read and resolve
|
||||||
|
|
||||||
- Use `shd_list_task_projects` when the request concerns a task project or its
|
- Use `shd_list_task_projects` when the request concerns a task project or its
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
See the repository root LICENSE: the SHD MCP Plugin License applies to this
|
||||||
|
widget.
|
||||||
|
This file is a local provenance notice; it does not grant additional rights.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
49b3ab8629540f2c1075c3e096e2990055ec56181e6dd02dcd998bc98d9c7069 index.html
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# SHD active-projects widget provenance
|
||||||
|
|
||||||
|
This is an SHD-authored MCP Apps resource. It is not copied from an external
|
||||||
|
widget bundle and has no OpenAI upstream dependency.
|
||||||
|
|
||||||
|
- Resource URI: `ui://shd/active-projects/v1.html`
|
||||||
|
- Media type: `text/html;profile=mcp-app`
|
||||||
|
- Source: `plugins/shd-mcp-plugin/widgets/active-projects/v1/index.html`
|
||||||
|
- Backend loader: `SHD_MCP_WIDGET_ROOT` or the repository path documented in
|
||||||
|
`scripts/mcp/mcp/resources/active-projects-widget.mjs`
|
||||||
|
- Integrity: verify `index.html` against `SHA256SUMS`.
|
||||||
|
- License: repository root `LICENSE`.
|
||||||
@@ -0,0 +1,496 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
--bg: #ffffff;
|
||||||
|
--surface: #f6f8fb;
|
||||||
|
--surface-hover: #eef3f9;
|
||||||
|
--border: #d9e1ec;
|
||||||
|
--text: #152235;
|
||||||
|
--muted: #63728a;
|
||||||
|
--accent: #1769e0;
|
||||||
|
--accent-soft: #e7f0ff;
|
||||||
|
--success: #16794b;
|
||||||
|
--success-soft: #e6f7ef;
|
||||||
|
--warning: #9a6100;
|
||||||
|
--warning-soft: #fff4d8;
|
||||||
|
--danger: #b42318;
|
||||||
|
--danger-soft: #ffebe9;
|
||||||
|
--shadow: 0 8px 24px rgba(25, 55, 95, .08);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: #172131;
|
||||||
|
--surface: #202c3e;
|
||||||
|
--surface-hover: #29384c;
|
||||||
|
--border: #39495f;
|
||||||
|
--text: #eef4fc;
|
||||||
|
--muted: #a8b6c9;
|
||||||
|
--accent: #75a9ff;
|
||||||
|
--accent-soft: #263f68;
|
||||||
|
--success: #70d4a4;
|
||||||
|
--success-soft: #173d31;
|
||||||
|
--warning: #f4c467;
|
||||||
|
--warning-soft: #4b3918;
|
||||||
|
--danger: #ff9b91;
|
||||||
|
--danger-soft: #512522;
|
||||||
|
--shadow: 0 8px 24px rgba(0, 0, 0, .22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body { margin: 0; background: var(--bg); color: var(--text); }
|
||||||
|
button, input { font: inherit; }
|
||||||
|
button { color: inherit; }
|
||||||
|
.app { padding: 16px; max-width: 900px; margin: 0 auto; }
|
||||||
|
.header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
||||||
|
h1 { margin: 0; font-size: 20px; line-height: 1.25; letter-spacing: -.01em; }
|
||||||
|
.subtitle { margin: 5px 0 0; color: var(--muted); font-size: 13px; }
|
||||||
|
.toolbar { display: flex; align-items: center; gap: 8px; margin-top: 16px; }
|
||||||
|
.search { flex: 1; min-width: 0; border: 1px solid var(--border); border-radius: 9px; padding: 9px 11px; background: var(--bg); color: var(--text); outline: none; }
|
||||||
|
.search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
||||||
|
.button { border: 1px solid var(--border); border-radius: 9px; padding: 8px 11px; background: var(--surface); cursor: pointer; white-space: nowrap; }
|
||||||
|
.button:hover { background: var(--surface-hover); }
|
||||||
|
.button:focus-visible, .project-row:focus-visible { outline: 3px solid var(--accent-soft); outline-offset: 2px; }
|
||||||
|
.button:disabled { cursor: wait; opacity: .62; }
|
||||||
|
.summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
|
||||||
|
.summary-item { border-radius: 999px; padding: 5px 9px; background: var(--surface); color: var(--muted); font-size: 12px; }
|
||||||
|
.summary-item strong { color: var(--text); font-weight: 650; }
|
||||||
|
.table { border: 1px solid var(--border); border-radius: 11px; overflow: hidden; box-shadow: var(--shadow); }
|
||||||
|
.table-head, .project-row { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(130px, .8fr) minmax(110px, .7fr); gap: 12px; align-items: center; }
|
||||||
|
.table-head { padding: 9px 12px; background: var(--surface); color: var(--muted); font-size: 11px; font-weight: 650; text-transform: uppercase; letter-spacing: .04em; }
|
||||||
|
.project-row { width: 100%; border: 0; border-top: 1px solid var(--border); padding: 12px; background: var(--bg); text-align: left; cursor: pointer; }
|
||||||
|
.project-row:hover { background: var(--surface-hover); }
|
||||||
|
.project-name { min-width: 0; }
|
||||||
|
.project-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 620; }
|
||||||
|
.project-code { display: block; margin-top: 3px; color: var(--muted); font-size: 12px; }
|
||||||
|
.deadline { color: var(--text); font-size: 13px; }
|
||||||
|
.deadline.missing { color: var(--muted); }
|
||||||
|
.deadline.overdue { color: var(--danger); font-weight: 620; }
|
||||||
|
.status { display: inline-flex; width: fit-content; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-radius: 999px; padding: 5px 9px; font-size: 12px; font-weight: 620; }
|
||||||
|
.status-neutral { background: var(--surface); color: var(--muted); }
|
||||||
|
.status-progress { background: var(--accent-soft); color: var(--accent); }
|
||||||
|
.status-done { background: var(--success-soft); color: var(--success); }
|
||||||
|
.status-warning { background: var(--warning-soft); color: var(--warning); }
|
||||||
|
.status-danger { background: var(--danger-soft); color: var(--danger); }
|
||||||
|
.empty, .error { padding: 22px 14px; color: var(--muted); text-align: center; }
|
||||||
|
.error { color: var(--danger); }
|
||||||
|
.details { margin-top: 14px; padding: 14px; border: 1px solid var(--border); border-radius: 11px; background: var(--surface); }
|
||||||
|
.details[hidden], .error[hidden] { display: none; }
|
||||||
|
.details-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
||||||
|
.details-title { margin: 0; font-size: 16px; }
|
||||||
|
.details-code { margin: 3px 0 0; color: var(--muted); font-size: 12px; }
|
||||||
|
.details-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 14px 0 0; }
|
||||||
|
.detail-label { display: block; margin-bottom: 3px; color: var(--muted); font-size: 11px; }
|
||||||
|
.detail-value { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
|
||||||
|
.loading { color: var(--muted); font-size: 13px; }
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.app { padding: 12px; }
|
||||||
|
.header { display: block; }
|
||||||
|
.header .button { margin-top: 10px; }
|
||||||
|
.table-head, .project-row { grid-template-columns: minmax(0, 1.4fr) minmax(100px, .8fr) minmax(90px, .7fr); gap: 8px; }
|
||||||
|
.table-head, .project-row { padding-left: 9px; padding-right: 9px; }
|
||||||
|
.details-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="app" aria-live="polite">
|
||||||
|
<header class="header">
|
||||||
|
<div>
|
||||||
|
<h1 data-i18n="title">Активные проекты</h1>
|
||||||
|
<p class="subtitle" data-i18n="subtitle">Сроки завершения и текущие статусы</p>
|
||||||
|
</div>
|
||||||
|
<button class="button" id="refresh" type="button" data-i18n="refresh">Обновить</button>
|
||||||
|
</header>
|
||||||
|
<div class="toolbar">
|
||||||
|
<input class="search" id="search" type="search" autocomplete="off" data-i18n-placeholder="search" placeholder="Поиск по проекту или коду">
|
||||||
|
</div>
|
||||||
|
<div class="summary" id="summary"></div>
|
||||||
|
<section class="table" aria-label="Проекты">
|
||||||
|
<div class="table-head">
|
||||||
|
<span data-i18n="project">Проект</span>
|
||||||
|
<span data-i18n="deadline">Дата завершения</span>
|
||||||
|
<span data-i18n="status">Статус</span>
|
||||||
|
</div>
|
||||||
|
<div id="rows"></div>
|
||||||
|
<div class="empty" id="empty" hidden></div>
|
||||||
|
<div class="error" id="error" hidden></div>
|
||||||
|
</section>
|
||||||
|
<section class="details" id="details" hidden>
|
||||||
|
<div class="details-header">
|
||||||
|
<div>
|
||||||
|
<h2 class="details-title" id="details-title"></h2>
|
||||||
|
<p class="details-code" id="details-code"></p>
|
||||||
|
</div>
|
||||||
|
<button class="button" id="close-details" type="button" data-i18n="close">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
<div class="details-grid" id="details-grid"></div>
|
||||||
|
<p class="loading" id="details-loading" hidden></p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var messages = {
|
||||||
|
ru: {
|
||||||
|
title: 'Активные проекты',
|
||||||
|
subtitle: 'Сроки завершения и текущие статусы',
|
||||||
|
refresh: 'Обновить',
|
||||||
|
search: 'Поиск по проекту или коду',
|
||||||
|
project: 'Проект',
|
||||||
|
deadline: 'Дата завершения',
|
||||||
|
status: 'Статус',
|
||||||
|
close: 'Закрыть',
|
||||||
|
missingDate: 'Не указана',
|
||||||
|
noProjects: 'Проекты не найдены.',
|
||||||
|
noMatch: 'По фильтру ничего не найдено.',
|
||||||
|
loading: 'Загрузка…',
|
||||||
|
loadingProject: 'Загрузка данных проекта…',
|
||||||
|
refreshError: 'Не удалось обновить список проектов.',
|
||||||
|
projectError: 'Не удалось получить данные проекта.',
|
||||||
|
total: 'Всего',
|
||||||
|
withoutDate: 'Без даты',
|
||||||
|
archived: 'Архивных',
|
||||||
|
phase: 'Этап',
|
||||||
|
timezone: 'Часовой пояс',
|
||||||
|
updated: 'Обновлён',
|
||||||
|
code: 'Код'
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
title: 'Active projects',
|
||||||
|
subtitle: 'Completion dates and current statuses',
|
||||||
|
refresh: 'Refresh',
|
||||||
|
search: 'Search by project or code',
|
||||||
|
project: 'Project',
|
||||||
|
deadline: 'Completion date',
|
||||||
|
status: 'Status',
|
||||||
|
close: 'Close',
|
||||||
|
missingDate: 'Not set',
|
||||||
|
noProjects: 'No projects found.',
|
||||||
|
noMatch: 'No projects match the filter.',
|
||||||
|
loading: 'Loading…',
|
||||||
|
loadingProject: 'Loading project data…',
|
||||||
|
refreshError: 'Could not refresh the project list.',
|
||||||
|
projectError: 'Could not load the project data.',
|
||||||
|
total: 'Total',
|
||||||
|
withoutDate: 'No date',
|
||||||
|
archived: 'Archived',
|
||||||
|
phase: 'Phase',
|
||||||
|
timezone: 'Timezone',
|
||||||
|
updated: 'Updated',
|
||||||
|
code: 'Code'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var locale = (document.documentElement.lang || 'ru').toLowerCase().indexOf('ru') === 0 ? 'ru' : 'en';
|
||||||
|
var t = messages[locale];
|
||||||
|
var currentSnapshot = null;
|
||||||
|
var currentProjects = [];
|
||||||
|
var latestToolInput = null;
|
||||||
|
var pendingRequests = new Map();
|
||||||
|
var nextRequestId = 1;
|
||||||
|
var mcpAppsProtocolVersion = '2026-01-26';
|
||||||
|
var busy = false;
|
||||||
|
|
||||||
|
var elements = {
|
||||||
|
refresh: document.getElementById('refresh'),
|
||||||
|
search: document.getElementById('search'),
|
||||||
|
summary: document.getElementById('summary'),
|
||||||
|
rows: document.getElementById('rows'),
|
||||||
|
empty: document.getElementById('empty'),
|
||||||
|
error: document.getElementById('error'),
|
||||||
|
details: document.getElementById('details'),
|
||||||
|
detailsTitle: document.getElementById('details-title'),
|
||||||
|
detailsCode: document.getElementById('details-code'),
|
||||||
|
detailsGrid: document.getElementById('details-grid'),
|
||||||
|
detailsLoading: document.getElementById('details-loading'),
|
||||||
|
closeDetails: document.getElementById('close-details')
|
||||||
|
};
|
||||||
|
|
||||||
|
function applyTranslations() {
|
||||||
|
document.querySelectorAll('[data-i18n]').forEach(function (node) {
|
||||||
|
var key = node.getAttribute('data-i18n');
|
||||||
|
if (t[key]) node.textContent = t[key];
|
||||||
|
});
|
||||||
|
document.querySelectorAll('[data-i18n-placeholder]').forEach(function (node) {
|
||||||
|
var key = node.getAttribute('data-i18n-placeholder');
|
||||||
|
if (t[key]) node.setAttribute('placeholder', t[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = nextRequestId++;
|
||||||
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, '*');
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var timeout = window.setTimeout(function () {
|
||||||
|
pendingRequests.delete(id);
|
||||||
|
reject(new Error('Host request timed out'));
|
||||||
|
}, 10000);
|
||||||
|
pendingRequests.set(id, { resolve: resolve, reject: reject, timeout: timeout });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify(method, params) {
|
||||||
|
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params || {} }, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeHost() {
|
||||||
|
return request('ui/initialize', {
|
||||||
|
protocolVersion: mcpAppsProtocolVersion,
|
||||||
|
appInfo: {
|
||||||
|
name: 'SHD Active Projects',
|
||||||
|
title: 'SHD Active Projects',
|
||||||
|
version: '1.0.0',
|
||||||
|
websiteUrl: 'https://shd.xyz.su'
|
||||||
|
},
|
||||||
|
appCapabilities: {}
|
||||||
|
}).then(function () {
|
||||||
|
notify('ui/notifications/initialized', {});
|
||||||
|
}).catch(function () {
|
||||||
|
// The window.openai compatibility bridge can work without the raw postMessage handshake.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePayload(value) {
|
||||||
|
if (!value) return null;
|
||||||
|
if (value.structuredContent) return value.structuredContent;
|
||||||
|
if (value.toolOutput) return value.toolOutput;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProjects(value) {
|
||||||
|
var payload = normalizePayload(value);
|
||||||
|
if (!payload || payload.error) return [];
|
||||||
|
if (Array.isArray(payload.projects)) return payload.projects.filter(function (item) { return item && typeof item === 'object'; });
|
||||||
|
var data = payload.data;
|
||||||
|
if (data && !Array.isArray(data) && Array.isArray(data.data)) data = data.data;
|
||||||
|
return Array.isArray(data) ? data.filter(function (item) { return item && typeof item === 'object'; }) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProjectCode(project) {
|
||||||
|
return String(project.code || project.project_code || project.contract_project_id || project.id || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProjectName(project) {
|
||||||
|
return String(project.display_name || project.title || project.name || getProjectCode(project) || '—');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatus(project) {
|
||||||
|
return String(project.status || project.phase || '—');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTone(status) {
|
||||||
|
var value = String(status).toLowerCase();
|
||||||
|
if (/done|complete|completed|closed|resolved|finished|готов|заверш/.test(value)) return 'done';
|
||||||
|
if (/cancel|reject|blocked|error|отмен|отклон|блок/.test(value)) return 'danger';
|
||||||
|
if (/progress|active|work|processing|в работе|актив/.test(value)) return 'progress';
|
||||||
|
if (/pending|review|pause|ожид|провер|пауза/.test(value)) return 'warning';
|
||||||
|
return 'neutral';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDeadlineTime(project) {
|
||||||
|
var value = project.deadline;
|
||||||
|
if (!value) return Number.POSITIVE_INFINITY;
|
||||||
|
var timestamp = Date.parse(String(value));
|
||||||
|
return Number.isNaN(timestamp) ? Number.POSITIVE_INFINITY : timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(value) {
|
||||||
|
if (!value) return t.missingDate;
|
||||||
|
var timestamp = Date.parse(String(value));
|
||||||
|
if (Number.isNaN(timestamp)) return String(value);
|
||||||
|
try {
|
||||||
|
return new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }).format(new Date(timestamp));
|
||||||
|
} catch (error) {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOverdue(project) {
|
||||||
|
var timestamp = getDeadlineTime(project);
|
||||||
|
var status = getStatus(project).toLowerCase();
|
||||||
|
return Number.isFinite(timestamp) && timestamp < Date.now() && !/done|complete|closed|resolved|заверш/.test(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeElement(tag, text, className) {
|
||||||
|
var node = document.createElement(tag);
|
||||||
|
if (className) node.className = className;
|
||||||
|
if (text !== undefined) node.textContent = text;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSummary(projects) {
|
||||||
|
elements.summary.replaceChildren();
|
||||||
|
var noDate = projects.filter(function (project) { return !project.deadline; }).length;
|
||||||
|
var archived = projects.filter(function (project) { return project.is_archived === true; }).length;
|
||||||
|
var total = makeElement('span', '', 'summary-item');
|
||||||
|
total.append(makeElement('strong', String(projects.length)), ' ' + t.total);
|
||||||
|
elements.summary.append(total);
|
||||||
|
if (noDate) {
|
||||||
|
var missing = makeElement('span', '', 'summary-item');
|
||||||
|
missing.append(makeElement('strong', String(noDate)), ' ' + t.withoutDate);
|
||||||
|
elements.summary.append(missing);
|
||||||
|
}
|
||||||
|
if (archived) {
|
||||||
|
var archivedNode = makeElement('span', '', 'summary-item');
|
||||||
|
archivedNode.append(makeElement('strong', String(archived)), ' ' + t.archived);
|
||||||
|
elements.summary.append(archivedNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRows() {
|
||||||
|
var query = elements.search.value.trim().toLowerCase();
|
||||||
|
var projects = currentProjects.filter(function (project) {
|
||||||
|
if (!query) return true;
|
||||||
|
return (getProjectName(project) + ' ' + getProjectCode(project) + ' ' + getStatus(project)).toLowerCase().indexOf(query) !== -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
elements.rows.replaceChildren();
|
||||||
|
elements.error.hidden = true;
|
||||||
|
elements.empty.hidden = projects.length !== 0;
|
||||||
|
elements.empty.textContent = currentProjects.length === 0 ? t.noProjects : t.noMatch;
|
||||||
|
projects.forEach(function (project) {
|
||||||
|
var row = makeElement('button', '', 'project-row');
|
||||||
|
row.type = 'button';
|
||||||
|
row.setAttribute('aria-label', getProjectName(project));
|
||||||
|
|
||||||
|
var name = makeElement('span', '', 'project-name');
|
||||||
|
name.append(makeElement('span', getProjectName(project), 'project-title'));
|
||||||
|
name.append(makeElement('span', getProjectCode(project), 'project-code'));
|
||||||
|
|
||||||
|
var deadline = makeElement('span', formatDate(project.deadline), 'deadline' + (project.deadline ? '' : ' missing') + (isOverdue(project) ? ' overdue' : ''));
|
||||||
|
var status = makeElement('span', getStatus(project), 'status status-' + getTone(getStatus(project)));
|
||||||
|
row.append(name, deadline, status);
|
||||||
|
row.addEventListener('click', function () { showProject(project); });
|
||||||
|
elements.rows.append(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(value) {
|
||||||
|
var payload = normalizePayload(value);
|
||||||
|
currentSnapshot = payload;
|
||||||
|
currentProjects = getProjects(payload).slice().sort(function (left, right) {
|
||||||
|
return getDeadlineTime(left) - getDeadlineTime(right) || getProjectName(left).localeCompare(getProjectName(right), locale);
|
||||||
|
});
|
||||||
|
renderSummary(currentProjects);
|
||||||
|
renderRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBusy(nextBusy, label) {
|
||||||
|
busy = nextBusy;
|
||||||
|
elements.refresh.disabled = nextBusy;
|
||||||
|
if (nextBusy) elements.refresh.textContent = label || t.loading;
|
||||||
|
else elements.refresh.textContent = t.refresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setError(message) {
|
||||||
|
elements.error.hidden = false;
|
||||||
|
elements.error.textContent = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapResult(value) {
|
||||||
|
var payload = normalizePayload(value);
|
||||||
|
if (value && value.isError) throw new Error(payload && payload.error ? payload.error : t.refreshError);
|
||||||
|
if (payload && payload.error) throw new Error(String(payload.error));
|
||||||
|
return payload || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
if (busy) return;
|
||||||
|
setBusy(true);
|
||||||
|
elements.error.hidden = true;
|
||||||
|
try {
|
||||||
|
var result = await request('tools/call', { name: 'shd_list_projects', arguments: { archived: false, limit: 100 } });
|
||||||
|
render(unwrapResult(result));
|
||||||
|
} catch (error) {
|
||||||
|
setError(error && error.message ? error.message : t.refreshError);
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDetail(label, value) {
|
||||||
|
var item = makeElement('div');
|
||||||
|
item.append(makeElement('span', label, 'detail-label'), makeElement('span', value || '—', 'detail-value'));
|
||||||
|
elements.detailsGrid.append(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDetailSnapshot(project) {
|
||||||
|
elements.detailsTitle.textContent = getProjectName(project);
|
||||||
|
elements.detailsCode.textContent = getProjectCode(project) ? t.code + ': ' + getProjectCode(project) : '';
|
||||||
|
elements.detailsGrid.replaceChildren();
|
||||||
|
addDetail(t.deadline, formatDate(project.deadline));
|
||||||
|
addDetail(t.status, getStatus(project));
|
||||||
|
addDetail(t.phase, String(project.phase || '—'));
|
||||||
|
addDetail(t.timezone, String(project.timezone || '—'));
|
||||||
|
addDetail(t.updated, formatDate(project.updated_at));
|
||||||
|
elements.details.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showProject(project) {
|
||||||
|
showDetailSnapshot(project);
|
||||||
|
var code = getProjectCode(project);
|
||||||
|
if (!code) return;
|
||||||
|
elements.detailsLoading.hidden = false;
|
||||||
|
elements.detailsLoading.textContent = t.loadingProject;
|
||||||
|
try {
|
||||||
|
var result = await request('tools/call', { name: 'shd_get_project', arguments: { project_code: code } });
|
||||||
|
var payload = unwrapResult(result);
|
||||||
|
var data = payload && payload.data;
|
||||||
|
if (Array.isArray(data)) data = data[0];
|
||||||
|
if (data && typeof data === 'object') showDetailSnapshot(data);
|
||||||
|
} catch (error) {
|
||||||
|
setError(error && error.message ? error.message : t.projectError);
|
||||||
|
} finally {
|
||||||
|
elements.detailsLoading.hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 && pendingRequests.has(message.id)) {
|
||||||
|
var pending = pendingRequests.get(message.id);
|
||||||
|
pendingRequests.delete(message.id);
|
||||||
|
window.clearTimeout(pending.timeout);
|
||||||
|
if (message.error) pending.reject(new Error(message.error.message || t.refreshError));
|
||||||
|
else pending.resolve(message.result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.method === 'ui/notifications/tool-input') {
|
||||||
|
latestToolInput = message.params || null;
|
||||||
|
if (!currentProjects.length) render(latestToolInput);
|
||||||
|
}
|
||||||
|
if (message.method === 'ui/notifications/tool-result') render(message.params && message.params.structuredContent);
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
elements.search.addEventListener('input', renderRows);
|
||||||
|
elements.refresh.addEventListener('click', refresh);
|
||||||
|
elements.closeDetails.addEventListener('click', function () { elements.details.hidden = true; });
|
||||||
|
applyTranslations();
|
||||||
|
void initializeHost();
|
||||||
|
|
||||||
|
var initialOutput = typeof window !== 'undefined' && window.openai ? window.openai.toolOutput : null;
|
||||||
|
if (initialOutput) render(initialOutput);
|
||||||
|
else {
|
||||||
|
latestToolInput = typeof window !== 'undefined' && window.openai ? window.openai.toolInput : null;
|
||||||
|
render(latestToolInput || currentSnapshot);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"name": "shd-active-projects",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"file": "index.html",
|
||||||
|
"bytes": 22732,
|
||||||
|
"gzipBytes": 6133,
|
||||||
|
"mediaType": "text/html;profile=mcp-app",
|
||||||
|
"uri": "ui://shd/active-projects/v1.html",
|
||||||
|
"sha256": "49b3ab8629540f2c1075c3e096e2990055ec56181e6dd02dcd998bc98d9c7069"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "../../../../../LICENSE",
|
||||||
|
"source": {
|
||||||
|
"repository": "https://github.com/bulava92/shd-mcp-plugin",
|
||||||
|
"path": "plugins/shd-mcp-plugin/widgets/active-projects/v1/index.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
PLUGIN = ROOT / "plugins" / "shd-mcp-plugin"
|
||||||
|
SKILLS = PLUGIN / "skills"
|
||||||
|
|
||||||
|
EXPECTED_SKILLS = {
|
||||||
|
"shd-active-projects",
|
||||||
|
"shd-analytics",
|
||||||
|
"shd-agents-operations",
|
||||||
|
"shd-discussions",
|
||||||
|
"shd-documents",
|
||||||
|
"shd-entity-resolution",
|
||||||
|
"shd-estimate-management",
|
||||||
|
"shd-event-positions",
|
||||||
|
"shd-finance-crm",
|
||||||
|
"shd-financial-account-audit",
|
||||||
|
"shd-gitea",
|
||||||
|
"shd-inventory",
|
||||||
|
"shd-notifications",
|
||||||
|
"shd-organizations-acl",
|
||||||
|
"shd-project-db-audit",
|
||||||
|
"shd-project-files",
|
||||||
|
"shd-project-status",
|
||||||
|
"shd-realtime-activity",
|
||||||
|
"shd-routing",
|
||||||
|
"shd-safe-bulk-change",
|
||||||
|
"shd-status-page",
|
||||||
|
"shd-task-workflow",
|
||||||
|
"shd-terms-contracts",
|
||||||
|
"shd-wiki-management",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PluginPackageTests(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.manifest = json.loads(
|
||||||
|
(PLUGIN / ".codex-plugin" / "plugin.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_manifest_points_to_real_package_parts(self):
|
||||||
|
self.assertEqual(self.manifest["name"], "shd-mcp-plugin")
|
||||||
|
self.assertRegex(self.manifest["version"], r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$")
|
||||||
|
self.assertTrue((PLUGIN / self.manifest["skills"].removeprefix("./")).is_dir())
|
||||||
|
self.assertTrue((PLUGIN / self.manifest["mcpServers"].removeprefix("./")).is_file())
|
||||||
|
interface = self.manifest["interface"]
|
||||||
|
for field in ("composerIcon", "logo"):
|
||||||
|
asset = interface[field]
|
||||||
|
self.assertTrue((PLUGIN / asset.removeprefix("./")).is_file(), field)
|
||||||
|
self.assertLessEqual(len(interface["defaultPrompt"]), 3)
|
||||||
|
|
||||||
|
def test_all_expected_skills_have_contract_files(self):
|
||||||
|
actual = {path.parent.name for path in SKILLS.glob("*/SKILL.md")}
|
||||||
|
self.assertEqual(actual, EXPECTED_SKILLS)
|
||||||
|
|
||||||
|
routing = (SKILLS / "shd-routing" / "SKILL.md").read_text(encoding="utf-8")
|
||||||
|
for name in EXPECTED_SKILLS - {"shd-routing"}:
|
||||||
|
self.assertIn(f"`{name}`", routing, name)
|
||||||
|
|
||||||
|
for name in sorted(EXPECTED_SKILLS):
|
||||||
|
skill_dir = SKILLS / name
|
||||||
|
skill_text = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
|
||||||
|
self.assertTrue(skill_text.startswith("---\n"), name)
|
||||||
|
self.assertRegex(skill_text, r"(?m)^name:\s*\S+", name)
|
||||||
|
self.assertRegex(skill_text, r"(?m)^description:\s*.+", name)
|
||||||
|
self.assertNotIn("[TODO:", skill_text, name)
|
||||||
|
|
||||||
|
references = list((skill_dir / "references").glob("*.md"))
|
||||||
|
self.assertTrue(references, f"{name} has no reference contract")
|
||||||
|
agents = skill_dir / "agents" / "openai.yaml"
|
||||||
|
agent_text = agents.read_text(encoding="utf-8")
|
||||||
|
self.assertIn("interface:", agent_text, name)
|
||||||
|
self.assertIn("display_name:", agent_text, name)
|
||||||
|
self.assertIn("short_description:", agent_text, name)
|
||||||
|
self.assertIn("allow_implicit_invocation: false", agent_text, name)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
WIDGET = ROOT / "plugins" / "shd-mcp-plugin" / "widgets" / "active-projects" / "v1"
|
||||||
|
|
||||||
|
|
||||||
|
class WidgetArtifactTests(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.manifest = json.loads((WIDGET / "manifest.json").read_text(encoding="utf-8"))
|
||||||
|
cls.resource = next(item for item in cls.manifest["resources"] if item["file"] == "index.html")
|
||||||
|
cls.html = (WIDGET / "index.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def test_manifest_and_checksums_match_artifact(self):
|
||||||
|
digest = hashlib.sha256(self.html.encode("utf-8")).hexdigest()
|
||||||
|
self.assertEqual(self.resource["uri"], "ui://shd/active-projects/v1.html")
|
||||||
|
self.assertEqual(self.resource["mediaType"], "text/html;profile=mcp-app")
|
||||||
|
self.assertEqual(self.resource["bytes"], len(self.html.encode("utf-8")))
|
||||||
|
self.assertEqual(self.resource["sha256"], digest)
|
||||||
|
|
||||||
|
checksum = (WIDGET / "SHA256SUMS").read_text(encoding="utf-8").strip().split()
|
||||||
|
self.assertEqual(checksum, [digest, "index.html"])
|
||||||
|
|
||||||
|
license_path = (WIDGET / self.manifest["license"]).resolve()
|
||||||
|
self.assertEqual(license_path, (ROOT / "LICENSE").resolve())
|
||||||
|
source_path = (ROOT / self.manifest["source"]["path"]).resolve()
|
||||||
|
self.assertEqual(source_path, (WIDGET / "index.html").resolve())
|
||||||
|
|
||||||
|
def test_widget_uses_portable_bridge_and_no_external_script(self):
|
||||||
|
for marker in (
|
||||||
|
"ui/initialize",
|
||||||
|
"ui/notifications/initialized",
|
||||||
|
"ui/notifications/tool-input",
|
||||||
|
"ui/notifications/tool-result",
|
||||||
|
"tools/call",
|
||||||
|
"window.openai",
|
||||||
|
):
|
||||||
|
self.assertIn(marker, self.html, marker)
|
||||||
|
self.assertNotIn("<script src=", self.html.lower())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user