@lovelybunch/api 1.0.7

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 (131) hide show
  1. package/dist/lib/gait-path.d.ts +13 -0
  2. package/dist/lib/gait-path.js +57 -0
  3. package/dist/lib/project-paths.d.ts +13 -0
  4. package/dist/lib/project-paths.js +57 -0
  5. package/dist/lib/storage/file-storage.d.ts +28 -0
  6. package/dist/lib/storage/file-storage.js +224 -0
  7. package/dist/lib/symlinks/symlink-manager.d.ts +66 -0
  8. package/dist/lib/symlinks/symlink-manager.js +444 -0
  9. package/dist/lib/symlinks/types.d.ts +23 -0
  10. package/dist/lib/symlinks/types.js +4 -0
  11. package/dist/lib/terminal/context-helper.d.ts +11 -0
  12. package/dist/lib/terminal/context-helper.js +164 -0
  13. package/dist/lib/terminal/global-manager.d.ts +2 -0
  14. package/dist/lib/terminal/global-manager.js +15 -0
  15. package/dist/lib/terminal/shell-utils.d.ts +33 -0
  16. package/dist/lib/terminal/shell-utils.js +176 -0
  17. package/dist/lib/terminal/terminal-manager.d.ts +26 -0
  18. package/dist/lib/terminal/terminal-manager.js +276 -0
  19. package/dist/lib/user-preferences.d.ts +48 -0
  20. package/dist/lib/user-preferences.js +87 -0
  21. package/dist/lib/utils.d.ts +2 -0
  22. package/dist/lib/utils.js +5 -0
  23. package/dist/routes/api/symlink-status/route.d.ts +1 -0
  24. package/dist/routes/api/symlink-status/route.js +37 -0
  25. package/dist/routes/api/symlinks/[id]/route.d.ts +19 -0
  26. package/dist/routes/api/symlinks/[id]/route.js +95 -0
  27. package/dist/routes/api/symlinks/[id]/toggle/route.d.ts +11 -0
  28. package/dist/routes/api/symlinks/[id]/toggle/route.js +32 -0
  29. package/dist/routes/api/symlinks/debug/route.d.ts +1 -0
  30. package/dist/routes/api/symlinks/debug/route.js +35 -0
  31. package/dist/routes/api/symlinks/route.d.ts +9 -0
  32. package/dist/routes/api/symlinks/route.js +72 -0
  33. package/dist/routes/api/toggle-symlink/route.d.ts +2 -0
  34. package/dist/routes/api/toggle-symlink/route.js +94 -0
  35. package/dist/routes/api/v1/agents/[id]/index.d.ts +1 -0
  36. package/dist/routes/api/v1/agents/[id]/index.js +1 -0
  37. package/dist/routes/api/v1/agents/[id]/route.d.ts +3 -0
  38. package/dist/routes/api/v1/agents/[id]/route.js +163 -0
  39. package/dist/routes/api/v1/agents/index.d.ts +1 -0
  40. package/dist/routes/api/v1/agents/index.js +1 -0
  41. package/dist/routes/api/v1/agents/route.d.ts +3 -0
  42. package/dist/routes/api/v1/agents/route.js +133 -0
  43. package/dist/routes/api/v1/ai/index.d.ts +3 -0
  44. package/dist/routes/api/v1/ai/index.js +5 -0
  45. package/dist/routes/api/v1/ai/route.d.ts +8 -0
  46. package/dist/routes/api/v1/ai/route.js +86 -0
  47. package/dist/routes/api/v1/chats/[id]/index.d.ts +3 -0
  48. package/dist/routes/api/v1/chats/[id]/index.js +6 -0
  49. package/dist/routes/api/v1/chats/[id]/route.d.ts +12 -0
  50. package/dist/routes/api/v1/chats/[id]/route.js +31 -0
  51. package/dist/routes/api/v1/chats/index.d.ts +3 -0
  52. package/dist/routes/api/v1/chats/index.js +6 -0
  53. package/dist/routes/api/v1/chats/route.d.ts +32 -0
  54. package/dist/routes/api/v1/chats/route.js +67 -0
  55. package/dist/routes/api/v1/config/index.d.ts +3 -0
  56. package/dist/routes/api/v1/config/index.js +5 -0
  57. package/dist/routes/api/v1/config/route.d.ts +9 -0
  58. package/dist/routes/api/v1/config/route.js +29 -0
  59. package/dist/routes/api/v1/context/[...path]/route.d.ts +16 -0
  60. package/dist/routes/api/v1/context/[...path]/route.js +107 -0
  61. package/dist/routes/api/v1/context/architecture/route.d.ts +3 -0
  62. package/dist/routes/api/v1/context/architecture/route.js +198 -0
  63. package/dist/routes/api/v1/context/index.d.ts +3 -0
  64. package/dist/routes/api/v1/context/index.js +9 -0
  65. package/dist/routes/api/v1/context/knowledge/[filename]/index.d.ts +1 -0
  66. package/dist/routes/api/v1/context/knowledge/[filename]/index.js +1 -0
  67. package/dist/routes/api/v1/context/knowledge/[filename]/route.d.ts +3 -0
  68. package/dist/routes/api/v1/context/knowledge/[filename]/route.js +165 -0
  69. package/dist/routes/api/v1/context/knowledge/index.d.ts +1 -0
  70. package/dist/routes/api/v1/context/knowledge/index.js +1 -0
  71. package/dist/routes/api/v1/context/knowledge/route.d.ts +3 -0
  72. package/dist/routes/api/v1/context/knowledge/route.js +121 -0
  73. package/dist/routes/api/v1/context/project/route.d.ts +3 -0
  74. package/dist/routes/api/v1/context/project/route.js +153 -0
  75. package/dist/routes/api/v1/proposals/[id]/route.d.ts +337 -0
  76. package/dist/routes/api/v1/proposals/[id]/route.js +99 -0
  77. package/dist/routes/api/v1/proposals/index.d.ts +3 -0
  78. package/dist/routes/api/v1/proposals/index.js +10 -0
  79. package/dist/routes/api/v1/proposals/route.d.ts +315 -0
  80. package/dist/routes/api/v1/proposals/route.js +103 -0
  81. package/dist/routes/api/v1/resources/[id]/index.d.ts +3 -0
  82. package/dist/routes/api/v1/resources/[id]/index.js +7 -0
  83. package/dist/routes/api/v1/resources/[id]/route.d.ts +46 -0
  84. package/dist/routes/api/v1/resources/[id]/route.js +143 -0
  85. package/dist/routes/api/v1/resources/[id]/thumbnail/index.d.ts +3 -0
  86. package/dist/routes/api/v1/resources/[id]/thumbnail/index.js +5 -0
  87. package/dist/routes/api/v1/resources/[id]/thumbnail/route.d.ts +2 -0
  88. package/dist/routes/api/v1/resources/[id]/thumbnail/route.js +50 -0
  89. package/dist/routes/api/v1/resources/index.d.ts +3 -0
  90. package/dist/routes/api/v1/resources/index.js +6 -0
  91. package/dist/routes/api/v1/resources/route.d.ts +51 -0
  92. package/dist/routes/api/v1/resources/route.js +147 -0
  93. package/dist/routes/api/v1/search/route.d.ts +3 -0
  94. package/dist/routes/api/v1/search/route.js +39 -0
  95. package/dist/routes/api/v1/terminal/[proposalId]/create/index.d.ts +3 -0
  96. package/dist/routes/api/v1/terminal/[proposalId]/create/index.js +5 -0
  97. package/dist/routes/api/v1/terminal/[proposalId]/create/route.d.ts +10 -0
  98. package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +27 -0
  99. package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.d.ts +3 -0
  100. package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.js +5 -0
  101. package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.d.ts +10 -0
  102. package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.js +21 -0
  103. package/dist/routes/api/v1/terminal/[proposalId]/resize/index.d.ts +3 -0
  104. package/dist/routes/api/v1/terminal/[proposalId]/resize/index.js +5 -0
  105. package/dist/routes/api/v1/terminal/[proposalId]/resize/route.d.ts +10 -0
  106. package/dist/routes/api/v1/terminal/[proposalId]/resize/route.js +21 -0
  107. package/dist/routes/api/v1/terminal/sessions/index.d.ts +3 -0
  108. package/dist/routes/api/v1/terminal/sessions/index.js +5 -0
  109. package/dist/routes/api/v1/terminal/sessions/route.d.ts +6 -0
  110. package/dist/routes/api/v1/terminal/sessions/route.js +29 -0
  111. package/dist/routes/api/v1/user/index.d.ts +3 -0
  112. package/dist/routes/api/v1/user/index.js +5 -0
  113. package/dist/routes/api/v1/user/preferences/route.d.ts +11 -0
  114. package/dist/routes/api/v1/user/preferences/route.js +31 -0
  115. package/dist/routes/api/v1/user/profile/route.d.ts +11 -0
  116. package/dist/routes/api/v1/user/profile/route.js +31 -0
  117. package/dist/routes/api/v1/user/settings/index.d.ts +1 -0
  118. package/dist/routes/api/v1/user/settings/index.js +1 -0
  119. package/dist/routes/api/v1/user/settings/route.d.ts +3 -0
  120. package/dist/routes/api/v1/user/settings/route.js +51 -0
  121. package/dist/server-with-static.d.ts +4 -0
  122. package/dist/server-with-static.js +144 -0
  123. package/dist/server.d.ts +1 -0
  124. package/dist/server.js +91 -0
  125. package/package.json +42 -0
  126. package/static/assets/index-BvTnrm0O.js +576 -0
  127. package/static/assets/index-Cm5dZHTl.css +33 -0
  128. package/static/assets/index-ORkAkJNi.js +576 -0
  129. package/static/assets/index-_Keadpms.js +576 -0
  130. package/static/index.html +17 -0
  131. package/static/vite.svg +1 -0
