Просмотр исходного кода

feat(canvas): Add terminal canvas for content drafting

Adds a split-pane canvas experience for Warp terminal:

- canvas-tui: Ink/React TUI app with live markdown preview
  - File watching via chokidar with 100ms debounce
  - ANSI-styled markdown rendering via marked-terminal
  - Scroll navigation and keyboard shortcuts
  - Email, message, and doc content templates

- /canvas command: start, write, read, clear, close subcommands
- Warp launch configuration for one-click split pane setup
- File-based IPC via .claude/canvas/content.md

Tech stack: Ink 5, React 18, TypeScript, chokidar, marked-terminal

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
0xDarkMatter 5 месяцев назад
Родитель
Сommit
81c2f8766f

+ 7 - 5
.claude-plugin/plugin.json

@@ -1,7 +1,7 @@
 {
   "name": "claude-mods",
-  "version": "1.3.0",
-  "description": "Custom commands, skills, and agents for Claude Code - session continuity, 23 expert agents, 30 skills, 4 rules, modern CLI tools",
+  "version": "1.4.0",
+  "description": "Custom commands, skills, and agents for Claude Code - session continuity, terminal canvas, 22 expert agents, 30 skills, 4 rules, modern CLI tools",
   "author": "0xDarkMatter",
   "repository": "https://github.com/0xDarkMatter/claude-mods",
   "license": "MIT",
@@ -12,7 +12,9 @@
     "skills",
     "session-management",
     "cli-tools",
-    "output-styles"
+    "output-styles",
+    "canvas",
+    "terminal-ui"
   ],
   "components": {
     "commands": [
@@ -25,7 +27,8 @@
       "commands/conclave.md",
       "commands/atomise.md",
       "commands/setperms.md",
-      "commands/pulse.md"
+      "commands/pulse.md",
+      "commands/canvas.md"
     ],
     "agents": [
       "agents/astro-expert.md",
@@ -41,7 +44,6 @@
       "agents/javascript-expert.md",
       "agents/laravel-expert.md",
       "agents/payloadcms-expert.md",
-      "agents/playwright-roulette-expert.md",
       "agents/postgres-expert.md",
       "agents/project-organizer.md",
       "agents/python-expert.md",

+ 4 - 0
canvas-tui/.gitignore

@@ -0,0 +1,4 @@
+node_modules/
+dist/
+*.log
+.DS_Store

+ 82 - 0
canvas-tui/README.md

@@ -0,0 +1,82 @@
+# @claude-mods/canvas-tui
+
+Terminal canvas for Claude Code - live markdown preview in split panes.
+
+## Installation
+
+```bash
+npm install -g @claude-mods/canvas-tui
+```
+
+Or run directly with npx:
+
+```bash
+npx @claude-mods/canvas-tui --watch
+```
+
+## Usage
+
+```bash
+# Watch default location (.claude/canvas/content.md)
+canvas-tui --watch
+
+# Watch specific directory
+canvas-tui --watch ./my-canvas
+
+# Watch specific file
+canvas-tui --file ./draft.md
+
+# Show help
+canvas-tui --help
+```
+
+## Keyboard Shortcuts
+
+| Key | Action |
+|-----|--------|
+| `q` | Quit |
+| `Ctrl+C` | Quit |
+| `Up/Down` | Scroll |
+| `g` | Go to top |
+| `G` | Go to bottom |
+| `r` | Refresh |
+
+## Integration with Claude Code
+
+This TUI works with the `/canvas` command in Claude Code:
+
+1. Run `/canvas start --type email` in Claude Code
+2. Open a split pane in your terminal
+3. Run `canvas-tui --watch` in the split pane
+4. Claude writes content, you see live preview
+
+## Warp Terminal
+
+For Warp users, install the launch configuration:
+
+```bash
+# Windows
+copy templates\warp\claude-canvas.yaml %APPDATA%\warp\Warp\data\launch_configurations\
+
+# macOS/Linux
+cp templates/warp/claude-canvas.yaml ~/.warp/launch_configurations/
+```
+
+Then open Warp Command Palette and search "Claude Canvas".
+
+## File Structure
+
+```
+.claude/canvas/
+├── content.md      # Shared content (Claude writes, TUI renders)
+└── meta.json       # Session metadata
+```
+
+## Requirements
+
+- Node.js >= 18.0.0
+- Terminal with ANSI color support
+
+## License
+
+MIT

+ 2 - 0
canvas-tui/bin/canvas.js

@@ -0,0 +1,2 @@
+#!/usr/bin/env node
+import('../dist/index.js');

+ 1305 - 0
canvas-tui/package-lock.json

@@ -0,0 +1,1305 @@
+{
+  "name": "@claude-mods/canvas-tui",
+  "version": "0.1.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "@claude-mods/canvas-tui",
+      "version": "0.1.0",
+      "license": "MIT",
+      "dependencies": {
+        "@inkjs/ui": "^2.0.0",
+        "chalk": "^5.3.0",
+        "chokidar": "^4.0.3",
+        "ink": "^5.0.1",
+        "marked": "^15.0.4",
+        "marked-terminal": "^7.2.1",
+        "meow": "^13.2.0",
+        "react": "^18.3.1"
+      },
+      "bin": {
+        "canvas-tui": "bin/canvas.js"
+      },
+      "devDependencies": {
+        "@types/node": "^22.10.5",
+        "@types/react": "^18.3.18",
+        "typescript": "^5.7.2"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@alcalzone/ansi-tokenize": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz",
+      "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^6.2.1",
+        "is-fullwidth-code-point": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=14.13.1"
+      }
+    },
+    "node_modules/@colors/colors": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
+      "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=0.1.90"
+      }
+    },
+    "node_modules/@inkjs/ui": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@inkjs/ui/-/ui-2.0.0.tgz",
+      "integrity": "sha512-5+8fJmwtF9UvikzLfph9sA+LS+l37Ij/szQltkuXLOAXwNkBX9innfzh4pLGXIB59vKEQUtc6D4qGvhD7h3pAg==",
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^5.3.0",
+        "cli-spinners": "^3.0.0",
+        "deepmerge": "^4.3.1",
+        "figures": "^6.1.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "ink": ">=5"
+      }
+    },
+    "node_modules/@sindresorhus/is": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+      "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/is?sponsor=1"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "22.19.3",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
+      "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.21.0"
+      }
+    },
+    "node_modules/@types/prop-types": {
+      "version": "15.7.15",
+      "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+      "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/react": {
+      "version": "18.3.27",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+      "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+      "devOptional": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/prop-types": "*",
+        "csstype": "^3.2.2"
+      }
+    },
+    "node_modules/ansi-escapes": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
+      "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
+      "license": "MIT",
+      "dependencies": {
+        "environment": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+      "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+      "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+      "license": "MIT"
+    },
+    "node_modules/auto-bind": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz",
+      "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "5.6.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+      "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/char-regex": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+      "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+      "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+      "license": "MIT",
+      "dependencies": {
+        "readdirp": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/cli-boxes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+      "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/cli-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
+      "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
+      "license": "MIT",
+      "dependencies": {
+        "restore-cursor": "^4.0.0"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/cli-highlight": {
+      "version": "2.1.11",
+      "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
+      "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==",
+      "license": "ISC",
+      "dependencies": {
+        "chalk": "^4.0.0",
+        "highlight.js": "^10.7.1",
+        "mz": "^2.4.0",
+        "parse5": "^5.1.1",
+        "parse5-htmlparser2-tree-adapter": "^6.0.0",
+        "yargs": "^16.0.0"
+      },
+      "bin": {
+        "highlight": "bin/highlight"
+      },
+      "engines": {
+        "node": ">=8.0.0",
+        "npm": ">=5.0.0"
+      }
+    },
+    "node_modules/cli-highlight/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/cli-highlight/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/cli-spinners": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.3.0.tgz",
+      "integrity": "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/cli-table3": {
+      "version": "0.6.5",
+      "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
+      "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
+      "license": "MIT",
+      "dependencies": {
+        "string-width": "^4.2.0"
+      },
+      "engines": {
+        "node": "10.* || >= 12.*"
+      },
+      "optionalDependencies": {
+        "@colors/colors": "1.5.0"
+      }
+    },
+    "node_modules/cli-table3/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cli-table3/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/cli-table3/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cli-table3/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cli-table3/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cli-truncate": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
+      "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
+      "license": "MIT",
+      "dependencies": {
+        "slice-ansi": "^5.0.0",
+        "string-width": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/cli-truncate/node_modules/slice-ansi": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+      "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^6.0.0",
+        "is-fullwidth-code-point": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+      }
+    },
+    "node_modules/cliui": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^7.0.0"
+      }
+    },
+    "node_modules/cliui/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/cliui/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/code-excerpt": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz",
+      "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==",
+      "license": "MIT",
+      "dependencies": {
+        "convert-to-spaces": "^2.0.1"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "license": "MIT"
+    },
+    "node_modules/convert-to-spaces": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz",
+      "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/deepmerge": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/emoji-regex": {
+      "version": "10.6.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+      "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+      "license": "MIT"
+    },
+    "node_modules/emojilib": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz",
+      "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==",
+      "license": "MIT"
+    },
+    "node_modules/environment": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+      "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/es-toolkit": {
+      "version": "1.43.0",
+      "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz",
+      "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==",
+      "license": "MIT",
+      "workspaces": [
+        "docs",
+        "benchmarks"
+      ]
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+      "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/figures": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+      "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-unicode-supported": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "license": "ISC",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/get-east-asian-width": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+      "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/highlight.js": {
+      "version": "10.7.3",
+      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
+      "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/indent-string": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz",
+      "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ink": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz",
+      "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==",
+      "license": "MIT",
+      "dependencies": {
+        "@alcalzone/ansi-tokenize": "^0.1.3",
+        "ansi-escapes": "^7.0.0",
+        "ansi-styles": "^6.2.1",
+        "auto-bind": "^5.0.1",
+        "chalk": "^5.3.0",
+        "cli-boxes": "^3.0.0",
+        "cli-cursor": "^4.0.0",
+        "cli-truncate": "^4.0.0",
+        "code-excerpt": "^4.0.0",
+        "es-toolkit": "^1.22.0",
+        "indent-string": "^5.0.0",
+        "is-in-ci": "^1.0.0",
+        "patch-console": "^2.0.0",
+        "react-reconciler": "^0.29.0",
+        "scheduler": "^0.23.0",
+        "signal-exit": "^3.0.7",
+        "slice-ansi": "^7.1.0",
+        "stack-utils": "^2.0.6",
+        "string-width": "^7.2.0",
+        "type-fest": "^4.27.0",
+        "widest-line": "^5.0.0",
+        "wrap-ansi": "^9.0.0",
+        "ws": "^8.18.0",
+        "yoga-layout": "~3.2.1"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "@types/react": ">=18.0.0",
+        "react": ">=18.0.0",
+        "react-devtools-core": "^4.19.1"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "react-devtools-core": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+      "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-in-ci": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz",
+      "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==",
+      "license": "MIT",
+      "bin": {
+        "is-in-ci": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-unicode-supported": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+      "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "license": "MIT"
+    },
+    "node_modules/loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      },
+      "bin": {
+        "loose-envify": "cli.js"
+      }
+    },
+    "node_modules/marked": {
+      "version": "15.0.12",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
+      "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
+      "license": "MIT",
+      "bin": {
+        "marked": "bin/marked.js"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
+    "node_modules/marked-terminal": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz",
+      "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-escapes": "^7.0.0",
+        "ansi-regex": "^6.1.0",
+        "chalk": "^5.4.1",
+        "cli-highlight": "^2.1.11",
+        "cli-table3": "^0.6.5",
+        "node-emoji": "^2.2.0",
+        "supports-hyperlinks": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      },
+      "peerDependencies": {
+        "marked": ">=1 <16"
+      }
+    },
+    "node_modules/meow": {
+      "version": "13.2.0",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
+      "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/mz": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0",
+        "object-assign": "^4.0.1",
+        "thenify-all": "^1.0.0"
+      }
+    },
+    "node_modules/node-emoji": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
+      "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==",
+      "license": "MIT",
+      "dependencies": {
+        "@sindresorhus/is": "^4.6.0",
+        "char-regex": "^1.0.2",
+        "emojilib": "^2.4.0",
+        "skin-tone": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/onetime": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-fn": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/parse5": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+      "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+      "license": "MIT"
+    },
+    "node_modules/parse5-htmlparser2-tree-adapter": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
+      "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
+      "license": "MIT",
+      "dependencies": {
+        "parse5": "^6.0.1"
+      }
+    },
+    "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
+      "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
+      "license": "MIT"
+    },
+    "node_modules/patch-console": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz",
+      "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      }
+    },
+    "node_modules/react": {
+      "version": "18.3.1",
+      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-reconciler": {
+      "version": "0.29.2",
+      "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz",
+      "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0",
+        "scheduler": "^0.23.2"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      },
+      "peerDependencies": {
+        "react": "^18.3.1"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+      "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14.18.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/restore-cursor": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
+      "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
+      "license": "MIT",
+      "dependencies": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.23.2",
+      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+      "license": "MIT",
+      "dependencies": {
+        "loose-envify": "^1.1.0"
+      }
+    },
+    "node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "license": "ISC"
+    },
+    "node_modules/skin-tone": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz",
+      "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==",
+      "license": "MIT",
+      "dependencies": {
+        "unicode-emoji-modifier-base": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/slice-ansi": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+      "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^6.2.1",
+        "is-fullwidth-code-point": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+      }
+    },
+    "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+      "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "get-east-asian-width": "^1.3.1"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/stack-utils": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+      "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+      "license": "MIT",
+      "dependencies": {
+        "escape-string-regexp": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+      "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^10.3.0",
+        "get-east-asian-width": "^1.0.0",
+        "strip-ansi": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+      "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-hyperlinks": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz",
+      "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0",
+        "supports-color": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=14.18"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1"
+      }
+    },
+    "node_modules/thenify": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0"
+      }
+    },
+    "node_modules/thenify-all": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+      "license": "MIT",
+      "dependencies": {
+        "thenify": ">= 3.1.0 < 4"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "4.41.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+      "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+      "license": "(MIT OR CC0-1.0)",
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "6.21.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/unicode-emoji-modifier-base": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz",
+      "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/widest-line": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz",
+      "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==",
+      "license": "MIT",
+      "dependencies": {
+        "string-width": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/wrap-ansi": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+      "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^6.2.1",
+        "string-width": "^7.0.0",
+        "strip-ansi": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/ws": {
+      "version": "8.19.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+      "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs": {
+      "version": "16.2.0",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+      "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^7.0.2",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.0",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^20.2.2"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "20.2.9",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yoga-layout": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
+      "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
+      "license": "MIT"
+    }
+  }
+}

