runboard 0.1.0 → 1.0.1
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 +16 -4
- package/dist/{chunk-U4SVYBXI.js → chunk-3NNMYWCN.js} +172 -55
- package/dist/cli.js +9 -7
- package/dist/mcp.js +4 -130
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -44,10 +44,22 @@ history becomes your trajectory record.
|
|
|
44
44
|
|
|
45
45
|
## Use it from your AI assistant
|
|
46
46
|
|
|
47
|
-
Portable `skills/` (SKILL.md) and an MCP server
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
Portable `skills/` (SKILL.md) and an MCP server let Claude, Cursor, Codex, Copilot, Gemini,
|
|
48
|
+
and other agents run the assessment conversation and persist results through the same core
|
|
49
|
+
— the numbers are always computed by the tool, never by the model.
|
|
50
|
+
|
|
51
|
+
The MCP server ships in this same package and runs locally over stdio (no network, no
|
|
52
|
+
account). Add it to your client with one zero-install command:
|
|
53
|
+
|
|
54
|
+
```jsonc
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"runboard": { "command": "npx", "args": ["-y", "runboard", "mcp"] }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
See [docs/mcp.md](./docs/mcp.md) for Claude Desktop, Cursor, and VS Code setup.
|
|
51
63
|
|
|
52
64
|
## Contributing
|
|
53
65
|
|
|
@@ -1,42 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
};
|
|
3
|
+
// mcp/server.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
6
7
|
|
|
7
|
-
// src/
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import path from "path";
|
|
12
|
-
function runboardPaths(root = process.cwd()) {
|
|
13
|
-
const dir = path.join(root, ".runboard");
|
|
14
|
-
return {
|
|
15
|
-
root,
|
|
16
|
-
dir,
|
|
17
|
-
config: path.join(dir, "config.yaml"),
|
|
18
|
-
rubric: path.join(dir, "rubric.yaml"),
|
|
19
|
-
assessmentsDir: path.join(dir, "assessments"),
|
|
20
|
-
reportsDir: path.join(dir, "reports"),
|
|
21
|
-
roadmap: path.join(dir, "roadmap.md"),
|
|
22
|
-
boardHtml: path.join(dir, "board.html")
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function assessmentFile(root, date) {
|
|
26
|
-
return path.join(runboardPaths(root).assessmentsDir, `${date}.md`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// src/commands/shared.ts
|
|
30
|
-
function requireInit(root = process.cwd()) {
|
|
31
|
-
if (!existsSync(runboardPaths(root).dir)) {
|
|
32
|
-
throw new UserError("No .runboard/ found here. Run `runboard init` first.");
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function requireAssessments(dates) {
|
|
36
|
-
if (dates.length === 0) {
|
|
37
|
-
throw new UserError("No assessments yet. Run `runboard assess` to record one.");
|
|
38
|
-
}
|
|
39
|
-
}
|
|
8
|
+
// src/version.ts
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
var pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
11
|
+
var VERSION = pkg.version;
|
|
40
12
|
|
|
41
13
|
// src/commands/assess.ts
|
|
42
14
|
import { cancel, intro, isCancel, note, outro, select, text } from "@clack/prompts";
|
|
@@ -79,9 +51,30 @@ function isLevel(value) {
|
|
|
79
51
|
}
|
|
80
52
|
|
|
81
53
|
// src/data/assessments.ts
|
|
82
|
-
import { existsSync
|
|
54
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
|
|
83
55
|
import path2 from "path";
|
|
84
56
|
import { parse, stringify } from "yaml";
|
|
57
|
+
|
|
58
|
+
// src/data/paths.ts
|
|
59
|
+
import path from "path";
|
|
60
|
+
function runboardPaths(root = process.cwd()) {
|
|
61
|
+
const dir = path.join(root, ".runboard");
|
|
62
|
+
return {
|
|
63
|
+
root,
|
|
64
|
+
dir,
|
|
65
|
+
config: path.join(dir, "config.yaml"),
|
|
66
|
+
rubric: path.join(dir, "rubric.yaml"),
|
|
67
|
+
assessmentsDir: path.join(dir, "assessments"),
|
|
68
|
+
reportsDir: path.join(dir, "reports"),
|
|
69
|
+
roadmap: path.join(dir, "roadmap.md"),
|
|
70
|
+
boardHtml: path.join(dir, "board.html")
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function assessmentFile(root, date) {
|
|
74
|
+
return path.join(runboardPaths(root).assessmentsDir, `${date}.md`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/data/assessments.ts
|
|
85
78
|
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
86
79
|
var AssessmentError = class extends Error {
|
|
87
80
|
};
|
|
@@ -159,14 +152,14 @@ function parseAssessment(text2, fallbackDate) {
|
|
|
159
152
|
}
|
|
160
153
|
function listAssessmentDates(root = process.cwd()) {
|
|
161
154
|
const dir = runboardPaths(root).assessmentsDir;
|
|
162
|
-
if (!
|
|
155
|
+
if (!existsSync(dir)) {
|
|
163
156
|
return [];
|
|
164
157
|
}
|
|
165
158
|
return readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => path2.basename(f, ".md")).filter((d) => DATE_RE.test(d)).sort();
|
|
166
159
|
}
|
|
167
160
|
function loadAssessment(date, root = process.cwd()) {
|
|
168
161
|
const file = assessmentFile(root, date);
|
|
169
|
-
return parseAssessment(
|
|
162
|
+
return parseAssessment(readFileSync2(file, "utf8"), date);
|
|
170
163
|
}
|
|
171
164
|
function loadAllAssessments(root = process.cwd()) {
|
|
172
165
|
return listAssessmentDates(root).map((date) => loadAssessment(date, root));
|
|
@@ -180,7 +173,7 @@ function saveAssessment(a, root = process.cwd(), options = {}) {
|
|
|
180
173
|
const dir = runboardPaths(root).assessmentsDir;
|
|
181
174
|
mkdirSync(dir, { recursive: true });
|
|
182
175
|
const file = assessmentFile(root, a.date);
|
|
183
|
-
if (
|
|
176
|
+
if (existsSync(file) && !options.force) {
|
|
184
177
|
throw new AssessmentError(
|
|
185
178
|
`An assessment for ${a.date} already exists. Re-run with --force to overwrite.`
|
|
186
179
|
);
|
|
@@ -190,7 +183,7 @@ function saveAssessment(a, root = process.cwd(), options = {}) {
|
|
|
190
183
|
}
|
|
191
184
|
|
|
192
185
|
// src/data/rubric.ts
|
|
193
|
-
import { readFileSync as
|
|
186
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
194
187
|
import path3 from "path";
|
|
195
188
|
import { fileURLToPath } from "url";
|
|
196
189
|
import { parse as parse2 } from "yaml";
|
|
@@ -202,7 +195,7 @@ function shippedRubricPath() {
|
|
|
202
195
|
];
|
|
203
196
|
for (const candidate of candidates) {
|
|
204
197
|
try {
|
|
205
|
-
|
|
198
|
+
readFileSync3(candidate);
|
|
206
199
|
return candidate;
|
|
207
200
|
} catch {
|
|
208
201
|
}
|
|
@@ -262,13 +255,32 @@ function parseDimension(raw) {
|
|
|
262
255
|
function loadRubric(filePath = shippedRubricPath()) {
|
|
263
256
|
let text2;
|
|
264
257
|
try {
|
|
265
|
-
text2 =
|
|
258
|
+
text2 = readFileSync3(filePath, "utf8");
|
|
266
259
|
} catch {
|
|
267
260
|
throw new Error(`Could not read rubric at ${filePath}.`);
|
|
268
261
|
}
|
|
269
262
|
return parseRubric(text2);
|
|
270
263
|
}
|
|
271
264
|
|
|
265
|
+
// src/commands/shared.ts
|
|
266
|
+
import { existsSync as existsSync2 } from "fs";
|
|
267
|
+
|
|
268
|
+
// src/core/errors.ts
|
|
269
|
+
var UserError = class extends Error {
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/commands/shared.ts
|
|
273
|
+
function requireInit(root = process.cwd()) {
|
|
274
|
+
if (!existsSync2(runboardPaths(root).dir)) {
|
|
275
|
+
throw new UserError("No .runboard/ found here. Run `runboard init` first.");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function requireAssessments(dates) {
|
|
279
|
+
if (dates.length === 0) {
|
|
280
|
+
throw new UserError("No assessments yet. Run `runboard assess` to record one.");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
272
284
|
// src/commands/assess.ts
|
|
273
285
|
function today() {
|
|
274
286
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -609,7 +621,7 @@ function detectTriggers(assessmentsChrono) {
|
|
|
609
621
|
}
|
|
610
622
|
|
|
611
623
|
// src/render/reports.ts
|
|
612
|
-
import { readFileSync as
|
|
624
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
613
625
|
import path4 from "path";
|
|
614
626
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
615
627
|
import { Eta } from "eta";
|
|
@@ -638,7 +650,7 @@ function templatesDir() {
|
|
|
638
650
|
const candidates = [path4.resolve(here, "../templates"), path4.resolve(here, "../../templates")];
|
|
639
651
|
for (const candidate of candidates) {
|
|
640
652
|
try {
|
|
641
|
-
|
|
653
|
+
readFileSync4(path4.join(candidate, "roadmap.eta"));
|
|
642
654
|
return candidate;
|
|
643
655
|
} catch {
|
|
644
656
|
}
|
|
@@ -881,24 +893,129 @@ function registerStatus(program) {
|
|
|
881
893
|
});
|
|
882
894
|
}
|
|
883
895
|
|
|
896
|
+
// mcp/handlers.ts
|
|
897
|
+
function handleAssess(args) {
|
|
898
|
+
const sets = Object.entries(args.scores).map(
|
|
899
|
+
([key, s]) => `${key}=${s.level}:${s.trajectory}:${s.evidence ?? ""}`
|
|
900
|
+
);
|
|
901
|
+
const { date, path: path7 } = runAssess({
|
|
902
|
+
root: args.root,
|
|
903
|
+
sets,
|
|
904
|
+
type: args.type,
|
|
905
|
+
force: args.force,
|
|
906
|
+
date: args.date
|
|
907
|
+
});
|
|
908
|
+
return { date, path: path7, written: true };
|
|
909
|
+
}
|
|
910
|
+
function handleBoard(ctx) {
|
|
911
|
+
const { summary, htmlPath } = runBoard({ root: ctx.root, html: ctx.html });
|
|
912
|
+
return {
|
|
913
|
+
cells: summary.cells,
|
|
914
|
+
average: formatAverage(summary.average),
|
|
915
|
+
trajectoryCounts: summary.trajectoryCounts,
|
|
916
|
+
...htmlPath ? { htmlPath } : {}
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
function handlePulse(ctx) {
|
|
920
|
+
const { path: path7, triggers } = runPulse({ root: ctx.root });
|
|
921
|
+
return { path: path7, triggers };
|
|
922
|
+
}
|
|
923
|
+
function handleRoadmap(ctx) {
|
|
924
|
+
const { path: path7 } = runRoadmap({ root: ctx.root });
|
|
925
|
+
const latest = latestAssessment(ctx.root);
|
|
926
|
+
if (!latest) throw new Error("unreachable");
|
|
927
|
+
return { path: path7, bindingConstraint: bindingConstraint(latest) };
|
|
928
|
+
}
|
|
929
|
+
function handleReport(ctx) {
|
|
930
|
+
const { path: path7 } = runReport({ root: ctx.root, type: ctx.type });
|
|
931
|
+
return { path: path7 };
|
|
932
|
+
}
|
|
933
|
+
function handleStatus(ctx) {
|
|
934
|
+
const s = runStatus(ctx.root);
|
|
935
|
+
return {
|
|
936
|
+
latestDate: s.latestDate ?? null,
|
|
937
|
+
average: s.average ?? null,
|
|
938
|
+
trajectoryCounts: s.trajectoryCounts ?? {},
|
|
939
|
+
activeTriggers: s.activeTriggers
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// mcp/server.ts
|
|
944
|
+
var scoreShape = z.object({
|
|
945
|
+
level: z.number().int().min(1).max(5),
|
|
946
|
+
trajectory: z.enum(["up", "flat", "down", "volatile"]),
|
|
947
|
+
evidence: z.string().default("")
|
|
948
|
+
});
|
|
949
|
+
function json(value) {
|
|
950
|
+
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
951
|
+
}
|
|
952
|
+
function buildServer() {
|
|
953
|
+
const server = new McpServer({ name: "runboard", version: VERSION });
|
|
954
|
+
server.registerTool(
|
|
955
|
+
"runboard_assess",
|
|
956
|
+
{
|
|
957
|
+
description: "Record a 9-dimension assessment (the model supplies scores).",
|
|
958
|
+
inputSchema: {
|
|
959
|
+
scores: z.record(z.string(), scoreShape),
|
|
960
|
+
type: z.enum(["baseline", "pulse", "quarterly", "event"]).optional(),
|
|
961
|
+
force: z.boolean().optional()
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
async (args) => json(handleAssess(args))
|
|
965
|
+
);
|
|
966
|
+
server.registerTool(
|
|
967
|
+
"runboard_board",
|
|
968
|
+
{
|
|
969
|
+
description: "Render the board summary; set html to also write board.html.",
|
|
970
|
+
inputSchema: { html: z.boolean().optional() }
|
|
971
|
+
},
|
|
972
|
+
async (args) => json(handleBoard({ html: args.html }))
|
|
973
|
+
);
|
|
974
|
+
server.registerTool(
|
|
975
|
+
"runboard_pulse",
|
|
976
|
+
{
|
|
977
|
+
description: "Compare the two latest assessments and flag stuck dimensions.",
|
|
978
|
+
inputSchema: {}
|
|
979
|
+
},
|
|
980
|
+
async () => json(handlePulse({}))
|
|
981
|
+
);
|
|
982
|
+
server.registerTool(
|
|
983
|
+
"runboard_roadmap",
|
|
984
|
+
{ description: "Generate a Now/Next/Later plan from the binding constraint.", inputSchema: {} },
|
|
985
|
+
async () => json(handleRoadmap({}))
|
|
986
|
+
);
|
|
987
|
+
server.registerTool(
|
|
988
|
+
"runboard_report",
|
|
989
|
+
{
|
|
990
|
+
description: "Render a report (board-update | baseline | monthly).",
|
|
991
|
+
inputSchema: { type: z.enum(["board-update", "baseline", "monthly"]) }
|
|
992
|
+
},
|
|
993
|
+
async (args) => json(handleReport({ type: args.type }))
|
|
994
|
+
);
|
|
995
|
+
server.registerTool(
|
|
996
|
+
"runboard_status",
|
|
997
|
+
{ description: "One-screen current state.", inputSchema: {} },
|
|
998
|
+
async () => json(handleStatus({}))
|
|
999
|
+
);
|
|
1000
|
+
return server;
|
|
1001
|
+
}
|
|
1002
|
+
async function startMcpServer() {
|
|
1003
|
+
const server = buildServer();
|
|
1004
|
+
const transport = new StdioServerTransport();
|
|
1005
|
+
await server.connect(transport);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
884
1008
|
export {
|
|
1009
|
+
VERSION,
|
|
885
1010
|
runboardPaths,
|
|
886
|
-
latestAssessment,
|
|
887
1011
|
shippedRubricPath,
|
|
888
1012
|
loadRubric,
|
|
889
1013
|
UserError,
|
|
890
|
-
runAssess,
|
|
891
1014
|
registerAssess,
|
|
892
|
-
formatAverage,
|
|
893
|
-
bindingConstraint,
|
|
894
|
-
runBoard,
|
|
895
1015
|
registerBoard,
|
|
896
|
-
runPulse,
|
|
897
1016
|
registerPulse,
|
|
898
|
-
runReport,
|
|
899
1017
|
registerReport,
|
|
900
|
-
runRoadmap,
|
|
901
1018
|
registerRoadmap,
|
|
902
|
-
|
|
903
|
-
|
|
1019
|
+
registerStatus,
|
|
1020
|
+
startMcpServer
|
|
904
1021
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
UserError,
|
|
4
|
+
VERSION,
|
|
4
5
|
loadRubric,
|
|
5
6
|
registerAssess,
|
|
6
7
|
registerBoard,
|
|
@@ -9,8 +10,9 @@ import {
|
|
|
9
10
|
registerRoadmap,
|
|
10
11
|
registerStatus,
|
|
11
12
|
runboardPaths,
|
|
12
|
-
shippedRubricPath
|
|
13
|
-
|
|
13
|
+
shippedRubricPath,
|
|
14
|
+
startMcpServer
|
|
15
|
+
} from "./chunk-3NNMYWCN.js";
|
|
14
16
|
|
|
15
17
|
// src/cli.ts
|
|
16
18
|
import { Command } from "commander";
|
|
@@ -64,7 +66,6 @@ ${created.map((c) => ` ${c}`).join("\n")}
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
// src/cli.ts
|
|
67
|
-
var VERSION = "0.1.0";
|
|
68
69
|
function buildProgram() {
|
|
69
70
|
const program = new Command();
|
|
70
71
|
program.name("runboard").description("Local-first technical-leadership maturity scorecard.").version(VERSION, "-v, --version");
|
|
@@ -75,6 +76,9 @@ function buildProgram() {
|
|
|
75
76
|
registerRoadmap(program);
|
|
76
77
|
registerReport(program);
|
|
77
78
|
registerStatus(program);
|
|
79
|
+
program.command("mcp").description("Start the MCP server over stdio (for tool-calling AI clients).").action(async () => {
|
|
80
|
+
await startMcpServer();
|
|
81
|
+
});
|
|
78
82
|
return program;
|
|
79
83
|
}
|
|
80
84
|
async function main(argv) {
|
|
@@ -91,12 +95,10 @@ async function main(argv) {
|
|
|
91
95
|
throw err;
|
|
92
96
|
}
|
|
93
97
|
}
|
|
98
|
+
|
|
99
|
+
// src/main.ts
|
|
94
100
|
main(process.argv).catch((err) => {
|
|
95
101
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
96
102
|
`);
|
|
97
103
|
process.exit(1);
|
|
98
104
|
});
|
|
99
|
-
export {
|
|
100
|
-
buildProgram,
|
|
101
|
-
main
|
|
102
|
-
};
|
package/dist/mcp.js
CHANGED
|
@@ -1,137 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
latestAssessment,
|
|
6
|
-
runAssess,
|
|
7
|
-
runBoard,
|
|
8
|
-
runPulse,
|
|
9
|
-
runReport,
|
|
10
|
-
runRoadmap,
|
|
11
|
-
runStatus
|
|
12
|
-
} from "./chunk-U4SVYBXI.js";
|
|
3
|
+
startMcpServer
|
|
4
|
+
} from "./chunk-3NNMYWCN.js";
|
|
13
5
|
|
|
14
|
-
// mcp/
|
|
15
|
-
|
|
16
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
17
|
-
import { z } from "zod";
|
|
18
|
-
|
|
19
|
-
// mcp/handlers.ts
|
|
20
|
-
function handleAssess(args) {
|
|
21
|
-
const sets = Object.entries(args.scores).map(
|
|
22
|
-
([key, s]) => `${key}=${s.level}:${s.trajectory}:${s.evidence ?? ""}`
|
|
23
|
-
);
|
|
24
|
-
const { date, path } = runAssess({
|
|
25
|
-
root: args.root,
|
|
26
|
-
sets,
|
|
27
|
-
type: args.type,
|
|
28
|
-
force: args.force,
|
|
29
|
-
date: args.date
|
|
30
|
-
});
|
|
31
|
-
return { date, path, written: true };
|
|
32
|
-
}
|
|
33
|
-
function handleBoard(ctx) {
|
|
34
|
-
const { summary, htmlPath } = runBoard({ root: ctx.root, html: ctx.html });
|
|
35
|
-
return {
|
|
36
|
-
cells: summary.cells,
|
|
37
|
-
average: formatAverage(summary.average),
|
|
38
|
-
trajectoryCounts: summary.trajectoryCounts,
|
|
39
|
-
...htmlPath ? { htmlPath } : {}
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
function handlePulse(ctx) {
|
|
43
|
-
const { path, triggers } = runPulse({ root: ctx.root });
|
|
44
|
-
return { path, triggers };
|
|
45
|
-
}
|
|
46
|
-
function handleRoadmap(ctx) {
|
|
47
|
-
const { path } = runRoadmap({ root: ctx.root });
|
|
48
|
-
const latest = latestAssessment(ctx.root);
|
|
49
|
-
if (!latest) throw new Error("unreachable");
|
|
50
|
-
return { path, bindingConstraint: bindingConstraint(latest) };
|
|
51
|
-
}
|
|
52
|
-
function handleReport(ctx) {
|
|
53
|
-
const { path } = runReport({ root: ctx.root, type: ctx.type });
|
|
54
|
-
return { path };
|
|
55
|
-
}
|
|
56
|
-
function handleStatus(ctx) {
|
|
57
|
-
const s = runStatus(ctx.root);
|
|
58
|
-
return {
|
|
59
|
-
latestDate: s.latestDate ?? null,
|
|
60
|
-
average: s.average ?? null,
|
|
61
|
-
trajectoryCounts: s.trajectoryCounts ?? {},
|
|
62
|
-
activeTriggers: s.activeTriggers
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// mcp/server.ts
|
|
67
|
-
var scoreShape = z.object({
|
|
68
|
-
level: z.number().int().min(1).max(5),
|
|
69
|
-
trajectory: z.enum(["up", "flat", "down", "volatile"]),
|
|
70
|
-
evidence: z.string().default("")
|
|
71
|
-
});
|
|
72
|
-
function json(value) {
|
|
73
|
-
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
74
|
-
}
|
|
75
|
-
function buildServer() {
|
|
76
|
-
const server = new McpServer({ name: "runboard", version: "0.1.0" });
|
|
77
|
-
server.registerTool(
|
|
78
|
-
"runboard_assess",
|
|
79
|
-
{
|
|
80
|
-
description: "Record a 9-dimension assessment (the model supplies scores).",
|
|
81
|
-
inputSchema: {
|
|
82
|
-
scores: z.record(z.string(), scoreShape),
|
|
83
|
-
type: z.enum(["baseline", "pulse", "quarterly", "event"]).optional(),
|
|
84
|
-
force: z.boolean().optional()
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
async (args) => json(handleAssess(args))
|
|
88
|
-
);
|
|
89
|
-
server.registerTool(
|
|
90
|
-
"runboard_board",
|
|
91
|
-
{
|
|
92
|
-
description: "Render the board summary; set html to also write board.html.",
|
|
93
|
-
inputSchema: { html: z.boolean().optional() }
|
|
94
|
-
},
|
|
95
|
-
async (args) => json(handleBoard({ html: args.html }))
|
|
96
|
-
);
|
|
97
|
-
server.registerTool(
|
|
98
|
-
"runboard_pulse",
|
|
99
|
-
{
|
|
100
|
-
description: "Compare the two latest assessments and flag stuck dimensions.",
|
|
101
|
-
inputSchema: {}
|
|
102
|
-
},
|
|
103
|
-
async () => json(handlePulse({}))
|
|
104
|
-
);
|
|
105
|
-
server.registerTool(
|
|
106
|
-
"runboard_roadmap",
|
|
107
|
-
{ description: "Generate a Now/Next/Later plan from the binding constraint.", inputSchema: {} },
|
|
108
|
-
async () => json(handleRoadmap({}))
|
|
109
|
-
);
|
|
110
|
-
server.registerTool(
|
|
111
|
-
"runboard_report",
|
|
112
|
-
{
|
|
113
|
-
description: "Render a report (board-update | baseline | monthly).",
|
|
114
|
-
inputSchema: { type: z.enum(["board-update", "baseline", "monthly"]) }
|
|
115
|
-
},
|
|
116
|
-
async (args) => json(handleReport({ type: args.type }))
|
|
117
|
-
);
|
|
118
|
-
server.registerTool(
|
|
119
|
-
"runboard_status",
|
|
120
|
-
{ description: "One-screen current state.", inputSchema: {} },
|
|
121
|
-
async () => json(handleStatus({}))
|
|
122
|
-
);
|
|
123
|
-
return server;
|
|
124
|
-
}
|
|
125
|
-
async function main() {
|
|
126
|
-
const server = buildServer();
|
|
127
|
-
const transport = new StdioServerTransport();
|
|
128
|
-
await server.connect(transport);
|
|
129
|
-
}
|
|
130
|
-
main().catch((err) => {
|
|
6
|
+
// mcp/main.ts
|
|
7
|
+
startMcpServer().catch((err) => {
|
|
131
8
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
132
9
|
`);
|
|
133
10
|
process.exit(1);
|
|
134
11
|
});
|
|
135
|
-
export {
|
|
136
|
-
buildServer
|
|
137
|
-
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runboard",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Local-first CLI for the Runboard technical-leadership maturity framework. Deterministic scoring core, portable AI adapters, no phone-home.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"test:watch": "vitest",
|
|
29
29
|
"lint": "biome check .",
|
|
30
30
|
"format": "biome format --write .",
|
|
31
|
-
"
|
|
31
|
+
"mcp:smoke": "node scripts/mcp-smoke.mjs",
|
|
32
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build && npm run mcp:smoke"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@clack/prompts": "^0.7.0",
|