@colmbus72/yeehaw 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 (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/app.d.ts +1 -0
  4. package/dist/app.js +414 -0
  5. package/dist/components/BarnHeader.d.ts +6 -0
  6. package/dist/components/BarnHeader.js +21 -0
  7. package/dist/components/BottomBar.d.ts +16 -0
  8. package/dist/components/BottomBar.js +7 -0
  9. package/dist/components/Header.d.ts +8 -0
  10. package/dist/components/Header.js +83 -0
  11. package/dist/components/HelpOverlay.d.ts +7 -0
  12. package/dist/components/HelpOverlay.js +17 -0
  13. package/dist/components/List.d.ts +17 -0
  14. package/dist/components/List.js +53 -0
  15. package/dist/components/Markdown.d.ts +8 -0
  16. package/dist/components/Markdown.js +23 -0
  17. package/dist/components/Panel.d.ts +10 -0
  18. package/dist/components/Panel.js +5 -0
  19. package/dist/components/PathInput.d.ts +9 -0
  20. package/dist/components/PathInput.js +141 -0
  21. package/dist/components/ScrollableMarkdown.d.ts +11 -0
  22. package/dist/components/ScrollableMarkdown.js +56 -0
  23. package/dist/components/StatusBar.d.ts +5 -0
  24. package/dist/components/StatusBar.js +20 -0
  25. package/dist/components/TextArea.d.ts +17 -0
  26. package/dist/components/TextArea.js +140 -0
  27. package/dist/components/index.d.ts +5 -0
  28. package/dist/components/index.js +5 -0
  29. package/dist/hooks/index.d.ts +3 -0
  30. package/dist/hooks/index.js +3 -0
  31. package/dist/hooks/useConfig.d.ts +11 -0
  32. package/dist/hooks/useConfig.js +36 -0
  33. package/dist/hooks/useRemoteYeehaw.d.ts +13 -0
  34. package/dist/hooks/useRemoteYeehaw.js +49 -0
  35. package/dist/hooks/useSessions.d.ts +11 -0
  36. package/dist/hooks/useSessions.js +46 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +34 -0
  39. package/dist/lib/config.d.ts +27 -0
  40. package/dist/lib/config.js +150 -0
  41. package/dist/lib/detection.d.ts +16 -0
  42. package/dist/lib/detection.js +41 -0
  43. package/dist/lib/editor.d.ts +5 -0
  44. package/dist/lib/editor.js +35 -0
  45. package/dist/lib/errors.d.ts +28 -0
  46. package/dist/lib/errors.js +48 -0
  47. package/dist/lib/git.d.ts +11 -0
  48. package/dist/lib/git.js +73 -0
  49. package/dist/lib/github.d.ts +43 -0
  50. package/dist/lib/github.js +111 -0
  51. package/dist/lib/hotkeys.d.ts +27 -0
  52. package/dist/lib/hotkeys.js +92 -0
  53. package/dist/lib/index.d.ts +10 -0
  54. package/dist/lib/index.js +10 -0
  55. package/dist/lib/livestock.d.ts +51 -0
  56. package/dist/lib/livestock.js +233 -0
  57. package/dist/lib/mcp-validation.d.ts +33 -0
  58. package/dist/lib/mcp-validation.js +62 -0
  59. package/dist/lib/paths.d.ts +8 -0
  60. package/dist/lib/paths.js +28 -0
  61. package/dist/lib/shell.d.ts +34 -0
  62. package/dist/lib/shell.js +61 -0
  63. package/dist/lib/ssh.d.ts +15 -0
  64. package/dist/lib/ssh.js +77 -0
  65. package/dist/lib/tmux-config.d.ts +3 -0
  66. package/dist/lib/tmux-config.js +42 -0
  67. package/dist/lib/tmux.d.ts +32 -0
  68. package/dist/lib/tmux.js +397 -0
  69. package/dist/mcp-server.d.ts +23 -0
  70. package/dist/mcp-server.js +825 -0
  71. package/dist/types.d.ts +89 -0
  72. package/dist/types.js +2 -0
  73. package/dist/views/BarnContext.d.ts +22 -0
  74. package/dist/views/BarnContext.js +252 -0
  75. package/dist/views/GlobalDashboard.d.ts +16 -0
  76. package/dist/views/GlobalDashboard.js +253 -0
  77. package/dist/views/Home.d.ts +11 -0
  78. package/dist/views/Home.js +27 -0
  79. package/dist/views/IssuesView.d.ts +7 -0
  80. package/dist/views/IssuesView.js +157 -0
  81. package/dist/views/LivestockDetailView.d.ts +11 -0
  82. package/dist/views/LivestockDetailView.js +140 -0
  83. package/dist/views/LogsView.d.ts +8 -0
  84. package/dist/views/LogsView.js +84 -0
  85. package/dist/views/NightSkyView.d.ts +5 -0
  86. package/dist/views/NightSkyView.js +441 -0
  87. package/dist/views/ProjectContext.d.ts +18 -0
  88. package/dist/views/ProjectContext.js +333 -0
  89. package/dist/views/Projects.d.ts +8 -0
  90. package/dist/views/Projects.js +20 -0
  91. package/dist/views/WikiView.d.ts +8 -0
  92. package/dist/views/WikiView.js +138 -0
  93. package/dist/views/index.d.ts +2 -0
  94. package/dist/views/index.js +2 -0
  95. package/package.json +65 -0
@@ -0,0 +1,8 @@
1
+ export declare const YEEHAW_DIR: string;
2
+ export declare const CONFIG_FILE: string;
3
+ export declare const PROJECTS_DIR: string;
4
+ export declare const BARNS_DIR: string;
5
+ export declare const SESSIONS_DIR: string;
6
+ export declare function getProjectPath(name: string): string;
7
+ export declare function getBarnPath(name: string): string;
8
+ export declare function getSessionPath(id: string): string;
@@ -0,0 +1,28 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ export const YEEHAW_DIR = join(homedir(), '.yeehaw');
4
+ export const CONFIG_FILE = join(YEEHAW_DIR, 'config.yaml');
5
+ export const PROJECTS_DIR = join(YEEHAW_DIR, 'projects');
6
+ export const BARNS_DIR = join(YEEHAW_DIR, 'barns');
7
+ export const SESSIONS_DIR = join(YEEHAW_DIR, 'sessions');
8
+ /**
9
+ * Validate a name to prevent path traversal attacks.
10
+ * Rejects names containing path separators or parent directory references.
11
+ */
12
+ function validateName(name, type) {
13
+ if (name.includes('/') || name.includes('\\') || name.includes('..') || name.includes('\0')) {
14
+ throw new Error(`Invalid ${type} name: contains forbidden characters`);
15
+ }
16
+ }
17
+ export function getProjectPath(name) {
18
+ validateName(name, 'project');
19
+ return join(PROJECTS_DIR, `${name}.yaml`);
20
+ }
21
+ export function getBarnPath(name) {
22
+ validateName(name, 'barn');
23
+ return join(BARNS_DIR, `${name}.yaml`);
24
+ }
25
+ export function getSessionPath(id) {
26
+ validateName(id, 'session');
27
+ return join(SESSIONS_DIR, `${id}.yaml`);
28
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Escape a string for safe use in a single-quoted shell argument.
3
+ * Single quotes are the safest quoting mechanism in shell - they prevent
4
+ * all interpretation except for single quotes themselves.
5
+ *
6
+ * Strategy: Replace ' with '\'' (end quote, escaped quote, start quote)
7
+ * Then wrap the whole thing in single quotes.
8
+ *
9
+ * Example: "foo'bar" becomes "'foo'\''bar'"
10
+ */
11
+ export declare function shellEscape(str: string): string;
12
+ /**
13
+ * Escape multiple arguments and join with spaces
14
+ */
15
+ export declare function shellEscapeArgs(...args: string[]): string;
16
+ /**
17
+ * Build a safe shell command from a template and arguments.
18
+ * Replaces {0}, {1}, etc. with escaped versions of the arguments.
19
+ *
20
+ * Example:
21
+ * shellCommand('cat {0}', '/path/with spaces')
22
+ * // Returns: "cat '/path/with spaces'"
23
+ *
24
+ * shellCommand('grep -i {0} {1}', 'pattern', '/path')
25
+ * // Returns: "grep -i 'pattern' '/path'"
26
+ */
27
+ export declare function shellCommand(template: string, ...args: string[]): string;
28
+ /**
29
+ * Escape a value for safe use in double quotes (for cases where we need variable expansion)
30
+ * This is less safe than single quotes but sometimes necessary.
31
+ *
32
+ * Escapes: $ ` \ " ! (and newlines)
33
+ */
34
+ export declare function shellEscapeDouble(str: string): string;
@@ -0,0 +1,61 @@
1
+ // src/lib/shell.ts
2
+ /**
3
+ * Escape a string for safe use in a single-quoted shell argument.
4
+ * Single quotes are the safest quoting mechanism in shell - they prevent
5
+ * all interpretation except for single quotes themselves.
6
+ *
7
+ * Strategy: Replace ' with '\'' (end quote, escaped quote, start quote)
8
+ * Then wrap the whole thing in single quotes.
9
+ *
10
+ * Example: "foo'bar" becomes "'foo'\''bar'"
11
+ */
12
+ export function shellEscape(str) {
13
+ // Handle empty string
14
+ if (str === '') {
15
+ return "''";
16
+ }
17
+ // If string contains no special characters, we can use it as-is
18
+ // But for safety, always quote
19
+ const escaped = str.replace(/'/g, "'\\''");
20
+ return `'${escaped}'`;
21
+ }
22
+ /**
23
+ * Escape multiple arguments and join with spaces
24
+ */
25
+ export function shellEscapeArgs(...args) {
26
+ return args.map(shellEscape).join(' ');
27
+ }
28
+ /**
29
+ * Build a safe shell command from a template and arguments.
30
+ * Replaces {0}, {1}, etc. with escaped versions of the arguments.
31
+ *
32
+ * Example:
33
+ * shellCommand('cat {0}', '/path/with spaces')
34
+ * // Returns: "cat '/path/with spaces'"
35
+ *
36
+ * shellCommand('grep -i {0} {1}', 'pattern', '/path')
37
+ * // Returns: "grep -i 'pattern' '/path'"
38
+ */
39
+ export function shellCommand(template, ...args) {
40
+ return template.replace(/\{(\d+)\}/g, (_, index) => {
41
+ const i = parseInt(index, 10);
42
+ if (i < args.length) {
43
+ return shellEscape(args[i]);
44
+ }
45
+ return `{${index}}`; // Leave unreplaced if no matching arg
46
+ });
47
+ }
48
+ /**
49
+ * Escape a value for safe use in double quotes (for cases where we need variable expansion)
50
+ * This is less safe than single quotes but sometimes necessary.
51
+ *
52
+ * Escapes: $ ` \ " ! (and newlines)
53
+ */
54
+ export function shellEscapeDouble(str) {
55
+ return str
56
+ .replace(/\\/g, '\\\\')
57
+ .replace(/"/g, '\\"')
58
+ .replace(/\$/g, '\\$')
59
+ .replace(/`/g, '\\`')
60
+ .replace(/!/g, '\\!');
61
+ }
@@ -0,0 +1,15 @@
1
+ export interface SshHost {
2
+ name: string;
3
+ hostname?: string;
4
+ user?: string;
5
+ port?: number;
6
+ identityFile?: string;
7
+ }
8
+ /**
9
+ * Parse ~/.ssh/config to find configured hosts
10
+ */
11
+ export declare function parseSshConfig(): SshHost[];
12
+ /**
13
+ * Get a list of SSH host names for quick selection
14
+ */
15
+ export declare function getSshHostNames(): string[];
@@ -0,0 +1,77 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ /**
5
+ * Parse ~/.ssh/config to find configured hosts
6
+ */
7
+ export function parseSshConfig() {
8
+ const configPath = join(homedir(), '.ssh', 'config');
9
+ if (!existsSync(configPath)) {
10
+ return [];
11
+ }
12
+ try {
13
+ const content = readFileSync(configPath, 'utf-8');
14
+ const lines = content.split('\n');
15
+ const hosts = [];
16
+ let currentHost = null;
17
+ for (const line of lines) {
18
+ const trimmed = line.trim();
19
+ // Skip comments and empty lines
20
+ if (trimmed.startsWith('#') || trimmed === '') {
21
+ continue;
22
+ }
23
+ // Parse key-value pairs
24
+ const match = trimmed.match(/^(\S+)\s+(.+)$/);
25
+ if (!match)
26
+ continue;
27
+ const [, key, value] = match;
28
+ const keyLower = key.toLowerCase();
29
+ if (keyLower === 'host') {
30
+ // Skip wildcard hosts
31
+ if (value.includes('*')) {
32
+ currentHost = null;
33
+ continue;
34
+ }
35
+ // Save previous host if exists
36
+ if (currentHost) {
37
+ hosts.push(currentHost);
38
+ }
39
+ currentHost = { name: value };
40
+ }
41
+ else if (currentHost) {
42
+ switch (keyLower) {
43
+ case 'hostname':
44
+ currentHost.hostname = value;
45
+ break;
46
+ case 'user':
47
+ currentHost.user = value;
48
+ break;
49
+ case 'port':
50
+ currentHost.port = parseInt(value, 10);
51
+ break;
52
+ case 'identityfile':
53
+ // Expand ~ in path
54
+ currentHost.identityFile = value.startsWith('~/')
55
+ ? join(homedir(), value.slice(2))
56
+ : value;
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ // Don't forget the last host
62
+ if (currentHost) {
63
+ hosts.push(currentHost);
64
+ }
65
+ // Filter out hosts without hostname (they're just aliases)
66
+ return hosts.filter((h) => h.hostname || h.name);
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
72
+ /**
73
+ * Get a list of SSH host names for quick selection
74
+ */
75
+ export function getSshHostNames() {
76
+ return parseSshConfig().map((h) => h.name);
77
+ }
@@ -0,0 +1,3 @@
1
+ export declare const TMUX_CONFIG_PATH: string;
2
+ export declare function generateTmuxConfig(): string;
3
+ export declare function writeTmuxConfig(): string;
@@ -0,0 +1,42 @@
1
+ import { writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { YEEHAW_DIR } from './paths.js';
4
+ export const TMUX_CONFIG_PATH = join(YEEHAW_DIR, 'tmux.conf');
5
+ export function generateTmuxConfig() {
6
+ return `
7
+ # Yeehaw tmux configuration
8
+ # Auto-generated - do not edit manually
9
+
10
+ # Scrollback and mouse support
11
+ set -g mouse on
12
+ set -g history-limit 50000
13
+
14
+ # Yeehaw keybindings
15
+ bind-key -n C-y select-window -t :0 # Return to dashboard
16
+ bind-key -n C-h previous-window # Go left one window
17
+ bind-key -n C-l next-window # Go right one window
18
+
19
+ # Status bar styling (Yeehaw brand colors)
20
+ set -g status-style "bg=#b8860b,fg=#1a1a1a"
21
+ set -g status-left "#[bold] YEEHAW "
22
+ set -g status-left-length 20
23
+ set -g status-right " Ctrl-y: dashboard "
24
+ set -g status-right-length 30
25
+
26
+ # Window status format
27
+ set -g window-status-format " #I:#W "
28
+ set -g window-status-current-format "#[bg=#daa520,fg=#1a1a1a,bold] #I:#W "
29
+
30
+ # Pane border styling
31
+ set -g pane-border-style "fg=#b8860b"
32
+ set -g pane-active-border-style "fg=#daa520"
33
+
34
+ # Message styling
35
+ set -g message-style "bg=#b8860b,fg=#1a1a1a"
36
+ `.trim();
37
+ }
38
+ export function writeTmuxConfig() {
39
+ const content = generateTmuxConfig();
40
+ writeFileSync(TMUX_CONFIG_PATH, content);
41
+ return TMUX_CONFIG_PATH;
42
+ }
@@ -0,0 +1,32 @@
1
+ export declare const YEEHAW_SESSION = "yeehaw";
2
+ export interface TmuxWindow {
3
+ index: number;
4
+ name: string;
5
+ active: boolean;
6
+ paneTitle: string;
7
+ paneCurrentCommand: string;
8
+ windowActivity: number;
9
+ }
10
+ export declare function hasTmux(): boolean;
11
+ export declare function isInsideYeehawSession(): boolean;
12
+ export declare function yeehawSessionExists(): boolean;
13
+ export declare function createYeehawSession(): void;
14
+ export declare function setupStatusBarHooks(): void;
15
+ export declare function attachToYeehaw(): void;
16
+ export declare function createClaudeWindow(workingDir: string, windowName: string): number;
17
+ export declare function createShellWindow(workingDir: string, windowName: string, shell?: string): number;
18
+ export declare function createSshWindow(windowName: string, host: string, user: string, port: number, identityFile: string, remotePath: string): number;
19
+ export declare function detachFromSession(): void;
20
+ export declare function killYeehawSession(): void;
21
+ export declare function switchToWindow(windowIndex: number): void;
22
+ export declare function listYeehawWindows(): TmuxWindow[];
23
+ export declare function killWindow(windowIndex: number): void;
24
+ /**
25
+ * Get formatted status text for a tmux window
26
+ */
27
+ export declare function getWindowStatus(window: TmuxWindow): string;
28
+ export declare function updateStatusBar(projectName?: string): void;
29
+ export declare function enterRemoteMode(barnName: string, host: string, user: string, port: number, identityFile: string): number;
30
+ export declare function exitRemoteMode(): void;
31
+ export declare function isInRemoteMode(): boolean;
32
+ export declare function getRemoteWindowIndex(): number | null;