@colmbus72/yeehaw 0.4.2 → 0.5.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.
@@ -0,0 +1,201 @@
1
+ import { execa } from 'execa';
2
+ import { buildSshCommand } from './livestock.js';
3
+ import { shellEscape } from './shell.js';
4
+ import { getErrorMessage } from './errors.js';
5
+ /**
6
+ * Services that are interesting for developers (databases, web servers, etc.)
7
+ */
8
+ const INTERESTING_SERVICE_PATTERNS = [
9
+ // Databases
10
+ 'mysql', 'mariadb', 'postgres', 'postgresql', 'mongodb', 'mongo',
11
+ // Caches
12
+ 'redis', 'memcached',
13
+ // Web servers
14
+ 'nginx', 'apache', 'httpd', 'caddy',
15
+ // PHP
16
+ 'php-fpm', 'php7', 'php8',
17
+ // Python
18
+ 'gunicorn', 'uvicorn', 'celery',
19
+ // Node
20
+ 'node', 'pm2',
21
+ // Mail
22
+ 'postfix', 'dovecot',
23
+ // Queue
24
+ 'rabbitmq', 'kafka',
25
+ // Search
26
+ 'elasticsearch', 'opensearch', 'meilisearch',
27
+ // Other
28
+ 'supervisor', 'docker',
29
+ ];
30
+ /**
31
+ * Check if a service name matches any interesting pattern
32
+ */
33
+ function isInterestingService(serviceName) {
34
+ const lower = serviceName.toLowerCase();
35
+ return INTERESTING_SERVICE_PATTERNS.some(pattern => lower.includes(pattern));
36
+ }
37
+ /**
38
+ * Extract a friendly name from a service name
39
+ * e.g., "mysql.service" -> "mysql", "php8.1-fpm.service" -> "php8.1-fpm"
40
+ */
41
+ function extractServiceName(service) {
42
+ return service.replace(/\.service$/, '');
43
+ }
44
+ /**
45
+ * Read logs from a critter (via journald or custom path)
46
+ */
47
+ export async function readCritterLogs(critter, barn, options = {}) {
48
+ const { lines = 100, pattern } = options;
49
+ const escapedLines = String(lines);
50
+ let cmd;
51
+ if (critter.use_journald !== false) {
52
+ // Use journalctl
53
+ const escapedService = shellEscape(critter.service);
54
+ cmd = `journalctl -u ${escapedService} -n ${escapedLines} --no-pager`;
55
+ if (pattern) {
56
+ const escapedPattern = shellEscape(pattern);
57
+ cmd += ` | grep -i ${escapedPattern} || true`;
58
+ }
59
+ }
60
+ else if (critter.log_path) {
61
+ // Use custom log path
62
+ const escapedLogPath = shellEscape(critter.log_path);
63
+ cmd = `tail -n ${escapedLines} ${escapedLogPath}`;
64
+ if (pattern) {
65
+ const escapedPattern = shellEscape(pattern);
66
+ cmd += ` | grep -i ${escapedPattern} || true`;
67
+ }
68
+ }
69
+ else {
70
+ return { content: '', error: 'Critter has no log_path and use_journald is disabled' };
71
+ }
72
+ // Local barn
73
+ if (barn.name === 'local') {
74
+ try {
75
+ const result = await execa('sh', ['-c', cmd]);
76
+ if (!result.stdout.trim()) {
77
+ return { content: '', error: `No logs found for ${critter.name}` };
78
+ }
79
+ return { content: result.stdout };
80
+ }
81
+ catch (err) {
82
+ return { content: '', error: `Failed to read logs: ${getErrorMessage(err)}` };
83
+ }
84
+ }
85
+ // Remote barn - SSH
86
+ if (!barn.host || !barn.user) {
87
+ return { content: '', error: `Barn '${barn.name}' is not configured for SSH` };
88
+ }
89
+ try {
90
+ const sshArgs = buildSshCommand(barn);
91
+ const result = await execa(sshArgs[0], [...sshArgs.slice(1), cmd]);
92
+ if (!result.stdout.trim()) {
93
+ return { content: '', error: `No logs found for ${critter.name}` };
94
+ }
95
+ return { content: result.stdout };
96
+ }
97
+ catch (err) {
98
+ return { content: '', error: `SSH error: ${getErrorMessage(err)}` };
99
+ }
100
+ }
101
+ /**
102
+ * Parse systemctl show output into key-value pairs
103
+ */
104
+ function parseSystemctlShow(output) {
105
+ const result = {};
106
+ for (const line of output.split('\n')) {
107
+ const eqIndex = line.indexOf('=');
108
+ if (eqIndex !== -1) {
109
+ const key = line.slice(0, eqIndex);
110
+ const value = line.slice(eqIndex + 1);
111
+ result[key] = value;
112
+ }
113
+ }
114
+ return result;
115
+ }
116
+ /**
117
+ * Discover critters (running services) on a barn
118
+ */
119
+ export async function discoverCritters(barn) {
120
+ // Command to list running services
121
+ const listCmd = 'systemctl list-units --type=service --state=running --no-pager --plain';
122
+ let output;
123
+ // Local barn
124
+ if (barn.name === 'local') {
125
+ try {
126
+ const result = await execa('sh', ['-c', listCmd]);
127
+ output = result.stdout;
128
+ }
129
+ catch (err) {
130
+ return { critters: [], error: `Failed to list services: ${getErrorMessage(err)}` };
131
+ }
132
+ }
133
+ else {
134
+ // Remote barn - SSH
135
+ if (!barn.host || !barn.user) {
136
+ return { critters: [], error: `Barn '${barn.name}' is not configured for SSH` };
137
+ }
138
+ try {
139
+ const sshArgs = buildSshCommand(barn);
140
+ const result = await execa(sshArgs[0], [...sshArgs.slice(1), listCmd]);
141
+ output = result.stdout;
142
+ }
143
+ catch (err) {
144
+ return { critters: [], error: `SSH error: ${getErrorMessage(err)}` };
145
+ }
146
+ }
147
+ // Parse the output - each line: "unit.service loaded active running description"
148
+ const lines = output.split('\n').filter(line => line.trim());
149
+ const discovered = [];
150
+ for (const line of lines) {
151
+ const parts = line.trim().split(/\s+/);
152
+ if (parts.length < 1)
153
+ continue;
154
+ const service = parts[0];
155
+ if (!service.endsWith('.service'))
156
+ continue;
157
+ if (!isInterestingService(service))
158
+ continue;
159
+ const suggested_name = extractServiceName(service);
160
+ // Try to get more details about this service
161
+ let binary;
162
+ let config_path;
163
+ const showCmd = `systemctl show ${shellEscape(service)} --property=ExecStart`;
164
+ try {
165
+ let showOutput;
166
+ if (barn.name === 'local') {
167
+ const showResult = await execa('sh', ['-c', showCmd]);
168
+ showOutput = showResult.stdout;
169
+ }
170
+ else {
171
+ const sshArgs = buildSshCommand(barn);
172
+ const showResult = await execa(sshArgs[0], [...sshArgs.slice(1), showCmd]);
173
+ showOutput = showResult.stdout;
174
+ }
175
+ const props = parseSystemctlShow(showOutput);
176
+ // Extract binary from ExecStart (format: { path=/usr/bin/mysqld ; argv[]=... })
177
+ if (props.ExecStart) {
178
+ const pathMatch = props.ExecStart.match(/path=([^\s;]+)/);
179
+ if (pathMatch) {
180
+ binary = pathMatch[1];
181
+ }
182
+ // Try to find config flags like --config= or -c
183
+ const configMatch = props.ExecStart.match(/--config[=\s]([^\s;]+)/);
184
+ if (configMatch) {
185
+ config_path = configMatch[1];
186
+ }
187
+ }
188
+ }
189
+ catch {
190
+ // Ignore errors getting details - just use basic info
191
+ }
192
+ discovered.push({
193
+ service,
194
+ suggested_name,
195
+ binary,
196
+ config_path,
197
+ status: 'running',
198
+ });
199
+ }
200
+ return { critters: discovered };
201
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Install the Claude hook script to ~/.yeehaw/bin/
3
+ */
4
+ export declare function installHookScript(): string;
5
+ /**
6
+ * Get the path to the hook script
7
+ */
8
+ export declare function getHookScriptPath(): string;
9
+ /**
10
+ * Get the Claude settings.json hooks configuration
11
+ */
12
+ export declare function getClaudeHooksConfig(): object;
13
+ /**
14
+ * Check if Claude hooks are already configured
15
+ */
16
+ export declare function checkClaudeHooksInstalled(): boolean;
17
+ /**
18
+ * Check if hook script exists
19
+ */
20
+ export declare function hookScriptExists(): boolean;
@@ -0,0 +1,91 @@
1
+ import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ import { HOOKS_DIR, SIGNALS_DIR } from './paths.js';
5
+ const HOOK_SCRIPT_NAME = 'claude-hook';
6
+ const HOOK_SCRIPT_CONTENT = `#!/bin/bash
7
+ # Yeehaw Claude Hook - writes session status for the CLI to read
8
+ STATUS="$1"
9
+ PANE_ID="\${TMUX_PANE:-unknown}"
10
+ SIGNAL_DIR="$HOME/.yeehaw/session-signals"
11
+ SIGNAL_FILE="$SIGNAL_DIR/\${PANE_ID//[^a-zA-Z0-9]/_}.json"
12
+
13
+ mkdir -p "$SIGNAL_DIR"
14
+ cat > "$SIGNAL_FILE" << EOF
15
+ {"status":"$STATUS","updated":$(date +%s)}
16
+ EOF
17
+ `;
18
+ /**
19
+ * Install the Claude hook script to ~/.yeehaw/bin/
20
+ */
21
+ export function installHookScript() {
22
+ // Ensure directories exist
23
+ if (!existsSync(HOOKS_DIR)) {
24
+ mkdirSync(HOOKS_DIR, { recursive: true });
25
+ }
26
+ if (!existsSync(SIGNALS_DIR)) {
27
+ mkdirSync(SIGNALS_DIR, { recursive: true });
28
+ }
29
+ const scriptPath = join(HOOKS_DIR, HOOK_SCRIPT_NAME);
30
+ writeFileSync(scriptPath, HOOK_SCRIPT_CONTENT, 'utf-8');
31
+ chmodSync(scriptPath, 0o755);
32
+ return scriptPath;
33
+ }
34
+ /**
35
+ * Get the path to the hook script
36
+ */
37
+ export function getHookScriptPath() {
38
+ return join(HOOKS_DIR, HOOK_SCRIPT_NAME);
39
+ }
40
+ /**
41
+ * Get the Claude settings.json hooks configuration
42
+ */
43
+ export function getClaudeHooksConfig() {
44
+ const hookPath = join(HOOKS_DIR, HOOK_SCRIPT_NAME);
45
+ return {
46
+ hooks: {
47
+ PreToolUse: [
48
+ {
49
+ matcher: '*',
50
+ hooks: [`${hookPath} working`],
51
+ },
52
+ ],
53
+ Stop: [
54
+ {
55
+ matcher: '*',
56
+ hooks: [`${hookPath} waiting`],
57
+ },
58
+ ],
59
+ Notification: [
60
+ {
61
+ matcher: 'idle_prompt',
62
+ hooks: [`${hookPath} waiting`],
63
+ },
64
+ ],
65
+ },
66
+ };
67
+ }
68
+ /**
69
+ * Check if Claude hooks are already configured
70
+ */
71
+ export function checkClaudeHooksInstalled() {
72
+ const claudeSettingsPath = join(homedir(), '.claude', 'settings.json');
73
+ if (!existsSync(claudeSettingsPath)) {
74
+ return false;
75
+ }
76
+ try {
77
+ const content = readFileSync(claudeSettingsPath, 'utf-8');
78
+ const settings = JSON.parse(content);
79
+ return settings.hooks?.PreToolUse?.some((h) => h.hooks?.some((cmd) => cmd.includes('yeehaw')));
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ /**
86
+ * Check if hook script exists
87
+ */
88
+ export function hookScriptExists() {
89
+ const scriptPath = join(HOOKS_DIR, HOOK_SCRIPT_NAME);
90
+ return existsSync(scriptPath);
91
+ }
@@ -2,38 +2,42 @@
2
2
  export const HOTKEYS = [
3
3
  // === GLOBAL (everywhere) ===
4
4
  { key: '?', description: 'Toggle help', category: 'system', scopes: ['global'] },
5
- { key: 'q', description: 'Back / Detach', category: 'system', scopes: ['global'] },
5
+ { key: 'q', description: 'Detach', category: 'system', scopes: ['global-dashboard'] },
6
+ { key: 'Q', description: 'Quit Yeehaw', category: 'system', scopes: ['global-dashboard'] },
6
7
  { key: 'Esc', description: 'Back / Cancel', category: 'system', scopes: ['global'] },
7
8
  // === LIST NAVIGATION (any focused list) ===
8
- { key: 'j/k', description: 'Navigate up/down', category: 'navigation', scopes: ['list'] },
9
- { key: 'g/G', description: 'Go to first/last', category: 'navigation', scopes: ['list'] },
10
- { key: 'Enter', description: 'Select item', category: 'navigation', scopes: ['list'] },
9
+ { key: 'j/k', description: 'Move up/down', category: 'navigation', scopes: ['list'] },
10
+ { key: 'g/G', description: 'Jump to first/last', category: 'navigation', scopes: ['list'] },
11
+ { key: 'Enter', description: 'Open selected item', category: 'navigation', scopes: ['list'] },
11
12
  // === CONTENT NAVIGATION (markdown panels) ===
12
13
  { key: 'j/k', description: 'Scroll up/down', category: 'navigation', scopes: ['content'] },
13
14
  { key: 'g/G', description: 'Jump to top/bottom', category: 'navigation', scopes: ['content'] },
14
15
  { key: 'PgUp/PgDn', description: 'Scroll page', category: 'navigation', scopes: ['content'] },
15
- // === GLOBAL DASHBOARD ===
16
+ // === PANEL NAVIGATION ===
16
17
  { key: 'Tab', description: 'Switch panel', category: 'navigation', scopes: ['global-dashboard', 'project-context', 'barn-context', 'wiki-view', 'issues-view'] },
17
- { key: 'Q', description: 'Quit Yeehaw', category: 'system', scopes: ['global-dashboard'] },
18
- { key: 'c', description: 'New Claude session', category: 'action', scopes: ['global-dashboard', 'project-context'] },
19
- { key: '1-9', description: 'Quick switch window', category: 'action', scopes: ['global-dashboard', 'project-context'] },
20
- { key: 's', description: 'Open shell session', category: 'action', scopes: ['global-dashboard', 'project-context', 'barn-context', 'livestock-detail'], panel: 'barns' },
21
- // === CONTEXT-AWARE ACTIONS ===
18
+ // === GLOBAL SESSION SWITCHING ===
19
+ { key: '1-9', description: 'Switch to session', category: 'action', scopes: ['global-dashboard', 'project-context'] },
20
+ // === ROW-LEVEL ACTIONS (shown on selected rows) ===
21
+ { key: 'c', description: 'Claude session (at path)', category: 'action', scopes: ['global-dashboard'], panel: 'projects' },
22
+ { key: 'c', description: 'Claude session (at path)', category: 'action', scopes: ['project-context'], panel: 'livestock' },
23
+ { key: 's', description: 'Shell into server', category: 'action', scopes: ['global-dashboard'], panel: 'barns' },
24
+ { key: 's', description: 'Shell into server', category: 'action', scopes: ['project-context', 'barn-context'], panel: 'livestock' },
25
+ // === CARD/PAGE-LEVEL ACTIONS ===
22
26
  { key: 'n', description: 'New (in focused panel)', category: 'action', scopes: ['global-dashboard', 'project-context', 'barn-context', 'wiki-view'] },
23
- { key: 'e', description: 'Edit selected', category: 'action', scopes: ['project-context', 'barn-context', 'wiki-view'] },
24
- { key: 'd', description: 'Delete (with confirm)', category: 'action', scopes: ['project-context', 'barn-context', 'wiki-view'] },
25
- { key: 'D', description: 'Delete container (type name)', category: 'action', scopes: ['project-context', 'barn-context'] },
26
- // === PROJECT CONTEXT ===
27
+ { key: 'e', description: 'Edit', category: 'action', scopes: ['project-context', 'barn-context', 'wiki-view', 'livestock-detail'] },
28
+ { key: 'd', description: 'Delete', category: 'action', scopes: ['project-context', 'barn-context', 'wiki-view'] },
29
+ { key: 'D', description: 'Delete container', category: 'action', scopes: ['project-context', 'barn-context'] },
30
+ // === PROJECT CONTEXT PAGE-LEVEL ===
27
31
  { key: 'w', description: 'Open wiki', category: 'action', scopes: ['project-context'] },
28
32
  { key: 'i', description: 'Open issues', category: 'action', scopes: ['project-context'] },
33
+ // === LIVESTOCK DETAIL PAGE-LEVEL ===
34
+ { key: 'c', description: 'Claude session', category: 'action', scopes: ['livestock-detail'] },
35
+ { key: 's', description: 'Shell session', category: 'action', scopes: ['livestock-detail'] },
36
+ { key: 'l', description: 'View logs', category: 'action', scopes: ['livestock-detail'] },
29
37
  // === ISSUES VIEW ===
30
- { key: 'r', description: 'Refresh issues', category: 'action', scopes: ['issues-view'] },
38
+ { key: 'r', description: 'Refresh', category: 'action', scopes: ['issues-view', 'logs-view'] },
31
39
  { key: 'o', description: 'Open in browser', category: 'action', scopes: ['issues-view'] },
32
- // === LIVESTOCK DETAIL ===
33
- { key: 'l', description: 'View logs', category: 'action', scopes: ['livestock-detail'] },
34
- // === LOGS VIEW ===
35
- { key: 'r', description: 'Refresh logs', category: 'action', scopes: ['logs-view'] },
36
- // === NIGHT SKY (screensaver) ===
40
+ // === NIGHT SKY ===
37
41
  { key: 'v', description: 'Visualizer', category: 'navigation', scopes: ['global-dashboard'] },
38
42
  { key: 'c', description: 'Spawn cloud', category: 'action', scopes: ['night-sky'] },
39
43
  { key: 'r', description: 'Randomize', category: 'action', scopes: ['night-sky'] },
@@ -3,6 +3,8 @@ export declare const CONFIG_FILE: string;
3
3
  export declare const PROJECTS_DIR: string;
4
4
  export declare const BARNS_DIR: string;
5
5
  export declare const SESSIONS_DIR: string;
6
+ export declare const SIGNALS_DIR: string;
7
+ export declare const HOOKS_DIR: string;
6
8
  export declare function getProjectPath(name: string): string;
7
9
  export declare function getBarnPath(name: string): string;
8
10
  export declare function getSessionPath(id: string): string;
package/dist/lib/paths.js CHANGED
@@ -5,6 +5,8 @@ export const CONFIG_FILE = join(YEEHAW_DIR, 'config.yaml');
5
5
  export const PROJECTS_DIR = join(YEEHAW_DIR, 'projects');
6
6
  export const BARNS_DIR = join(YEEHAW_DIR, 'barns');
7
7
  export const SESSIONS_DIR = join(YEEHAW_DIR, 'sessions');
8
+ export const SIGNALS_DIR = join(YEEHAW_DIR, 'session-signals');
9
+ export const HOOKS_DIR = join(YEEHAW_DIR, 'bin');
8
10
  /**
9
11
  * Validate a name to prevent path traversal attacks.
10
12
  * Rejects names containing path separators or parent directory references.
@@ -0,0 +1,30 @@
1
+ export type SessionStatus = 'working' | 'waiting' | 'idle' | 'error';
2
+ export interface SessionSignal {
3
+ status: SessionStatus;
4
+ updated: number;
5
+ }
6
+ export interface WindowStatusInfo {
7
+ text: string;
8
+ status: SessionStatus;
9
+ icon: string;
10
+ }
11
+ /**
12
+ * Read signal file for a tmux pane
13
+ */
14
+ export declare function readSignal(paneId: string): SessionSignal | null;
15
+ /**
16
+ * Get status icon for a session status
17
+ */
18
+ export declare function getStatusIcon(status: SessionStatus): string;
19
+ /**
20
+ * Ensure the signals directory exists
21
+ */
22
+ export declare function ensureSignalsDir(): void;
23
+ /**
24
+ * Clean up signal file for a pane
25
+ */
26
+ export declare function cleanupSignal(paneId: string): void;
27
+ /**
28
+ * Clean up all stale signal files (older than 1 hour)
29
+ */
30
+ export declare function cleanupStaleSignals(): void;
@@ -0,0 +1,104 @@
1
+ import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { SIGNALS_DIR } from './paths.js';
4
+ const STATUS_ICONS = {
5
+ working: '⠿',
6
+ waiting: '◆',
7
+ idle: '○',
8
+ error: '✖',
9
+ };
10
+ const SIGNAL_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
11
+ /**
12
+ * Sanitize pane ID to create a safe filename
13
+ */
14
+ function sanitizePaneId(paneId) {
15
+ return paneId.replace(/[^a-zA-Z0-9]/g, '_');
16
+ }
17
+ /**
18
+ * Read signal file for a tmux pane
19
+ */
20
+ export function readSignal(paneId) {
21
+ if (!existsSync(SIGNALS_DIR))
22
+ return null;
23
+ const filename = `${sanitizePaneId(paneId)}.json`;
24
+ const filepath = join(SIGNALS_DIR, filename);
25
+ if (!existsSync(filepath))
26
+ return null;
27
+ try {
28
+ const content = readFileSync(filepath, 'utf-8');
29
+ const signal = JSON.parse(content);
30
+ // Check if signal is stale
31
+ const ageMs = Date.now() - signal.updated * 1000;
32
+ if (ageMs > SIGNAL_MAX_AGE_MS) {
33
+ return null;
34
+ }
35
+ return signal;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * Get status icon for a session status
43
+ */
44
+ export function getStatusIcon(status) {
45
+ return STATUS_ICONS[status];
46
+ }
47
+ /**
48
+ * Ensure the signals directory exists
49
+ */
50
+ export function ensureSignalsDir() {
51
+ if (!existsSync(SIGNALS_DIR)) {
52
+ mkdirSync(SIGNALS_DIR, { recursive: true });
53
+ }
54
+ }
55
+ /**
56
+ * Clean up signal file for a pane
57
+ */
58
+ export function cleanupSignal(paneId) {
59
+ const filename = `${sanitizePaneId(paneId)}.json`;
60
+ const filepath = join(SIGNALS_DIR, filename);
61
+ try {
62
+ if (existsSync(filepath)) {
63
+ unlinkSync(filepath);
64
+ }
65
+ }
66
+ catch {
67
+ // Ignore cleanup errors
68
+ }
69
+ }
70
+ /**
71
+ * Clean up all stale signal files (older than 1 hour)
72
+ */
73
+ export function cleanupStaleSignals() {
74
+ if (!existsSync(SIGNALS_DIR))
75
+ return;
76
+ const STALE_AGE_MS = 60 * 60 * 1000; // 1 hour
77
+ const now = Date.now();
78
+ try {
79
+ const files = readdirSync(SIGNALS_DIR).filter(f => f.endsWith('.json'));
80
+ for (const file of files) {
81
+ const filepath = join(SIGNALS_DIR, file);
82
+ try {
83
+ const content = readFileSync(filepath, 'utf-8');
84
+ const signal = JSON.parse(content);
85
+ const ageMs = now - signal.updated * 1000;
86
+ if (ageMs > STALE_AGE_MS) {
87
+ unlinkSync(filepath);
88
+ }
89
+ }
90
+ catch {
91
+ // Delete malformed files
92
+ try {
93
+ unlinkSync(filepath);
94
+ }
95
+ catch {
96
+ // Ignore
97
+ }
98
+ }
99
+ }
100
+ }
101
+ catch {
102
+ // Ignore errors
103
+ }
104
+ }
@@ -1,11 +1,16 @@
1
+ import { type WindowStatusInfo } from './signals.js';
2
+ export type { WindowStatusInfo, SessionStatus } from './signals.js';
1
3
  export declare const YEEHAW_SESSION = "yeehaw";
4
+ export type WindowType = 'claude' | 'shell' | 'ssh' | '';
2
5
  export interface TmuxWindow {
3
6
  index: number;
4
7
  name: string;
5
8
  active: boolean;
9
+ paneId: string;
6
10
  paneTitle: string;
7
11
  paneCurrentCommand: string;
8
12
  windowActivity: number;
13
+ type: WindowType;
9
14
  }
10
15
  export declare function hasTmux(): boolean;
11
16
  export declare function isInsideYeehawSession(): boolean;
@@ -23,13 +28,14 @@ export declare function createShellWindow(workingDir: string, windowName: string
23
28
  export declare function createSshWindow(windowName: string, host: string, user: string, port: number, identityFile: string, remotePath: string): number;
24
29
  export declare function detachFromSession(): void;
25
30
  export declare function killYeehawSession(): void;
31
+ export declare function restartYeehaw(): void;
26
32
  export declare function switchToWindow(windowIndex: number): void;
27
33
  export declare function listYeehawWindows(): TmuxWindow[];
28
34
  export declare function killWindow(windowIndex: number): void;
29
35
  /**
30
- * Get formatted status text for a tmux window
36
+ * Get formatted status info for a tmux window
31
37
  */
32
- export declare function getWindowStatus(window: TmuxWindow): string;
38
+ export declare function getWindowStatus(window: TmuxWindow): WindowStatusInfo;
33
39
  export declare function updateStatusBar(projectName?: string): void;
34
40
  export declare function enterRemoteMode(barnName: string, host: string, user: string, port: number, identityFile: string): number;
35
41
  export declare function exitRemoteMode(): void;