+ 55 - 0
canvas-tui/package.json

@@ -0,0 +1,55 @@
+{
+  "name": "@claude-mods/canvas-tui",
+  "version": "0.1.0",
+  "description": "Terminal canvas for Claude Code - live markdown preview in split panes",
+  "type": "module",
+  "main": "dist/index.js",
+  "bin": {
+    "canvas-tui": "./bin/canvas.js"
+  },
+  "scripts": {
+    "build": "tsc",
+    "dev": "tsc --watch",
+    "start": "node bin/canvas.js",
+    "test": "node --test"
+  },
+  "keywords": [
+    "claude",
+    "claude-code",
+    "terminal",
+    "tui",
+    "canvas",
+    "markdown",
+    "ink",
+    "react"
+  ],
+  "author": "0xDarkMatter",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/0xDarkMatter/claude-mods.git",
+    "directory": "canvas-tui"
+  },
+  "engines": {
+    "node": ">=18.0.0"
+  },
+  "dependencies": {
+    "ink": "^5.0.1",
+    "@inkjs/ui": "^2.0.0",
+    "react": "^18.3.1",
+    "chokidar": "^4.0.3",
+    "marked": "^15.0.4",
+    "marked-terminal": "^7.2.1",
+    "chalk": "^5.3.0",
+    "meow": "^13.2.0"
+  },
+  "devDependencies": {
+    "@types/node": "^22.10.5",
+    "@types/react": "^18.3.18",
+    "typescript": "^5.7.2"
+  },
+  "files": [
+    "dist",
+    "bin"
+  ]
+}

