@thehammer/danx-dashboard-mcp 0.1.310 → 0.1.311
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 +17 -47
- package/dist/index.js +8 -16
- 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, dir, card_id
|
|
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 a design on an existing card (DX-4077, DX-5164). `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,16 +1,18 @@
|
|
|
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
|
|
3
|
+
* a revision of an existing card's design.
|
|
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
|
|
7
7
|
* client `danxbot_api` uses, so it carries the session's credential, trace and session headers.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
+
* options for work that has no card yet, create the card first.
|
|
11
|
+
*
|
|
12
|
+
* 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
|
+
* dashboard answers; a refusal comes back verbatim), then the checkout and the build, then the screenshot, and only THEN the revision is
|
|
14
|
+
* posted. A failed build or screenshot never leaves anything on the card; a refused revision comes back verbatim with the `card_id`
|
|
15
|
+
* and the built page's path.
|
|
14
16
|
*/
|
|
15
17
|
import path from "node:path";
|
|
16
18
|
import { buildDesign, newBuildId, writeLocalHtml } from "./design-build.js";
|
|
@@ -26,42 +28,23 @@ function field(body, name, where) {
|
|
|
26
28
|
fail(`${where} answered without "${name}"`);
|
|
27
29
|
return value;
|
|
28
30
|
}
|
|
29
|
-
/** The board the card lives on
|
|
30
|
-
async function
|
|
31
|
-
if (args.card_id === undefined) {
|
|
32
|
-
const board = args.board ?? deps.serverBoard;
|
|
33
|
-
if (board === undefined)
|
|
34
|
-
fail("no board: this server has none of its own, so name one in `board` (`<repo>:<slug>`)");
|
|
35
|
-
return { ok: true, board };
|
|
36
|
-
}
|
|
31
|
+
/** The board the card lives on, read from the card: a revision goes on the card's own board. */
|
|
32
|
+
async function readCardBoard(client, cardId) {
|
|
37
33
|
const card = await danxbotApi(client, {
|
|
38
34
|
method: "GET",
|
|
39
|
-
path: `/api/issues/${encodeURIComponent(
|
|
35
|
+
path: `/api/issues/${encodeURIComponent(cardId)}`,
|
|
40
36
|
query: { fields: { board_id: true } },
|
|
41
37
|
});
|
|
42
38
|
if (!card.ok)
|
|
43
39
|
return card;
|
|
44
|
-
|
|
45
|
-
if (args.board !== undefined && args.board !== board) {
|
|
46
|
-
fail(`card ${args.card_id} lives on board ${board}, not ${args.board}: a revision goes on the card's own board, so omit \`board\` or name ${board}`);
|
|
47
|
-
}
|
|
48
|
-
return { ok: true, board };
|
|
40
|
+
return { ok: true, board: field(card.body, "board_id", `GET /api/issues/${cardId}`) };
|
|
49
41
|
}
|
|
50
42
|
export async function designPublish(client, args, deps) {
|
|
51
|
-
// DX-4729: refused before any call, so a blank note never leaves a built page
|
|
43
|
+
// DX-4729: refused before any call, so a blank note never leaves a built page or a screenshot behind.
|
|
52
44
|
if (args.note.trim() === "")
|
|
53
45
|
fail("`note` is required: say what changed since the last revision, which decision note it answers and what is still open");
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const given = ["title", "summary", "description"].filter((k) => args[k] !== undefined);
|
|
57
|
-
if (given.length > 0)
|
|
58
|
-
fail(`${given.map((k) => `\`${k}\``).join(", ")} name the card this call CREATES, so they cannot be combined with \`card_id\`: drop them to add a revision to ${args.card_id}`);
|
|
59
|
-
}
|
|
60
|
-
// No card to add to: it is created here, and a card needs a title and a summary. Refused before any call.
|
|
61
|
-
if (args.card_id === undefined && (args.title === undefined || args.summary === undefined)) {
|
|
62
|
-
fail("`title` and `summary` are required without `card_id` (they name the Design card this creates)");
|
|
63
|
-
}
|
|
64
|
-
const boardOrRefusal = await resolveBoard(client, args, deps);
|
|
46
|
+
const { card_id: cardId } = args;
|
|
47
|
+
const boardOrRefusal = await readCardBoard(client, cardId);
|
|
65
48
|
if (!boardOrRefusal.ok)
|
|
66
49
|
return boardOrRefusal;
|
|
67
50
|
const { board } = boardOrRefusal;
|
|
@@ -87,19 +70,6 @@ export async function designPublish(client, args, deps) {
|
|
|
87
70
|
const { html, buildId } = await buildDesign({ ...checked, buildId: newBuildId() });
|
|
88
71
|
const localHtmlPath = await writeLocalHtml(buildId, html);
|
|
89
72
|
const shot = await captureScreenshot(html, { rootAbs: checked.rootAbs, cwd: deps.cwd });
|
|
90
|
-
let cardId = args.card_id;
|
|
91
|
-
if (cardId === undefined) {
|
|
92
|
-
const created = await danxbotApi(client, {
|
|
93
|
-
method: "POST",
|
|
94
|
-
path: "/api/issues",
|
|
95
|
-
// `description` is a creation-time seed: the route requires a string.
|
|
96
|
-
body: { type: "Design", plan: "mine", board, title: args.title, summary: args.summary, description: args.description ?? "" },
|
|
97
|
-
board,
|
|
98
|
-
});
|
|
99
|
-
if (!created.ok)
|
|
100
|
-
return { ...created, local_html_path: localHtmlPath };
|
|
101
|
-
cardId = field(created.body?.issue, "id", "POST /api/issues");
|
|
102
|
-
}
|
|
103
73
|
let published;
|
|
104
74
|
try {
|
|
105
75
|
published = await danxbotApi(client, {
|
|
@@ -118,8 +88,8 @@ export async function designPublish(client, args, deps) {
|
|
|
118
88
|
});
|
|
119
89
|
}
|
|
120
90
|
catch (err) {
|
|
121
|
-
// A network or server fault
|
|
122
|
-
throw new Error(`${err.message} (
|
|
91
|
+
// A network or server fault: the page is built and still matters to the retry.
|
|
92
|
+
throw new Error(`${err.message} (no revision was published to ${cardId}; the page is at ${localHtmlPath})`);
|
|
123
93
|
}
|
|
124
94
|
if (!published.ok)
|
|
125
95
|
return { ...published, card_id: cardId, local_html_path: localHtmlPath };
|
package/dist/index.js
CHANGED
|
@@ -422,7 +422,7 @@ plain background (what the thing is, why it exists, what is wrong or what the ch
|
|
|
422
422
|
Define each term a newcomer would not know. Cite exact ids, SHAs and paths. Be brief; never transcribe your
|
|
423
423
|
reasoning. A title or summary alone must let the reader decide.
|
|
424
424
|
|
|
425
|
-
DESIGN. UI designs and style options go on
|
|
425
|
+
DESIGN. UI designs and style options go on the card they concern (\`design_publish\`), never an Artifact or HTML page.
|
|
426
426
|
Decide choices yourself; show alternatives as design states, never ask.
|
|
427
427
|
`;
|
|
428
428
|
assertWithinTextLimit("the server instructions", SERVER_INSTRUCTIONS);
|
|
@@ -646,16 +646,16 @@ 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 a
|
|
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 a design on an existing card. " +
|
|
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
|
-
"
|
|
654
|
-
"
|
|
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
|
+
"Returns `{card_id, revision_no, dashboard_url, local_html_path, " +
|
|
655
655
|
"screenshot}`: `local_html_path` is the built page, written before the upload so you can open it yourself; `screenshot` is " +
|
|
656
656
|
"`captured` (1280x800) or `none: <reason>` (no Playwright in the repo: the publish still succeeds). Build errors and a " +
|
|
657
|
-
"refused revision (400, 413) come back verbatim
|
|
658
|
-
"design and call again
|
|
657
|
+
"refused revision (400, 409, 413) come back verbatim with the `card_id`: fix the " +
|
|
658
|
+
"design and call again. Limits: the frame has no network, so a component that fetches its own data fails inside " +
|
|
659
659
|
"it (use fixture props); React only; targets are a dashboard setting, read from `design_targets` of `GET /api/repos/<repo>`, never a file.", {
|
|
660
660
|
target: z.string().min(1).describe("A design target's name, as listed in `design_targets` of `GET /api/repos/<repo>` (`danxbot_api`, fields {design_targets: true})."),
|
|
661
661
|
note: z
|
|
@@ -666,16 +666,8 @@ strictTool("design_publish", "Build a design against this repo's REAL components
|
|
|
666
666
|
.string()
|
|
667
667
|
.min(1)
|
|
668
668
|
.describe("The design directory: absolute, or relative to this server's cwd. Inside the target's root, holding `Design.tsx`."),
|
|
669
|
-
card_id: z.string().min(1).
|
|
670
|
-
|
|
671
|
-
summary: z.string().min(1).optional().describe("The new card's summary. Required without `card_id`; refused with it."),
|
|
672
|
-
description: z.string().optional().describe("The new card's description; default empty. Refused with `card_id`."),
|
|
673
|
-
board: z
|
|
674
|
-
.string()
|
|
675
|
-
.min(1)
|
|
676
|
-
.optional()
|
|
677
|
-
.describe("Board id `<repo>:<slug>` for the new card; omit for this server's board. With `card_id` the card's own board is used."),
|
|
678
|
-
}, async (args) => guardedJson(() => designPublish(client, args, { serverBoard: config.board, baseUrl: config.baseUrl, cwd: process.cwd() })));
|
|
669
|
+
card_id: z.string().min(1).describe("The card this design goes on: any existing card but an Epic or Feature."),
|
|
670
|
+
}, async (args) => guardedJson(() => designPublish(client, args, { baseUrl: config.baseUrl, cwd: process.cwd() })));
|
|
679
671
|
// ---------------- request_permission (DX-4434, parent DX-4432) ----------------
|
|
680
672
|
strictTool("request_permission", "Ask a person to grant this key ONE MORE permission. Call it when a call was refused `403 forbidden` naming a " +
|
|
681
673
|
"`permission`, or when you know you need one more. It returns AT ONCE with `{state, approvalUrl, confirmCode, " +
|
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.311",
|
|
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",
|