@typecaast/mcp 0.1.0 → 0.1.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/LICENSE +201 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
// package.json
|
|
11
11
|
var package_default = {
|
|
12
12
|
name: "@typecaast/mcp",
|
|
13
|
-
version: "0.1.
|
|
13
|
+
version: "0.1.1",
|
|
14
14
|
description: "Model Context Protocol server for authoring & validating Typecaast configs.",
|
|
15
15
|
license: "Apache-2.0",
|
|
16
16
|
repository: {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../package.json","../../../docs/authoring-configs.md","../../../docs/pacing.md","../../../docs/message-content.md","../src/docs.ts","../src/core.ts","../../../registry/skins.json"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\n\nasync function main(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err: unknown) => {\n console.error(\"typecaast-mcp failed to start:\", err);\n process.exit(1);\n});\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport pkg from \"../package.json\";\nimport { DOCS } from \"./docs.js\";\nimport { SKINS, jsonSchema, scaffoldConfig, validate } from \"./core.js\";\n\n/** A tool result that returns a value as pretty-printed JSON text. */\nfunction jsonText(value: unknown): {\n content: { type: \"text\"; text: string }[];\n} {\n return {\n content: [{ type: \"text\", text: JSON.stringify(value, null, 2) }],\n };\n}\n\n/** Build the Typecaast MCP server with all tools + doc resources registered. */\nexport function createServer(): McpServer {\n const server = new McpServer({ name: \"typecaast\", version: pkg.version });\n\n server.registerTool(\n \"validate_config\",\n {\n title: \"Validate a Typecaast config\",\n description:\n \"Validate a Typecaast config (object or JSON string) against the schema and semantic checks. Returns { valid, diagnostics }. Run this after authoring or editing a config (e.g. one drafted from a screenshot).\",\n inputSchema: {\n config: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .describe(\"The config, as a JSON string or an object.\"),\n },\n },\n ({ config }) => jsonText(validate(config)),\n );\n\n server.registerTool(\n \"get_json_schema\",\n {\n title: \"Get the config JSON Schema\",\n description:\n 'Return the Typecaast config JSON Schema (draft-07). Reference it from a config via a \"$schema\" line, or use it to build a config skeleton.',\n inputSchema: {},\n },\n () => jsonText(jsonSchema()),\n );\n\n server.registerTool(\n \"list_skins\",\n {\n title: \"List built-in skins\",\n description:\n \"List the built-in skins (set one as meta.skin.id), with display name, supported themes, and a one-line summary.\",\n inputSchema: {},\n },\n () => jsonText(SKINS),\n );\n\n server.registerTool(\n \"scaffold_config\",\n {\n title: \"Scaffold a starter config\",\n description:\n 'Return a minimal valid Typecaast config for the given skin id (default \"slack\") — a starting point to edit.',\n inputSchema: {\n skinId: z\n .string()\n .optional()\n .describe('Skin id, e.g. \"slack\" (default), \"imessage\", \"discord\".'),\n },\n },\n ({ skinId }) => jsonText(scaffoldConfig(skinId)),\n );\n\n server.registerTool(\n \"get_docs\",\n {\n title: \"Read an authoring guide\",\n description: `Return a Typecaast authoring guide as markdown. Slugs: ${DOCS.map(\n (d) => d.slug,\n ).join(\", \")}. Omit \"slug\" to list them.`,\n inputSchema: {\n slug: z\n .string()\n .optional()\n .describe(\n 'Guide slug, e.g. \"pacing\". Omit to list available guides.',\n ),\n },\n },\n ({ slug }) => {\n if (!slug) {\n return {\n content: [\n {\n type: \"text\",\n text: DOCS.map((d) => `- ${d.slug}: ${d.title}`).join(\"\\n\"),\n },\n ],\n };\n }\n const doc = DOCS.find((d) => d.slug === slug);\n if (!doc) {\n return {\n content: [\n {\n type: \"text\",\n text: `Unknown guide \"${slug}\". Available: ${DOCS.map(\n (d) => d.slug,\n ).join(\", \")}.`,\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: \"text\", text: doc.text }] };\n },\n );\n\n for (const doc of DOCS) {\n server.registerResource(\n `docs-${doc.slug}`,\n `typecaast://docs/${doc.slug}`,\n {\n title: doc.title,\n description: `Typecaast authoring guide: ${doc.title}`,\n mimeType: \"text/markdown\",\n },\n (uri) => ({\n contents: [\n { uri: uri.href, mimeType: \"text/markdown\", text: doc.text },\n ],\n }),\n );\n }\n\n return server;\n}\n","{\n \"name\": \"@typecaast/mcp\",\n \"version\": \"0.1.0\",\n \"description\": \"Model Context Protocol server for authoring & validating Typecaast configs.\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/corywatilo/typecaast.git\",\n \"directory\": \"packages/mcp\"\n },\n \"type\": \"module\",\n \"bin\": {\n \"typecaast-mcp\": \"./dist/index.js\"\n },\n \"main\": \"./dist/index.js\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src\",\n \"test\": \"vitest run\",\n \"clean\": \"rm -rf dist .turbo\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@typecaast/schema\": \"workspace:*\",\n \"zod\": \"^4.4.3\"\n }\n}\n","# Authoring configs by hand\n\nA Typecaast simulation is **one JSON config**. The visual playground\n(<https://typecaast.com/playground>) is the easy way to build one, but the config\nis plain JSON — you can write or edit it by hand in any editor (or have an LLM do\nit). This is the reference for doing that without the playground.\n\n> **Timing lives in its own guide.** \"Why are my messages too fast / too close\n> together?\" and \"how do I get ~1–2s between messages?\" are answered in\n> [pacing.md](./pacing.md). Read that one for anything about gaps, delays, and\n> typing speed.\n\n## The shape\n\n```json\n{\n \"$schema\": \"https://typecaast.com/schema/v1/typecaast.schema.json\",\n \"version\": 1,\n \"meta\": {\n \"canvas\": { \"width\": 880, \"height\": 720 },\n \"skin\": { \"id\": \"slack\" }\n },\n \"participants\": [\n { \"id\": \"me\", \"name\": \"You\", \"isSelf\": true },\n { \"id\": \"sam\", \"name\": \"Sam\" }\n ],\n \"pacing\": { \"readingWpm\": 240 },\n \"timeline\": [\n { \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\" },\n { \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\" }\n ]\n}\n```\n\nThat's a complete, valid config. Five top-level keys:\n\n| Key | Required | What it is |\n| -------------- | -------- | --------------------------------------------------------------------- |\n| `version` | yes | Config schema version. Currently `1` (a literal). |\n| `meta` | yes | Canvas, skin, theme, fps — how it renders. |\n| `participants` | yes | Who is in the conversation. |\n| `pacing` | no | Global timing model. Omit for defaults. See [pacing.md](./pacing.md). |\n| `timeline` | yes | The ordered list of steps that play out. |\n\nAdd the `$schema` line at the top and your editor gives you autocomplete and\ninline validation against the live schema. It's optional but recommended.\n\n## `meta`\n\n```json\n\"meta\": {\n \"canvas\": { \"width\": 880, \"height\": 720 },\n \"skin\": { \"id\": \"slack\", \"options\": { \"channel\": \"#alerts\" } },\n \"fps\": 30,\n \"fit\": \"reflow\",\n \"theme\": \"auto\",\n \"seed\": 42,\n \"background\": \"transparent\",\n \"assets\": \"inline\",\n \"composer\": \"auto\",\n \"loop\": false\n}\n```\n\n| Field | Default | Notes |\n| ------------ | --------------- | ----------------------------------------------------------------------------------------------------- |\n| `canvas` | — | `{ width, height }` in px. Required. The authoring reference size / the video frame. |\n| `skin` | — | `{ id, options? }`. Required. `id` picks the skin (see [Skins](#skins)); `options` are skin-specific. |\n| `fps` | `30` | Frames per second for video export. |\n| `fit` | `\"reflow\"` | `reflow` (re-wrap to container), `scale` (CSS-scale the canvas), or `fixed` (pin to px). |\n| `theme` | `\"auto\"` | `light`, `dark`, or `auto` (follows the host page; video resolves `auto` → `light`). |\n| `seed` | `42` | Seeds all deterministic jitter — same seed ⇒ identical timings. |\n| `background` | `\"transparent\"` | `\"transparent\"` or any CSS color. |\n| `assets` | `\"inline\"` | `inline` (embed images as data URLs) or `url` (reference hosted images). |\n| `composer` | `\"auto\"` | Reply box: `auto` (shown only while typing/sending), `always`, or `never`. |\n| `loop` | `false` | Auto-replay at the end (unless the `<Typecaast>` consumer passes an explicit `loop`). |\n\n## `participants`\n\nAn array of speakers. Reference them everywhere by `id`.\n\n```json\n\"participants\": [\n { \"id\": \"me\", \"name\": \"You\", \"isSelf\": true },\n { \"id\": \"sam\", \"name\": \"Sam\", \"avatar\": \"https://…/sam.png\", \"color\": \"#5b3a8e\" },\n { \"id\": \"bot\", \"name\": \"PostHog\", \"kind\": \"app\" }\n]\n```\n\n| Field | Required | Notes |\n| -------- | -------- | -------------------------------------------------------------------------------- |\n| `id` | yes | Stable id used by `from` / `target` / `by` in the timeline. |\n| `name` | yes | Display name. |\n| `avatar` | no | Data URL, or a hosted URL (per `meta.assets`). |\n| `color` | no | Accent color (CSS) some skins use for the author. |\n| `isSelf` | no | Marks the viewer — rendered on the \"self\" side and as the composer's author. |\n| `kind` | no | `\"person\"` (default) or `\"app\"` (a bot/integration; skins render it distinctly). |\n\nSet `isSelf: true` on exactly one participant if you use the composer\n(`composerType` / `send`).\n\n## `timeline` — the steps\n\nThe timeline is an ordered array. Each step has a `type` and its own fields. Two\nfields are shared by **every** step:\n\n- `id` — optional. Give a step an id so a later `reaction`, `edit`, `delete`, or\n `readReceipt` can target it.\n- `instant` — optional `true` to reveal with no animation and **no computed\n pacing gap** (handy when you want to control spacing yourself — see\n [pacing.md](./pacing.md)).\n\nSteps that target an earlier message take a `target`: either a message's `id` or\nthe literal `\"$prev\"` (the most-recent message). `target` defaults to `\"$prev\"`.\n\n### The step types\n\n| `type` | Purpose |\n| -------------- | ------------------------------------------------------------------------ |\n| `message` | An incoming message (optionally preceded by a typing indicator). |\n| `typing` | A standalone typing indicator (no message need follow). |\n| `composerType` | The `isSelf` participant typing into the composer, char by char. |\n| `send` | Commit the composer's current text to the thread. |\n| `reaction` | An emoji reaction landing on a target message. |\n| `edit` | Replace a previously sent message's body. |\n| `delete` | Remove a previously sent message. |\n| `readReceipt` | A read-receipt indicator (skins that support it). |\n| `system` | A system / notice line (e.g. \"Sam joined #alerts\") — not a chat message. |\n| `delay` | An explicit pause on the timeline. See [pacing.md](./pacing.md). |\n\n### `message`\n\n```json\n{ \"type\": \"message\", \"from\": \"sam\", \"text\": \"on it\" }\n```\n\n| Field | Notes |\n| --------- | ----------------------------------------------------------------------------------------------------- |\n| `from` | Participant id. Required. |\n| `text` | Message body. Supports Slack-style mrkdwn — see [message-content.md](./message-content.md). |\n| `images` | `[{ src, alt?, width?, height? }]` — in-message images. |\n| `content` | Explicit Block Kit content nodes (wins over `text`) — see [message-content.md](./message-content.md). |\n| `typing` | Show a typing indicator first: `true`, or `{ \"showTypingFor\": 1800 }` (ms). |\n\nModel an app \"card\" as a `message` from an `app` participant carrying `content`\n(header/section/context/actions/…), **not** a `system` step.\n\n### `typing`\n\n```json\n{ \"type\": \"typing\", \"from\": \"sam\", \"showTypingFor\": 1500 }\n```\n\n`showTypingFor` is in **milliseconds** (defaults to ~1500ms if omitted).\n\n### `composerType` and `send`\n\n```json\n{ \"type\": \"composerType\", \"from\": \"me\", \"text\": \"let me check…\" },\n{ \"type\": \"send\" }\n```\n\n`composerType` types into the composer (the `from` should be your `isSelf`\nparticipant); `send` then posts it. `send.from` is optional — it inherits the\ncomposer's author. `composerType.typingDuration` (ms) overrides the computed\ntyping time.\n\n### `reaction`\n\n```json\n{ \"type\": \"reaction\", \"target\": \"$prev\", \"emoji\": \"🚀\", \"delay\": 800 }\n```\n\n| Field | Notes |\n| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `emoji` | The emoji. Required. |\n| `target` | Message id or `\"$prev\"` (default). |\n| `from` | Optional reactor. |\n| `shortcode` | Optional shortcode without colons (e.g. `\"rocket\"`) — shown in some skins' tooltips. |\n| `delay` | Gap (ms) from when the target appears before the reaction lands. **This `delay` is a field on the reaction, not the `delay` step type** — don't confuse them (see [pacing.md](./pacing.md)). |\n\n### `edit` and `delete`\n\n```json\n{ \"type\": \"edit\", \"target\": \"m2\", \"text\": \"fixed typo\" },\n{ \"type\": \"delete\", \"target\": \"m3\" }\n```\n\nBoth default `target` to `\"$prev\"`. `edit` takes the same body fields as\n`message` (`text` / `images` / `content`).\n\n### `readReceipt`\n\n```json\n{ \"type\": \"readReceipt\", \"by\": \"sam\", \"target\": \"m2\" }\n```\n\n`by` (who read it) and `target` (up to which message) are both optional.\n\n### `system`\n\n```json\n{ \"type\": \"system\", \"text\": \"Sam joined #alerts\" }\n```\n\nA centered notice / tool-output line, rendered distinctly per skin. Takes the\nsame body fields as `message`.\n\n### `delay`\n\n```json\n{ \"type\": \"delay\", \"duration\": 1500 }\n```\n\nAn explicit pause. `duration` is in **milliseconds** and is **required**. This is\nthe most predictable way to space messages out — see [pacing.md](./pacing.md).\n\n## Skins\n\n`meta.skin.id` selects the skin. The conversation content is the same regardless\nof skin; the skin decides how it looks. Built-in ids:\n\n`slack` · `telegram` · `claude-code` · `imessage` · `messages-macos` ·\n`whatsapp` · `cursor` · `discord`\n\nSome steps aren't rendered by every skin (e.g. a terminal skin has no\nreactions). The skin's capabilities decide; unsupported steps are dropped. Pick a\nskin that supports the steps you use.\n\n## Validate it\n\nAlways validate after editing by hand:\n\n- **CLI:** `npx @typecaast/cli validate my-config.json` (exit 0 = OK; prints\n `E_*` errors / `W_*` warnings with locations and hints — see\n [errors.md](./errors.md)).\n- **MCP:** if you've added the [`@typecaast/mcp`](../packages/mcp/README.md)\n server to your editor, call its `validate_config` tool. This is the easy loop\n when drafting a config (e.g. from a screenshot) in your own project.\n\n## See also\n\n- [pacing.md](./pacing.md) — timing, delays, and the \"1–2s between messages\" recipes.\n- [message-content.md](./message-content.md) — message bodies: mrkdwn + Block Kit.\n- The JSON Schema: <https://typecaast.com/schema/v1/typecaast.schema.json>.\n- Machine-readable index for LLMs: <https://typecaast.com/llms.txt>.\n </content>\n","# Pacing & timing\n\nTypecaast **auto-paces** a conversation so you don't have to time every step by\nhand: it derives the gap before each message from how long the previous message\ntakes to \"read\", and how long typing takes from the text length. This is why a\nconfig with no timing fields still plays at a human rhythm.\n\nIt's also the #1 source of \"why is this too fast / why are my messages bunched\nup?\" — including the common ask, **\"I want ~1–2s between messages.\"** This guide\nexplains the model and the levers. (For the full step reference, see\n[authoring-configs.md](./authoring-configs.md).)\n\n## The model\n\nTiming is controlled by the optional top-level `pacing` object. Omit it and you\nget the full default model:\n\n```json\n\"pacing\": {\n \"readingWpm\": 240,\n \"typingCps\": 14,\n \"humanize\": 0.15,\n \"startDelayMs\": 400\n}\n```\n\n| Field | Default | Unit | What it controls |\n| -------------- | ------- | --------- | ------------------------------------------------------------------------------- |\n| `readingWpm` | `240` | words/min | The gap **before an incoming message** ≈ the time to read the **previous** one. |\n| `typingCps` | `14` | chars/sec | Composer typing speed, and how long a typing indicator shows before a message. |\n| `humanize` | `0.15` | fraction | ±15% **seeded** jitter on computed gaps/durations so it doesn't feel robotic. |\n| `startDelayMs` | `400` | ms | Delay before the very first event. |\n\nEvery value is overridable per step in the timeline; per-step values win over the\ncomputed ones.\n\n### How the gap is computed\n\nThe key thing to understand: **the gap before a message scales with the length of\nthe _previous_ message**, because it models the other person reading it before\nreplying. Roughly:\n\n```\nwords ≈ characters / 5\ngap(ms) ≈ (words / readingWpm) × 60000 (then ±humanize jitter)\n```\n\nSo at the default 240 wpm:\n\n- A short line like `\"ship it?\"` (~1.6 words) → gap ≈ **400ms**.\n- A sentence of ~40 characters (~8 words) → gap ≈ **2000ms**.\n\n**This is the gotcha:** with short messages, the default gaps are short (a few\nhundred ms), so the conversation feels snappy/bunched. Lowering `readingWpm` or\nadding explicit `delay` steps is how you stretch it out.\n\n### Other automatic durations\n\n- Typing indicator before a message (`message.typing: true`) lasts about as long\n as it would take to type that message at `typingCps`. A standalone `typing`\n step with no `showTypingFor` defaults to ~1500ms.\n- A `reaction` with no `delay` lands ~1000ms after its target appears.\n- `instant: true` on a step skips the reveal animation **and** the computed gap.\n\n### Determinism\n\nAll jitter is seeded from `meta.seed` (default `42`). The same `(config, seed)`\nalways produces identical timings — there is no `Math.random()`. Change `seed`\nto get a different (but still repeatable) jitter pattern.\n\n## \"I want ~1–2 seconds between messages\"\n\nFour ways, most predictable first.\n\n### 1. Insert a `delay` step (recommended — exact and explicit)\n\nA `delay` step is a fixed pause whose `duration` is in **milliseconds**:\n\n```json\n\"timeline\": [\n { \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\" },\n { \"type\": \"delay\", \"duration\": 1500 },\n { \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\" }\n]\n```\n\nThat guarantees ~1.5s between those two messages regardless of their length. Use\nit between each pair of messages you want spaced out. It's the most reliable knob\nbecause it doesn't depend on text length.\n\n> **Don't confuse the two `delay`s.** The `delay` **step type** (`{ \"type\":\n\"delay\", \"duration\": … }`) is a standalone pause. A `reaction` step also has a\n> `delay` **field** — that one is the gap before the _reaction_ lands on its\n> target. Different things.\n\n### 2. Lower `readingWpm` globally\n\nSlowing the \"reading speed\" lengthens every auto-computed gap at once:\n\n```json\n\"pacing\": { \"readingWpm\": 80 }\n```\n\nAt ~80 wpm a short `\"ship it?\"` reply gaps ~1.2s; longer messages gap\nproportionally more. Good for a uniformly slower feel. **Caveat:** because the\ngap still scales with text length, very short messages stay shorter than very\nlong ones — if you need a precise gap, use a `delay` step (#1).\n\nRough guide for a short one-liner: ~120 wpm ≈ 0.8s, ~80 wpm ≈ 1.2s, ~60 wpm ≈\n1.6s.\n\n### 3. `instant` + `delay` for full manual control\n\nDrop the automatic gaps entirely and place every pause yourself:\n\n```json\n{ \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\", \"instant\": true },\n{ \"type\": \"delay\", \"duration\": 1200 },\n{ \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\", \"instant\": true }\n```\n\n`instant` removes a step's computed gap and reveal animation, so the only spacing\nis your `delay` steps. Use this when you want a precisely choreographed timeline.\n\n### 4. Pad with a typing indicator\n\nA typing indicator before a message both adds time and reads naturally:\n\n```json\n{ \"type\": \"typing\", \"from\": \"sam\", \"showTypingFor\": 1500 },\n{ \"type\": \"message\", \"from\": \"sam\", \"text\": \"shipping 🚀\" }\n```\n\nor inline on the message:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"sam\",\n \"text\": \"shipping 🚀\",\n \"typing\": { \"showTypingFor\": 1500 }\n}\n```\n\n`showTypingFor` is in **milliseconds**.\n\n## Worked example\n\nTwo short messages with a deliberate 1.5s gap:\n\n```json\n{\n \"$schema\": \"https://typecaast.com/schema/v1/typecaast.schema.json\",\n \"version\": 1,\n \"meta\": {\n \"canvas\": { \"width\": 600, \"height\": 400 },\n \"skin\": { \"id\": \"imessage\" }\n },\n \"participants\": [\n { \"id\": \"me\", \"name\": \"You\", \"isSelf\": true },\n { \"id\": \"sam\", \"name\": \"Sam\" }\n ],\n \"timeline\": [\n { \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\" },\n { \"type\": \"delay\", \"duration\": 1500 },\n { \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\" }\n ]\n}\n```\n\nTimeline: first message reveals at ~`startDelayMs` (400ms); the `delay` holds\n1500ms; the reply reveals ~1.5s later. Without the `delay`, that gap would be the\ndefault ~400ms reading time of `\"ship it?\"`.\n\n## See also\n\n- [authoring-configs.md](./authoring-configs.md) — the full config + step reference.\n- [message-content.md](./message-content.md) — message bodies (mrkdwn + Block Kit).\n </content>\n","# Authoring message content\n\nA message's body is authored in its `content`. The simplest form is a plain\n`text` string; richer messages (especially **app messages**) use **Block Kit**\ncontent nodes — the same primitives a real Slack app emits.\n\n> The easiest way to author this is the [playground](https://typecaast.com/playground)\n> — it has a typed block editor. This doc is the reference for the JSON it\n> produces. Point your editor at the bundled JSON Schema\n> (`\"$schema\": \".../typecaast.schema.json\"`) for autocomplete.\n\n## Plain text + mrkdwn\n\nMost messages just need `text`. It's parsed for inline marks:\n\n```json\n{ \"type\": \"message\", \"from\": \"cory\", \"text\": \"shipped *it* — see `deploy.ts`\" }\n```\n\n| Syntax | Renders | Inline node |\n| ---------------------- | ----------------------------------------------------- | ------------ |\n| `*bold*` | **bold** | `bold` |\n| `_italic_` | _italic_ | `italic` |\n| `~strike~` | ~~strike~~ | `strike` |\n| `` `code` `` | `code` | `code` |\n| `https://…` | a link | `link` |\n| `@name` | a mention pill | `mention` |\n| `<@id>` | a mention resolved to that participant's display name | `mention` |\n| emoji (`🟠`, `:tada:`) | the glyph | left in text |\n\n`<@id>` is the robust way to mention someone whose display name has spaces\n(`<@joe>` → \"@Joe Saunderson\"); it resolves against `participants` at compile.\n\nIn-message images use the `images` sugar:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"cory\",\n \"text\": \"here's the toast:\",\n \"images\": [{ \"src\": \"./toast.png\", \"alt\": \"error toast\", \"width\": 320 }]\n}\n```\n\n## App messages (Block Kit)\n\nAn **app message** is a normal `message` from a participant with\n`\"kind\": \"app\"` — the sender drives the \"APP\" badge — whose `content` is a list\nof Block Kit nodes. **Slack renders the full set; other skins show the text and\nskip the rest** (a Slack-targeted config degrades gracefully elsewhere).\n\nBlocks carry a `text` string (parsed like above) or pre-resolved `spans`:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"posthog\",\n \"content\": [\n { \"type\": \"header\", \"text\": \"perf(inbox): Fix 26s admin inbox load\" },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"text\",\n \"text\": \"🟠 *P2* · Session replay · rvenvy/rvenvy-ai\"\n }\n ]\n },\n { \"type\": \"section\", \"text\": \"Sales reps hit prolonged loading spinners…\" },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"text\",\n \"text\": \"2 signals · Suggested reviewers: <@joe> <@cory>\"\n }\n ]\n },\n { \"type\": \"divider\" },\n {\n \"type\": \"actions\",\n \"elements\": [\n { \"type\": \"button\", \"label\": \"Review PR\", \"href\": \"https://…\" },\n { \"type\": \"button\", \"label\": \"Open in PostHog\" },\n { \"type\": \"button\", \"label\": \"Dismiss\", \"style\": \"danger\" }\n ]\n }\n ]\n}\n```\n\n### Block types\n\n| Node | Shape | Notes |\n| ------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `header` | `{ text }` | Large bold heading (plain text). |\n| `section` | `{ text \\| spans, accessory?, fields? }` | A paragraph. `accessory` is a `button` or `image` to its right; `fields` is a 2-column grid of `{ text \\| spans }`. |\n| `context` | `{ elements: [...] }` | Small muted row; each element is `{ type:\"text\", text }` or `{ type:\"image\", src, alt? }`. |\n| `divider` | `{}` | A horizontal rule. |\n| `actions` | `{ elements: [button…] }` | A row of buttons. |\n| `image` | `{ src, alt?, width?, height? }` | A standalone image. |\n| `codeblock` | `{ text, lang? }` | A monospaced preformatted box (Slack's fenced ` ``` ` block). `text` is **literal** — whitespace and newlines are kept and it is **not** parsed for marks. |\n| `attachment` | `{ color?, content: [...] }` | Nested blocks behind a colored left bar (the legacy \"attachment\" look). `color` is any CSS color. |\n\n### Buttons\n\nA `button` element appears in an `actions` block or as a `section` accessory:\n\n```json\n{\n \"type\": \"button\",\n \"label\": \"View PR\",\n \"href\": \"https://…\",\n \"style\": \"primary\"\n}\n```\n\n- `style`: `\"primary\"` (filled green), `\"danger\"` (red outline), or omit for the\n default outlined button.\n- `href`: with one, the button is a real link (opens in a new tab); without one\n it renders inert.\n\n### Code blocks\n\nFor a table, log, or snippet, use a `codeblock` — Slack's fenced ` ``` `\nblock, a monospaced box that preserves whitespace and newlines verbatim. Unlike\nthe inline `` `code` `` mark (a single-line pill inside a sentence), a `codeblock`\nis its own block and its `text` is **literal**: `*`, `~`, `<@id>` etc. are shown\nas typed, never parsed. Author the canvas wide enough that the widest line fits on\none line, then scale the embed down on the page (see below).\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"posthog\",\n \"content\": [\n {\n \"type\": \"codeblock\",\n \"text\": \"Metric A B\\nReturned 64% 96%\\nActive weeks 2.9 7.2\"\n }\n ]\n}\n```\n\nA wide monospace block won't reflow, so give the instance a wide\n`meta.canvas.width` and set `\"fit\": \"scale\"`; the host wraps it in a sized\ncontainer and the whole instance scales down to fit (the canvas keeps its internal\npixel width, so the table stays one line per row).\n\n### The colored bar (attachment)\n\nTo get the legacy left-bar card look, wrap blocks in an `attachment`:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"posthog\",\n \"content\": [\n {\n \"type\": \"attachment\",\n \"color\": \"#36a64f\",\n \"content\": [\n { \"type\": \"section\", \"text\": \"Pull request opened.\" },\n {\n \"type\": \"actions\",\n \"elements\": [\n { \"type\": \"button\", \"label\": \"View PR\", \"style\": \"primary\" }\n ]\n }\n ]\n }\n ]\n}\n```\n\n## System / notice lines\n\nThe `system` step is **not** an app card — it's a non-message notice line\n(\"X joined #channel\", an agent's tool-output line), rendered distinctly per skin\n(centered/muted in Slack, iMessage, WhatsApp; an agent line in Cursor/Claude\nCode). It carries only `from?` + text/`content`:\n\n```json\n{ \"type\": \"system\", \"from\": \"posthog\", \"text\": \"Cory joined #signals\" }\n```\n\n## The reply box\n\nThe composer (\"reply box\") visibility is set by `meta.composer`: `\"auto\"`\n(default — shown only while someone is composing), `\"always\"` (always shown,\nidle when nobody is typing — no `isSelf` participant required), or `\"never\"`.\n","// The authoring guides, inlined as strings at build time (esbuild `text`\n// loader). Source of truth is the repo's /docs — these are the same files the\n// site serves. Kept out of core.ts so the unit tests don't need a .md loader.\nimport authoringConfigs from \"../../../docs/authoring-configs.md\";\nimport pacing from \"../../../docs/pacing.md\";\nimport messageContent from \"../../../docs/message-content.md\";\n\nexport interface DocEntry {\n slug: string;\n title: string;\n text: string;\n}\n\nexport const DOCS: DocEntry[] = [\n {\n slug: \"authoring-configs\",\n title: \"Authoring configs by hand\",\n text: authoringConfigs,\n },\n { slug: \"pacing\", title: \"Pacing & timing\", text: pacing },\n {\n slug: \"message-content\",\n title: \"Message content\",\n text: messageContent,\n },\n];\n","// Pure, dependency-light helpers — no MCP SDK, no .md imports — so they're\n// trivially unit-testable. The SDK wiring lives in server.ts.\nimport {\n validateConfig,\n configJsonSchema,\n type Diagnostic,\n} from \"@typecaast/schema\";\nimport skinsRegistry from \"../../../registry/skins.json\";\n\nexport const SCHEMA_ID =\n \"https://typecaast.com/schema/v1/typecaast.schema.json\";\n\nexport interface SkinInfo {\n id: string;\n name: string;\n themes: string[];\n summary: string;\n}\n\n/**\n * Slim built-in skin manifest derived from registry/skins.json — no React, so\n * the server stays Node-pure. (Capabilities aren't included to avoid importing\n * the skin components; `themes` + `summary` are enough to pick one.)\n */\nexport const SKINS: SkinInfo[] = skinsRegistry.skins\n .filter((s) => s.official)\n .map((s) => ({\n id: s.id,\n name: s.name,\n themes: s.themes,\n summary: s.summary,\n }));\n\n/** The JSON Schema, wrapped with the same $id/title as the site + artifact. */\nexport function jsonSchema(): Record<string, unknown> {\n return {\n $schema: \"http://json-schema.org/draft-07/schema#\",\n $id: SCHEMA_ID,\n title: \"Typecaast config\",\n ...configJsonSchema(),\n };\n}\n\nexport interface ValidationResult {\n valid: boolean;\n diagnostics: Diagnostic[];\n /** Set when the input wasn't valid JSON (couldn't even be parsed). */\n error?: string;\n}\n\n/** Validate a config given as an object or a JSON string. */\nexport function validate(config: unknown): ValidationResult {\n let value = config;\n if (typeof config === \"string\") {\n try {\n value = JSON.parse(config);\n } catch (e) {\n return {\n valid: false,\n diagnostics: [],\n error: `Invalid JSON: ${(e as Error).message}`,\n };\n }\n }\n const diagnostics = validateConfig(value);\n const valid = !diagnostics.some((d) => d.severity === \"error\");\n return { valid, diagnostics };\n}\n\n/** A minimal valid config to start from, for the given skin (default slack). */\nexport function scaffoldConfig(skinId = \"slack\"): Record<string, unknown> {\n return {\n $schema: SCHEMA_ID,\n version: 1,\n meta: { canvas: { width: 600, height: 760 }, skin: { id: skinId } },\n participants: [\n { id: \"me\", name: \"You\", isSelf: true },\n { id: \"sam\", name: \"Sam\" },\n ],\n timeline: [\n { type: \"message\", from: \"sam\", text: \"ship it?\" },\n { type: \"delay\", duration: 1500 },\n { type: \"message\", from: \"me\", text: \"shipping 🚀\" },\n ],\n };\n}\n","{\n \"$schema\": \"./skins.schema.json\",\n \"version\": 1,\n \"skins\": [\n {\n \"id\": \"slack\",\n \"name\": \"Slack\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"slack\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"Lato\"],\n \"summary\": \"Slack-style thread: app cards, reactions, replies.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"telegram\",\n \"name\": \"Telegram\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"telegram\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"Roboto\"],\n \"summary\": \"Telegram chat: bubbles with tails, reactions, bot inline buttons.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"claude-code\",\n \"name\": \"Claude Code (TUI)\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"claudeCode\",\n \"themes\": [\"dark\"],\n \"intendedFonts\": [\"JetBrains Mono\"],\n \"summary\": \"Terminal UI with streaming output and a spinner.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"imessage\",\n \"name\": \"iMessage (iOS)\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"imessage\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"SF Pro (→ Inter)\"],\n \"summary\": \"iPhone Messages: bubbles, tapbacks, status bar, keyboard.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"messages-macos\",\n \"name\": \"Messages (macOS)\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"messagesMacos\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"SF Pro (→ Inter)\"],\n \"summary\": \"Desktop Messages: window chrome + conversation sidebar.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"whatsapp\",\n \"name\": \"WhatsApp\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"whatsapp\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"Helvetica Neue (system)\"],\n \"summary\": \"WhatsApp: in-bubble timestamps + double-tick receipts.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"cursor\",\n \"name\": \"Cursor panel\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"cursor\",\n \"themes\": [\"dark\", \"light\"],\n \"intendedFonts\": [\"gg sans (system)\"],\n \"summary\": \"Cursor AI side-panel: prompts, responses, model chips.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"discord\",\n \"name\": \"Discord\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"discord\",\n \"themes\": [\"dark\"],\n \"intendedFonts\": [\"gg sans (→ Noto Sans)\"],\n \"summary\": \"Discord channel: role colors, grouped messages, reactions.\",\n \"verified\": \"2026-Q2\"\n }\n ]\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../package.json","../../../docs/authoring-configs.md","../../../docs/pacing.md","../../../docs/message-content.md","../src/docs.ts","../src/core.ts","../../../registry/skins.json"],"sourcesContent":["import { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\n\nasync function main(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err: unknown) => {\n console.error(\"typecaast-mcp failed to start:\", err);\n process.exit(1);\n});\n","import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport pkg from \"../package.json\";\nimport { DOCS } from \"./docs.js\";\nimport { SKINS, jsonSchema, scaffoldConfig, validate } from \"./core.js\";\n\n/** A tool result that returns a value as pretty-printed JSON text. */\nfunction jsonText(value: unknown): {\n content: { type: \"text\"; text: string }[];\n} {\n return {\n content: [{ type: \"text\", text: JSON.stringify(value, null, 2) }],\n };\n}\n\n/** Build the Typecaast MCP server with all tools + doc resources registered. */\nexport function createServer(): McpServer {\n const server = new McpServer({ name: \"typecaast\", version: pkg.version });\n\n server.registerTool(\n \"validate_config\",\n {\n title: \"Validate a Typecaast config\",\n description:\n \"Validate a Typecaast config (object or JSON string) against the schema and semantic checks. Returns { valid, diagnostics }. Run this after authoring or editing a config (e.g. one drafted from a screenshot).\",\n inputSchema: {\n config: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .describe(\"The config, as a JSON string or an object.\"),\n },\n },\n ({ config }) => jsonText(validate(config)),\n );\n\n server.registerTool(\n \"get_json_schema\",\n {\n title: \"Get the config JSON Schema\",\n description:\n 'Return the Typecaast config JSON Schema (draft-07). Reference it from a config via a \"$schema\" line, or use it to build a config skeleton.',\n inputSchema: {},\n },\n () => jsonText(jsonSchema()),\n );\n\n server.registerTool(\n \"list_skins\",\n {\n title: \"List built-in skins\",\n description:\n \"List the built-in skins (set one as meta.skin.id), with display name, supported themes, and a one-line summary.\",\n inputSchema: {},\n },\n () => jsonText(SKINS),\n );\n\n server.registerTool(\n \"scaffold_config\",\n {\n title: \"Scaffold a starter config\",\n description:\n 'Return a minimal valid Typecaast config for the given skin id (default \"slack\") — a starting point to edit.',\n inputSchema: {\n skinId: z\n .string()\n .optional()\n .describe('Skin id, e.g. \"slack\" (default), \"imessage\", \"discord\".'),\n },\n },\n ({ skinId }) => jsonText(scaffoldConfig(skinId)),\n );\n\n server.registerTool(\n \"get_docs\",\n {\n title: \"Read an authoring guide\",\n description: `Return a Typecaast authoring guide as markdown. Slugs: ${DOCS.map(\n (d) => d.slug,\n ).join(\", \")}. Omit \"slug\" to list them.`,\n inputSchema: {\n slug: z\n .string()\n .optional()\n .describe(\n 'Guide slug, e.g. \"pacing\". Omit to list available guides.',\n ),\n },\n },\n ({ slug }) => {\n if (!slug) {\n return {\n content: [\n {\n type: \"text\",\n text: DOCS.map((d) => `- ${d.slug}: ${d.title}`).join(\"\\n\"),\n },\n ],\n };\n }\n const doc = DOCS.find((d) => d.slug === slug);\n if (!doc) {\n return {\n content: [\n {\n type: \"text\",\n text: `Unknown guide \"${slug}\". Available: ${DOCS.map(\n (d) => d.slug,\n ).join(\", \")}.`,\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: \"text\", text: doc.text }] };\n },\n );\n\n for (const doc of DOCS) {\n server.registerResource(\n `docs-${doc.slug}`,\n `typecaast://docs/${doc.slug}`,\n {\n title: doc.title,\n description: `Typecaast authoring guide: ${doc.title}`,\n mimeType: \"text/markdown\",\n },\n (uri) => ({\n contents: [\n { uri: uri.href, mimeType: \"text/markdown\", text: doc.text },\n ],\n }),\n );\n }\n\n return server;\n}\n","{\n \"name\": \"@typecaast/mcp\",\n \"version\": \"0.1.1\",\n \"description\": \"Model Context Protocol server for authoring & validating Typecaast configs.\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/corywatilo/typecaast.git\",\n \"directory\": \"packages/mcp\"\n },\n \"type\": \"module\",\n \"bin\": {\n \"typecaast-mcp\": \"./dist/index.js\"\n },\n \"main\": \"./dist/index.js\",\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src\",\n \"test\": \"vitest run\",\n \"clean\": \"rm -rf dist .turbo\"\n },\n \"dependencies\": {\n \"@modelcontextprotocol/sdk\": \"^1.29.0\",\n \"@typecaast/schema\": \"workspace:*\",\n \"zod\": \"^4.4.3\"\n }\n}\n","# Authoring configs by hand\n\nA Typecaast simulation is **one JSON config**. The visual playground\n(<https://typecaast.com/playground>) is the easy way to build one, but the config\nis plain JSON — you can write or edit it by hand in any editor (or have an LLM do\nit). This is the reference for doing that without the playground.\n\n> **Timing lives in its own guide.** \"Why are my messages too fast / too close\n> together?\" and \"how do I get ~1–2s between messages?\" are answered in\n> [pacing.md](./pacing.md). Read that one for anything about gaps, delays, and\n> typing speed.\n\n## The shape\n\n```json\n{\n \"$schema\": \"https://typecaast.com/schema/v1/typecaast.schema.json\",\n \"version\": 1,\n \"meta\": {\n \"canvas\": { \"width\": 880, \"height\": 720 },\n \"skin\": { \"id\": \"slack\" }\n },\n \"participants\": [\n { \"id\": \"me\", \"name\": \"You\", \"isSelf\": true },\n { \"id\": \"sam\", \"name\": \"Sam\" }\n ],\n \"pacing\": { \"readingWpm\": 240 },\n \"timeline\": [\n { \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\" },\n { \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\" }\n ]\n}\n```\n\nThat's a complete, valid config. Five top-level keys:\n\n| Key | Required | What it is |\n| -------------- | -------- | --------------------------------------------------------------------- |\n| `version` | yes | Config schema version. Currently `1` (a literal). |\n| `meta` | yes | Canvas, skin, theme, fps — how it renders. |\n| `participants` | yes | Who is in the conversation. |\n| `pacing` | no | Global timing model. Omit for defaults. See [pacing.md](./pacing.md). |\n| `timeline` | yes | The ordered list of steps that play out. |\n\nAdd the `$schema` line at the top and your editor gives you autocomplete and\ninline validation against the live schema. It's optional but recommended.\n\n## `meta`\n\n```json\n\"meta\": {\n \"canvas\": { \"width\": 880, \"height\": 720 },\n \"skin\": { \"id\": \"slack\", \"options\": { \"channel\": \"#alerts\" } },\n \"fps\": 30,\n \"fit\": \"reflow\",\n \"theme\": \"auto\",\n \"seed\": 42,\n \"background\": \"transparent\",\n \"assets\": \"inline\",\n \"composer\": \"auto\",\n \"loop\": false\n}\n```\n\n| Field | Default | Notes |\n| ------------ | --------------- | ----------------------------------------------------------------------------------------------------- |\n| `canvas` | — | `{ width, height }` in px. Required. The authoring reference size / the video frame. |\n| `skin` | — | `{ id, options? }`. Required. `id` picks the skin (see [Skins](#skins)); `options` are skin-specific. |\n| `fps` | `30` | Frames per second for video export. |\n| `fit` | `\"reflow\"` | `reflow` (re-wrap to container), `scale` (CSS-scale the canvas), or `fixed` (pin to px). |\n| `theme` | `\"auto\"` | `light`, `dark`, or `auto` (follows the host page; video resolves `auto` → `light`). |\n| `seed` | `42` | Seeds all deterministic jitter — same seed ⇒ identical timings. |\n| `background` | `\"transparent\"` | `\"transparent\"` or any CSS color. |\n| `assets` | `\"inline\"` | `inline` (embed images as data URLs) or `url` (reference hosted images). |\n| `composer` | `\"auto\"` | Reply box: `auto` (shown only while typing/sending), `always`, or `never`. |\n| `loop` | `false` | Auto-replay at the end (unless the `<Typecaast>` consumer passes an explicit `loop`). |\n\n## `participants`\n\nAn array of speakers. Reference them everywhere by `id`.\n\n```json\n\"participants\": [\n { \"id\": \"me\", \"name\": \"You\", \"isSelf\": true },\n { \"id\": \"sam\", \"name\": \"Sam\", \"avatar\": \"https://…/sam.png\", \"color\": \"#5b3a8e\" },\n { \"id\": \"bot\", \"name\": \"PostHog\", \"kind\": \"app\" }\n]\n```\n\n| Field | Required | Notes |\n| -------- | -------- | -------------------------------------------------------------------------------- |\n| `id` | yes | Stable id used by `from` / `target` / `by` in the timeline. |\n| `name` | yes | Display name. |\n| `avatar` | no | Data URL, or a hosted URL (per `meta.assets`). |\n| `color` | no | Accent color (CSS) some skins use for the author. |\n| `isSelf` | no | Marks the viewer — rendered on the \"self\" side and as the composer's author. |\n| `kind` | no | `\"person\"` (default) or `\"app\"` (a bot/integration; skins render it distinctly). |\n\nSet `isSelf: true` on exactly one participant if you use the composer\n(`composerType` / `send`).\n\n## `timeline` — the steps\n\nThe timeline is an ordered array. Each step has a `type` and its own fields. Two\nfields are shared by **every** step:\n\n- `id` — optional. Give a step an id so a later `reaction`, `edit`, `delete`, or\n `readReceipt` can target it.\n- `instant` — optional `true` to reveal with no animation and **no computed\n pacing gap** (handy when you want to control spacing yourself — see\n [pacing.md](./pacing.md)).\n\nSteps that target an earlier message take a `target`: either a message's `id` or\nthe literal `\"$prev\"` (the most-recent message). `target` defaults to `\"$prev\"`.\n\n### The step types\n\n| `type` | Purpose |\n| -------------- | ------------------------------------------------------------------------ |\n| `message` | An incoming message (optionally preceded by a typing indicator). |\n| `typing` | A standalone typing indicator (no message need follow). |\n| `composerType` | The `isSelf` participant typing into the composer, char by char. |\n| `send` | Commit the composer's current text to the thread. |\n| `reaction` | An emoji reaction landing on a target message. |\n| `edit` | Replace a previously sent message's body. |\n| `delete` | Remove a previously sent message. |\n| `readReceipt` | A read-receipt indicator (skins that support it). |\n| `system` | A system / notice line (e.g. \"Sam joined #alerts\") — not a chat message. |\n| `delay` | An explicit pause on the timeline. See [pacing.md](./pacing.md). |\n\n### `message`\n\n```json\n{ \"type\": \"message\", \"from\": \"sam\", \"text\": \"on it\" }\n```\n\n| Field | Notes |\n| --------- | ----------------------------------------------------------------------------------------------------- |\n| `from` | Participant id. Required. |\n| `text` | Message body. Supports Slack-style mrkdwn — see [message-content.md](./message-content.md). |\n| `images` | `[{ src, alt?, width?, height? }]` — in-message images. |\n| `content` | Explicit Block Kit content nodes (wins over `text`) — see [message-content.md](./message-content.md). |\n| `typing` | Show a typing indicator first: `true`, or `{ \"showTypingFor\": 1800 }` (ms). |\n\nModel an app \"card\" as a `message` from an `app` participant carrying `content`\n(header/section/context/actions/…), **not** a `system` step.\n\n### `typing`\n\n```json\n{ \"type\": \"typing\", \"from\": \"sam\", \"showTypingFor\": 1500 }\n```\n\n`showTypingFor` is in **milliseconds** (defaults to ~1500ms if omitted).\n\n### `composerType` and `send`\n\n```json\n{ \"type\": \"composerType\", \"from\": \"me\", \"text\": \"let me check…\" },\n{ \"type\": \"send\" }\n```\n\n`composerType` types into the composer (the `from` should be your `isSelf`\nparticipant); `send` then posts it. `send.from` is optional — it inherits the\ncomposer's author. `composerType.typingDuration` (ms) overrides the computed\ntyping time.\n\n### `reaction`\n\n```json\n{ \"type\": \"reaction\", \"target\": \"$prev\", \"emoji\": \"🚀\", \"delay\": 800 }\n```\n\n| Field | Notes |\n| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `emoji` | The emoji. Required. |\n| `target` | Message id or `\"$prev\"` (default). |\n| `from` | Optional reactor. |\n| `shortcode` | Optional shortcode without colons (e.g. `\"rocket\"`) — shown in some skins' tooltips. |\n| `delay` | Gap (ms) from when the target appears before the reaction lands. **This `delay` is a field on the reaction, not the `delay` step type** — don't confuse them (see [pacing.md](./pacing.md)). |\n\n### `edit` and `delete`\n\n```json\n{ \"type\": \"edit\", \"target\": \"m2\", \"text\": \"fixed typo\" },\n{ \"type\": \"delete\", \"target\": \"m3\" }\n```\n\nBoth default `target` to `\"$prev\"`. `edit` takes the same body fields as\n`message` (`text` / `images` / `content`).\n\n### `readReceipt`\n\n```json\n{ \"type\": \"readReceipt\", \"by\": \"sam\", \"target\": \"m2\" }\n```\n\n`by` (who read it) and `target` (up to which message) are both optional.\n\n### `system`\n\n```json\n{ \"type\": \"system\", \"text\": \"Sam joined #alerts\" }\n```\n\nA centered notice / tool-output line, rendered distinctly per skin. Takes the\nsame body fields as `message`.\n\n### `delay`\n\n```json\n{ \"type\": \"delay\", \"duration\": 1500 }\n```\n\nAn explicit pause. `duration` is in **milliseconds** and is **required**. This is\nthe most predictable way to space messages out — see [pacing.md](./pacing.md).\n\n## Skins\n\n`meta.skin.id` selects the skin. The conversation content is the same regardless\nof skin; the skin decides how it looks. Built-in ids:\n\n`slack` · `telegram` · `claude-code` · `imessage` · `messages-macos` ·\n`whatsapp` · `cursor` · `discord`\n\nSome steps aren't rendered by every skin (e.g. a terminal skin has no\nreactions). The skin's capabilities decide; unsupported steps are dropped. Pick a\nskin that supports the steps you use.\n\n## Validate it\n\nAlways validate after editing by hand:\n\n- **CLI:** `npx @typecaast/cli validate my-config.json` (exit 0 = OK; prints\n `E_*` errors / `W_*` warnings with locations and hints — see\n [errors.md](./errors.md)).\n- **MCP:** if you've added the [`@typecaast/mcp`](../packages/mcp/README.md)\n server to your editor, call its `validate_config` tool. This is the easy loop\n when drafting a config (e.g. from a screenshot) in your own project.\n\n## See also\n\n- [pacing.md](./pacing.md) — timing, delays, and the \"1–2s between messages\" recipes.\n- [message-content.md](./message-content.md) — message bodies: mrkdwn + Block Kit.\n- The JSON Schema: <https://typecaast.com/schema/v1/typecaast.schema.json>.\n- Machine-readable index for LLMs: <https://typecaast.com/llms.txt>.\n </content>\n","# Pacing & timing\n\nTypecaast **auto-paces** a conversation so you don't have to time every step by\nhand: it derives the gap before each message from how long the previous message\ntakes to \"read\", and how long typing takes from the text length. This is why a\nconfig with no timing fields still plays at a human rhythm.\n\nIt's also the #1 source of \"why is this too fast / why are my messages bunched\nup?\" — including the common ask, **\"I want ~1–2s between messages.\"** This guide\nexplains the model and the levers. (For the full step reference, see\n[authoring-configs.md](./authoring-configs.md).)\n\n## The model\n\nTiming is controlled by the optional top-level `pacing` object. Omit it and you\nget the full default model:\n\n```json\n\"pacing\": {\n \"readingWpm\": 240,\n \"typingCps\": 14,\n \"humanize\": 0.15,\n \"startDelayMs\": 400\n}\n```\n\n| Field | Default | Unit | What it controls |\n| -------------- | ------- | --------- | ------------------------------------------------------------------------------- |\n| `readingWpm` | `240` | words/min | The gap **before an incoming message** ≈ the time to read the **previous** one. |\n| `typingCps` | `14` | chars/sec | Composer typing speed, and how long a typing indicator shows before a message. |\n| `humanize` | `0.15` | fraction | ±15% **seeded** jitter on computed gaps/durations so it doesn't feel robotic. |\n| `startDelayMs` | `400` | ms | Delay before the very first event. |\n\nEvery value is overridable per step in the timeline; per-step values win over the\ncomputed ones.\n\n### How the gap is computed\n\nThe key thing to understand: **the gap before a message scales with the length of\nthe _previous_ message**, because it models the other person reading it before\nreplying. Roughly:\n\n```\nwords ≈ characters / 5\ngap(ms) ≈ (words / readingWpm) × 60000 (then ±humanize jitter)\n```\n\nSo at the default 240 wpm:\n\n- A short line like `\"ship it?\"` (~1.6 words) → gap ≈ **400ms**.\n- A sentence of ~40 characters (~8 words) → gap ≈ **2000ms**.\n\n**This is the gotcha:** with short messages, the default gaps are short (a few\nhundred ms), so the conversation feels snappy/bunched. Lowering `readingWpm` or\nadding explicit `delay` steps is how you stretch it out.\n\n### Other automatic durations\n\n- Typing indicator before a message (`message.typing: true`) lasts about as long\n as it would take to type that message at `typingCps`. A standalone `typing`\n step with no `showTypingFor` defaults to ~1500ms.\n- A `reaction` with no `delay` lands ~1000ms after its target appears.\n- `instant: true` on a step skips the reveal animation **and** the computed gap.\n\n### Determinism\n\nAll jitter is seeded from `meta.seed` (default `42`). The same `(config, seed)`\nalways produces identical timings — there is no `Math.random()`. Change `seed`\nto get a different (but still repeatable) jitter pattern.\n\n## \"I want ~1–2 seconds between messages\"\n\nFour ways, most predictable first.\n\n### 1. Insert a `delay` step (recommended — exact and explicit)\n\nA `delay` step is a fixed pause whose `duration` is in **milliseconds**:\n\n```json\n\"timeline\": [\n { \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\" },\n { \"type\": \"delay\", \"duration\": 1500 },\n { \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\" }\n]\n```\n\nThat guarantees ~1.5s between those two messages regardless of their length. Use\nit between each pair of messages you want spaced out. It's the most reliable knob\nbecause it doesn't depend on text length.\n\n> **Don't confuse the two `delay`s.** The `delay` **step type** (`{ \"type\":\n\"delay\", \"duration\": … }`) is a standalone pause. A `reaction` step also has a\n> `delay` **field** — that one is the gap before the _reaction_ lands on its\n> target. Different things.\n\n### 2. Lower `readingWpm` globally\n\nSlowing the \"reading speed\" lengthens every auto-computed gap at once:\n\n```json\n\"pacing\": { \"readingWpm\": 80 }\n```\n\nAt ~80 wpm a short `\"ship it?\"` reply gaps ~1.2s; longer messages gap\nproportionally more. Good for a uniformly slower feel. **Caveat:** because the\ngap still scales with text length, very short messages stay shorter than very\nlong ones — if you need a precise gap, use a `delay` step (#1).\n\nRough guide for a short one-liner: ~120 wpm ≈ 0.8s, ~80 wpm ≈ 1.2s, ~60 wpm ≈\n1.6s.\n\n### 3. `instant` + `delay` for full manual control\n\nDrop the automatic gaps entirely and place every pause yourself:\n\n```json\n{ \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\", \"instant\": true },\n{ \"type\": \"delay\", \"duration\": 1200 },\n{ \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\", \"instant\": true }\n```\n\n`instant` removes a step's computed gap and reveal animation, so the only spacing\nis your `delay` steps. Use this when you want a precisely choreographed timeline.\n\n### 4. Pad with a typing indicator\n\nA typing indicator before a message both adds time and reads naturally:\n\n```json\n{ \"type\": \"typing\", \"from\": \"sam\", \"showTypingFor\": 1500 },\n{ \"type\": \"message\", \"from\": \"sam\", \"text\": \"shipping 🚀\" }\n```\n\nor inline on the message:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"sam\",\n \"text\": \"shipping 🚀\",\n \"typing\": { \"showTypingFor\": 1500 }\n}\n```\n\n`showTypingFor` is in **milliseconds**.\n\n## Worked example\n\nTwo short messages with a deliberate 1.5s gap:\n\n```json\n{\n \"$schema\": \"https://typecaast.com/schema/v1/typecaast.schema.json\",\n \"version\": 1,\n \"meta\": {\n \"canvas\": { \"width\": 600, \"height\": 400 },\n \"skin\": { \"id\": \"imessage\" }\n },\n \"participants\": [\n { \"id\": \"me\", \"name\": \"You\", \"isSelf\": true },\n { \"id\": \"sam\", \"name\": \"Sam\" }\n ],\n \"timeline\": [\n { \"type\": \"message\", \"from\": \"sam\", \"text\": \"ship it?\" },\n { \"type\": \"delay\", \"duration\": 1500 },\n { \"type\": \"message\", \"from\": \"me\", \"text\": \"shipping 🚀\" }\n ]\n}\n```\n\nTimeline: first message reveals at ~`startDelayMs` (400ms); the `delay` holds\n1500ms; the reply reveals ~1.5s later. Without the `delay`, that gap would be the\ndefault ~400ms reading time of `\"ship it?\"`.\n\n## See also\n\n- [authoring-configs.md](./authoring-configs.md) — the full config + step reference.\n- [message-content.md](./message-content.md) — message bodies (mrkdwn + Block Kit).\n </content>\n","# Authoring message content\n\nA message's body is authored in its `content`. The simplest form is a plain\n`text` string; richer messages (especially **app messages**) use **Block Kit**\ncontent nodes — the same primitives a real Slack app emits.\n\n> The easiest way to author this is the [playground](https://typecaast.com/playground)\n> — it has a typed block editor. This doc is the reference for the JSON it\n> produces. Point your editor at the bundled JSON Schema\n> (`\"$schema\": \".../typecaast.schema.json\"`) for autocomplete.\n\n## Plain text + mrkdwn\n\nMost messages just need `text`. It's parsed for inline marks:\n\n```json\n{ \"type\": \"message\", \"from\": \"cory\", \"text\": \"shipped *it* — see `deploy.ts`\" }\n```\n\n| Syntax | Renders | Inline node |\n| ---------------------- | ----------------------------------------------------- | ------------ |\n| `*bold*` | **bold** | `bold` |\n| `_italic_` | _italic_ | `italic` |\n| `~strike~` | ~~strike~~ | `strike` |\n| `` `code` `` | `code` | `code` |\n| `https://…` | a link | `link` |\n| `@name` | a mention pill | `mention` |\n| `<@id>` | a mention resolved to that participant's display name | `mention` |\n| emoji (`🟠`, `:tada:`) | the glyph | left in text |\n\n`<@id>` is the robust way to mention someone whose display name has spaces\n(`<@joe>` → \"@Joe Saunderson\"); it resolves against `participants` at compile.\n\nIn-message images use the `images` sugar:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"cory\",\n \"text\": \"here's the toast:\",\n \"images\": [{ \"src\": \"./toast.png\", \"alt\": \"error toast\", \"width\": 320 }]\n}\n```\n\n## App messages (Block Kit)\n\nAn **app message** is a normal `message` from a participant with\n`\"kind\": \"app\"` — the sender drives the \"APP\" badge — whose `content` is a list\nof Block Kit nodes. **Slack renders the full set; other skins show the text and\nskip the rest** (a Slack-targeted config degrades gracefully elsewhere).\n\nBlocks carry a `text` string (parsed like above) or pre-resolved `spans`:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"posthog\",\n \"content\": [\n { \"type\": \"header\", \"text\": \"perf(inbox): Fix 26s admin inbox load\" },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"text\",\n \"text\": \"🟠 *P2* · Session replay · rvenvy/rvenvy-ai\"\n }\n ]\n },\n { \"type\": \"section\", \"text\": \"Sales reps hit prolonged loading spinners…\" },\n {\n \"type\": \"context\",\n \"elements\": [\n {\n \"type\": \"text\",\n \"text\": \"2 signals · Suggested reviewers: <@joe> <@cory>\"\n }\n ]\n },\n { \"type\": \"divider\" },\n {\n \"type\": \"actions\",\n \"elements\": [\n { \"type\": \"button\", \"label\": \"Review PR\", \"href\": \"https://…\" },\n { \"type\": \"button\", \"label\": \"Open in PostHog\" },\n { \"type\": \"button\", \"label\": \"Dismiss\", \"style\": \"danger\" }\n ]\n }\n ]\n}\n```\n\n### Block types\n\n| Node | Shape | Notes |\n| ------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `header` | `{ text }` | Large bold heading (plain text). |\n| `section` | `{ text \\| spans, accessory?, fields? }` | A paragraph. `accessory` is a `button` or `image` to its right; `fields` is a 2-column grid of `{ text \\| spans }`. |\n| `context` | `{ elements: [...] }` | Small muted row; each element is `{ type:\"text\", text }` or `{ type:\"image\", src, alt? }`. |\n| `divider` | `{}` | A horizontal rule. |\n| `actions` | `{ elements: [button…] }` | A row of buttons. |\n| `image` | `{ src, alt?, width?, height? }` | A standalone image. |\n| `codeblock` | `{ text, lang? }` | A monospaced preformatted box (Slack's fenced ` ``` ` block). `text` is **literal** — whitespace and newlines are kept and it is **not** parsed for marks. |\n| `attachment` | `{ color?, content: [...] }` | Nested blocks behind a colored left bar (the legacy \"attachment\" look). `color` is any CSS color. |\n\n### Buttons\n\nA `button` element appears in an `actions` block or as a `section` accessory:\n\n```json\n{\n \"type\": \"button\",\n \"label\": \"View PR\",\n \"href\": \"https://…\",\n \"style\": \"primary\"\n}\n```\n\n- `style`: `\"primary\"` (filled green), `\"danger\"` (red outline), or omit for the\n default outlined button.\n- `href`: with one, the button is a real link (opens in a new tab); without one\n it renders inert.\n\n### Code blocks\n\nFor a table, log, or snippet, use a `codeblock` — Slack's fenced ` ``` `\nblock, a monospaced box that preserves whitespace and newlines verbatim. Unlike\nthe inline `` `code` `` mark (a single-line pill inside a sentence), a `codeblock`\nis its own block and its `text` is **literal**: `*`, `~`, `<@id>` etc. are shown\nas typed, never parsed. Author the canvas wide enough that the widest line fits on\none line, then scale the embed down on the page (see below).\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"posthog\",\n \"content\": [\n {\n \"type\": \"codeblock\",\n \"text\": \"Metric A B\\nReturned 64% 96%\\nActive weeks 2.9 7.2\"\n }\n ]\n}\n```\n\nA wide monospace block won't reflow, so give the instance a wide\n`meta.canvas.width` and set `\"fit\": \"scale\"`; the host wraps it in a sized\ncontainer and the whole instance scales down to fit (the canvas keeps its internal\npixel width, so the table stays one line per row).\n\n### The colored bar (attachment)\n\nTo get the legacy left-bar card look, wrap blocks in an `attachment`:\n\n```json\n{\n \"type\": \"message\",\n \"from\": \"posthog\",\n \"content\": [\n {\n \"type\": \"attachment\",\n \"color\": \"#36a64f\",\n \"content\": [\n { \"type\": \"section\", \"text\": \"Pull request opened.\" },\n {\n \"type\": \"actions\",\n \"elements\": [\n { \"type\": \"button\", \"label\": \"View PR\", \"style\": \"primary\" }\n ]\n }\n ]\n }\n ]\n}\n```\n\n## System / notice lines\n\nThe `system` step is **not** an app card — it's a non-message notice line\n(\"X joined #channel\", an agent's tool-output line), rendered distinctly per skin\n(centered/muted in Slack, iMessage, WhatsApp; an agent line in Cursor/Claude\nCode). It carries only `from?` + text/`content`:\n\n```json\n{ \"type\": \"system\", \"from\": \"posthog\", \"text\": \"Cory joined #signals\" }\n```\n\n## The reply box\n\nThe composer (\"reply box\") visibility is set by `meta.composer`: `\"auto\"`\n(default — shown only while someone is composing), `\"always\"` (always shown,\nidle when nobody is typing — no `isSelf` participant required), or `\"never\"`.\n","// The authoring guides, inlined as strings at build time (esbuild `text`\n// loader). Source of truth is the repo's /docs — these are the same files the\n// site serves. Kept out of core.ts so the unit tests don't need a .md loader.\nimport authoringConfigs from \"../../../docs/authoring-configs.md\";\nimport pacing from \"../../../docs/pacing.md\";\nimport messageContent from \"../../../docs/message-content.md\";\n\nexport interface DocEntry {\n slug: string;\n title: string;\n text: string;\n}\n\nexport const DOCS: DocEntry[] = [\n {\n slug: \"authoring-configs\",\n title: \"Authoring configs by hand\",\n text: authoringConfigs,\n },\n { slug: \"pacing\", title: \"Pacing & timing\", text: pacing },\n {\n slug: \"message-content\",\n title: \"Message content\",\n text: messageContent,\n },\n];\n","// Pure, dependency-light helpers — no MCP SDK, no .md imports — so they're\n// trivially unit-testable. The SDK wiring lives in server.ts.\nimport {\n validateConfig,\n configJsonSchema,\n type Diagnostic,\n} from \"@typecaast/schema\";\nimport skinsRegistry from \"../../../registry/skins.json\";\n\nexport const SCHEMA_ID =\n \"https://typecaast.com/schema/v1/typecaast.schema.json\";\n\nexport interface SkinInfo {\n id: string;\n name: string;\n themes: string[];\n summary: string;\n}\n\n/**\n * Slim built-in skin manifest derived from registry/skins.json — no React, so\n * the server stays Node-pure. (Capabilities aren't included to avoid importing\n * the skin components; `themes` + `summary` are enough to pick one.)\n */\nexport const SKINS: SkinInfo[] = skinsRegistry.skins\n .filter((s) => s.official)\n .map((s) => ({\n id: s.id,\n name: s.name,\n themes: s.themes,\n summary: s.summary,\n }));\n\n/** The JSON Schema, wrapped with the same $id/title as the site + artifact. */\nexport function jsonSchema(): Record<string, unknown> {\n return {\n $schema: \"http://json-schema.org/draft-07/schema#\",\n $id: SCHEMA_ID,\n title: \"Typecaast config\",\n ...configJsonSchema(),\n };\n}\n\nexport interface ValidationResult {\n valid: boolean;\n diagnostics: Diagnostic[];\n /** Set when the input wasn't valid JSON (couldn't even be parsed). */\n error?: string;\n}\n\n/** Validate a config given as an object or a JSON string. */\nexport function validate(config: unknown): ValidationResult {\n let value = config;\n if (typeof config === \"string\") {\n try {\n value = JSON.parse(config);\n } catch (e) {\n return {\n valid: false,\n diagnostics: [],\n error: `Invalid JSON: ${(e as Error).message}`,\n };\n }\n }\n const diagnostics = validateConfig(value);\n const valid = !diagnostics.some((d) => d.severity === \"error\");\n return { valid, diagnostics };\n}\n\n/** A minimal valid config to start from, for the given skin (default slack). */\nexport function scaffoldConfig(skinId = \"slack\"): Record<string, unknown> {\n return {\n $schema: SCHEMA_ID,\n version: 1,\n meta: { canvas: { width: 600, height: 760 }, skin: { id: skinId } },\n participants: [\n { id: \"me\", name: \"You\", isSelf: true },\n { id: \"sam\", name: \"Sam\" },\n ],\n timeline: [\n { type: \"message\", from: \"sam\", text: \"ship it?\" },\n { type: \"delay\", duration: 1500 },\n { type: \"message\", from: \"me\", text: \"shipping 🚀\" },\n ],\n };\n}\n","{\n \"$schema\": \"./skins.schema.json\",\n \"version\": 1,\n \"skins\": [\n {\n \"id\": \"slack\",\n \"name\": \"Slack\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"slack\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"Lato\"],\n \"summary\": \"Slack-style thread: app cards, reactions, replies.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"telegram\",\n \"name\": \"Telegram\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"telegram\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"Roboto\"],\n \"summary\": \"Telegram chat: bubbles with tails, reactions, bot inline buttons.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"claude-code\",\n \"name\": \"Claude Code (TUI)\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"claudeCode\",\n \"themes\": [\"dark\"],\n \"intendedFonts\": [\"JetBrains Mono\"],\n \"summary\": \"Terminal UI with streaming output and a spinner.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"imessage\",\n \"name\": \"iMessage (iOS)\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"imessage\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"SF Pro (→ Inter)\"],\n \"summary\": \"iPhone Messages: bubbles, tapbacks, status bar, keyboard.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"messages-macos\",\n \"name\": \"Messages (macOS)\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"messagesMacos\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"SF Pro (→ Inter)\"],\n \"summary\": \"Desktop Messages: window chrome + conversation sidebar.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"whatsapp\",\n \"name\": \"WhatsApp\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"whatsapp\",\n \"themes\": [\"light\", \"dark\"],\n \"intendedFonts\": [\"Helvetica Neue (system)\"],\n \"summary\": \"WhatsApp: in-bubble timestamps + double-tick receipts.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"cursor\",\n \"name\": \"Cursor panel\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"cursor\",\n \"themes\": [\"dark\", \"light\"],\n \"intendedFonts\": [\"gg sans (system)\"],\n \"summary\": \"Cursor AI side-panel: prompts, responses, model chips.\",\n \"verified\": \"2026-Q2\"\n },\n {\n \"id\": \"discord\",\n \"name\": \"Discord\",\n \"official\": true,\n \"author\": \"Typecaast\",\n \"package\": \"@typecaast/skins\",\n \"export\": \"discord\",\n \"themes\": [\"dark\"],\n \"intendedFonts\": [\"gg sans (→ Noto Sans)\"],\n \"summary\": \"Discord channel: role colors, grouped messages, reactions.\",\n \"verified\": \"2026-Q2\"\n }\n ]\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typecaast/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Model Context Protocol server for authoring & validating Typecaast configs.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
+
"zod": "^4.4.3",
|
|
22
|
+
"@typecaast/schema": "0.4.1"
|
|
23
|
+
},
|
|
19
24
|
"scripts": {
|
|
20
25
|
"build": "tsup",
|
|
21
26
|
"dev": "tsup --watch",
|
|
@@ -23,10 +28,5 @@
|
|
|
23
28
|
"lint": "eslint src",
|
|
24
29
|
"test": "vitest run",
|
|
25
30
|
"clean": "rm -rf dist .turbo"
|
|
26
|
-
},
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@typecaast/schema": "workspace:*",
|
|
30
|
-
"zod": "^4.4.3"
|
|
31
31
|
}
|
|
32
|
-
}
|
|
32
|
+
}
|