@joeyshi12/casper 0.7.3 → 0.8.0

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.
Files changed (69) hide show
  1. package/README.md +12 -1
  2. package/dist/agents/casper.json +11 -2
  3. package/dist/mcp.js +412 -0
  4. package/dist/server.js +528 -31
  5. package/dist/web/assets/{abnfDiagram-N423BO3Z-5RMwP9tv.js → abnfDiagram-N423BO3Z-J5DaDTt8.js} +1 -1
  6. package/dist/web/assets/{arc-DEuUsPX5.js → arc-CXWeATz6.js} +1 -1
  7. package/dist/web/assets/{architectureDiagram-T3A2C74G-k4h0zktw.js → architectureDiagram-T3A2C74G-ecXfY9My.js} +1 -1
  8. package/dist/web/assets/{blockDiagram-VBNYF7ZC-qTl9ZS2k.js → blockDiagram-VBNYF7ZC-D5ClKOWV.js} +1 -1
  9. package/dist/web/assets/{c4Diagram-5PPSVZJV-R6o_zt-Q.js → c4Diagram-5PPSVZJV-BxW2-O4j.js} +1 -1
  10. package/dist/web/assets/channel-BXz-ycSk.js +1 -0
  11. package/dist/web/assets/{chunk-2GRJ4B5K-CrJyVfhF.js → chunk-2GRJ4B5K-BBWhz2x0.js} +1 -1
  12. package/dist/web/assets/{chunk-2Q5K7J3B-BvGurkBq.js → chunk-2Q5K7J3B-CfD68qFQ.js} +1 -1
  13. package/dist/web/assets/{chunk-5RXB4S5H-DuE267ol.js → chunk-5RXB4S5H-DaznI8_V.js} +1 -1
  14. package/dist/web/assets/{chunk-5VM5RSS4-DAVY20oh.js → chunk-5VM5RSS4-BYxWXCvw.js} +1 -1
  15. package/dist/web/assets/{chunk-6Q2QTUOP-DvkSg8Wl.js → chunk-6Q2QTUOP-C8zmDMoc.js} +1 -1
  16. package/dist/web/assets/{chunk-GF5L2VYU-B8KQ7FAJ.js → chunk-GF5L2VYU-BXGR_P0f.js} +1 -1
  17. package/dist/web/assets/{chunk-JWPE2WC7-D2J6xFCq.js → chunk-JWPE2WC7-DsHbIvH7.js} +1 -1
  18. package/dist/web/assets/{chunk-KBJHAD2P-BfgGkRN_.js → chunk-KBJHAD2P-B8G6QD3I.js} +1 -1
  19. package/dist/web/assets/{chunk-RYQCIY6F-C0uby4vT.js → chunk-RYQCIY6F-B1rujkgY.js} +1 -1
  20. package/dist/web/assets/{chunk-XXDRQBXY-BAZLB2J1.js → chunk-XXDRQBXY-hjvfwopv.js} +1 -1
  21. package/dist/web/assets/classDiagram-JCYQIIEL-CRBj8GMD.js +1 -0
  22. package/dist/web/assets/classDiagram-v2-OCEON4UE-CRBj8GMD.js +1 -0
  23. package/dist/web/assets/{cose-bilkent-JH36ORCC-8FFjV-r2.js → cose-bilkent-JH36ORCC-D4S1nlYR.js} +1 -1
  24. package/dist/web/assets/{cynefin-VYW2F7L2-CDT1GZi_.js → cynefin-VYW2F7L2-DtN8m_GE.js} +1 -1
  25. package/dist/web/assets/{cynefinDiagram-MW4NZA55-DMs5fXGJ.js → cynefinDiagram-MW4NZA55-Dkk-H0Nw.js} +1 -1
  26. package/dist/web/assets/{dagre-VZM6K2ZE-BUgnrG7T.js → dagre-VZM6K2ZE-BSkCOa-Z.js} +1 -1
  27. package/dist/web/assets/{diagram-7IWD3JNH-DUJeTMJj.js → diagram-7IWD3JNH-Bses1wzI.js} +1 -1
  28. package/dist/web/assets/{diagram-B4RE2ZJO-BsVTn5rU.js → diagram-B4RE2ZJO-BW7sntb1.js} +1 -1
  29. package/dist/web/assets/{diagram-LBJQPF4R--uxNHPeF.js → diagram-LBJQPF4R-DHzgX2HL.js} +1 -1
  30. package/dist/web/assets/{diagram-Q27KOJAE-GYAROo1s.js → diagram-Q27KOJAE-mcXdgoK9.js} +1 -1
  31. package/dist/web/assets/{diagram-UB23O5K3-CpJvxU_K.js → diagram-UB23O5K3-BO5P9LAa.js} +1 -1
  32. package/dist/web/assets/{ebnfDiagram-BXEA7PRR-l3iEqoSP.js → ebnfDiagram-BXEA7PRR-F0zOfsYe.js} +1 -1
  33. package/dist/web/assets/{erDiagram-JOGREHBK-BoFHrs3B.js → erDiagram-JOGREHBK-aOYH_ZnE.js} +1 -1
  34. package/dist/web/assets/{flowDiagram-UKHOOZJN-CXMq4-uD.js → flowDiagram-UKHOOZJN-DmtkMJ60.js} +1 -1
  35. package/dist/web/assets/{ganttDiagram-PKOTCBZU-YMv8lvSL.js → ganttDiagram-PKOTCBZU-BFy8QpeS.js} +1 -1
  36. package/dist/web/assets/{gitGraphDiagram-DS77QQ5N-BgrfufCE.js → gitGraphDiagram-DS77QQ5N-CjbCeQfE.js} +1 -1
  37. package/dist/web/assets/{index-IBYN7wml.js → index-Bbzvwn2R.js} +207 -53
  38. package/dist/web/assets/{index-D7QnyK89.css → index-POlrwDS0.css} +1 -1
  39. package/dist/web/assets/{infoDiagram-6WML65LV-DMpqFKuC.js → infoDiagram-6WML65LV-CGFvyGjJ.js} +1 -1
  40. package/dist/web/assets/{ishikawaDiagram-WSZJBQD7-BKdkzZXu.js → ishikawaDiagram-WSZJBQD7-BbVf1sRd.js} +1 -1
  41. package/dist/web/assets/{journeyDiagram-NVQOT4AX-NPjJ9Fq8.js → journeyDiagram-NVQOT4AX-BNevDB-1.js} +1 -1
  42. package/dist/web/assets/{kanban-definition-27J2QSJJ-MSVfDvXb.js → kanban-definition-27J2QSJJ-BdmYFEey.js} +1 -1
  43. package/dist/web/assets/{linear-Bugxdj26.js → linear-DJt4yr3f.js} +1 -1
  44. package/dist/web/assets/{mermaid.core-BrC5_l7I.js → mermaid.core-D-YupbG3.js} +5 -5
  45. package/dist/web/assets/{mindmap-definition-FAOFIHXS-yB60YQKy.js → mindmap-definition-FAOFIHXS-toscoNiP.js} +1 -1
  46. package/dist/web/assets/{pegDiagram-VL7TDLO6-BfdGiZwB.js → pegDiagram-VL7TDLO6-42qysB7S.js} +1 -1
  47. package/dist/web/assets/{pieDiagram-7S7Q4E2Y-BuJwU-QQ.js → pieDiagram-7S7Q4E2Y-DeC8rSjm.js} +1 -1
  48. package/dist/web/assets/{quadrantDiagram-CIZ2JOQS-DvYy4rS6.js → quadrantDiagram-CIZ2JOQS-BsmBMVxR.js} +1 -1
  49. package/dist/web/assets/{railroadDiagram-AXF67PYL-D0Slzbqv.js → railroadDiagram-AXF67PYL-CUEYoy5N.js} +1 -1
  50. package/dist/web/assets/{requirementDiagram-LRYGKXZP-O1O4JCzu.js → requirementDiagram-LRYGKXZP-QOIVNSdo.js} +1 -1
  51. package/dist/web/assets/{sankeyDiagram-W5VNT64P-WTewsL_K.js → sankeyDiagram-W5VNT64P-BTvBZ4O6.js} +1 -1
  52. package/dist/web/assets/{sequenceDiagram-SI44F4Z6-BUoBi4kR.js → sequenceDiagram-SI44F4Z6-PgaCw-bZ.js} +1 -1
  53. package/dist/web/assets/{sizeCapture-X5ZJPWSS-BV9d3RVn.js → sizeCapture-X5ZJPWSS-xVBXwmWY.js} +1 -1
  54. package/dist/web/assets/{stateDiagram-OKZ733FA-BqdAeMq4.js → stateDiagram-OKZ733FA-BWvlFx8p.js} +1 -1
  55. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-CWroHbLn.js +1 -0
  56. package/dist/web/assets/{swimlanes-SLNWSIFB-Dn0Z0yF4.js → swimlanes-SLNWSIFB-B6SREfpi.js} +2 -2
  57. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-B7_kKLQV.js +8 -0
  58. package/dist/web/assets/{timeline-definition-Z64GVDOM-RXIqWmas.js → timeline-definition-Z64GVDOM-pOrViFO-.js} +1 -1
  59. package/dist/web/assets/{vennDiagram-T6HMQDX7-Djv2Kody.js → vennDiagram-T6HMQDX7-DXbf3Z8V.js} +1 -1
  60. package/dist/web/assets/{wardleyDiagram-T6FBY63Y-k8z1iXMI.js → wardleyDiagram-T6FBY63Y-BulJVCrV.js} +1 -1
  61. package/dist/web/assets/{xychartDiagram-ELKLHX3M-DBRaiGnz.js → xychartDiagram-ELKLHX3M-MI1zCRjW.js} +1 -1
  62. package/dist/web/index.html +2 -2
  63. package/dist/web/sw.js +1 -1
  64. package/package.json +1 -1
  65. package/dist/web/assets/channel-BQC-40u0.js +0 -1
  66. package/dist/web/assets/classDiagram-JCYQIIEL-9L45zm4i.js +0 -1
  67. package/dist/web/assets/classDiagram-v2-OCEON4UE-9L45zm4i.js +0 -1
  68. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-DQeB4eKD.js +0 -1
  69. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-Dl6pHbTK.js +0 -8