+ 113 - 0
canvas-tui/src/app.tsx

@@ -0,0 +1,113 @@
+import React, { useState, useEffect } from 'react';
+import { Box, Text, useApp, useInput, useStdout } from 'ink';
+import { Header } from './components/Header.js';
+import { MarkdownView } from './components/MarkdownView.js';
+import { StatusBar } from './components/StatusBar.js';
+import { useFileWatcher } from './hooks/useFileWatcher.js';
+import { readMeta, type CanvasMeta } from './lib/ipc.js';
+
+interface AppProps {
+  watchPath: string;
+  watchDir: string;
+}
+
+export const App: React.FC<AppProps> = ({ watchPath, watchDir }) => {
+  const { exit } = useApp();
+  const { stdout } = useStdout();
+  const [content, setContent] = useState<string>('');
+  const [meta, setMeta] = useState<CanvasMeta | null>(null);
+  const [syncStatus, setSyncStatus] = useState<'waiting' | 'synced' | 'watching'>('waiting');
+  const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
+  const [scrollOffset, setScrollOffset] = useState(0);
+
+  // Terminal dimensions
+  const rows = stdout?.rows || 24;
+  const cols = stdout?.columns || 80;
+
+  // File watcher
+  const { content: watchedContent, error } = useFileWatcher(watchPath);
+
+  // Update content when file changes
+  useEffect(() => {
+    if (watchedContent !== null) {
+      setContent(watchedContent);
+      setSyncStatus('synced');
+      setLastUpdate(new Date());
+
+      // Also read meta file
+      const metaPath = watchDir + '/meta.json';
+      readMeta(metaPath).then(setMeta).catch(() => {});
+    }
+  }, [watchedContent, watchDir]);
+
+  // Keyboard input
+  useInput((input, key) => {
+    if (input === 'q' || (key.ctrl && input === 'c')) {
+      exit();
+    }
+    if (key.upArrow) {
+      setScrollOffset(prev => Math.max(0, prev - 1));
+    }
+    if (key.downArrow) {
+      setScrollOffset(prev => prev + 1);
+    }
+    if (input === 'g') {
+      setScrollOffset(0); // Go to top
+    }
+    if (input === 'G') {
+      // Go to bottom - handled in MarkdownView
+      setScrollOffset(999999);
+    }
+    if (input === 'r') {
+      // Force refresh - re-read file
+      setSyncStatus('watching');
+    }
+  });
+
+  // Determine status message
+  let statusMessage = '';
+  if (error) {
+    statusMessage = `Error: ${error}`;
+  } else if (syncStatus === 'waiting') {
+    statusMessage = `Waiting for content at ${watchPath}...`;
+  } else if (syncStatus === 'synced' && lastUpdate) {
+    statusMessage = `Last updated: ${lastUpdate.toLocaleTimeString()}`;
+  }
+
+  return (
+    <Box flexDirection="column" height={rows}>
+      <Header
+        title="Canvas"
+        contentType={meta?.contentType || 'doc'}
+        width={cols}
+      />
+
+      <Box flexGrow={1} flexDirection="column" overflow="hidden">
+        {content ? (
+          <MarkdownView
+            content={content}
+            scrollOffset={scrollOffset}
+            maxHeight={rows - 4}
+          />
+        ) : (
+          <Box padding={1}>
+            <Text color="gray">
+              {error ? (
+                <Text color="red">{error}</Text>
+              ) : (
+                `Watching ${watchPath} for changes...\n\nUse /canvas write in Claude Code to send content here.`
+              )}
+            </Text>
+          </Box>
+        )}
+      </Box>
+
+      <StatusBar
+        status={syncStatus}
+        message={statusMessage}
+        hints="q: quit | arrows: scroll | r: refresh"
+        width={cols}
+      />
+    </Box>
+  );
+};

