trykeyline 0.0.0-stage → 0.1.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 (96) hide show
  1. package/.claude-plugin/marketplace.json +18 -0
  2. package/LICENSE +37 -0
  3. package/README.md +117 -2
  4. package/bin/keyline.mjs +280 -0
  5. package/package.json +38 -4
  6. package/plugins/keyline/.claude-plugin/plugin.json +14 -0
  7. package/plugins/keyline/skills/keyline/SKILL.md +57 -0
  8. package/plugins/keyline/skills/keyline/commands/audit.md +25 -0
  9. package/plugins/keyline/skills/keyline/commands/color.md +7 -0
  10. package/plugins/keyline/skills/keyline/commands/critique.md +43 -0
  11. package/plugins/keyline/skills/keyline/commands/depth.md +7 -0
  12. package/plugins/keyline/skills/keyline/commands/init.md +15 -0
  13. package/plugins/keyline/skills/keyline/commands/layout.md +7 -0
  14. package/plugins/keyline/skills/keyline/commands/motion.md +7 -0
  15. package/plugins/keyline/skills/keyline/commands/patterns.md +7 -0
  16. package/plugins/keyline/skills/keyline/commands/polish.md +19 -0
  17. package/plugins/keyline/skills/keyline/commands/refine.md +29 -0
  18. package/plugins/keyline/skills/keyline/commands/sync.md +17 -0
  19. package/plugins/keyline/skills/keyline/commands/type.md +7 -0
  20. package/plugins/keyline/skills/keyline/domains/color/deriving-a-ramp.md +144 -0
  21. package/plugins/keyline/skills/keyline/domains/color/guide.md +243 -0
  22. package/plugins/keyline/skills/keyline/domains/color/status-and-exceptions.md +174 -0
  23. package/plugins/keyline/skills/keyline/domains/color/theming-mechanics.md +121 -0
  24. package/plugins/keyline/skills/keyline/domains/depth/elevation-recipes.md +191 -0
  25. package/plugins/keyline/skills/keyline/domains/depth/fallbacks-and-performance.md +132 -0
  26. package/plugins/keyline/skills/keyline/domains/depth/guide.md +255 -0
  27. package/plugins/keyline/skills/keyline/domains/depth/translucent-materials.md +159 -0
  28. package/plugins/keyline/skills/keyline/domains/layout/forks-gating-and-disclosures.md +201 -0
  29. package/plugins/keyline/skills/keyline/domains/layout/guide.md +284 -0
  30. package/plugins/keyline/skills/keyline/domains/layout/measure-and-rhythm.md +193 -0
  31. package/plugins/keyline/skills/keyline/domains/layout/radius-lines-and-layers.md +122 -0
  32. package/plugins/keyline/skills/keyline/domains/motion/guards.md +162 -0
  33. package/plugins/keyline/skills/keyline/domains/motion/guide.md +234 -0
  34. package/plugins/keyline/skills/keyline/domains/motion/ladder-and-curves.md +139 -0
  35. package/plugins/keyline/skills/keyline/domains/motion/patterns.md +196 -0
  36. package/plugins/keyline/skills/keyline/domains/motion/performance.md +121 -0
  37. package/plugins/keyline/skills/keyline/domains/patterns/actions-and-forms.md +204 -0
  38. package/plugins/keyline/skills/keyline/domains/patterns/agents-and-live-states.md +111 -0
  39. package/plugins/keyline/skills/keyline/domains/patterns/collections-and-dashboards.md +131 -0
  40. package/plugins/keyline/skills/keyline/domains/patterns/copy.md +123 -0
  41. package/plugins/keyline/skills/keyline/domains/patterns/guide.md +216 -0
  42. package/plugins/keyline/skills/keyline/domains/patterns/iteration.md +78 -0
  43. package/plugins/keyline/skills/keyline/domains/type/display-tier.md +108 -0
  44. package/plugins/keyline/skills/keyline/domains/type/guide.md +251 -0
  45. package/plugins/keyline/skills/keyline/domains/type/ladders.md +169 -0
  46. package/plugins/keyline/skills/keyline/domains/type/numerals-and-loading.md +107 -0
  47. package/plugins/keyline/skills/keyline/domains/type/prose-and-emphasis.md +125 -0
  48. package/plugins/keyline/skills/keyline/domains/type/type-audit.md +90 -0
  49. package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +319 -0
  50. package/plugins/keyline/skills/keyline/glass/color/assets/theme.ts +119 -0
  51. package/plugins/keyline/skills/keyline/glass/color/guide.md +222 -0
  52. package/plugins/keyline/skills/keyline/glass/color/intent-ramp.md +136 -0
  53. package/plugins/keyline/skills/keyline/glass/color/theming.md +112 -0
  54. package/plugins/keyline/skills/keyline/glass/depth/assets/GlassPanel.tsx +171 -0
  55. package/plugins/keyline/skills/keyline/glass/depth/assets/glass.css +322 -0
  56. package/plugins/keyline/skills/keyline/glass/depth/blur-and-tint.md +50 -0
  57. package/plugins/keyline/skills/keyline/glass/depth/depth-cues.md +87 -0
  58. package/plugins/keyline/skills/keyline/glass/depth/guide.md +216 -0
  59. package/plugins/keyline/skills/keyline/glass/depth/performance-and-fallbacks.md +37 -0
  60. package/plugins/keyline/skills/keyline/glass/layout/adaptive-presentations.md +231 -0
  61. package/plugins/keyline/skills/keyline/glass/layout/assets/SectionNav.tsx +244 -0
  62. package/plugins/keyline/skills/keyline/glass/layout/assets/layout-tokens.css +199 -0
  63. package/plugins/keyline/skills/keyline/glass/layout/guide.md +258 -0
  64. package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +134 -0
  65. package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +102 -0
  66. package/plugins/keyline/skills/keyline/glass/motion/assets/DropMenu.tsx +106 -0
  67. package/plugins/keyline/skills/keyline/glass/motion/assets/motion-presets.ts +210 -0
  68. package/plugins/keyline/skills/keyline/glass/motion/assets/motion-tokens.css +176 -0
  69. package/plugins/keyline/skills/keyline/glass/motion/assets/useReveal.ts +65 -0
  70. package/plugins/keyline/skills/keyline/glass/motion/assets/useScrollTick.ts +183 -0
  71. package/plugins/keyline/skills/keyline/glass/motion/easing-and-duration.md +108 -0
  72. package/plugins/keyline/skills/keyline/glass/motion/guide.md +286 -0
  73. package/plugins/keyline/skills/keyline/glass/motion/reduced-motion.md +81 -0
  74. package/plugins/keyline/skills/keyline/glass/motion/scroll-performance.md +58 -0
  75. package/plugins/keyline/skills/keyline/glass/motion/shared-element.md +161 -0
  76. package/plugins/keyline/skills/keyline/glass/motion/sound.md +192 -0
  77. package/plugins/keyline/skills/keyline/glass/motion/springs.md +51 -0
  78. package/plugins/keyline/skills/keyline/glass/type/assets/prose.tsx +268 -0
  79. package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +414 -0
  80. package/plugins/keyline/skills/keyline/glass/type/guide.md +222 -0
  81. package/plugins/keyline/skills/keyline/glass/type/long-form-prose.md +151 -0
  82. package/plugins/keyline/skills/keyline/glass/type/scale.md +69 -0
  83. package/plugins/keyline/skills/keyline/glass/type/weight-and-tracking.md +61 -0
  84. package/plugins/keyline/skills/keyline/scripts/check/css.mjs +112 -0
  85. package/plugins/keyline/skills/keyline/scripts/check/design.mjs +67 -0
  86. package/plugins/keyline/skills/keyline/scripts/check/files.mjs +44 -0
  87. package/plugins/keyline/skills/keyline/scripts/check/markup.mjs +149 -0
  88. package/plugins/keyline/skills/keyline/scripts/check/rules.mjs +584 -0
  89. package/plugins/keyline/skills/keyline/scripts/check.mjs +294 -0
  90. package/plugins/keyline/skills/keyline/scripts/contrast.mjs +153 -0
  91. package/plugins/keyline/skills/keyline/scripts/scan.mjs +340 -0
  92. package/plugins/keyline/skills/keyline/system/design-md.md +136 -0
  93. package/plugins/keyline/skills/keyline/system/detection.md +53 -0
  94. package/plugins/keyline/skills/keyline/system/guide.md +101 -0
  95. package/plugins/keyline/skills/keyline/system/presets.md +58 -0
  96. package/plugins/keyline/skills/keyline/system/roles.md +150 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "keyline",