package/dist/server.js CHANGED
@@ -86,14 +86,38 @@ function sourceFile() {
86
86
  const workspace = path4.resolve(here, "../../../assets/agents/casper.json");
87
87
  return fs3.existsSync(packaged) ? packaged : workspace;
88
88
  }
89
- function sha256(text) {
90
- return crypto2.createHash("sha256").update(text).digest("hex");
89
+ function mcpServerPath() {
90
+ const here = path4.dirname(url.fileURLToPath(import.meta.url));
91
+ const candidates = [
92
+ path4.resolve(here, "mcp.js"),
93
+ // bundled: beside server.js
94
+ path4.resolve(here, "../../dist/mcp.js")
95
+ // from source, if built
96
+ ];
97
+ return candidates.find((c) => fs3.existsSync(c)) ?? null;
98
+ }
99
+ function withMcpServer(json) {
100
+ const mcp = mcpServerPath();
101
+ if (!mcp) return json;
102
+ const agent = JSON.parse(json);
103
+ agent.mcpServers = {
104
+ casper: {
105
+ command: process.execPath,
106
+ args: [mcp],
107
+ env: {},
108
+ timeout: 1e4
109
+ }
110
+ };
111
+ return JSON.stringify(agent, null, 2) + "\n";
112
+ }
113
+ function sha256(text2) {
114
+ return crypto2.createHash("sha256").update(text2).digest("hex");
91
115
  }
92
116
  function installAgentFile(home2, dataDir) {
93
117
  const src = sourceFile();
94
118
  const target = path4.join(home2, ".kiro", "agents", "casper.json");
95
119
  if (!fs3.existsSync(src)) return { action: "no-source", target };
96
- const desired = fs3.readFileSync(src, "utf8");
120
+ const desired = withMcpServer(fs3.readFileSync(src, "utf8"));
97
121
  const stampFile = path4.join(dataDir, "agent-stamp");
98
122
  const stamp = fs3.existsSync(stampFile) ? fs3.readFileSync(stampFile, "utf8").trim() : "";
99
123
  const write = () => {
@@ -611,20 +635,20 @@ var init_acp = __esm({
611
635
  });
612
636
 
613
637
  // shared/dist/attachments.js
614
- function attachmentPaths(text) {
615
- const line = text.split("\n").find((l) => l.startsWith(ATTACHMENTS_PREFIX));
638
+ function attachmentPaths(text2) {
639
+ const line = text2.split("\n").find((l) => l.startsWith(ATTACHMENTS_PREFIX));
616
640
  if (!line)
617
641
  return [];
618
642
  return line.slice(ATTACHMENTS_PREFIX.length).split(", ").map((s) => s.trim()).filter(Boolean);
619
643
  }
620
- function imageAttachmentPaths(text) {
621
- return attachmentPaths(text).filter((p) => {
644
+ function imageAttachmentPaths(text2) {
645
+ return attachmentPaths(text2).filter((p) => {
622
646
  const lower = p.toLowerCase();
623
647
  return IMAGE_EXTS.some((ext) => lower.endsWith(ext));
624
648
  });
625
649
  }
626
- function stripAttachmentsLine(text) {
627
- return text.split("\n").filter((l) => !l.startsWith(ATTACHMENTS_PREFIX)).join("\n").trim();
650
+ function stripAttachmentsLine(text2) {
651
+ return text2.split("\n").filter((l) => !l.startsWith(ATTACHMENTS_PREFIX)).join("\n").trim();
628
652
  }
629
653
  var ATTACHMENTS_PREFIX, IMAGE_EXTS;
630
654
  var init_attachments = __esm({
@@ -1304,17 +1328,17 @@ async function hydrateTranscript(sessionId) {
1304
1328
  const pushMsg = (msg) => items.push({ type: "message", message: msg });
1305
1329
  if (entry.kind === "Prompt") {
1306
1330
  const raw2 = textOf("text");
1307
- const text = stripAttachmentsLine(raw2);
1331
+ const text2 = stripAttachmentsLine(raw2);
1308
1332
  const imagePaths = imageAttachmentPaths(raw2);
1309
- if (text.trim() || imagePaths.length)
1310
- pushMsg({ id: `u-${baseId}`, role: "user", text, timestamp: ts, imagePaths });
1333
+ if (text2.trim() || imagePaths.length)
1334
+ pushMsg({ id: `u-${baseId}`, role: "user", text: text2, timestamp: ts, imagePaths });
1311
1335
  } else if (entry.kind === "AssistantMessage") {
1312
1336
  const thinking = textOf("thinking");
1313
1337
  if (thinking.trim())
1314
1338
  pushMsg({ id: `t-${baseId}`, role: "thinking", text: thinking, timestamp: ts });
1315
- const text = textOf("text");
1316
- if (text.trim())
1317
- pushMsg({ id: `a-${baseId}`, role: "assistant", text, timestamp: ts });
1339
+ const text2 = textOf("text");
1340
+ if (text2.trim())
1341
+ pushMsg({ id: `a-${baseId}`, role: "assistant", text: text2, timestamp: ts });
1318
1342
  for (const c of content) {
1319
1343
  const d = toolUseData(c);
1320
1344
  if (!d) continue;
@@ -2107,8 +2131,8 @@ async function listAgents() {
2107
2131
  cwd: config.defaultCwd,
2108
2132
  maxBuffer: 2 * 1024 * 1024
2109
2133
  });
2110
- const text = (stderr || "") + "\n" + (stdout || "");
2111
- for (const rawLine of text.split("\n")) {
2134
+ const text2 = (stderr || "") + "\n" + (stdout || "");
2135
+ for (const rawLine of text2.split("\n")) {
2112
2136
  const line = rawLine.replace(ANSI, "");
2113
2137
  const m = /^\s{0,2}(\*\s)?([A-Za-z0-9_-]+)\s{2,}(Global|Workspace|Local)\b/.exec(
2114
2138
  line
@@ -3271,7 +3295,8 @@ var init_server = __esm({
3271
3295
  // server/src/cli/doctor.ts
3272
3296
  var doctor_exports = {};
3273
3297
  __export(doctor_exports, {
3274
- doctor: () => doctor
3298
+ doctor: () => doctor,
3299
+ mcpWiring: () => mcpWiring
3275
3300
  });
3276
3301
  import { execFileSync as execFileSync3 } from "node:child_process";
3277
3302
  import fs14 from "node:fs";
@@ -3289,6 +3314,28 @@ function which(bin) {
3289
3314
  }
3290
3315
  return void 0;
3291
3316
  }
3317
+ function mcpWiring(agentJson, resolve) {
3318
+ let servers;
3319
+ try {
3320
+ servers = JSON.parse(agentJson).mcpServers;
3321
+ } catch {
3322
+ return { ok: false, detail: "the agent file is not valid JSON" };
3323
+ }
3324
+ const server = servers?.casper;
3325
+ if (!server) {
3326
+ return { ok: false, detail: "not declared in the agent file, so widgets are unavailable" };
3327
+ }
3328
+ const command = typeof server.command === "string" ? server.command : "";
3329
+ const args = Array.isArray(server.args) ? server.args.filter((a) => typeof a === "string") : [];
3330
+ if (!resolve(command)) {
3331
+ return { ok: false, detail: `${command || "(no command)"} not found - widgets are unavailable` };
3332
+ }
3333
+ const script = args.find((a) => a.endsWith(".js"));
3334
+ if (script && !fs14.existsSync(script)) {
3335
+ return { ok: false, detail: `${script} is gone - delete the agent file and restart to rewrite it` };
3336
+ }
3337
+ return { ok: true, detail: script ?? `${command} ${args.join(" ")}` };
3338
+ }
3292
3339
  function checks() {
3293
3340
  const out = [];
3294
3341
  out.push({
@@ -3357,11 +3404,16 @@ function checks() {
3357
3404
  }
3358
3405
  );
3359
3406
  const agent = path15.join(config.kiroSessionsDir, "..", "..", "agents", "casper.json");
3407
+ const hasAgent = fs14.existsSync(agent);
3360
3408
  out.push({
3361
- ok: fs14.existsSync(agent),
3409
+ ok: hasAgent,
3362
3410
  name: "casper agent",
3363
- detail: fs14.existsSync(agent) ? agent : `${agent} missing - it is installed on first run`
3411
+ detail: hasAgent ? agent : `${agent} missing - it is installed on first run`
3364
3412
  });
3413
+ if (hasAgent) {
3414
+ const wiring = mcpWiring(fs14.readFileSync(agent, "utf8"), which);
3415
+ out.push({ ok: wiring.ok, name: "casper mcp", detail: wiring.detail });
3416
+ }
3365
3417
  return out;
3366
3418
  }
3367
3419
  function doctor() {
@@ -3388,6 +3440,445 @@ var init_doctor = __esm({
3388
3440
  }
3389
3441
  });
3390
3442
 
3443
+ // server/src/mcp/guidelines.ts
3444
+ function getGuidelines(modules) {
3445
+ const parts = [CORE];
3446
+ const seen = /* @__PURE__ */ new Set();
3447
+ for (const mod of modules) {
3448
+ const sections = MODULE_SECTIONS[mod];
3449
+ if (!sections) continue;
3450
+ for (const section of sections) {
3451
+ if (seen.has(section)) continue;
3452
+ seen.add(section);
3453
+ parts.push(section);
3454
+ }
3455
+ }
3456
+ return parts.join("\n\n");
3457
+ }
3458
+ var MODULES, CORE, COLOR, UI, CHARTS, SVG, DIAGRAMS, ART, MODULE_SECTIONS;
3459
+ var init_guidelines = __esm({
3460
+ "server/src/mcp/guidelines.ts"() {
3461
+ "use strict";
3462
+ MODULES = ["interactive", "chart", "diagram", "art"];
3463
+ CORE = `# Widgets
3464
+
3465
+ A widget is a self-contained HTML fragment rendered inline in the conversation. No
3466
+ doctype, html, head or body tag - just content. It is not a file and not a
3467
+ deliverable; it is part of what you are saying. Reach for one when a picture
3468
+ answers better than a paragraph.
3469
+
3470
+ ## Structure, in this order
3471
+
3472
+ 1. A short \`<style>\` block.
3473
+ 2. The content markup.
3474
+ 3. A single \`<script>\` at the end.
3475
+
3476
+ Order matters. Content renders as it arrives, and scripts run only once the message
3477
+ is complete, so a widget whose script sits at the top looks inert until the end.
3478
+
3479
+ ## Hard rules
3480
+
3481
+ - No gradients, drop shadows or blur. They flash while content is patched in.
3482
+ - No HTML comments. They cost tokens and buy nothing.
3483
+ - Two font weights only: 400 and 500. Never 600 or 700.
3484
+ - Sentence case for every label, heading and button. Never Title Case, never ALL CAPS.
3485
+ - Never hardcode a colour that has a variable below.
3486
+ - Size text in px, 11 to 15 for body and labels, up to 18 for a single heading.
3487
+ - Keep the whole widget under 600px tall unless the content genuinely needs more.
3488
+
3489
+ ## The sandbox
3490
+
3491
+ The frame has an opaque origin. localStorage, sessionStorage, cookies and requests
3492
+ to Casper all throw or fail. Hold state in a variable for the life of the page.
3493
+
3494
+ Libraries load from these hosts only: cdnjs.cloudflare.com, cdn.jsdelivr.net,
3495
+ unpkg.com, esm.sh. Anything else is refused by the content security policy, as is
3496
+ eval and new Function, so pick libraries that don't rely on them.
3497
+
3498
+ ## Theme
3499
+
3500
+ The background is transparent and the app's own variables are set. Use them:
3501
+
3502
+ --color-text-primary, --color-text-secondary
3503
+ --color-background-primary, --color-background-secondary
3504
+ --color-border
3505
+ --color-accent, --color-accent-alt
3506
+ --color-teal, --color-green, --color-yellow, --color-orange, --color-red
3507
+ --font-body, --font-mono
3508
+
3509
+ Read them in script with getComputedStyle(document.documentElement).getPropertyValue.
3510
+
3511
+ ## Talking back
3512
+
3513
+ casper.sendPrompt(text) sends a message as if the user typed it, indistinguishable
3514
+ in the transcript from one they wrote. Most widgets need no button at all. Add one
3515
+ only when the obvious next step is a question they would otherwise type out by hand,
3516
+ and never as decoration or to invite engagement. One at most.`;
3517
+ COLOR = `## Colour
3518
+
3519
+ Six accents are available: --color-accent (purple), --color-accent-alt (pink),
3520
+ --color-teal, --color-green, --color-yellow, --color-orange, --color-red.
3521
+
3522
+ Colour encodes meaning, never sequence. If two series differ only by being
3523
+ different series, use one accent at two opacities rather than two hues. Two or
3524
+ three hues per widget is the ceiling.
3525
+
3526
+ Semantics people already expect: green for success or growth, red for error or
3527
+ loss, orange for warning or pending, teal for neutral information, purple for the
3528
+ primary subject.
3529
+
3530
+ For text on a filled background, use --color-background-primary as the text colour
3531
+ rather than black. For a tint, use the accent at 12 to 20 percent opacity with the
3532
+ accent itself as the border.`;
3533
+ UI = `## Components
3534
+
3535
+ Card: background --color-background-secondary, 1px --color-border, radius 10px,
3536
+ padding 12px 14px. No shadow.
3537
+
3538
+ Metric: label 11px --color-text-secondary above a 20px 500-weight value. Group
3539
+ metrics in a grid with 12px gaps, never a table.
3540
+
3541
+ Button: 12px text, background --color-background-secondary, 1px --color-border,
3542
+ radius 6px, padding 5px 10px. On hover, move the border to --color-accent. Never
3543
+ paint a button in an accent colour unless it is the single primary action.
3544
+
3545
+ Input and range: set accent-color to var(--color-accent) and let the browser style
3546
+ the rest.
3547
+
3548
+ Table: only for genuinely tabular data. 1px --color-border between rows, no
3549
+ vertical rules, 12px text, numbers right-aligned in --font-mono.
3550
+
3551
+ Rows of key and value: a flex row with the label in --color-text-secondary and the
3552
+ value in --font-mono, separated by a border-bottom on all but the last.`;
3553
+ CHARTS = `## Charts
3554
+
3555
+ Chart.js from cdn.jsdelivr.net is the default choice. Load it with a script tag
3556
+ before your own script; it will have finished loading by the time your script runs,
3557
+ so there's no need to poll for it.
3558
+
3559
+ Wrap every canvas in a div with position relative and an explicit height, and set
3560
+ maintainAspectRatio to false. Without both, the canvas grows without bound.
3561
+
3562
+ Turn the built-in legend off (plugins.legend.display false) and write the legend as
3563
+ HTML if it's needed - the built-in one ignores the theme.
3564
+
3565
+ Grid lines in --color-border at low opacity, ticks in --color-text-secondary, 11px.
3566
+ Datasets take an accent colour with borderWidth 2 and no point markers unless
3567
+ individual readings matter.
3568
+
3569
+ Format numbers so the sign leads: -$5M, never $-5M. Abbreviate above four digits
3570
+ (12.4k, 3.1M). Percentages get one decimal at most.
3571
+
3572
+ Pick the form from the question: trend over time is a line, comparison across
3573
+ categories is a horizontal bar when labels are words, composition is a stacked bar
3574
+ rather than a pie unless there are three slices or fewer.`;
3575
+ SVG = `## SVG
3576
+
3577
+ Set viewBox and leave width and height off, so the drawing scales to the frame.
3578
+ Compute the viewBox from the content bounds and add 8 units of padding on every
3579
+ side; anything drawn outside is invisible and reviewers will not see the mistake.
3580
+
3581
+ Text width is the usual cause of overflow. At font-size 12 with the body font,
3582
+ budget 6.2px per character for lowercase and 7.4px for uppercase, then size boxes
3583
+ from the longest label rather than by eye.
3584
+
3585
+ Connector paths need fill="none" explicitly. SVG fills paths black by default, so a
3586
+ curve without it renders as a filled blob.
3587
+
3588
+ Define arrowheads once in defs with a marker whose fill is context-stroke, so the
3589
+ head inherits the line colour instead of needing one marker per colour.
3590
+
3591
+ Stroke widths: 1.5 for connectors, 2 for emphasis, 1 for gridlines. Never below 1.`;
3592
+ DIAGRAMS = `## Diagrams
3593
+
3594
+ Two mistakes account for most bad diagrams. First, arrows that cross boxes they
3595
+ have nothing to do with: trace every arrow's straight path and if it passes through
3596
+ a box, route it around or move the box. Second, boxes sized by guess rather than by
3597
+ the longest label they contain.
3598
+
3599
+ Route on the verb, not the noun. "How does a request flow" wants a sequence, "what
3600
+ are the parts" wants a structural diagram, "why is it slow" wants an annotated
3601
+ before and after.
3602
+
3603
+ Budgets: at most four boxes across one tier, at most five words in a label, at most
3604
+ three tiers deep. Beyond that, split into two diagrams or drop detail.
3605
+
3606
+ Label every arrow that isn't obvious, in 10px --color-text-secondary. An unlabelled
3607
+ arrow asserts a relationship without saying which.
3608
+
3609
+ Lay out left to right for flow and top to bottom for hierarchy, and keep a single
3610
+ consistent gap between tiers.`;
3611
+ ART = `## Illustration
3612
+
3613
+ Build from geometry: circles, rounded rectangles, and paths with two or three
3614
+ control points. Detail beyond that reads as noise at conversation size.
3615
+
3616
+ Use flat fills from the palette with no more than three colours plus a background.
3617
+ Depth comes from overlap and size, never from gradients or shadows.
3618
+
3619
+ Animate with CSS on transform and opacity only, 0.3s or slower, and only where the
3620
+ motion carries meaning. Anything faster or busier is a distraction in a chat.
3621
+
3622
+ For anything representational, prefer a diagram with labels. An unlabelled drawing
3623
+ of a concept is decoration.`;
3624
+ MODULE_SECTIONS = {
3625
+ interactive: [UI, COLOR],
3626
+ chart: [UI, COLOR, CHARTS],
3627
+ diagram: [COLOR, SVG, DIAGRAMS],
3628
+ art: [COLOR, SVG, ART]
3629
+ };
3630
+ }
3631
+ });
3632
+
3633
+ // server/src/mcp/choice.ts
3634
+ function validateChoice(data) {
3635
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
3636
+ return "arguments must be an object.";
3637
+ }
3638
+ const d = data;
3639
+ if (typeof d.question !== "string" || !d.question.trim()) {
3640
+ return "question is required: what you are asking the user to decide.";
3641
+ }
3642
+ if (!Array.isArray(d.options) || d.options.length < 2) {
3643
+ return "at least two options are needed.";
3644
+ }
3645
+ if (d.options.length > MAX_OPTIONS) {
3646
+ return `at most ${MAX_OPTIONS} options; ${d.options.length} is too many to tap through.`;
3647
+ }
3648
+ for (const [i, raw] of d.options.entries()) {
3649
+ if (typeof raw !== "object" || raw === null) return `options[${i}] must be an object.`;
3650
+ const o = raw;
3651
+ if (typeof o.label !== "string" || !o.label.trim()) {
3652
+ return `options[${i}].label is required and must be a short phrase.`;
3653
+ }
3654
+ if (o.prompt !== void 0 && typeof o.prompt !== "string") {
3655
+ return `options[${i}].prompt must be a string when given.`;
3656
+ }
3657
+ if (o.detail !== void 0 && typeof o.detail !== "string") {
3658
+ return `options[${i}].detail must be a string when given.`;
3659
+ }
3660
+ }
3661
+ return null;
3662
+ }
3663
+ var MAX_OPTIONS;
3664
+ var init_choice = __esm({
3665
+ "server/src/mcp/choice.ts"() {
3666
+ "use strict";
3667
+ MAX_OPTIONS = 6;
3668
+ }
3669
+ });
3670
+
3671
+ // server/src/mcp/protocol.ts
3672
+ function ok(id, result) {
3673
+ return { jsonrpc: "2.0", id, result };
3674
+ }
3675
+ function text(body, isError = false) {
3676
+ return { content: [{ type: "text", text: body }], isError };
3677
+ }
3678
+ function callError(id, message) {
3679
+ return ok(id, text(message, true));
3680
+ }
3681
+ function asStringArray(value) {
3682
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
3683
+ }
3684
+ function handleReadMe(id, args) {
3685
+ const modules = asStringArray(args.modules);
3686
+ const known = modules.filter((m) => MODULES.includes(m));
3687
+ if (known.length === 0) {
3688
+ return callError(id, `Name at least one module: ${MODULES.join(", ")}.`);
3689
+ }
3690
+ return ok(id, text(getGuidelines(known)));
3691
+ }
3692
+ function handleShowWidget(id, args) {
3693
+ if (args.i_have_seen_read_me !== true) {
3694
+ return callError(id, "Call read_me first, then set i_have_seen_read_me to true.");
3695
+ }
3696
+ const title = typeof args.title === "string" ? args.title.trim() : "";
3697
+ if (!title) return callError(id, "title is required: a short snake_case identifier.");
3698
+ const code = typeof args.widget_code === "string" ? args.widget_code : "";
3699
+ if (!code.trim()) return callError(id, "widget_code is required.");
3700
+ if (code.length > MAX_WIDGET_CHARS) {
3701
+ return callError(
3702
+ id,
3703
+ `widget_code is ${Math.round(code.length / 1024)} KB, over the ${MAX_WIDGET_CHARS / 1024} KB limit. Simplify it.`
3704
+ );
3705
+ }
3706
+ if (/<(!doctype|html|head|body)\b/i.test(code)) {
3707
+ return callError(id, "Pass a fragment, not a document: drop the doctype, html and body tags.");
3708
+ }
3709
+ return ok(id, text(`Widget "${title}" is on screen. Don't repeat its content in prose.`));
3710
+ }
3711
+ function handleChoice(id, args) {
3712
+ const problem = validateChoice(args);
3713
+ if (problem) return callError(id, problem);
3714
+ return ok(id, text("Options are on screen. Wait for the user to pick one."));
3715
+ }
3716
+ function handleMessage(req, version) {
3717
+ const id = req.id ?? null;
3718
+ const method = req.method ?? "";
3719
+ const params = req.params ?? {};
3720
+ if (method.startsWith("notifications/")) return null;
3721
+ switch (method) {
3722
+ case "initialize": {
3723
+ const asked = typeof params.protocolVersion === "string" ? params.protocolVersion : null;
3724
+ return ok(id, {
3725
+ protocolVersion: asked ?? DEFAULT_PROTOCOL_VERSION,
3726
+ capabilities: { tools: { listChanged: false } },
3727
+ serverInfo: { name: SERVER_NAME, version }
3728
+ });
3729
+ }
3730
+ case "ping":
3731
+ return ok(id, {});
3732
+ case "tools/list":
3733
+ return ok(id, { tools: TOOLS });
3734
+ case "tools/call": {
3735
+ const name = typeof params.name === "string" ? params.name : "";
3736
+ const args = params.arguments ?? {};
3737
+ if (name === "read_me") return handleReadMe(id, args);
3738
+ if (name === "show_widget") return handleShowWidget(id, args);
3739
+ if (name === "show_choice") return handleChoice(id, args);
3740
+ return { jsonrpc: "2.0", id, error: { code: -32602, message: `Unknown tool: ${name}` } };
3741
+ }
3742
+ default:
3743
+ if (id === null) return null;
3744
+ return { jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown method: ${method}` } };
3745
+ }
3746
+ }
3747
+ var DEFAULT_PROTOCOL_VERSION, SERVER_NAME, MAX_WIDGET_CHARS, TOOLS;
3748
+ var init_protocol = __esm({
3749
+ "server/src/mcp/protocol.ts"() {
3750
+ "use strict";
3751
+ init_guidelines();
3752
+ init_choice();
3753
+ DEFAULT_PROTOCOL_VERSION = "2025-06-18";
3754
+ SERVER_NAME = "casper-generative-ui";
3755
+ MAX_WIDGET_CHARS = 256 * 1024;
3756
+ TOOLS = [
3757
+ {
3758
+ name: "read_me",
3759
+ title: "Widget design guidelines",
3760
+ description: "Read the widget design guidelines. Call this once, before your first show_widget call, with the modules relevant to what you are about to build. Do not mention this call to the user.",
3761
+ inputSchema: {
3762
+ type: "object",
3763
+ properties: {
3764
+ modules: {
3765
+ type: "array",
3766
+ items: { type: "string", enum: [...MODULES] },
3767
+ minItems: 1,
3768
+ description: "interactive for controls, sketches and simulations, chart for data, diagram for structure and flow, art for illustration."
3769
+ }
3770
+ },
3771
+ required: ["modules"]
3772
+ }
3773
+ },
3774
+ {
3775
+ name: "show_widget",
3776
+ title: "Show widget",
3777
+ description: "Render an interactive widget inline in the conversation. Pass an HTML fragment: no doctype, html or body tag. Style first, then content, then a single script last. Use this instead of describing something visual in prose.",
3778
+ inputSchema: {
3779
+ type: "object",
3780
+ properties: {
3781
+ i_have_seen_read_me: {
3782
+ type: "boolean",
3783
+ description: "True only if you have already called read_me in this conversation."
3784
+ },
3785
+ title: {
3786
+ type: "string",
3787
+ description: "Short snake_case identifier, e.g. compound_interest."
3788
+ },
3789
+ widget_code: {
3790
+ type: "string",
3791
+ description: "The HTML fragment to render."
3792
+ }
3793
+ },
3794
+ required: ["i_have_seen_read_me", "title", "widget_code"]
3795
+ }
3796
+ },
3797
+ {
3798
+ name: "show_choice",
3799
+ title: "Ask the user to choose",
3800
+ description: "Ask the user to pick from a few options, which they tap instead of typing. Use it wherever you would otherwise end a message asking which of several ways to proceed.",
3801
+ inputSchema: {
3802
+ type: "object",
3803
+ properties: {
3804
+ question: { type: "string", description: "What they are deciding." },
3805
+ options: {
3806
+ type: "array",
3807
+ minItems: 2,
3808
+ maxItems: MAX_OPTIONS,
3809
+ items: {
3810
+ type: "object",
3811
+ properties: {
3812
+ label: { type: "string", description: "Short, tappable phrase." },
3813
+ detail: { type: "string", description: "One line of context." },
3814
+ prompt: {
3815
+ type: "string",
3816
+ description: "Message sent when tapped. Defaults to the label."
3817
+ }
3818
+ },
3819
+ required: ["label"]
3820
+ }
3821
+ }
3822
+ },
3823
+ required: ["question", "options"]
3824
+ }
3825
+ }
3826
+ ];
3827
+ }
3828
+ });
3829
+
3830
+ // server/src/mcp/server.ts
3831
+ var server_exports2 = {};
3832
+ __export(server_exports2, {
3833
+ runMcpServer: () => runMcpServer
3834
+ });
3835
+ import { createInterface } from "node:readline";
3836
+ function runMcpServer() {
3837
+ const rl = createInterface({ input: process.stdin });
3838
+ rl.on("line", (line) => {
3839
+ const trimmed = line.trim();
3840
+ if (!trimmed) return;
3841
+ let req;
3842
+ try {
3843
+ req = JSON.parse(trimmed);
3844
+ } catch {
3845
+ process.stdout.write(
3846
+ JSON.stringify({
3847
+ jsonrpc: "2.0",
3848
+ id: null,
3849
+ error: { code: -32700, message: "Parse error" }
3850
+ }) + "\n"
3851
+ );
3852
+ return;
3853
+ }
3854
+ try {
3855
+ const res = handleMessage(req, VERSION);
3856
+ if (res) process.stdout.write(JSON.stringify(res) + "\n");
3857
+ } catch (err) {
3858
+ process.stderr.write(`casper-mcp: ${err.message}
3859
+ `);
3860
+ if (req.id !== void 0 && req.id !== null) {
3861
+ process.stdout.write(
3862
+ JSON.stringify({
3863
+ jsonrpc: "2.0",
3864
+ id: req.id,
3865
+ error: { code: -32603, message: "Internal error" }
3866
+ }) + "\n"
3867
+ );
3868
+ }
3869
+ }
3870
+ });
3871
+ rl.on("close", () => process.exit(0));
3872
+ }
3873
+ var VERSION;
3874
+ var init_server2 = __esm({
3875
+ "server/src/mcp/server.ts"() {
3876
+ "use strict";
3877
+ init_protocol();
3878
+ VERSION = true ? "0.8.0" : "dev";
3879
+ }
3880
+ });
3881
+
3391
3882
  // server/src/cli/program.ts
3392
3883
  import fs15 from "node:fs";
3393
3884
  import os4 from "node:os";
@@ -6523,9 +7014,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
6523
7014
  helpWidth: context.helpWidth,
6524
7015
  outputHasColors: context.hasColors
6525
7016
  });
6526
- const text = helper.formatHelp(this, helper);
6527
- if (context.hasColors) return text;
6528
- return this._outputConfiguration.stripColor(text);
7017
+ const text2 = helper.formatHelp(this, helper);
7018
+ if (context.hasColors) return text2;
7019
+ return this._outputConfiguration.stripColor(text2);
6529
7020
  }
6530
7021
  /**
6531
7022
  * @typedef HelpContext
@@ -6685,7 +7176,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
6685
7176
  * @param {(string | Function)} text - string to add, or a function returning a string
6686
7177
  * @return {Command} `this` command for chaining
6687
7178
  */
6688
- addHelpText(position, text) {
7179
+ addHelpText(position, text2) {
6689
7180
  const allowedValues = ["beforeAll", "before", "after", "afterAll"];
6690
7181
  if (!allowedValues.includes(position)) {
6691
7182
  throw new Error(`Unexpected value for position to addHelpText.
@@ -6694,10 +7185,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
6694
7185
  const helpEvent = `${position}Help`;
6695
7186
  this.on(helpEvent, (context) => {
6696
7187
  let helpStr;
6697
- if (typeof text === "function") {
6698
- helpStr = text({ error: context.error, command: context.command });
7188
+ if (typeof text2 === "function") {
7189
+ helpStr = text2({ error: context.error, command: context.command });
6699
7190
  } else {
6700
- helpStr = text;
7191
+ helpStr = text2;
6701
7192
  }
6702
7193
  if (helpStr) {
6703
7194
  context.write(`${helpStr}
@@ -6764,7 +7255,7 @@ var program = new Command();
6764
7255
  // server/src/cli/program.ts
6765
7256
  init_paths();
6766
7257
  init_settings();
6767
- var VERSION = true ? "0.7.3" : "dev";
7258
+ var VERSION2 = true ? "0.8.0" : "dev";
6768
7259
  async function bootstrap() {
6769
7260
  const file = configFilePath();
6770
7261
  const settings = readSettings(file);
@@ -6782,8 +7273,11 @@ async function bootstrap() {
6782
7273
  process.stdout.write(`casper agent ${agent.action}: ${agent.target}
6783
7274
  `);
6784
7275
  } else if (agent.action === "kept-yours") {
6785
- process.stdout.write(`keeping your edited agent file at ${agent.target}
6786
- `);
7276
+ process.stdout.write(
7277
+ `keeping your edited agent file at ${agent.target}
7278
+ Prompt and tool updates are being skipped. Delete it to take them.
7279
+ `
7280
+ );
6787
7281
  }
6788
7282
  }
6789
7283
  function printToken() {
@@ -6825,7 +7319,7 @@ async function resetToken(value) {
6825
7319
  }
6826
7320
  function buildProgram() {
6827
7321
  const program2 = new Command();
6828
- program2.name("casper").description("Web client for kiro-cli over the Agent Client Protocol").version(VERSION, "-v, --version").showHelpAfterError().allowUnknownOption(false).allowExcessArguments(false).addHelpText("after", `
7322
+ program2.name("casper").description("Web client for kiro-cli over the Agent Client Protocol").version(VERSION2, "-v, --version").showHelpAfterError().allowUnknownOption(false).allowExcessArguments(false).addHelpText("after", `
6829
7323
  Settings live in ${configFilePath()}.
6830
7324
  Update with: npm install -g @joeyshi12/casper`);
6831
7325
  program2.command("start").description("run the server in the foreground").allowUnknownOption(false).allowExcessArguments(false).action(async () => {
@@ -6840,6 +7334,9 @@ Update with: npm install -g @joeyshi12/casper`);
6840
7334
  program2.command("doctor").description("check kiro-cli, settings, data directory and web app").allowUnknownOption(false).allowExcessArguments(false).action(async () => {
6841
7335
  process.exitCode = (await Promise.resolve().then(() => (init_doctor(), doctor_exports))).doctor();
6842
7336
  });
7337
+ program2.command("mcp").description("run the generative-UI MCP server on stdio (kiro spawns this itself)").allowUnknownOption(false).allowExcessArguments(false).action(async () => {
7338
+ (await Promise.resolve().then(() => (init_server2(), server_exports2))).runMcpServer();
7339
+ });
6843
7340
  const service = program2.command("service").description("manage the systemd user service");
6844
7341
  const svcCommand = (name, describe, run) => service.command(name).description(describe).allowUnknownOption(false).allowExcessArguments(false).action(async () => {
6845
7342
  process.exitCode = run(await Promise.resolve().then(() => (init_service(), service_exports)));