+ 28 - 0
canvas-tui/src/components/Header.tsx

@@ -0,0 +1,28 @@
+import React from 'react';
+import { Box, Text } from 'ink';
+
+interface HeaderProps {
+  title: string;
+  contentType: string;
+  width: number;
+}
+
+export const Header: React.FC<HeaderProps> = ({ title, contentType, width }) => {
+  const typeLabel = contentType.charAt(0).toUpperCase() + contentType.slice(1);
+  const leftContent = ` ${title} `;
+  const rightContent = ` ${typeLabel} `;
+
+  // Calculate padding for centering
+  const totalContentLength = leftContent.length + rightContent.length;
+  const padding = Math.max(0, width - totalContentLength - 2);
+
+  return (
+    <Box borderStyle="single" borderBottom={true} borderTop={false} borderLeft={false} borderRight={false}>
+      <Box width={width}>
+        <Text bold color="blue">{leftContent}</Text>
+        <Text>{' '.repeat(padding)}</Text>
+        <Text color="gray">{rightContent}</Text>
+      </Box>
+    </Box>
+  );
+};

+ 47 - 0
canvas-tui/src/components/MarkdownView.tsx

@@ -0,0 +1,47 @@
+import React, { useMemo } from 'react';
+import { Box, Text } from 'ink';
+import { useMarkdown } from '../hooks/useMarkdown.js';
+
+interface MarkdownViewProps {
+  content: string;
+  scrollOffset: number;
+  maxHeight: number;
+}
+
+export const MarkdownView: React.FC<MarkdownViewProps> = ({
+  content,
+  scrollOffset,
+  maxHeight
+}) => {
+  const rendered = useMarkdown(content);
+
+  // Split into lines for scrolling
+  const lines = useMemo(() => {
+    return rendered.split('\n');
+  }, [rendered]);
+
+  // Calculate visible window
+  const totalLines = lines.length;
+  const clampedOffset = Math.min(scrollOffset, Math.max(0, totalLines - maxHeight));
+  const visibleLines = lines.slice(clampedOffset, clampedOffset + maxHeight);
+
+  // Scroll indicator
+  const showScrollUp = clampedOffset > 0;
+  const showScrollDown = clampedOffset + maxHeight < totalLines;
+
+  return (
+    <Box flexDirection="column" paddingX={1}>
+      {showScrollUp && (
+        <Text color="gray">--- more above ({clampedOffset} lines) ---</Text>
+      )}
+
+      {visibleLines.map((line, index) => (
+        <Text key={`${clampedOffset}-${index}`}>{line || ' '}</Text>
+      ))}
+
+      {showScrollDown && (
+        <Text color="gray">--- more below ({totalLines - clampedOffset - maxHeight} lines) ---</Text>
+      )}
+    </Box>
+  );
+};

