@thehammer/danx-dashboard-mcp 0.1.311 → 0.1.313
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/design-publish.js +23 -6
- package/dist/index.js +5 -2
- package/dist/listen.js +8 -3
- package/dist/route-board-scope.generated.js +1 -0
- package/dist/tool-access.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ All exposed as `mcp__danx-dashboard__<name>` once wired through the workspace `m
|
|
|
50
50
|
| `danxbot_api_list` | No args. Lists the routes this caller's credential may reach as `{routes: ["METHOD /path"]}` (one bare string per route, exactly what `danxbot_api_spec` takes; a dispatch's own callbacks are in it) — call this first when you don't know the route for what you need to do. |
|
|
51
51
|
| `danxbot_api_spec` | `{endpoints}` (comma-joined `METHOD /path` list, required, min 1). Returns the exact OpenAPI contract for those routes — call this when you know the route but not its exact request/response shape. |
|
|
52
52
|
| `attach_file` | `{card_id, path, replaces?, primary?, filename?, content_type?}` — the server reads the LOCAL file at `path` (absolute, `/tmp/a.png` or `C:\x\a.png`) and uploads it, so its bytes never pass through the agent's context. `replaces: <attachment id>` swaps that attachment's file in place (`PUT /api/issues/:id/attachments/:aid`: same id, name and cover flag) instead of adding one; `primary: true` makes it the card's single cover, atomically with the write. Reply: `{issue: <compact row>, attachment_id, url, thumbnail_url, is_primary}`. Shown to a key holding `cards.edit` (DX-4523); a dispatch profile still receives it only when its catalog selection names it (`slack-agent` and `work`, DX-4386, through `extraTools`). A screenshot of browser-visible work: first `primary: true`, then `replaces` at each milestone. |
|
|
53
|
-
| `design_publish` | `{target, note, dir, card_id}` — builds a design with the TARGET repo's own Vite (resolved from the target's root at run time: this package depends on neither `vite` nor `playwright`) and publishes it as a revision of
|
|
53
|
+
| `design_publish` | `{target, note, dir, card_id, design_no | design_title}` — builds a design with the TARGET repo's own Vite (resolved from the target's root at run time: this package depends on neither `vite` nor `playwright`) and publishes it as a revision of one of an existing card's designs, or as a new design on it (DX-4077, DX-5164, DX-5193): a card holds several designs, so pass exactly one of `design_no` (add a revision to that design, `POST /api/issues/<card>/designs/<no>/revisions`) or `design_title` (start a new design, `POST /api/issues/<card>/designs`; the title is fixed for good), both-or-neither is refused before anything is built; the answer carries `{card_id, design_no, revision_no, dashboard_url, local_html_path, screenshot}`. `target` names one of the repo's design targets, read from the dashboard (`GET /api/repos/<repo>`, edited in Settings > Board > General), never from a file; `dir` holds `Design.tsx` under the target's root. The repo is the board's `repo_name` (`GET /api/boards/<board>`). The build runs in `<root>/.danx-design/<build-id>/` (removed afterwards), in a short-lived `node` child process that exits when the build ends, so the server never holds the design's worktree files open and no esbuild outlives the publish (DX-4727), the page is written to `<tmpdir>/danx-design/<build-id>.html` before the upload, and a 1280x800 screenshot is taken when Playwright resolves from the target or this server's cwd (`screenshot: "none: <reason>"` otherwise, never a failed publish). `card_id` is required and names an existing card (any but an Epic or Feature); the tool never creates a card, and a refused revision comes back verbatim with the `card_id`. Shown to a key holding `cards.edit`; opt-in, so a dispatch profile receives it only when its catalog selection names it (none does yet). |
|
|
54
54
|
| `request_permission` | `{permissions, reason}` — asks a person to grant this key ONE MORE permission (see "Asking for one more permission" above). Answers at once with the approval URL and confirm code; the grant is picked up in the background. |
|
|
55
55
|
|
|
56
56
|
There is no dedicated `resource_fields` tool (removed DX-3465 — it was a thin wrapper `danxbot_api` already reaches with no agent-specific behavior earning it its own tool). What an opt-in field TREE may name for one resource (`{resource, description, always, hashes, fields, relations}`) is `danxbot_api({method:"GET", path:"/api/resources/<resource>/fields"})` — install-global, no `board`; `resource: "issue"` is the root, and a relation's own `resource` in the response is what to call this again with, one level deeper.
|
package/dist/design-publish.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DX-4077 - the `design_publish` flow: build a design against the repo's real components with the repo's own Vite, and publish it as
|
|
3
|
-
* a revision of an existing card's design.
|
|
3
|
+
* a revision of one of an existing card's designs, or as the first revision of a new design on it (DX-5193).
|
|
4
4
|
*
|
|
5
5
|
* It lives in this package, not behind `danxbot_api`, because it is agent-specific: the build runs where the source is (the agent's
|
|
6
6
|
* own checkout or worktree) and the dashboard holds no connected repo's source and no Vite. Every dashboard call goes through the same
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* The design goes on an EXISTING card (`card_id`, any card but an Epic or Feature, DX-5164): this tool never creates a card. To show
|
|
10
10
|
* options for work that has no card yet, create the card first.
|
|
11
11
|
*
|
|
12
|
+
* DX-5193: a card holds SEVERAL designs. `design_no` adds a revision to that existing design (`POST .../designs/<no>/revisions`);
|
|
13
|
+
* `design_title` starts a new design (`POST .../designs`, the title fixed for good). Exactly one of the two, refused before any call.
|
|
14
|
+
*
|
|
12
15
|
* ORDER, so a failure leaves as little behind as it can: the arguments, then the card's board, then the repo and its targets (the
|
|
13
16
|
* dashboard answers; a refusal comes back verbatim), then the checkout and the build, then the screenshot, and only THEN the revision is
|
|
14
17
|
* posted. A failed build or screenshot never leaves anything on the card; a refused revision comes back verbatim with the `card_id`
|
|
@@ -44,6 +47,13 @@ export async function designPublish(client, args, deps) {
|
|
|
44
47
|
if (args.note.trim() === "")
|
|
45
48
|
fail("`note` is required: say what changed since the last revision, which decision note it answers and what is still open");
|
|
46
49
|
const { card_id: cardId } = args;
|
|
50
|
+
// DX-5193: exactly one of design_no / design_title, refused before any call so a bad pair never builds a page.
|
|
51
|
+
const hasNo = args.design_no !== undefined;
|
|
52
|
+
const hasTitle = args.design_title !== undefined;
|
|
53
|
+
if (hasNo === hasTitle)
|
|
54
|
+
fail("pass exactly one of `design_no` (add a revision to that design of the card) or `design_title` (start a new design on the card)");
|
|
55
|
+
if (args.design_title?.trim() === "")
|
|
56
|
+
fail("`design_title` must not be blank");
|
|
47
57
|
const boardOrRefusal = await readCardBoard(client, cardId);
|
|
48
58
|
if (!boardOrRefusal.ok)
|
|
49
59
|
return boardOrRefusal;
|
|
@@ -70,12 +80,16 @@ export async function designPublish(client, args, deps) {
|
|
|
70
80
|
const { html, buildId } = await buildDesign({ ...checked, buildId: newBuildId() });
|
|
71
81
|
const localHtmlPath = await writeLocalHtml(buildId, html);
|
|
72
82
|
const shot = await captureScreenshot(html, { rootAbs: checked.rootAbs, cwd: deps.cwd });
|
|
83
|
+
const designRoute = hasTitle
|
|
84
|
+
? `/api/issues/${encodeURIComponent(cardId)}/designs`
|
|
85
|
+
: `/api/issues/${encodeURIComponent(cardId)}/designs/${args.design_no}/revisions`;
|
|
73
86
|
let published;
|
|
74
87
|
try {
|
|
75
88
|
published = await danxbotApi(client, {
|
|
76
89
|
method: "POST",
|
|
77
|
-
path:
|
|
90
|
+
path: designRoute,
|
|
78
91
|
body: {
|
|
92
|
+
...(hasTitle ? { title: args.design_title } : {}),
|
|
79
93
|
note: args.note,
|
|
80
94
|
target: target.name,
|
|
81
95
|
entry: repoRelative(repoRoot, path.join(checked.dirAbs, DESIGN_FILE)),
|
|
@@ -93,12 +107,15 @@ export async function designPublish(client, args, deps) {
|
|
|
93
107
|
}
|
|
94
108
|
if (!published.ok)
|
|
95
109
|
return { ...published, card_id: cardId, local_html_path: localHtmlPath };
|
|
96
|
-
const
|
|
97
|
-
if (typeof
|
|
98
|
-
fail(`POST
|
|
110
|
+
const answer = published.body;
|
|
111
|
+
if (typeof answer?.design_no !== "number")
|
|
112
|
+
fail(`POST ${designRoute} answered without a design_no`);
|
|
113
|
+
if (typeof answer.revision_no !== "number")
|
|
114
|
+
fail(`POST ${designRoute} answered without a revision_no`);
|
|
99
115
|
const body = {
|
|
100
116
|
card_id: cardId,
|
|
101
|
-
|
|
117
|
+
design_no: answer.design_no,
|
|
118
|
+
revision_no: answer.revision_no,
|
|
102
119
|
dashboard_url: `${deps.baseUrl.replace(/\/+$/, "")}/board/${cardId}`,
|
|
103
120
|
local_html_path: localHtmlPath,
|
|
104
121
|
screenshot: shot.screenshot,
|
package/dist/index.js
CHANGED
|
@@ -646,12 +646,13 @@ strictTool("attach_file", "Attach a LOCAL file to a card. This server reads and
|
|
|
646
646
|
// `MCP_SERVER_OPT_IN_TOOLS`) and granted to no profile here: a default tool would add its schema to every default-all role's first
|
|
647
647
|
// turn, and a role with no worktree or toolchain can only fail it. The package takes no `vite` or `playwright` dependency: both are
|
|
648
648
|
// resolved from the target repo at run time (`design-build.ts`, `design-screenshot.ts`).
|
|
649
|
-
strictTool("design_publish", "Build a design against this repo's REAL components with the repo's own Vite and publish it as a revision of
|
|
649
|
+
strictTool("design_publish", "Build a design against this repo's REAL components with the repo's own Vite and publish it as a revision of one of an existing card's designs, or as a new design on it. " +
|
|
650
650
|
"The design is a directory holding `Design.tsx` (a default-exported React component) plus any helpers; it may be a whole " +
|
|
651
651
|
"front-end app, and how to split its states is your call. Read the board's design prompt first: `danxbot_api` GET " +
|
|
652
652
|
"/api/boards/<board> with fields {controls: true}, the row keyed `design.prompt`, field `value`. " +
|
|
653
653
|
"`card_id` names the card the design goes on (any card but an Epic or Feature); this tool never creates a card, so to show options for work that has no card yet, create the card first. " +
|
|
654
|
-
"
|
|
654
|
+
"A card holds several designs: pass `design_title` to start a NEW design, or `design_no` (from the card's `designs` relation) to add a revision to an existing one; exactly one. " +
|
|
655
|
+
"Returns `{card_id, design_no, revision_no, dashboard_url, local_html_path, " +
|
|
655
656
|
"screenshot}`: `local_html_path` is the built page, written before the upload so you can open it yourself; `screenshot` is " +
|
|
656
657
|
"`captured` (1280x800) or `none: <reason>` (no Playwright in the repo: the publish still succeeds). Build errors and a " +
|
|
657
658
|
"refused revision (400, 409, 413) come back verbatim with the `card_id`: fix the " +
|
|
@@ -667,6 +668,8 @@ strictTool("design_publish", "Build a design against this repo's REAL components
|
|
|
667
668
|
.min(1)
|
|
668
669
|
.describe("The design directory: absolute, or relative to this server's cwd. Inside the target's root, holding `Design.tsx`."),
|
|
669
670
|
card_id: z.string().min(1).describe("The card this design goes on: any existing card but an Epic or Feature."),
|
|
671
|
+
design_no: z.number().int().positive().optional().describe("Add a revision to this existing design of the card (its `designs` relation). Not with `design_title`."),
|
|
672
|
+
design_title: z.string().min(1).optional().describe("Start a NEW design on the card with this title, fixed for good. Not with `design_no`."),
|
|
670
673
|
}, async (args) => guardedJson(() => designPublish(client, args, { baseUrl: config.baseUrl, cwd: process.cwd() })));
|
|
671
674
|
// ---------------- request_permission (DX-4434, parent DX-4432) ----------------
|
|
672
675
|
strictTool("request_permission", "Ask a person to grant this key ONE MORE permission. Call it when a call was refused `403 forbidden` naming a " +
|
package/dist/listen.js
CHANGED
|
@@ -195,6 +195,10 @@ export function invalidEventReason(value) {
|
|
|
195
195
|
case "design_decided": {
|
|
196
196
|
if (typeof d.decision_id !== "number")
|
|
197
197
|
return "detail.decision_id is not a number";
|
|
198
|
+
if (typeof d.design_no !== "number")
|
|
199
|
+
return "detail.design_no is not a number";
|
|
200
|
+
if (typeof d.design_title !== "string")
|
|
201
|
+
return "detail.design_title is not a string";
|
|
198
202
|
if (typeof d.revision_no !== "number")
|
|
199
203
|
return "detail.revision_no is not a number";
|
|
200
204
|
if (!isDesignOutcome(d.outcome))
|
|
@@ -286,11 +290,12 @@ function describe(event) {
|
|
|
286
290
|
// DX-4078: names the outcome, the revision and the note, and what the agent does next, so it never has to re-read the card to learn the verdict.
|
|
287
291
|
case "design_decided": {
|
|
288
292
|
const note = d.note;
|
|
289
|
-
|
|
293
|
+
// DX-5193: a card holds several designs, so the line names which one the verdict closes.
|
|
294
|
+
const revision = `revision ${d.revision_no} of design ${d.design_no} "${d.design_title}"`;
|
|
290
295
|
if (d.outcome === "approved")
|
|
291
|
-
return `${who} APPROVED ${revision}${note === null ? "" : ` — note: ${quoted(note)}`}.
|
|
296
|
+
return `${who} APPROVED ${revision}${note === null ? "" : ` — note: ${quoted(note)}`}. That design is closed; build from it`;
|
|
292
297
|
if (d.outcome === "rejected")
|
|
293
|
-
return `${who} REJECTED ${revision} — reason: ${quoted(note)}.
|
|
298
|
+
return `${who} REJECTED ${revision} — reason: ${quoted(note)}. That design is closed; do not continue it`;
|
|
294
299
|
return `${who} requested changes on ${revision} — ${quoted(note)}. Publish a new revision that addresses it`;
|
|
295
300
|
}
|
|
296
301
|
default:
|
|
@@ -40,6 +40,7 @@ export const GET_ROUTE_BOARD_SCOPE = [
|
|
|
40
40
|
["/api/plans/mine/records", "refuse"],
|
|
41
41
|
["/api/platform/signup-codes", "refuse"],
|
|
42
42
|
["/api/platform/teams", "refuse"],
|
|
43
|
+
["/api/plugins", "refuse"],
|
|
43
44
|
["/api/reminders", "refuse"],
|
|
44
45
|
["/api/repos", "refuse"],
|
|
45
46
|
["/api/security-incidents", "refuse"],
|
package/dist/tool-access.js
CHANGED
|
@@ -42,7 +42,7 @@ export const TOOL_ACCESS = {
|
|
|
42
42
|
danxbot_api_spec: BOOTSTRAP,
|
|
43
43
|
// `POST /api/issues/:id/attachments`
|
|
44
44
|
attach_file: needs("cards.edit"),
|
|
45
|
-
// DX-4077 - builds in the agent's own checkout, then `POST /api/issues/:id/design/revisions` (`cards.edit
|
|
45
|
+
// DX-4077 - builds in the agent's own checkout, then `POST /api/issues/:id/designs` (a new design) or `POST /api/issues/:id/designs/:designNo/revisions` (the next revision), both `cards.edit` (DX-4076, DX-5193). The routes it
|
|
46
46
|
// reads on the way (`GET /api/boards/:id`, `GET /api/repos/:id`, `GET /api/issues/:id`) and the card it creates are separate
|
|
47
47
|
// checks on the dashboard whose refusals come back verbatim.
|
|
48
48
|
design_publish: needs("cards.edit"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-dashboard-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.313",
|
|
4
4
|
"description": "Stdio MCP server fronting danxbot's dashboard API for agents (operator sessions and dispatched workers alike): the generic danxbot_api/danxbot_api_list/danxbot_api_spec tools reach any route the caller's credential may call, plus dedicated agent-specific tools (plan_connect, request_permission, attach_file, and danxbot_slack_reply) (DX-3465, PLN-11 R-25).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|