3
+ "owner": {
4
+ "name": "Mihir Modi",
5
+ "url": "https://github.com/Mihirmodi27"
6
+ },
7
+ "metadata": {
8
+ "description": "Keyline: interface design laws for coding agents, applied in your project's own tokens.",
9
+ "version": "0.1.0"
10
+ },
11
+ "plugins": [
12
+ {
13
+ "name": "keyline",
14
+ "source": "./plugins/keyline",
15
+ "description": "One skill with eleven commands — init, sync, critique, audit, type, color, layout, depth, motion, patterns, polish — that applies one set of interface laws through a DESIGN.md written in your project's own names."
16
+ }
17
+ ]
18
+ }
package/LICENSE ADDED
@@ -0,0 +1,37 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mihir Modi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ WHAT THIS LICENCE DOES AND DOESN'T COVER
26
+
27
+ The licence above covers the template: the components, hooks, styles, build
28
+ configuration and scripts.
29
+
30
+ It does not cover content. Text, photographs, designs, writing, résumés, logos
31
+ and brand marks belong to whoever made them, are not licensed here, and are not
32
+ yours to republish by virtue of having a copy of this repository. If you cloned
33
+ this from someone's personal site, replace their content with your own.
34
+
35
+ Third-party marks in `src/lib/stackIcons.tsx` are generated from simple-icons
36
+ (CC0-1.0). The brands they represent remain the property of their owners and
37
+ their trademark guidelines still apply.
package/README.md CHANGED
@@ -1,3 +1,118 @@
1
- # Temporary Holding Version
1
+ # Keyline
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Interface design laws for coding agents.
4
+
5
+ Keyline reads your codebase once and writes a `DESIGN.md` that maps your own token names onto its roles and picks a preset for your reader. From then on your agent critiques, audits and fixes type, colour, layout, depth, motion and product patterns through that file: the same laws on every project, your values on yours.
6
+
7
+ ```sh
8
+ npx trykeyline install
9
+ ```
10
+
11
+ The npm package is `trykeyline`, because npm holds `keyline` too close to an older package's name. Everything else, the skill and the commands in your agent, is `keyline`.
12
+
13
+ Then, in your agent:
14
+
15
+ ```
16
+ /keyline init
17
+ ```
18
+
19
+ ## Commands
20
+
21
+ | Command | Does |
22
+ |---|---|
23
+ | `/keyline init` | Scans the codebase, maps its tokens onto roles, picks a preset, writes `DESIGN.md` with the open issues it found |
24
+ | `/keyline sync` | Reconciles `DESIGN.md` with code that has moved since |
25
+ | `/keyline critique` | A designer's read of one screen: what the reader feels first, what's working, the few changes that matter |
26
+ | `/keyline audit` | Every law checked with evidence, per domain, scored 0–4 |
27
+ | `/keyline type` | Type scale, weights, leading, tracking, emphasis, numerals, font loading |
28
+ | `/keyline color` | Tokens by intent, text tiers by measured contrast, status colours, themes |
29
+ | `/keyline layout` | Measure, spacing that groups, radii, line weights, z-index, breakpoints, empty states |
30
+ | `/keyline depth` | Elevation, shadows, edges, scrims, translucent surfaces and their fallbacks |
31
+ | `/keyline motion` | Durations, curves, springs, open and close, reduced motion, scroll |
32
+ | `/keyline patterns` | Labels, buttons, destructive actions, forms, agent UIs, dashboards |
33
+ | `/keyline polish` | All six domains in order, one reviewable pass each |
34
+
35
+ You don't have to name a command: ask for "tighter spacing" or "a review of the settings page" and the agent picks one.
36
+
37
+ `critique`, `audit`, `init` and `sync` change no code. The refining commands change only what their domain owns, in your token names, and write decisions back to `DESIGN.md` so the next session starts from them.
38
+
39
+ ## The detector
40
+
41
+ Twenty-four of the laws can be checked from source alone, with no model and no API key:
42
+
43
+ ```sh
44
+ npx trykeyline check # human report; exits 1 on errors
45
+ npx trykeyline check --json # every finding, for CI or an agent
46
+ npx trykeyline check --strict # warnings fail the run too
47
+ ```
48
+
49
+ It reads CSS, Tailwind classes, inline styles, `<style>` blocks and CSS-in-JS, in React, Vue, Svelte, Astro and plain HTML, and it reads `DESIGN.md` so the verdicts follow your preset and your motion and z-index ladders. A few of the things it catches: `transition: all`; animation with no reduced-motion branch; an outline removed with nothing in its place; `100vh`; `z-index: 9999`; colours typed inline instead of tokens; translucency with no `@supports` fallback; pinch-zoom disabled; uppercase labels and Title Case buttons where the preset says sentence case. `npx trykeyline check --rules` lists all of them, each with the law it comes from.
50
+
51
+ `/keyline audit` runs it first and spends its own reading on what a detector can't see — hierarchy, grouping, whether a box should exist at all.
52
+
53
+ Silence a line with a `keyline-ignore` comment on it or the line above, naming the rule if you like (`/* keyline-ignore type/uppercase */`); a whole file with `keyline-ignore-file`. Leave out paths with `--ignore src/content`.
54
+
55
+ In CI:
56
+
57
+ ```yaml
58
+ - run: npx trykeyline check
59
+ ```
60
+
61
+ ## Presets
62
+
63
+ The laws hold everywhere. A preset sets the dials — how far the type scale climbs, how much colour is spent, how long motion runs — for what your reader is doing:
64
+
65
+ | Preset | For a reader who is |
66
+ |---|---|
67
+ | `editorial` | Reading: articles, docs, portfolios, changelogs |
68
+ | `product` | Working: dashboards, tools, anything used daily |
69
+ | `expressive` | Being persuaded: landing pages, launches, campaigns |
70
+ | `glass` | Getting [modimihir.com](https://modimihir.com)'s exact system, with drop-in tokens and components |
71
+
72
+ Mixed projects are normal: `product`, with the marketing routes on `expressive`. Any dial can be moved, and the move is recorded in `DESIGN.md` with its reason.
73
+
74
+ ## Installing
75
+
76
+ `npx trykeyline install` asks which agent you use and copies the skill into that agent's folder. Flags answer up front:
77
+
78
+ ```sh
79
+ npx trykeyline install --tool codex,cursor # several agents
80
+ npx trykeyline install --tool claude-code --user # every project on this machine
81
+ npx trykeyline update # refresh every copy already installed
82
+ ```
83
+
84
+ | Agent | `--tool` | This project | Every project (`--user`) |
85
+ |---|---|---|---|
86
+ | Claude Code | `claude-code` | `.claude/skills` | `~/.claude/skills` |
87
+ | Codex | `codex` | `.agents/skills` | `~/.codex/skills` |
88
+ | Cursor | `cursor` | `.agents/skills` | `~/.cursor/skills` |
89
+ | Gemini CLI | `gemini-cli` | `.agents/skills` | `~/.gemini/skills` |
90
+ | GitHub Copilot | `github-copilot` | `.agents/skills` | `~/.copilot/skills` |
91
+ | OpenCode | `opencode` | `.agents/skills` | `~/.config/opencode/skills` |
92
+ | Windsurf | `windsurf` | `.windsurf/skills` | `~/.codeium/windsurf/skills` |
93
+ | Any agent that reads `.agents/skills` | `agents` | `.agents/skills` | `~/.agents/skills` |
94
+
95
+ **Claude Code plugin:**
96
+
97
+ ```
98
+ /plugin marketplace add Mihirmodi27/keyline
99
+ /plugin install keyline@keyline
100
+ ```
101
+
102
+ Claude Code names a plugin's skills after the plugin, so installed this way the command is `/keyline:keyline init`, `/keyline:keyline critique` and so on. Installed with `npx trykeyline install`, it's `/keyline`.
103
+
104
+ **With the [`skills` CLI](https://github.com/vercel-labs/skills):** `npx skills add Mihirmodi27/keyline --agent claude-code`.
105
+
106
+ Or run the installer straight from GitHub: `npx github:Mihirmodi27/keyline install`.
107
+
108
+ ### Coming from interface-skills
109
+
110
+ Keyline replaces both interface-skills packages: the adaptable set became the commands, and the Glass package became the `glass` preset. `npx trykeyline install` finds the old skills and offers to remove them, since their descriptions would compete with Keyline's for the same requests. A `DESIGN.md` written by the old setup skill works as it is.
111
+
112
+ ## Requirements
113
+
114
+ Node 18 or later, for the installer and for the three scripts the skill runs: the detector, a token inventory and a contrast checker. None of them needs a dependency, a model or an API key.
115
+
116
+ ## Licence
117
+
118
+ MIT © Mihir Modi
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Keyline's command line.
4
+ *
5
+ * npx trykeyline install asks which coding agent
6
+ * npx trykeyline install --tool codex,cursor answers up front
7
+ * npx trykeyline install --user for every project on this machine
8
+ * npx trykeyline update refresh every copy already installed
9
+ *
10
+ * Keyline is one skill folder. Each coding agent reads skills from its own
11
+ * folder: Claude Code and Windsurf have their own; Codex, Cursor, Gemini CLI,
12
+ * GitHub Copilot, OpenCode and most others share `.agents/skills` in a project.
13
+ * The folder is always copied, never symlinked, so an agent reads real files,
14
+ * and agents that share a folder share one copy.
15
+ *
16
+ * The skill ships inside this package, so installing copies rather than
17
+ * downloads: it works offline and installs exactly the version npx resolved.
18
+ */
19
+ import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync } from "node:fs";
20
+ import { fileURLToPath } from "node:url";
21
+ import { homedir } from "node:os";
22
+ import { join, relative, resolve } from "node:path";
23
+ import { createInterface } from "node:readline/promises";
24
+
25
+ const SOURCE = fileURLToPath(new URL("../plugins/keyline/skills/keyline/", import.meta.url));
26
+ const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
27
+ const NAME = "keyline";
28
+
29
+ /**
30
+ * Where each agent reads skills from — in a project, and for the whole machine.
31
+ * The ids and folders match the `skills` CLI (vercel-labs/skills), so a skill
32
+ * installed either way lands in the same place.
33
+ */
34
+ const home = homedir();
35
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || join(home, ".config");
36
+ const TOOLS = {
37
+ "claude-code": {
38
+ title: "Claude Code",
39
+ project: ".claude/skills",
40
+ global: join(process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"), "skills"),
41
+ },
42
+ codex: {
43
+ title: "Codex",
44
+ project: ".agents/skills",
45
+ global: join(process.env.CODEX_HOME?.trim() || join(home, ".codex"), "skills"),
46
+ },
47
+ cursor: { title: "Cursor", project: ".agents/skills", global: join(home, ".cursor", "skills") },
48
+ "gemini-cli": { title: "Gemini CLI", project: ".agents/skills", global: join(home, ".gemini", "skills") },
49
+ "github-copilot": { title: "GitHub Copilot", project: ".agents/skills", global: join(home, ".copilot", "skills") },
50
+ opencode: { title: "OpenCode", project: ".agents/skills", global: join(configHome, "opencode", "skills") },
51
+ windsurf: { title: "Windsurf", project: ".windsurf/skills", global: join(home, ".codeium", "windsurf", "skills") },
52
+ agents: {
53
+ title: "Any other agent that reads .agents/skills",
54
+ project: ".agents/skills",
55
+ global: join(home, ".agents", "skills"),
56
+ },
57
+ };
58
+ const ALIASES = { claude: "claude-code", gemini: "gemini-cli", copilot: "github-copilot", other: "agents" };
59
+ const TOOL_IDS = Object.keys(TOOLS);
60
+
61
+ /**
62
+ * Keyline replaces the interface-skills packages. Left installed beside it,
63
+ * their descriptions compete with Keyline's for the same requests, so install
64
+ * offers to remove them.
65
+ */
66
+ const SUPERSEDED = [
67
+ "design-system-setup",
68
+ "typographic-system",
69
+ "color-and-theming",
70
+ "layout-and-hierarchy",
71
+ "surfaces-and-depth",
72
+ "interface-motion",
73
+ "product-patterns",
74
+ "glass-typography",
75
+ "glass-color",
76
+ "glass-layout",
77
+ "glass-and-depth",
78
+ "glass-motion",
79
+ ];
80
+
81
+ const USAGE = `Keyline ${version} — interface design laws for coding agents.
82
+
83
+ npx trykeyline install [options] install the skill
84
+ npx trykeyline update [options] refresh every copy already installed
85
+ npx trykeyline check [paths…] the laws that can be checked without a model
86
+ (--json, --strict; see npx trykeyline check --help)
87
+ npx trykeyline tools list the agents it installs for
88
+
89
+ Options:
90
+ -t, --tool <tool[,tool…]> which agent — asked for when not given, Claude Code
91
+ when there's no terminal to ask on. One of:
92
+ ${TOOL_IDS.join(", ")}
93
+ --user for every project on this machine, not just this one
94
+ --dir <path> into <path>, whatever the agent
95
+ -f, --force overwrite a copy that's already there
96
+ -h, --help this
97
+ -v, --version the version
98
+
99
+ Then, in your agent: /keyline init`;
100
+
101
+ function die(message) {
102
+ console.error(`keyline: ${message}`);
103
+ process.exit(1);
104
+ }
105
+
106
+ function toolId(name) {
107
+ const id = ALIASES[name] ?? name;
108
+ if (!TOOLS[id]) die(`no agent called '${name}' — use one of: ${TOOL_IDS.join(", ")}`);
109
+ return id;
110
+ }
111
+
112
+ /* ── Arguments ──────────────────────────────────────────────────────────── */
113
+
114
+ const argv = process.argv.slice(2);
115
+
116
+ // `check` is the detector, which lives in the skill so an agent can run it without this CLI.
117
+ if (argv[0] === "check") {
118
+ const { run } = await import(new URL("../plugins/keyline/skills/keyline/scripts/check.mjs", import.meta.url));
119
+ process.exit(run(argv.slice(1)));
120
+ }
121
+ const command = argv[0] && !argv[0].startsWith("-") ? argv.shift() : null;
122
+ let dir = null;
123
+ let user = false;
124
+ let force = false;
125
+ const tools = [];
126
+
127
+ for (let i = 0; i < argv.length; i++) {
128
+ const arg = argv[i];
129
+ const value = () => (arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : argv[++i]);
130
+ if (arg === "--user") user = true;
131
+ else if (arg === "--dir" || arg.startsWith("--dir=")) {
132
+ const path = value();
133
+ if (!path) die("--dir needs a path");
134
+ dir = resolve(path);
135
+ } else if (["-t", "--tool", "--agent"].includes(arg) || arg.startsWith("--tool=") || arg.startsWith("--agent=")) {
136
+ const v = value();
137
+ if (!v) die("--tool needs an agent name");
138
+ for (const name of v.split(",").map((s) => s.trim()).filter(Boolean)) tools.push(toolId(name));
139
+ } else if (arg === "-f" || arg === "--force") force = true;
140
+ else if (arg === "-h" || arg === "--help") {
141
+ console.log(USAGE);
142
+ process.exit(0);
143
+ } else if (arg === "-v" || arg === "--version") {
144
+ console.log(version);
145
+ process.exit(0);
146
+ } else die(`unknown option: ${arg} (try --help)`);
147
+ }
148
+
149
+ if (!command) {
150
+ console.log(USAGE);
151
+ process.exit(0);
152
+ }
153
+ if (command === "tools") {
154
+ for (const id of TOOL_IDS) console.log(`${id.padEnd(16)} ${TOOLS[id].title}`);
155
+ process.exit(0);
156
+ }
157
+ if (command !== "install" && command !== "update") die(`no command called '${command}' (try --help)`);
158
+
159
+ /* ── Helpers ────────────────────────────────────────────────────────────── */
160
+
161
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
162
+
163
+ async function prompt(question) {
164
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
165
+ try {
166
+ return (await rl.question(question)).trim();
167
+ } catch {
168
+ // Ctrl+C or Ctrl+D at a question: stop quietly, having written nothing yet for this question.
169
+ console.log("\nCancelled.");
170
+ process.exit(130);
171
+ } finally {
172
+ rl.close();
173
+ }
174
+ }
175
+
176
+ async function askTools() {
177
+ if (!interactive) {
178
+ console.log("No terminal to ask on, so installing for Claude Code. Pass --tool to choose (try `keyline tools`).\n");
179
+ return ["claude-code"];
180
+ }
181
+ console.log("Which agent?\n");
182
+ TOOL_IDS.forEach((id, i) => console.log(` ${i + 1}) ${TOOLS[id].title}`));
183
+ console.log();
184
+ for (;;) {
185
+ const answer = (await prompt("Choose one or more, like 1 or 1,2 [1]: ")) || "1";
186
+ const picks = answer.split(/[\s,]+/).filter(Boolean).map(Number);
187
+ if (picks.length && picks.every((n) => Number.isInteger(n) && n >= 1 && n <= TOOL_IDS.length)) {
188
+ console.log();
189
+ return [...new Set(picks.map((n) => TOOL_IDS[n - 1]))];
190
+ }
191
+ console.log(` Type numbers from 1 to ${TOOL_IDS.length}, separated by commas.`);
192
+ }
193
+ }
194
+
195
+ const shown = (path) => (path.startsWith(process.cwd()) ? `./${relative(process.cwd(), path)}` : path.replace(home, "~"));
196
+
197
+ /** The folders to install into, each with the agents that read it. */
198
+ function destinations(ids, global) {
199
+ const byPath = new Map();
200
+ for (const id of ids) {
201
+ const path = global ? TOOLS[id].global : resolve(TOOLS[id].project);
202
+ byPath.set(path, [...(byPath.get(path) ?? []), TOOLS[id].title]);
203
+ }
204
+ // More than two readers only happens on an update sweep of the shared
205
+ // .agents/skills folder, where naming all six says less than naming the folder.
206
+ return [...byPath].map(([path, readers]) => ({
207
+ path,
208
+ label: readers.length > 2 ? `Every agent that reads ${shown(path)}` : readers.join(", "),
209
+ }));
210
+ }
211
+
212
+ const installedAt = (path) => existsSync(join(path, NAME, "SKILL.md"));
213
+ const supersededAt = (path) => SUPERSEDED.filter((s) => existsSync(join(path, s)));
214
+
215
+ function copyTo(path) {
216
+ const target = join(path, NAME);
217
+ mkdirSync(path, { recursive: true });
218
+ rmSync(target, { recursive: true, force: true });
219
+ cpSync(SOURCE, target, { recursive: true });
220
+ }
221
+
222
+ async function retire(path) {
223
+ const old = supersededAt(path);
224
+ if (old.length === 0) return;
225
+ console.log(` Found interface-skills here, which Keyline replaces: ${old.join(", ")}.`);
226
+ const links = old.filter((s) => lstatSync(join(path, s)).isSymbolicLink());
227
+ if (links.length) console.log(` (${links.join(", ")} ${links.length === 1 ? "is a link" : "are links"}; removing ${links.length === 1 ? "it" : "them"} leaves the target alone.)`);
228
+ if (!interactive) {
229
+ console.log(" Left in place — remove them by hand, or rerun in a terminal, so they don't compete with Keyline.");
230
+ return;
231
+ }
232
+ const answer = ((await prompt(" Remove them? [Y/n]: ")) || "y").toLowerCase();
233
+ if (!answer.startsWith("y")) return;
234
+ for (const s of old) rmSync(join(path, s), { recursive: true, force: true });
235
+ console.log(` ✓ removed ${old.length}`);
236
+ }
237
+
238
+ if (!existsSync(join(SOURCE, "SKILL.md"))) die("the package is missing its SKILL.md — reinstall it");
239
+
240
+ /* ── update ─────────────────────────────────────────────────────────────── */
241
+
242
+ if (command === "update") {
243
+ // Every place Keyline is already installed — in this project and on this machine.
244
+ const scopes = dir ? [{ path: dir, label: shown(dir) }] : [...destinations(TOOL_IDS, false), ...destinations(TOOL_IDS, true)];
245
+ const found = scopes.filter((d) => installedAt(d.path));
246
+ if (found.length === 0) {
247
+ console.log("Keyline isn't installed here or for this user — run `npx trykeyline install`.");
248
+ process.exit(0);
249
+ }
250
+ for (const d of found) {
251
+ copyTo(d.path);
252
+ console.log(`✓ ${d.label} → ${shown(join(d.path, NAME))}`);
253
+ }
254
+ console.log(`\nKeyline ${version}, ${found.length} ${found.length === 1 ? "copy" : "copies"} updated.`);
255
+ process.exit(0);
256
+ }
257
+
258
+ /* ── install ────────────────────────────────────────────────────────────── */
259
+
260
+ const dests = dir ? [{ path: dir, label: shown(dir) }] : destinations(tools.length ? tools : await askTools(), user);
261
+ let installed = 0;
262
+
263
+ for (const d of dests) {
264
+ console.log(`${d.label} → ${shown(join(d.path, NAME))}`);
265
+ if (installedAt(d.path) && !force) {
266
+ console.log(" · already installed — `npx trykeyline update` refreshes it, --force overwrites it");
267
+ } else {
268
+ copyTo(d.path);
269
+ console.log(` ✓ keyline ${version}`);
270
+ installed++;
271
+ }
272
+ await retire(d.path);
273
+ console.log();
274
+ }
275
+
276
+ if (installed > 0) {
277
+ console.log("Next, in your agent:");
278
+ console.log(" /keyline init scan this project and write DESIGN.md");
279
+ console.log(" /keyline critique then a designer's read of any screen");
280
+ }
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "trykeyline",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Interface design laws for coding agents: one skill that reads your codebase, writes a DESIGN.md in your own token names, then critiques, audits and fixes type, colour, layout, depth, motion and product patterns through it.",
5
+ "keywords": [
6
+ "design",
7
+ "design-system",
8
+ "ui",
9
+ "typography",
10
+ "accessibility",
11
+ "agent-skills",
12
+ "claude-code",
13
+ "codex",
14
+ "cursor",
15
+ "skills"
16
+ ],
17
+ "homepage": "https://github.com/Mihirmodi27/keyline#readme",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/Mihirmodi27/keyline.git"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Mihir Modi (https://modimihir.com)",
24
+ "type": "module",
25
+ "bin": {
26
+ "keyline": "bin/keyline.mjs",
27
+ "trykeyline": "bin/keyline.mjs"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "plugins",
32
+ ".claude-plugin"
33
+ ],
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "scripts": {
38
+ "test": "node --test \"test/**/*.test.mjs\""
39
+ }
40
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "keyline",
3
+ "displayName": "Keyline",
4
+ "version": "0.1.0",
5
+ "description": "Interface design laws for coding agents. /keyline init reads your codebase and writes a DESIGN.md in your own token names; critique, audit, type, color, layout, depth, motion, patterns and polish work through it, with dials set by a preset (editorial, product, expressive or glass).",
6
+ "author": {
7
+ "name": "Mihir Modi",
8
+ "url": "https://modimihir.com"
9
+ },
10
+ "homepage": "https://github.com/Mihirmodi27/keyline#readme",
11
+ "repository": "https://github.com/Mihirmodi27/keyline",
12
+ "license": "MIT",
13
+ "keywords": ["design-system", "design-tokens", "typography", "motion", "colour", "layout", "accessibility"]
14
+ }
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: keyline
3
+ description: "Design, review and fix interfaces with one set of laws applied in the project's own tokens, names and character. Commands: init (scan the codebase and write DESIGN.md), sync, critique, audit, type, color, layout, depth, motion, patterns, polish. Use when the user wants to set up or document a design system, review or audit a UI, or improve typography, type scale, font weights, colour, contrast, theming, dark mode, spacing, padding, alignment, layout, hierarchy, radii, z-index, cards, shadows, elevation, glass or translucent surfaces, animation, transitions, hover and focus states, reduced motion, UI copy, labels, buttons, forms, dialogs, empty states, dashboards, tables, agent and AI chat interfaces — on websites, landing pages, apps and components. Also for screens that feel generic, boxy, cluttered, flat, noisy, inconsistent or template-made, and for building new UI that should look considered from the first commit. Not for backend-only work."
4
+ ---
5
+
6
+ # Keyline
7
+
8
+ Keyline gives an agent the judgement of a careful interface designer, as laws it can apply and check. **The laws are the same for every project; the values never are.** A setup pass reads the codebase once and writes `DESIGN.md`, which maps the project's own token names onto Keyline's roles and picks a preset for its reader. Every other command works through that file, in the project's names.
9
+
10
+ Paths in these files are relative to this skill's folder. In shell commands it is written `<keyline>` — substitute the folder this file is in.
11
+
12
+ ## Running it
13
+
14
+ `/keyline <command> [target]` — the target is a route, file, directory or component; without one, the command takes the whole project, or the screen the conversation is about.
15
+
16
+ | Command | Does | Changes code |
17
+ |---|---|---|
18
+ | `init` | Scans the codebase, maps its tokens onto roles, picks a preset, writes `DESIGN.md` with the open issues it found | No |
19
+ | `sync` | Reconciles `DESIGN.md` with code that has moved since: drift, new tokens, issues fixed or new | No |
20
+ | `critique` | A designer's read of one screen: what the reader feels first, what's working, the few changes that matter most | No |
21
+ | `audit` | Every law checked with evidence, per domain, scored; findings go to `DESIGN.md` | No |
22
+ | `type` | Type scale, weights, leading, tracking, emphasis, numerals, fonts | Yes |
23
+ | `color` | Tokens by intent, text tiers by measured contrast, status colours, themes | Yes |
24
+ | `layout` | Measure, spacing that groups, radii, line weights, z-index, breakpoints, empty states | Yes |
25
+ | `depth` | Elevation levels, shadows, edges, scrims, translucent surfaces and their fallbacks | Yes |
26
+ | `motion` | Durations, curves, springs, open and close, reduced motion, scroll, hover intent | Yes |
27
+ | `patterns` | Behaviour and words: labels, buttons, destructive actions, forms, agent UIs, dashboards | Yes |
28
+ | `polish` | All six domains in order, one reviewable pass each | Yes |
29
+
30
+ **No command named?** Pick the one the request is about, say which, and go: "set up", "document", "extract" → `init`; "review", "what's wrong", "feedback" → `critique`; "check everything", "lint", "score" → `audit`; "font", "headings", "typography" → `type`; "colour", "contrast", "dark mode" → `color`; "spacing", "padding", "alignment", "cramped" → `layout`; "shadow", "card", "glass", "modal" → `depth`; "animate", "transition", "feels janky" → `motion`; "copy", "labels", "form", "dialog" → `patterns`; "make it better", "clean this up", "finish" → `polish`. Building something new runs the relevant domains' **Building it for a project** sections, in polish order. `colour`, `typography`, `spacing`, `animate` and `copy` work as aliases.
31
+
32
+ ## Before any command
33
+
34
+ 1. **Read `DESIGN.md`** at the project root. `## Profile` gives the preset and any areas on another preset; each domain has its own section; `## Decisions` overrides the preset's defaults and must be respected; `## Open issues` is what's already known.
35
+ 2. **No `DESIGN.md`:** for `init`, that's the job. For anything else, run `init`'s scan and mapping for the domains this command touches, do the task, then recommend a full `/keyline init`. Don't refuse.
36
+ 3. **Load the command's file**, `commands/<command>.md`. It says which guides to read; read them, don't work from memory of them.
37
+ 4. **Preset `glass`:** the exact system from modimihir.com. Read `glass/<domain>/guide.md` beside each domain guide; its values and `assets/` are the recipes.
38
+
39
+ ## What every command holds to
40
+
41
+ - **The project's names, always.** Reason in roles (`text.secondary`, `duration.small`), write code in whatever the project calls them. Never rename a project's tokens; never add a second token system beside the first.
42
+ - **Measure, don't estimate.** Mechanical laws are checked by `<keyline>/scripts/check.mjs`; contrast comes from `<keyline>/scripts/contrast.mjs`; inventories from `<keyline>/scripts/scan.mjs`; counts from the code. A finding carries its evidence: a ratio, a count, a `file:line`.
43
+ - **Laws hold under every preset; dials move between them.** A change that would break a law, or move a dial off the preset, is said out loud and asked about first. When the user agrees, it goes under `## Decisions` with its date and reason.
44
+ - **Keep what the design already chose.** An existing interface keeps its brand colour and gradients wherever they do a job, and its corners stay at least as round. Only a law removes something, and the report names that law: a pass that leaves the screen greyer or boxier than it found it has to say why.
45
+ - **Findings are not fixes.** `init`, `sync`, `critique` and `audit` change no code. The refining commands change only what their domain owns, and name the owning domain for anything else they notice.
46
+ - **Write back.** A decision that changes the system — a new role, a moved dial, a law knowingly broken — goes into `DESIGN.md`, so the next session starts from it. Fixed issues come off `## Open issues`.
47
+ - **Look at the result.** When the project can be rendered, render it after a change and look at both themes, a narrow width, and reduced motion where they apply. Code that reads right can still look wrong.
48
+
49
+ ## Where things are
50
+
51
+ | Folder | Holds |
52
+ |---|---|
53
+ | `commands/` | One file per command: what to read, the steps, what to report |
54
+ | `system/` | Setup: the canonical `roles.md`, the four `presets.md`, the `design-md.md` format, `detection.md` for each stack |
55
+ | `domains/<domain>/` | Each domain's `guide.md` — laws, dials per preset, building, auditing, checklist — and its deeper references |
56
+ | `glass/<domain>/` | The `glass` preset: modimihir.com's exact values, with drop-in tokens and components in `assets/` |
57
+ | `scripts/` | `check.mjs` (the detector: 24 mechanical laws, `--json`, exit codes for CI), `scan.mjs` (token and value inventory) and `contrast.mjs` (measured contrast, translucency composited). Node 18+, no dependencies |
@@ -0,0 +1,25 @@
1
+ # `/keyline audit`
2
+
3
+ Every law, checked with evidence, per domain — the full inspection `critique` is the summary of. Findings go into `DESIGN.md` so later sessions start from them.
4
+
5
+ **Read:** `DESIGN.md`, then the `## Auditing existing code` and `## Checklist` sections of every `domains/<domain>/guide.md` (all six unless the user narrows it). Under preset `glass`, also each `glass/<domain>/guide.md` checklist.
6
+
7
+ **Steps:**
8
+
9
+ 1. **Detector:** `node <keyline>/scripts/check.mjs --root <project root> --json --ignore <prose and example dirs>`. Every finding it returns is evidence already — file, line, law — so start each domain from its findings and spend your reading on what it can't see: hierarchy, grouping, whether a box should exist. Treat a finding as a lead, not a verdict; one you judge wrong goes in the report as such, and gets a `keyline-ignore` comment only if the user agrees.
10
+ 2. **Inventory:** `node <keyline>/scripts/scan.mjs . --ignore <prose and example dirs>`.
11
+ 3. **Contrast:** every text tier, the focus ring (`--min 3`) and status colours, per theme, with `<keyline>/scripts/contrast.mjs`.
12
+ 4. **Per domain, in polish order** — type, colour, layout, depth, motion, patterns — run that guide's audit: its searches, then its evidence per law. Render where the guide asks you to look (widths, themes, reduced motion, keyboard).
13
+ 5. **Score each domain** from what you found, the detector's findings and yours together:
14
+
15
+ | Score | Means |
16
+ |---|---|
17
+ | 4 | Every law holds; nothing to report |
18
+ | 3 | Small strays — a handful of one-off values, nothing a reader would notice |
19
+ | 2 | A law broken in a way a reader feels — a hierarchy inversion, a failed contrast tier, a box around everything |
20
+ | 1 | Several laws broken across templates; the domain isn't working as a system |
21
+ | 0 | No system: values typed inline, nothing on a ladder |
22
+
23
+ **Report:** a table of the six domains with score and finding count, then findings grouped by domain, worst first, one line each with evidence — `file:line`, a ratio, a count — and the law, with its UX-law name where one explains it (the table in `commands/critique.md`). Close with the order to fix in: usually the lowest score first, unless a law in another domain blocks it.
24
+
25
+ **Write back:** every finding under `## Open issues` in `DESIGN.md`, replacing entries now fixed. **Changes no code.**
@@ -0,0 +1,7 @@
1
+ # `/keyline color`
2
+
3
+ **Guide:** `domains/color/guide.md` · **`DESIGN.md` section:** `## Colour` · **Procedure:** `commands/refine.md` · **Alias:** `colour`
4
+
5
+ Makes colour mean something: tokens named for the job they do rather than their lightness, text tiers chosen by measured contrast and kept strictly descending, a focus colour that's never lost, status colours as foreground and background pairs, colour spent on what needs a person rather than on decoration, and themes that switch without a flash.
6
+
7
+ Typical finds: one grey doing duty as both a border and a caption; a tier that passes 4.5:1 but is indistinguishable from its neighbour; a second accent hue; success green on steps the reader has simply passed (outside an expressive brand's counted exceptions); a gradient that wasn't re-picked for dark. Every ratio comes from `<keyline>/scripts/contrast.mjs`. Deeper references: `deriving-a-ramp.md`, `status-and-exceptions.md`, `theming-mechanics.md`.