index.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. #!/usr/bin/env node
  2. import React from 'react';
  3. import fs from 'fs';
  4. import path from 'path';
  5. import { render } from 'ink';
  6. import meow from 'meow';
  7. import { App } from './app.js';
  8. const cli = meow(`
  9. Usage
  10. $ canvas [options]
  11. Options
  12. --watch, -w Watch directory (default: .claude/canvas)
  13. --file, -f Specific file to watch
  14. --no-mouse Disable mouse wheel scrolling
  15. --help Show this help
  16. --version Show version
  17. Examples
  18. $ canvas # Just run it - defaults work
  19. $ canvas --file ./draft.md # Watch specific file
  20. Controls
  21. ↑↓ / Mouse wheel Scroll content
  22. g / G Top / bottom
  23. Tab Open file selector
  24. e Edit in external editor
  25. m Toggle mouse capture
  26. q Quit
  27. Terminal Setup
  28. Warp: Ctrl+Shift+D to split, run 'canvas' in new pane
  29. tmux: tmux split-window -h 'canvas'
  30. iTerm2: Cmd+D to split, run 'canvas' in new pane
  31. `, {
  32. importMeta: import.meta,
  33. flags: {
  34. watch: {
  35. type: 'string',
  36. shortFlag: 'w',
  37. default: '.claude/canvas'
  38. },
  39. file: {
  40. type: 'string',
  41. shortFlag: 'f'
  42. },
  43. noMouse: {
  44. type: 'boolean',
  45. default: false
  46. }
  47. }
  48. });
  49. // Find canvas directory by searching up from CWD
  50. function findCanvasDir(startDir: string = process.cwd()): string | null {
  51. let current = startDir;
  52. const root = path.parse(current).root;
  53. while (current !== root) {
  54. const candidate = path.join(current, '.claude', 'canvas');
  55. if (fs.existsSync(candidate)) {
  56. return candidate;
  57. }
  58. current = path.dirname(current);
  59. }
  60. return null;
  61. }
  62. // Determine watch directory - find it automatically or use explicit path
  63. function getWatchDir(explicitPath: string): string {
  64. // If user specified absolute path, use it
  65. if (path.isAbsolute(explicitPath)) {
  66. return explicitPath;
  67. }
  68. // Try to find .claude/canvas by searching up
  69. const found = findCanvasDir();
  70. if (found) {
  71. return found;
  72. }
  73. // Fall back to relative path from CWD
  74. return path.resolve(explicitPath);
  75. }
  76. // Determine initial file to watch
  77. function getInitialFile(watchDir: string, specificFile?: string): string {
  78. if (specificFile) return path.resolve(specificFile);
  79. // Check drafts directory first
  80. const draftsDir = path.join(watchDir, 'drafts');
  81. try {
  82. if (fs.existsSync(draftsDir)) {
  83. const files = fs.readdirSync(draftsDir)
  84. .filter(f => f.endsWith('.md') || f.endsWith('.txt'))
  85. .sort((a, b) => {
  86. const statA = fs.statSync(path.join(draftsDir, a));
  87. const statB = fs.statSync(path.join(draftsDir, b));
  88. return statB.mtime.getTime() - statA.mtime.getTime();
  89. });
  90. if (files.length > 0) {
  91. return path.join(draftsDir, files[0]);
  92. }
  93. }
  94. } catch {
  95. // Fall through to default
  96. }
  97. // Fallback to content.md in watch dir
  98. return path.join(watchDir, 'content.md');
  99. }
  100. const watchDir = getWatchDir(cli.flags.watch);
  101. const watchPath = getInitialFile(watchDir, cli.flags.file);
  102. render(<App watchPath={watchPath} watchDir={watchDir} enableMouse={!cli.flags.noMouse} />);