+ 45 - 0
canvas-tui/src/components/StatusBar.tsx

@@ -0,0 +1,45 @@
+import React from 'react';
+import { Box, Text } from 'ink';
+
+interface StatusBarProps {
+  status: 'waiting' | 'synced' | 'watching';
+  message: string;
+  hints: string;
+  width: number;
+}
+
+export const StatusBar: React.FC<StatusBarProps> = ({ status, message, hints, width }) => {
+  const statusColors: Record<string, string> = {
+    waiting: 'yellow',
+    synced: 'green',
+    watching: 'cyan'
+  };
+
+  const statusIcons: Record<string, string> = {
+    waiting: '...',
+    synced: '***',
+    watching: '>>>'
+  };
+
+  const statusColor = statusColors[status] || 'white';
+  const statusIcon = statusIcons[status] || '?';
+
+  return (
+    <Box
+      borderStyle="single"
+      borderTop={true}
+      borderBottom={false}
+      borderLeft={false}
+      borderRight={false}
+      flexDirection="column"
+    >
+      <Box width={width} justifyContent="space-between">
+        <Box>
+          <Text color={statusColor}>[{statusIcon}]</Text>
+          <Text> {message}</Text>
+        </Box>
+        <Text color="gray">{hints}</Text>
+      </Box>
+    </Box>
+  );
+};

+ 78 - 0
canvas-tui/src/hooks/useFileWatcher.ts

@@ -0,0 +1,78 @@
+import { useState, useEffect } from 'react';
+import { watch } from 'chokidar';
+import { readFile } from 'fs/promises';
+import { existsSync } from 'fs';
+
+interface FileWatcherResult {
+  content: string | null;
+  error: string | null;
+  isWatching: boolean;
+}
+
+export function useFileWatcher(filePath: string): FileWatcherResult {
+  const [content, setContent] = useState<string | null>(null);
+  const [error, setError] = useState<string | null>(null);
+  const [isWatching, setIsWatching] = useState(false);
+
+  useEffect(() => {
+    let watcher: ReturnType<typeof watch> | null = null;
+
+    const readContent = async () => {
+      try {
+        if (existsSync(filePath)) {
+          const data = await readFile(filePath, 'utf-8');
+          setContent(data);
+          setError(null);
+        }
+      } catch (err) {
+        setError(`Failed to read file: ${err instanceof Error ? err.message : String(err)}`);
+      }
+    };
+
+    const startWatching = () => {
+      // Initial read
+      readContent();
+
+      // Set up watcher
+      watcher = watch(filePath, {
+        persistent: true,
+        ignoreInitial: false,
+        awaitWriteFinish: {
+          stabilityThreshold: 100,
+          pollInterval: 50
+        }
+      });
+
+      watcher.on('add', () => {
+        readContent();
+        setIsWatching(true);
+      });
+
+      watcher.on('change', () => {
+        readContent();
+      });
+
+      watcher.on('unlink', () => {
+        setContent(null);
+        setError('File was deleted');
+      });
+
+      watcher.on('error', (err: unknown) => {
+        const message = err instanceof Error ? err.message : String(err);
+        setError(`Watcher error: ${message}`);
+      });
+
+      setIsWatching(true);
+    };
+
+    startWatching();
+
+    return () => {
+      if (watcher) {
+        watcher.close();
+      }
+    };
+  }, [filePath]);
+
+  return { content, error, isWatching };
+}

+ 73 - 0
canvas-tui/src/hooks/useMarkdown.ts

@@ -0,0 +1,73 @@
+import { useMemo } from 'react';
+import { marked } from 'marked';
+import { markedTerminal } from 'marked-terminal';
+import chalk from 'chalk';
+
+// Configure marked with terminal renderer
+marked.use(
+  markedTerminal({
+    // Colors for different elements
+    code: chalk.cyan,
+    blockquote: chalk.gray.italic,
+    html: chalk.gray,
+    heading: chalk.bold.blue,
+    firstHeading: chalk.bold.blue.underline,
+    hr: chalk.gray,
+    listitem: chalk.white,
+    list: (body: string) => body,
+    table: chalk.white,
+    paragraph: chalk.white,
+    strong: chalk.bold,
+    em: chalk.italic,
+    codespan: chalk.yellow,
+    del: chalk.strikethrough,
+    link: chalk.blue.underline,
+    href: chalk.blue.underline,
+
+    // Table rendering
+    tableOptions: {
+      chars: {
+        top: '-',
+        'top-mid': '+',
+        'top-left': '+',
+        'top-right': '+',
+        bottom: '-',
+        'bottom-mid': '+',
+        'bottom-left': '+',
+        'bottom-right': '+',
+        left: '|',
+        'left-mid': '+',
+        mid: '-',
+        'mid-mid': '+',
+        right: '|',
+        'right-mid': '+',
+        middle: '|'
+      }
+    },
+
+    // Misc settings
+    reflowText: true,
+    width: 80,
+    showSectionPrefix: false,
+    tab: 2
+  })
+);
+
+export function useMarkdown(content: string): string {
+  return useMemo(() => {
+    if (!content) return '';
+
+    try {
+      // Parse markdown to terminal-formatted string
+      const rendered = marked.parse(content);
+      // marked returns Promise in some configs, but sync with markedTerminal
+      if (typeof rendered === 'string') {
+        return rendered.trim();
+      }
+      return content; // Fallback to raw content
+    } catch (err) {
+      console.error('Markdown parse error:', err);
+      return content; // Return raw content on error
+    }
+  }, [content]);
+}