@@ -0,0 +1,33 @@
1
+ export type ShellType = 'bash' | 'zsh' | 'fish' | 'sh' | 'system';
2
+ export interface ShellInfo {
3
+ name: ShellType;
4
+ displayName: string;
5
+ path: string;
6
+ available: boolean;
7
+ }
8
+ /**
9
+ * Get the default system shell
10
+ */
11
+ export declare function getSystemShell(): string;
12
+ /**
13
+ * Get information about available shells on the system
14
+ */
15
+ export declare function getAvailableShells(): Promise<ShellInfo[]>;
16
+ /**
17
+ * Get the shell executable path based on user preference
18
+ */
19
+ export declare function getShellPath(preference?: ShellType): Promise<string>;
20
+ /**
21
+ * Get shell arguments for spawning a new shell with an init script
22
+ */
23
+ export declare function getShellArgs(shellPath: string, initScriptPath: string): string[];
24
+ /**
25
+ * Prepare shell-specific initialization
26
+ */
27
+ export declare function prepareShellInit(shellPath: string, initScript: string): {
28
+ script: string;
29
+ needsEnvVar?: {
30
+ name: string;
31
+ value: string;
32
+ };
33
+ };
@@ -0,0 +1,176 @@
1
+ import { promises as fs } from 'fs';
2
+ import os from 'os';
3
+ /**
4
+ * Get the default system shell
5
+ */
6
+ export function getSystemShell() {
7
+ // Get the user's default shell from environment or passwd
8
+ const userShell = process.env.SHELL || '/bin/bash';
9
+ return userShell;
10
+ }
11
+ /**
12
+ * Check if a shell executable exists and is accessible
13
+ */
14
+ async function isShellAvailable(shellPath) {
15
+ try {
16
+ await fs.access(shellPath, fs.constants.X_OK);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ /**
24
+ * Get information about available shells on the system
25
+ */
26
+ export async function getAvailableShells() {
27
+ const shells = [
28
+ {
29
+ name: 'bash',
30
+ displayName: 'Bash',
31
+ path: '/bin/bash',
32
+ available: false,
33
+ },
34
+ {
35
+ name: 'zsh',
36
+ displayName: 'Zsh',
37
+ path: '/bin/zsh',
38
+ available: false,
39
+ },
40
+ {
41
+ name: 'fish',
42
+ displayName: 'Fish',
43
+ path: '/usr/local/bin/fish',
44
+ available: false,
45
+ },
46
+ {
47
+ name: 'sh',
48
+ displayName: 'Sh (Bourne Shell)',
49
+ path: '/bin/sh',
50
+ available: false,
51
+ },
52
+ {
53
+ name: 'system',
54
+ displayName: 'System Default',
55
+ path: getSystemShell(),
56
+ available: true,
57
+ },
58
+ ];
59
+ // Check availability of each shell
60
+ for (const shell of shells) {
61
+ if (shell.name !== 'system') {
62
+ shell.available = await isShellAvailable(shell.path);
63
+ // For fish, also check alternative location
64
+ if (shell.name === 'fish' && !shell.available) {
65
+ const altPath = '/opt/homebrew/bin/fish';
66
+ if (await isShellAvailable(altPath)) {
67
+ shell.path = altPath;
68
+ shell.available = true;
69
+ }
70
+ }
71
+ // For zsh on macOS, check alternative locations
72
+ if (shell.name === 'zsh' && !shell.available && os.platform() === 'darwin') {
73
+ const altPaths = ['/usr/local/bin/zsh', '/opt/homebrew/bin/zsh'];
74
+ for (const altPath of altPaths) {
75
+ if (await isShellAvailable(altPath)) {
76
+ shell.path = altPath;
77
+ shell.available = true;
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ return shells;
85
+ }
86
+ /**
87
+ * Get the shell executable path based on user preference
88
+ */
89
+ export async function getShellPath(preference = 'bash') {
90
+ if (preference === 'system') {
91
+ return getSystemShell();
92
+ }
93
+ const shells = await getAvailableShells();
94
+ const selectedShell = shells.find(s => s.name === preference);
95
+ if (selectedShell && selectedShell.available) {
96
+ return selectedShell.path;
97
+ }
98
+ // Fallback to bash if preferred shell is not available
99
+ const bashShell = shells.find(s => s.name === 'bash');
100
+ if (bashShell && bashShell.available) {
101
+ return bashShell.path;
102
+ }
103
+ // Ultimate fallback to system shell
104
+ return getSystemShell();
105
+ }
106
+ /**
107
+ * Get shell arguments for spawning a new shell with an init script
108
+ */
109
+ export function getShellArgs(shellPath, initScriptPath) {
110
+ const shellName = shellPath.split('/').pop() || 'bash';
111
+ switch (shellName) {
112
+ case 'bash':
113
+ return ['--init-file', initScriptPath];
114
+ case 'zsh':
115
+ // For zsh, we need to use ZDOTDIR environment variable
116
+ // The init script will need to be named .zshrc in a temp directory
117
+ return ['-i'];
118
+ case 'fish':
119
+ // Fish uses --init-command
120
+ return ['--init-command', `source ${initScriptPath}`];
121
+ case 'sh':
122
+ // Basic sh doesn't support init files the same way
123
+ // We'll need to source it manually after start
124
+ return [];
125
+ default:
126
+ // Try bash-style for unknown shells
127
+ return ['--init-file', initScriptPath];
128
+ }
129
+ }
130
+ /**
131
+ * Prepare shell-specific initialization
132
+ */
133
+ export function prepareShellInit(shellPath, initScript) {
134
+ const shellName = shellPath.split('/').pop() || 'bash';
135
+ switch (shellName) {
136
+ case 'zsh':
137
+ // For zsh, we need to create a proper .zshrc that sources the system one first
138
+ // Use proper zsh syntax and prevent content echo
139
+ // Also fix the prompt to use zsh syntax
140
+ const zshScript = initScript
141
+ // Replace bash-style prompt with zsh-style prompt
142
+ .replace(/export PS1=.*$/m, 'export PROMPT="%F{cyan}[${GAIT_PROPOSAL_ID}]%f %~ %# "');
143
+ return {
144
+ script: `#!/bin/zsh
145
+ # GAIT Zsh Initialization
146
+
147
+ # Source system zshrc if it exists (suppress any output)
148
+ [ -f ~/.zshrc ] && source ~/.zshrc > /dev/null 2>&1 || true
149
+
150
+ # Custom GAIT initialization
151
+ ${zshScript}`,
152
+ needsEnvVar: { name: 'ZDOTDIR', value: '' } // Will be set to temp dir
153
+ };
154
+ case 'fish':
155
+ // Fish script syntax is different
156
+ const fishScript = initScript
157
+ .replace(/export\\s+(\\w+)=(.*)/g, 'set -gx $1 $2')
158
+ .replace(/alias\\s+(\\w+)=(.*)/g, 'alias $1 $2')
159
+ // Fish prompt syntax
160
+ .replace(/export PS1=.*$/m, 'function fish_prompt\n echo -n (set_color cyan)"[$GAIT_PROPOSAL_ID]"(set_color normal)" "(prompt_pwd)" > "\nend');
161
+ return {
162
+ script: fishScript
163
+ };
164
+ case 'bash':
165
+ default:
166
+ // Bash uses the script with proper PS1 escaping
167
+ const bashScript = initScript.replace(/export PS1="\[(.*)\] \\w \$ "/, 'export PS1="\\[\\033[36m\\][$GAIT_PROPOSAL_ID]\\[\\033[0m\\] \\w \\$ "');
168
+ return {
169
+ script: `# Source system bashrc if it exists
170
+ [ -f ~/.bashrc ] && source ~/.bashrc
171
+
172
+ # Custom GAIT initialization
173
+ ${bashScript}`
174
+ };
175
+ }
176
+ }
@@ -0,0 +1,26 @@
1
+ import { WebSocket } from 'ws';
2
+ import { ShellType } from './shell-utils.js';
3
+ export interface TerminalSession {
4
+ id: string;
5
+ proposalId: string;
6
+ pty: any;
7
+ websocket?: WebSocket;
8
+ createdAt: Date;
9
+ lastActivity: Date;
10
+ enableLogging?: boolean;
11
+ logFilePath?: string;
12
+ }
13
+ export declare class TerminalManager {
14
+ private sessions;
15
+ private cleanupInterval;
16
+ constructor();
17
+ createSession(proposalId: string, enableLogging?: boolean, shellPreference?: ShellType): Promise<TerminalSession>;
18
+ getSession(sessionId: string): TerminalSession | undefined;
19
+ getSessionsByProposal(proposalId: string): TerminalSession[];
20
+ attachWebSocket(sessionId: string, ws: WebSocket): boolean;
21
+ destroySession(sessionId: string): boolean;
22
+ resizeSession(sessionId: string, cols: number, rows: number): boolean;
23
+ private cleanupInactiveSessions;
24
+ getAllSessions(): TerminalSession[];
25
+ destroy(): void;
26
+ }
@@ -0,0 +1,276 @@
1
+ import * as pty from 'node-pty';
2
+ import { WebSocket } from 'ws';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { createInitScript } from './context-helper.js';
6
+ import { getShellPath, getShellArgs, prepareShellInit } from './shell-utils.js';
7
+ export class TerminalManager {
8
+ sessions = new Map();
9
+ cleanupInterval;
10
+ constructor() {
11
+ // Clean up inactive sessions every 5 minutes
12
+ this.cleanupInterval = setInterval(() => {
13
+ this.cleanupInactiveSessions();
14
+ }, 5 * 60 * 1000);
15
+ }
16
+ async createSession(proposalId, enableLogging = false, shellPreference = 'bash') {
17
+ const sessionId = `${proposalId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
18
+ // Get the project root directory
19
+ const projectRoot = process.env.GAIT_DATA_PATH ?
20
+ path.resolve(process.env.GAIT_DATA_PATH) :
21
+ process.cwd();
22
+ // Prepare context information
23
+ const contextInfo = {
24
+ proposalId,
25
+ proposalPath: path.join(projectRoot, '.gait', 'proposals', `${proposalId}.md`),
26
+ contextPath: path.join(projectRoot, '.gait', 'context'),
27
+ projectRoot,
28
+ };
29
+ // Get the shell path based on user preference
30
+ const shellPath = await getShellPath(shellPreference);
31
+ const shellName = shellPath.split('/').pop() || 'bash';
32
+ // Create initialization script
33
+ const baseInitScript = createInitScript(contextInfo);
34
+ const { script: initScript, needsEnvVar } = prepareShellInit(shellPath, baseInitScript);
35
+ // Determine init script path and name based on shell
36
+ let initScriptPath;
37
+ let tempDirForZsh;
38
+ if (shellName === 'zsh' && needsEnvVar) {
39
+ // For zsh, create a temp directory with .zshrc
40
+ tempDirForZsh = path.join(projectRoot, '.gait', 'tmp', `zsh-${sessionId}`);
41
+ fs.mkdirSync(tempDirForZsh, { recursive: true });
42
+ initScriptPath = path.join(tempDirForZsh, '.zshrc');
43
+ }
44
+ else {
45
+ // For other shells, use regular init script
46
+ initScriptPath = path.join(projectRoot, '.gait', 'tmp', `init-${sessionId}.sh`);
47
+ const tmpDir = path.dirname(initScriptPath);
48
+ fs.mkdirSync(tmpDir, { recursive: true });
49
+ }
50
+ try {
51
+ fs.writeFileSync(initScriptPath, initScript);
52
+ fs.chmodSync(initScriptPath, '755');
53
+ }
54
+ catch (error) {
55
+ console.error('Error creating init script:', error);
56
+ }
57
+ // Set up logging if enabled
58
+ let logFilePath;
59
+ if (enableLogging) {
60
+ const sessionsDir = path.join(projectRoot, '.gait', 'sessions');
61
+ try {
62
+ fs.mkdirSync(sessionsDir, { recursive: true });
63
+ logFilePath = path.join(sessionsDir, `${sessionId}.log`);
64
+ // Create log file with session header
65
+ const logHeader = `# Terminal Session Log\n# Session ID: ${sessionId}\n# Proposal ID: ${proposalId}\n# Created: ${new Date().toISOString()}\n\n`;
66
+ fs.writeFileSync(logFilePath, logHeader);
67
+ }
68
+ catch (error) {
69
+ console.error('Error setting up session logging:', error);
70
+ logFilePath = undefined;
71
+ }
72
+ }
73
+ // Get shell arguments
74
+ const shellArgs = getShellArgs(shellPath, initScriptPath);
75
+ // Prepare environment variables
76
+ const env = {
77
+ ...process.env,
78
+ GAIT_PROPOSAL_ID: proposalId,
79
+ GAIT_CONTEXT_PATH: path.join(projectRoot, '.gait', 'context'),
80
+ GAIT_PROPOSAL_PATH: path.join(projectRoot, '.gait', 'proposals', `${proposalId}.md`),
81
+ TERM: 'xterm-256color',
82
+ COLORTERM: 'truecolor',
83
+ };
84
+ // Add special environment variable for zsh if needed
85
+ if (tempDirForZsh) {
86
+ env.ZDOTDIR = tempDirForZsh;
87
+ }
88
+ // Create PTY with environment variables for context
89
+ const ptyProcess = pty.spawn(shellPath, shellArgs, {
90
+ name: 'xterm-color',
91
+ cols: 80,
92
+ rows: 24,
93
+ cwd: projectRoot,
94
+ env,
95
+ });
96
+ const session = {
97
+ id: sessionId,
98
+ proposalId,
99
+ pty: ptyProcess,
100
+ createdAt: new Date(),
101
+ lastActivity: new Date(),
102
+ enableLogging,
103
+ logFilePath,
104
+ };
105
+ this.sessions.set(sessionId, session);
106
+ // Set up PTY event handlers
107
+ ptyProcess.onData((data) => {
108
+ session.lastActivity = new Date();
109
+ // Log data if logging is enabled
110
+ if (session.enableLogging && session.logFilePath) {
111
+ try {
112
+ const timestamp = new Date().toISOString();
113
+ const logEntry = `[${timestamp}] OUTPUT: ${data}`;
114
+ fs.appendFileSync(session.logFilePath, logEntry);
115
+ }
116
+ catch (error) {
117
+ console.error('Error writing to session log:', error);
118
+ }
119
+ }
120
+ if (session.websocket && session.websocket.readyState === WebSocket.OPEN) {
121
+ session.websocket.send(JSON.stringify({
122
+ type: 'data',
123
+ data: data,
124
+ }));
125
+ }
126
+ });
127
+ ptyProcess.onExit((e) => {
128
+ // Log session end if logging is enabled
129
+ if (session.enableLogging && session.logFilePath) {
130
+ try {
131
+ const timestamp = new Date().toISOString();
132
+ const logEntry = `\n[${timestamp}] SESSION ENDED: Exit code ${e.exitCode}\n`;
133
+ fs.appendFileSync(session.logFilePath, logEntry);
134
+ }
135
+ catch (error) {
136
+ console.error('Error writing session end to log:', error);
137
+ }
138
+ }
139
+ // Clean up init script and temp directories
140
+ try {
141
+ if (fs.existsSync(initScriptPath)) {
142
+ fs.unlinkSync(initScriptPath);
143
+ }
144
+ // Clean up zsh temp directory if it exists
145
+ if (tempDirForZsh && fs.existsSync(tempDirForZsh)) {
146
+ fs.rmSync(tempDirForZsh, { recursive: true, force: true });
147
+ }
148
+ }
149
+ catch (error) {
150
+ console.error('Error cleaning up init script:', error);
151
+ }
152
+ if (session.websocket && session.websocket.readyState === WebSocket.OPEN) {
153
+ session.websocket.send(JSON.stringify({
154
+ type: 'exit',
155
+ exitCode: e.exitCode,
156
+ }));
157
+ }
158
+ this.destroySession(sessionId);
159
+ });
160
+ return session;
161
+ }
162
+ getSession(sessionId) {
163
+ return this.sessions.get(sessionId);
164
+ }
165
+ getSessionsByProposal(proposalId) {
166
+ return Array.from(this.sessions.values()).filter(session => session.proposalId === proposalId);
167
+ }
168
+ attachWebSocket(sessionId, ws) {
169
+ const session = this.sessions.get(sessionId);
170
+ if (!session) {
171
+ return false;
172
+ }
173
+ session.websocket = ws;
174
+ session.lastActivity = new Date();
175
+ // Set up WebSocket event handlers
176
+ ws.on('message', (message) => {
177
+ try {
178
+ const data = JSON.parse(message.toString());
179
+ switch (data.type) {
180
+ case 'input':
181
+ // Log input if logging is enabled
182
+ if (session.enableLogging && session.logFilePath) {
183
+ try {
184
+ const timestamp = new Date().toISOString();
185
+ const logEntry = `[${timestamp}] INPUT: ${data.data}`;
186
+ fs.appendFileSync(session.logFilePath, logEntry);
187
+ }
188
+ catch (error) {
189
+ console.error('Error writing input to session log:', error);
190
+ }
191
+ }
192
+ session.pty.write(data.data);
193
+ session.lastActivity = new Date();
194
+ break;
195
+ case 'resize':
196
+ session.pty.resize(data.cols, data.rows);
197
+ break;
198
+ }
199
+ }
200
+ catch (error) {
201
+ console.error('Error processing WebSocket message:', error);
202
+ }
203
+ });
204
+ ws.on('close', () => {
205
+ if (session.websocket === ws) {
206
+ session.websocket = undefined;
207
+ }
208
+ });
209
+ ws.on('error', (error) => {
210
+ console.error('WebSocket error:', error);
211
+ if (session.websocket === ws) {
212
+ session.websocket = undefined;
213
+ }
214
+ });
215
+ return true;
216
+ }
217
+ destroySession(sessionId) {
218
+ const session = this.sessions.get(sessionId);
219
+ if (!session) {
220
+ return false;
221
+ }
222
+ // Close WebSocket if connected
223
+ if (session.websocket && session.websocket.readyState === WebSocket.OPEN) {
224
+ session.websocket.close();
225
+ }
226
+ // Kill PTY process
227
+ try {
228
+ session.pty.kill();
229
+ }
230
+ catch (error) {
231
+ console.error('Error killing PTY process:', error);
232
+ }
233
+ this.sessions.delete(sessionId);
234
+ return true;
235
+ }
236
+ resizeSession(sessionId, cols, rows) {
237
+ const session = this.sessions.get(sessionId);
238
+ if (!session) {
239
+ return false;
240
+ }
241
+ try {
242
+ session.pty.resize(cols, rows);
243
+ session.lastActivity = new Date();
244
+ return true;
245
+ }
246
+ catch (error) {
247
+ console.error('Error resizing terminal:', error);
248
+ return false;
249
+ }
250
+ }
251
+ cleanupInactiveSessions() {
252
+ const now = new Date();
253
+ const maxInactiveTime = 30 * 60 * 1000; // 30 minutes
254
+ for (const [sessionId, session] of Array.from(this.sessions.entries())) {
255
+ const inactiveTime = now.getTime() - session.lastActivity.getTime();
256
+ if (inactiveTime > maxInactiveTime) {
257
+ console.log(`Cleaning up inactive session: ${sessionId}`);
258
+ this.destroySession(sessionId);
259
+ }
260
+ }
261
+ }
262
+ getAllSessions() {
263
+ return Array.from(this.sessions.values());
264
+ }
265
+ destroy() {
266
+ // Clean up all sessions
267
+ for (const sessionId of Array.from(this.sessions.keys())) {
268
+ this.destroySession(sessionId);
269
+ }
270
+ // Clear cleanup interval
271
+ if (this.cleanupInterval) {
272
+ clearInterval(this.cleanupInterval);
273
+ }
274
+ }
275
+ }
276
+ // We'll use the global manager instead of creating a direct export
@@ -0,0 +1,48 @@
1
+ export interface UserProfile {
2
+ firstName?: string;
3
+ lastName?: string;
4
+ email?: string;
5
+ role?: 'developer' | 'designer' | 'product-manager' | 'other';
6
+ }
7
+ export interface UserPreferences {
8
+ theme?: 'light' | 'dark' | 'coconut' | 'system';
9
+ defaultEditor?: 'cursor' | 'vscode' | 'vim' | 'emacs';
10
+ terminalShell?: 'bash' | 'zsh' | 'fish' | 'sh' | 'system';
11
+ autoSaveInterval?: number;
12
+ notifications?: {
13
+ email?: boolean;
14
+ desktop?: boolean;
15
+ slack?: boolean;
16
+ };
17
+ }
18
+ export interface UserSettings {
19
+ profile: UserProfile;
20
+ preferences: UserPreferences;
21
+ }
22
+ /**
23
+ * Ensure the ~/.gait directory exists
24
+ */
25
+ export declare function ensureGaitHomeDirectory(): Promise<void>;
26
+ /**
27
+ * Load user settings from ~/.gait/user-settings.json
28
+ */
29
+ export declare function loadUserSettings(): Promise<UserSettings>;
30
+ /**
31
+ * Save user settings to ~/.gait/user-settings.json
32
+ */
33
+ export declare function saveUserSettings(settings: UserSettings): Promise<void>;
34
+ /**
35
+ * Update user profile information
36
+ */
37
+ export declare function updateUserProfile(profile: Partial<UserProfile>): Promise<UserSettings>;
38
+ /**
39
+ * Update user preferences
40
+ */
41
+ export declare function updateUserPreferences(preferences: Partial<UserPreferences>): Promise<UserSettings>;
42
+ /**
43
+ * Get the author information for Change Proposals
44
+ */
45
+ export declare function getAuthorInfo(): Promise<{
46
+ name: string;
47
+ email: string;
48
+ }>;
@@ -0,0 +1,87 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ const GAIT_HOME_DIR = path.join(os.homedir(), '.gait');
5
+ const USER_SETTINGS_FILE = path.join(GAIT_HOME_DIR, 'user-settings.json');
6
+ /**
7
+ * Ensure the ~/.gait directory exists
8
+ */
9
+ export async function ensureGaitHomeDirectory() {
10
+ try {
11
+ await fs.access(GAIT_HOME_DIR);
12
+ }
13
+ catch {
14
+ await fs.mkdir(GAIT_HOME_DIR, { recursive: true });
15
+ }
16
+ }
17
+ /**
18
+ * Load user settings from ~/.gait/user-settings.json
19
+ */
20
+ export async function loadUserSettings() {
21
+ try {
22
+ await ensureGaitHomeDirectory();
23
+ const data = await fs.readFile(USER_SETTINGS_FILE, 'utf-8');
24
+ return JSON.parse(data);
25
+ }
26
+ catch {
27
+ // Return default settings if file doesn't exist or is invalid
28
+ return {
29
+ profile: {},
30
+ preferences: {
31
+ theme: 'system',
32
+ defaultEditor: 'cursor',
33
+ terminalShell: 'bash',
34
+ autoSaveInterval: 30,
35
+ notifications: {
36
+ email: true,
37
+ desktop: true,
38
+ slack: false,
39
+ },
40
+ },
41
+ };
42
+ }
43
+ }
44
+ /**
45
+ * Save user settings to ~/.gait/user-settings.json
46
+ */
47
+ export async function saveUserSettings(settings) {
48
+ await ensureGaitHomeDirectory();
49
+ await fs.writeFile(USER_SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
50
+ }
51
+ /**
52
+ * Update user profile information
53
+ */
54
+ export async function updateUserProfile(profile) {
55
+ const settings = await loadUserSettings();
56
+ settings.profile = { ...settings.profile, ...profile };
57
+ await saveUserSettings(settings);
58
+ return settings;
59
+ }
60
+ /**
61
+ * Update user preferences
62
+ */
63
+ export async function updateUserPreferences(preferences) {
64
+ const settings = await loadUserSettings();
65
+ settings.preferences = {
66
+ ...settings.preferences,
67
+ ...preferences,
68
+ notifications: {
69
+ ...settings.preferences.notifications,
70
+ ...preferences.notifications,
71
+ }
72
+ };
73
+ await saveUserSettings(settings);
74
+ return settings;
75
+ }
76
+ /**
77
+ * Get the author information for Change Proposals
78
+ */
79
+ export async function getAuthorInfo() {
80
+ const settings = await loadUserSettings();
81
+ const { firstName, lastName, email } = settings.profile;
82
+ const name = [firstName, lastName].filter(Boolean).join(' ') || 'Current User';
83
+ return {
84
+ name,
85
+ email: email || 'user@example.com',
86
+ };
87
+ }
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): any;
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1 @@
1
+ export declare function GET(): Promise<any>;