@thehammer/danx-dashboard-mcp 0.1.309 → 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 +2 -2
- package/dist/design-publish.js +17 -47
- package/dist/index.js +10 -18
- package/dist/plan-events-relay.js +1 -1
- package/dist/tool-access.js +2 -2
- package/dist/urgent-records.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, 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.
|
|
@@ -61,7 +61,7 @@ Card and plan reads/writes go through `danxbot_api` against the issue/plan route
|
|
|
61
61
|
|
|
62
62
|
## `plan_events_wait` — a working session's plan events (DX-4233)
|
|
63
63
|
|
|
64
|
-
A program calls this, not a person: the danxbot Claude Code plugin's relay loop, inside the session, calls it over and over on the session's own danx-dashboard MCP server and hands every event it gets to the session.
|
|
64
|
+
A program calls this, not a person: the danxbot Claude Code plugin's relay loop, inside the session, calls it over and over on the session's own danx-dashboard MCP server and hands every event it gets to the session. An orchestrator dispatch (a headless session that works one plan) has no plugin relay, so it keeps ONE background waiter sub-agent that loops on this tool until events exist and returns them with the last cursor, and the orchestrator passes that cursor to the next waiter it arms (DX-4667); the `orchestrator` profile alone is granted the tool on a dispatch (it is an opt-in tool). The `bridge` subcommand is gone: no process is spawned, no `npx` cold start, and nothing outlives the session, because the stream lives in the MCP server, which ends with its session.
|
|
65
65
|
|
|
66
66
|
Input `{plan_id: number | null, cursor: string | null, timeout_ms: 1000..30000, transcript_path?: string}`. `plan_id` is `null` for a signed-in session on no plan (DX-4882): its stream still carries the pacing and resume messages of its Claude account, and it receives no plan events. A move between a plan and `null` starts clean like a move between plans: the plan-scoped records of the plan left are dropped, the session-scoped ones kept. Answer, one of:
|
|
67
67
|
|
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
|
@@ -170,7 +170,7 @@ let permissionRequests;
|
|
|
170
170
|
// DX-4523 - late-bound with `access` and `client`; see `tool-surface.ts` and `permission-watch.ts`.
|
|
171
171
|
let toolAccessSync;
|
|
172
172
|
let permissionWatch;
|
|
173
|
-
// DX-4233 - late-bound with `access`; see `plan-events-relay.ts`. The plugin's relay loop (a person's session) and
|
|
173
|
+
// DX-4233 - late-bound with `access`; see `plan-events-relay.ts`. The plugin's relay loop (a person's session) and an orchestrator's waiter sub-agent (DX-4667) call `plan_events_wait` on it.
|
|
174
174
|
let planEventRelay;
|
|
175
175
|
/**
|
|
176
176
|
* Compose the dispatch's qualified board id (`<repo>:<slug>`) from the two env
|
|
@@ -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, " +
|
|
@@ -799,7 +791,7 @@ async (args) => {
|
|
|
799
791
|
// ---------------- plan_events_wait (DX-4233) ----------------
|
|
800
792
|
strictTool("plan_events_wait",
|
|
801
793
|
// DX-4882 - `plan_id` is null for a signed-in session on no plan: its stream still carries its account's pacing and resume messages.
|
|
802
|
-
// DX-4233 - for the danxbot plugin's relay loop. DX-4667 - and for a background waiter sub-agent of
|
|
794
|
+
// DX-4233 - for the danxbot plugin's relay loop. DX-4667 - and for a background waiter sub-agent of an orchestrator, which
|
|
803
795
|
// loops on it until events exist and returns them (and the cursor) to the orchestrator it wakes. It answers `events` (each
|
|
804
796
|
// `{cursor, text, urgent}`; empty after `timeout_ms`), the cursor to send being the last delivered event's, or `stopped` with the remedy
|
|
805
797
|
// to tell the session. General-audience text (DX-4550): nothing here names our own setup.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* DX-4233 - the IN-PROCESS relay of a session's plan events: what the `plan_events_wait` tool serves.
|
|
3
3
|
*
|
|
4
4
|
* WHO CALLS IT. In a person's session, the danxbot plugin's relay loop calls `plan_events_wait` over and over on this
|
|
5
|
-
* session's own danx-dashboard MCP server and hands every record it gets to the session. In
|
|
5
|
+
* session's own danx-dashboard MCP server and hands every record it gets to the session. In an orchestrator's dispatch
|
|
6
6
|
* (DX-4667), which loads no relay, a background waiter sub-agent calls it the same way and returns the records and the last
|
|
7
7
|
* cursor to the orchestrator. This class is everything on the
|
|
8
8
|
* server side of that call: it opens the session's dashboard event stream (the mint / stream / heartbeat contract of
|
package/dist/tool-access.js
CHANGED
|
@@ -48,9 +48,9 @@ export const TOOL_ACCESS = {
|
|
|
48
48
|
design_publish: needs("cards.edit"),
|
|
49
49
|
// DX-4233 - the plugin's relay waits on the session's plan events here: it opens the listener ticket
|
|
50
50
|
// (`POST /api/plan-sessions/me/stream-ticket`) and heartbeats (`.../me/heartbeat`), both `plans.view`.
|
|
51
|
-
// DX-4667 - not operator-session-only:
|
|
51
|
+
// DX-4667 - not operator-session-only: an orchestrator's dispatch calls it too, through a background waiter sub-agent that
|
|
52
52
|
// wakes the orchestrator when a person answers. What keeps it off every other dispatch is the opt-in (`MCP_SERVER_OPT_IN_TOOLS`,
|
|
53
|
-
// `src/tokens/tool-defs.ts`) granted to the `
|
|
53
|
+
// `src/tokens/tool-defs.ts`) granted to the `orchestrator` profile alone, not this table.
|
|
54
54
|
plan_events_wait: needs("plans.view"),
|
|
55
55
|
// DX-4632 - the plugin's session-start hook asks which plan the session this one replaced was working. A restarted session boots
|
|
56
56
|
// signed out, so it holds no permission: like `plan_connect` it is a bootstrap tool, and a person's session's only (a dispatch
|
package/dist/urgent-records.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DX-4721 - THE ONE PLACE that decides whether a relayed plan-event record is URGENT.
|
|
3
3
|
*
|
|
4
|
-
* Background. `plan_events_wait` hands the caller (the danxbot plugin's relay, or
|
|
4
|
+
* Background. `plan_events_wait` hands the caller (the danxbot plugin's relay, or an orchestrator's waiter sub-agent) a
|
|
5
5
|
* stream of records. The caller delivers an ordinary record after the session's running turn ends, and adds an URGENT one to
|
|
6
6
|
* the running turn at once. Which record is which is decided here, by the record's KIND, and nowhere else: every site that
|
|
7
7
|
* creates a record names its source and calls `isUrgent`; none writes an `urgent` literal of its own. The flag is a required
|
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",
|