+ 38 - 0
canvas-tui/src/index.tsx

@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+import React from 'react';
+import { render } from 'ink';
+import meow from 'meow';
+import { App } from './app.js';
+
+const cli = meow(`
+  Usage
+    $ canvas-tui [options]
+
+  Options
+    --watch, -w     Watch directory for changes (default: .claude/canvas)
+    --file, -f      Specific file to watch
+    --help          Show this help message
+    --version       Show version
+
+  Examples
+    $ canvas-tui --watch
+    $ canvas-tui --watch .claude/canvas
+    $ canvas-tui --file ./draft.md
+`, {
+  importMeta: import.meta,
+  flags: {
+    watch: {
+      type: 'string',
+      shortFlag: 'w',
+      default: '.claude/canvas'
+    },
+    file: {
+      type: 'string',
+      shortFlag: 'f'
+    }
+  }
+});
+
+const watchPath = cli.flags.file || `${cli.flags.watch}/content.md`;
+
+render(<App watchPath={watchPath} watchDir={cli.flags.watch} />);

+ 105 - 0
canvas-tui/src/lib/ipc.ts

@@ -0,0 +1,105 @@
+import { readFile, writeFile, mkdir } from 'fs/promises';
+import { existsSync } from 'fs';
+import { dirname } from 'path';
+
+export interface CanvasMeta {
+  version: string;
+  contentType: 'email' | 'message' | 'doc';
+  mode: 'view' | 'edit';
+  claudeLastWrite: string | null;
+  userLastEdit: string | null;
+  title?: string;
+}
+
+const DEFAULT_META: CanvasMeta = {
+  version: '1.0',
+  contentType: 'doc',
+  mode: 'view',
+  claudeLastWrite: null,
+  userLastEdit: null
+};
+
+/**
+ * Read meta.json from canvas directory
+ */
+export async function readMeta(metaPath: string): Promise<CanvasMeta> {
+  try {
+    if (!existsSync(metaPath)) {
+      return DEFAULT_META;
+    }
+    const data = await readFile(metaPath, 'utf-8');
+    return { ...DEFAULT_META, ...JSON.parse(data) };
+  } catch {
+    return DEFAULT_META;
+  }
+}
+
+/**
+ * Write meta.json to canvas directory
+ */
+export async function writeMeta(metaPath: string, meta: Partial<CanvasMeta>): Promise<void> {
+  const dir = dirname(metaPath);
+  if (!existsSync(dir)) {
+    await mkdir(dir, { recursive: true });
+  }
+
+  const existing = await readMeta(metaPath);
+  const updated: CanvasMeta = {
+    ...existing,
+    ...meta
+  };
+
+  await writeFile(metaPath, JSON.stringify(updated, null, 2), 'utf-8');
+}
+
+/**
+ * Read content from canvas content file
+ */
+export async function readContent(contentPath: string): Promise<string | null> {
+  try {
+    if (!existsSync(contentPath)) {
+      return null;
+    }
+    return await readFile(contentPath, 'utf-8');
+  } catch {
+    return null;
+  }
+}
+
+/**
+ * Write content to canvas content file
+ */
+export async function writeContent(contentPath: string, content: string): Promise<void> {
+  const dir = dirname(contentPath);
+  if (!existsSync(dir)) {
+    await mkdir(dir, { recursive: true });
+  }
+  await writeFile(contentPath, content, 'utf-8');
+}
+
+/**
+ * Initialize canvas directory with default files
+ */
+export async function initCanvas(
+  canvasDir: string,
+  contentType: CanvasMeta['contentType'] = 'doc'
+): Promise<void> {
+  const contentPath = `${canvasDir}/content.md`;
+  const metaPath = `${canvasDir}/meta.json`;
+
+  // Ensure directory exists
+  if (!existsSync(canvasDir)) {
+    await mkdir(canvasDir, { recursive: true });
+  }
+
+  // Create meta file
+  await writeMeta(metaPath, {
+    contentType,
+    claudeLastWrite: new Date().toISOString()
+  });
+
+  // Create empty content file if it doesn't exist
+  if (!existsSync(contentPath)) {
+    await writeContent(contentPath, '');
+  }
+}

+ 93 - 0
canvas-tui/src/lib/templates.ts

@@ -0,0 +1,93 @@
+export interface ContentTemplate {
+  name: string;
+  description: string;
+  template: string;
+}
+
+export const templates: Record<string, ContentTemplate> = {
+  email: {
+    name: 'Email',
+    description: 'Professional email format with subject, greeting, and signature',
+    template: `# Email Draft
+
+**To:**
+**Subject:**
+
+---
+
+Hi [Name],
+
+[Your message here]
+
+Best regards,
+[Your name]
+
+---
+*Draft started: ${new Date().toLocaleString()}*
+`
+  },
+
+  message: {
+    name: 'Message',
+    description: 'Casual message format for Slack, Teams, or Discord',
+    template: `# Message Draft
+
+**To:** #channel / @person
+
+---
+
+[Your message here]
+
+---
+*Draft started: ${new Date().toLocaleString()}*
+`
+  },
+
+  doc: {
+    name: 'Document',
+    description: 'Structured markdown document with sections',
+    template: `# Document Title
+
+## Overview
+
+[Brief description of the document's purpose]
+
+## Details
+
+[Main content here]
+
+## Summary
+
+[Key takeaways]
+
+---
+*Draft started: ${new Date().toLocaleString()}*
+`
+  }
+};
+
+/**
+ * Get a template by type, with current timestamp
+ */
+export function getTemplate(type: keyof typeof templates): string {
+  const template = templates[type];
+  if (!template) {
+    return templates.doc.template;
+  }
+  // Replace timestamp placeholder with current time
+  return template.template.replace(
+    /\$\{new Date\(\)\.toLocaleString\(\)\}/g,
+    new Date().toLocaleString()
+  );
+}
+
+/**
+ * List available template types
+ */
+export function listTemplates(): Array<{ type: string; name: string; description: string }> {
+  return Object.entries(templates).map(([type, template]) => ({
+    type,
+    name: template.name,
+    description: template.description
+  }));
+}

