@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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +414 -0
- package/dist/components/BarnHeader.d.ts +6 -0
- package/dist/components/BarnHeader.js +21 -0
- package/dist/components/BottomBar.d.ts +16 -0
- package/dist/components/BottomBar.js +7 -0
- package/dist/components/Header.d.ts +8 -0
- package/dist/components/Header.js +83 -0
- package/dist/components/HelpOverlay.d.ts +7 -0
- package/dist/components/HelpOverlay.js +17 -0
- package/dist/components/List.d.ts +17 -0
- package/dist/components/List.js +53 -0
- package/dist/components/Markdown.d.ts +8 -0
- package/dist/components/Markdown.js +23 -0
- package/dist/components/Panel.d.ts +10 -0
- package/dist/components/Panel.js +5 -0
- package/dist/components/PathInput.d.ts +9 -0
- package/dist/components/PathInput.js +141 -0
- package/dist/components/ScrollableMarkdown.d.ts +11 -0
- package/dist/components/ScrollableMarkdown.js +56 -0
- package/dist/components/StatusBar.d.ts +5 -0
- package/dist/components/StatusBar.js +20 -0
- package/dist/components/TextArea.d.ts +17 -0
- package/dist/components/TextArea.js +140 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +5 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/useConfig.d.ts +11 -0
- package/dist/hooks/useConfig.js +36 -0
- package/dist/hooks/useRemoteYeehaw.d.ts +13 -0
- package/dist/hooks/useRemoteYeehaw.js +49 -0
- package/dist/hooks/useSessions.d.ts +11 -0
- package/dist/hooks/useSessions.js +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +34 -0
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.js +150 -0
- package/dist/lib/detection.d.ts +16 -0
- package/dist/lib/detection.js +41 -0
- package/dist/lib/editor.d.ts +5 -0
- package/dist/lib/editor.js +35 -0
- package/dist/lib/errors.d.ts +28 -0
- package/dist/lib/errors.js +48 -0
- package/dist/lib/git.d.ts +11 -0
- package/dist/lib/git.js +73 -0
- package/dist/lib/github.d.ts +43 -0
- package/dist/lib/github.js +111 -0
- package/dist/lib/hotkeys.d.ts +27 -0
- package/dist/lib/hotkeys.js +92 -0
- package/dist/lib/index.d.ts +10 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/livestock.d.ts +51 -0
- package/dist/lib/livestock.js +233 -0
- package/dist/lib/mcp-validation.d.ts +33 -0
- package/dist/lib/mcp-validation.js +62 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +28 -0
- package/dist/lib/shell.d.ts +34 -0
- package/dist/lib/shell.js +61 -0
- package/dist/lib/ssh.d.ts +15 -0
- package/dist/lib/ssh.js +77 -0
- package/dist/lib/tmux-config.d.ts +3 -0
- package/dist/lib/tmux-config.js +42 -0
- package/dist/lib/tmux.d.ts +32 -0
- package/dist/lib/tmux.js +397 -0
- package/dist/mcp-server.d.ts +23 -0
- package/dist/mcp-server.js +825 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +2 -0
- package/dist/views/BarnContext.d.ts +22 -0
- package/dist/views/BarnContext.js +252 -0
- package/dist/views/GlobalDashboard.d.ts +16 -0
- package/dist/views/GlobalDashboard.js +253 -0
- package/dist/views/Home.d.ts +11 -0
- package/dist/views/Home.js +27 -0
- package/dist/views/IssuesView.d.ts +7 -0
- package/dist/views/IssuesView.js +157 -0
- package/dist/views/LivestockDetailView.d.ts +11 -0
- package/dist/views/LivestockDetailView.js +140 -0
- package/dist/views/LogsView.d.ts +8 -0
- package/dist/views/LogsView.js +84 -0
- package/dist/views/NightSkyView.d.ts +5 -0
- package/dist/views/NightSkyView.js +441 -0
- package/dist/views/ProjectContext.d.ts +18 -0
- package/dist/views/ProjectContext.js +333 -0
- package/dist/views/Projects.d.ts +8 -0
- package/dist/views/Projects.js +20 -0
- package/dist/views/WikiView.d.ts +8 -0
- package/dist/views/WikiView.js +138 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +2 -0
- 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[];
|
package/dist/lib/ssh.js
ADDED
|
@@ -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,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;
|