+ 31 - 0
canvas-tui/src/types/marked-terminal.d.ts

@@ -0,0 +1,31 @@
+declare module 'marked-terminal' {
+  import { MarkedExtension } from 'marked';
+
+  interface MarkedTerminalOptions {
+    code?: (code: string) => string;
+    blockquote?: (quote: string) => string;
+    html?: (html: string) => string;
+    heading?: (text: string, level: number) => string;
+    firstHeading?: (text: string) => string;
+    hr?: () => string;
+    listitem?: (text: string) => string;
+    list?: (body: string, ordered: boolean) => string;
+    table?: (header: string, body: string) => string;
+    paragraph?: (text: string) => string;
+    strong?: (text: string) => string;
+    em?: (text: string) => string;
+    codespan?: (code: string) => string;
+    del?: (text: string) => string;
+    link?: (href: string, title: string, text: string) => string;
+    href?: (href: string) => string;
+    tableOptions?: {
+      chars?: Record<string, string>;
+    };
+    reflowText?: boolean;
+    width?: number;
+    showSectionPrefix?: boolean;
+    tab?: number;
+  }
+
+  export function markedTerminal(options?: MarkedTerminalOptions): MarkedExtension;
+}

+ 22 - 0
canvas-tui/tsconfig.json

@@ -0,0 +1,22 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",
+    "lib": ["ES2022"],
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "declaration": true,
+    "declarationMap": true,
+    "sourceMap": true,
+    "jsx": "react-jsx",
+    "resolveJsonModule": true,
+    "isolatedModules": true
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules", "dist"]
+}

+ 343 - 0
commands/canvas.md

@@ -0,0 +1,343 @@
+---
+description: "Terminal canvas for content drafting with live preview. Start split-pane sessions for email, message, and document composition. Triggers on: canvas, draft, compose, write content."
+---
+
+# Canvas - Terminal Content Drafting
+
+Terminal canvas for interactive content drafting with Claude. Creates a split-pane experience in Warp terminal where Claude writes content and you see live markdown preview.
+
+## Arguments
+
+$ARGUMENTS
+
+- `start [--type email|message|doc]`: Initialize canvas session
+- `write "content"`: Write/update content in canvas
+- `read`: Read current canvas content back
+- `clear`: Clear canvas content
+- `close`: End canvas session and clean up
+
+## Architecture
+
+```
+/canvas <subcommand> [options]
+    │
+    ├─→ /canvas start [--type email|message|doc]
+    │     ├─ Create .claude/canvas/ directory
+    │     ├─ Initialize content.md with template
+    │     ├─ Initialize meta.json with state
+    │     ├─ Detect Warp terminal
+    │     └─ Output setup instructions
+    │
+    ├─→ /canvas write "content"
+    │     ├─ Write content to .claude/canvas/content.md
+    │     ├─ Update meta.json timestamp
+    │     └─ Canvas TUI auto-refreshes
+    │
+    ├─→ /canvas read
+    │     ├─ Read .claude/canvas/content.md
+    │     └─ Return content for Claude to process
+    │
+    ├─→ /canvas clear
+    │     ├─ Clear content.md (keep structure)
+    │     └─ Reset meta.json
+    │
+    └─→ /canvas close
+          ├─ Optional: copy content to clipboard
+          └─ Clean up .claude/canvas/
+```
+
+---
+
+## Workflow
+
+### Starting a Canvas Session
+
+```
+User: "Help me draft an email to my manager about the project delay"
+
+Claude: I'll help you draft that email. Starting canvas mode...
+
+[Executes internally:]
+1. mkdir -p .claude/canvas
+2. Write email template to .claude/canvas/content.md
+3. Write meta.json with contentType: "email"
+
+[Output:]
+Canvas initialized with email template.
+
+To see live preview, open Warp and:
+1. Press Cmd+Shift+D (or Ctrl+Shift+D on Windows) to split pane
+2. In the new pane, run: npx @claude-mods/canvas-tui --watch
+
+Or use the launch configuration:
+  warp://launch/claude-canvas
+```
+
+### Writing Content
+
+```
+[Claude writes the email draft:]
+
+/canvas write "# Email Draft
+
+**To:** manager@company.com
+**Subject:** Project Timeline Update
+
+---
+
+Hi Sarah,
+
+I wanted to give you a heads-up about a delay in the Phoenix project...
+
+Best regards,
+[Name]"
+
+[Canvas TUI instantly shows the rendered markdown]
+```
+
+### Reading Edits
+
+```
+User: "I edited the email in the canvas, can you make it more formal?"
+
+[Claude reads current content:]
+/canvas read
+
+[Returns content from .claude/canvas/content.md with user's edits]
+
+[Claude can now rewrite based on user's changes]
+```
+
+---
+
+## Execution
+
+### /canvas start
+
+**Step 1: Create IPC Directory**
+
+```bash
+mkdir -p .claude/canvas
+```
+
+**Step 2: Select Template**
+
+Based on `--type` flag (default: doc):
+
+| Type | Template |
+|------|----------|
+| email | Subject line, To/CC fields, greeting, body, signature |
+| message | Casual format for Slack/Teams/Discord |
+| doc | Structured markdown with sections |
+
+**Step 3: Initialize Files**
+
+Write `.claude/canvas/content.md`:
+```markdown
+# Email Draft
+
+**To:**
+**Subject:**
+
+---
+
+Hi [Name],
+
+[Your message here]
+
+Best regards,
+[Your name]
+```
+
+Write `.claude/canvas/meta.json`:
+```json
+{
+  "version": "1.0",
+  "contentType": "email",
+  "mode": "view",
+  "claudeLastWrite": "2025-01-08T10:30:00Z",
+  "userLastEdit": null
+}
+```
+
+**Step 4: Output Instructions**
+
+```
+Canvas ready with email template.
+
+Setup (one-time):
+  npm install -g @claude-mods/canvas-tui
+
+To view canvas:
+  1. Split your terminal (Cmd+Shift+D in Warp)
+  2. Run: canvas-tui --watch
+
+I'll write your content and you'll see it update in real-time.
+```
+
+### /canvas write
+
+**Parameters:**
+- Content (required): Markdown string to write
+
+**Execution:**
+
+1. Ensure `.claude/canvas/` exists
+2. Write content to `.claude/canvas/content.md`
+3. Update `meta.json` with `claudeLastWrite` timestamp
+4. Canvas TUI detects change via chokidar and re-renders
+
+**Output:**
+```
+Content updated in canvas.
+```
+
+### /canvas read
+
+**Execution:**
+
+1. Read `.claude/canvas/content.md`
+2. Return content as string
+
+**Use Case:** After user edits content in canvas, Claude reads it back to incorporate changes.
+
+### /canvas clear
+
+**Execution:**
+
+1. Read current `meta.json` to preserve contentType
+2. Write empty template to `content.md`
+3. Reset timestamps in `meta.json`
+
+### /canvas close
+
+**Execution:**
+
+1. Optionally copy final content to clipboard (if requested)
+2. Remove `.claude/canvas/` directory
+3. Confirm cleanup
+
+---
+
+## Templates
+
+### Email Template
+
+```markdown
+# Email Draft
+
+**To:**
+**CC:**
+**Subject:**
+
+---
+
+Hi [Name],
+
+[Your message here]
+
+Best regards,
+[Your name]
+
+---
+*Draft started: {timestamp}*
+```
+
+### Message Template
+
+```markdown
+# Message Draft
+
+**To:** #channel / @person
+
+---
+
+[Your message here]
+
+---
+*Draft started: {timestamp}*
+```
+
+### Document Template
+
+```markdown
+# Document Title
+
+## Overview
+
+[Brief description]
+
+## Details
+
+[Main content]
+
+## Summary
+
+[Key takeaways]
+
+---
+*Draft started: {timestamp}*
+```
+
+---
+
+## Integration
+
+### Warp Launch Configuration
+
+Install the launch config for one-click split pane setup:
+
+**Location:** `~/.warp/launch_configurations/claude-canvas.yaml`
+
+```yaml
+name: Claude Canvas
+windows:
+  - tabs:
+      - title: Claude Canvas
+        color: Blue
+        layout:
+          split_direction: vertical
+          panes:
+            - is_focused: true
+            - commands:
+                - exec: "npx @claude-mods/canvas-tui --watch"
+```
+
+**Usage:**
+1. Open Warp Command Palette (Cmd+P)
+2. Search "Claude Canvas"
+3. Select to open split layout
+
+### Canvas TUI Package
+
+```bash
+# Install globally
+npm install -g @claude-mods/canvas-tui
+
+# Or run via npx
+npx @claude-mods/canvas-tui --watch
+
+# Options
+canvas-tui --watch              # Watch .claude/canvas/content.md
+canvas-tui --file ./draft.md    # Watch specific file
+canvas-tui --help               # Show help
+```
+
+---
+
+## File Locations
+
+| File | Purpose |
+|------|---------|
+| `.claude/canvas/content.md` | Shared content file |
+| `.claude/canvas/meta.json` | Session metadata |
+| `~/.warp/launch_configurations/claude-canvas.yaml` | Warp split config |
+
+---
+
+## Notes
+
+- Canvas TUI is view-only in MVP; edit mode planned for Phase 2
+- File watching uses chokidar with 100ms debounce
+- Works best with Warp terminal but compatible with any terminal that supports split panes
+- Content persists until `/canvas close` is called

+ 30 - 0
templates/warp/claude-canvas.yaml

@@ -0,0 +1,30 @@
+# Warp Launch Configuration for Claude Canvas
+#
+# Installation:
+#   Windows: Copy to %APPDATA%\warp\Warp\data\launch_configurations\
+#   macOS:   Copy to ~/.warp/launch_configurations/
+#   Linux:   Copy to ~/.warp/launch_configurations/
+#
+# Usage:
+#   1. Open Warp Command Palette (Cmd+P / Ctrl+Shift+P)
+#   2. Search "Claude Canvas"
+#   3. Select to open split layout
+#
+# Or trigger via URI:
+#   warp://launch/claude-canvas
+#
+---
+name: Claude Canvas
+windows:
+  - active_tab_index: 0
+    tabs:
+      - title: Claude Canvas
+        color: Blue
+        layout:
+          split_direction: vertical
+          panes:
+            # Left pane: Your main terminal (Claude Code runs here)
+            - is_focused: true
+            # Right pane: Canvas TUI watching for content
+            - commands:
+                - exec: "npx @claude-mods/canvas-tui --watch"