gsd-agent 1.0.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 (155) hide show
  1. package/README.md +221 -0
  2. package/bin/cli.js +313 -0
  3. package/dist/auth-flow.d.ts +50 -0
  4. package/dist/auth-flow.d.ts.map +1 -0
  5. package/dist/auth-flow.js +233 -0
  6. package/dist/auth-flow.js.map +1 -0
  7. package/dist/auth.d.ts +42 -0
  8. package/dist/auth.d.ts.map +1 -0
  9. package/dist/auth.js +117 -0
  10. package/dist/auth.js.map +1 -0
  11. package/dist/command-executor.d.ts +44 -0
  12. package/dist/command-executor.d.ts.map +1 -0
  13. package/dist/command-executor.js +193 -0
  14. package/dist/command-executor.js.map +1 -0
  15. package/dist/command-executor.test.d.ts +8 -0
  16. package/dist/command-executor.test.d.ts.map +1 -0
  17. package/dist/command-executor.test.js +87 -0
  18. package/dist/command-executor.test.js.map +1 -0
  19. package/dist/command-queue.d.ts +44 -0
  20. package/dist/command-queue.d.ts.map +1 -0
  21. package/dist/command-queue.js +184 -0
  22. package/dist/command-queue.js.map +1 -0
  23. package/dist/command-queue.test.d.ts +7 -0
  24. package/dist/command-queue.test.d.ts.map +1 -0
  25. package/dist/command-queue.test.js +220 -0
  26. package/dist/command-queue.test.js.map +1 -0
  27. package/dist/config.d.ts +25 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +103 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/conflict-resolver.d.ts +43 -0
  32. package/dist/conflict-resolver.d.ts.map +1 -0
  33. package/dist/conflict-resolver.js +91 -0
  34. package/dist/conflict-resolver.js.map +1 -0
  35. package/dist/conflict-resolver.test.d.ts +7 -0
  36. package/dist/conflict-resolver.test.d.ts.map +1 -0
  37. package/dist/conflict-resolver.test.js +123 -0
  38. package/dist/conflict-resolver.test.js.map +1 -0
  39. package/dist/discovery.d.ts +59 -0
  40. package/dist/discovery.d.ts.map +1 -0
  41. package/dist/discovery.js +180 -0
  42. package/dist/discovery.js.map +1 -0
  43. package/dist/discovery.test.d.ts +8 -0
  44. package/dist/discovery.test.d.ts.map +1 -0
  45. package/dist/discovery.test.js +132 -0
  46. package/dist/discovery.test.js.map +1 -0
  47. package/dist/hash.d.ts +20 -0
  48. package/dist/hash.d.ts.map +1 -0
  49. package/dist/hash.js +35 -0
  50. package/dist/hash.js.map +1 -0
  51. package/dist/hash.test.d.ts +7 -0
  52. package/dist/hash.test.d.ts.map +1 -0
  53. package/dist/hash.test.js +58 -0
  54. package/dist/hash.test.js.map +1 -0
  55. package/dist/index.d.ts +11 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +202 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/integration.test.d.ts +8 -0
  60. package/dist/integration.test.d.ts.map +1 -0
  61. package/dist/integration.test.js +37 -0
  62. package/dist/integration.test.js.map +1 -0
  63. package/dist/logger.d.ts +68 -0
  64. package/dist/logger.d.ts.map +1 -0
  65. package/dist/logger.js +159 -0
  66. package/dist/logger.js.map +1 -0
  67. package/dist/output-streamer.d.ts +27 -0
  68. package/dist/output-streamer.d.ts.map +1 -0
  69. package/dist/output-streamer.js +71 -0
  70. package/dist/output-streamer.js.map +1 -0
  71. package/dist/output-streamer.test.d.ts +7 -0
  72. package/dist/output-streamer.test.d.ts.map +1 -0
  73. package/dist/output-streamer.test.js +90 -0
  74. package/dist/output-streamer.test.js.map +1 -0
  75. package/dist/realtime-subscriber.d.ts +63 -0
  76. package/dist/realtime-subscriber.d.ts.map +1 -0
  77. package/dist/realtime-subscriber.js +201 -0
  78. package/dist/realtime-subscriber.js.map +1 -0
  79. package/dist/realtime-subscriber.test.d.ts +7 -0
  80. package/dist/realtime-subscriber.test.d.ts.map +1 -0
  81. package/dist/realtime-subscriber.test.js +183 -0
  82. package/dist/realtime-subscriber.test.js.map +1 -0
  83. package/dist/reconnection-manager.d.ts +88 -0
  84. package/dist/reconnection-manager.d.ts.map +1 -0
  85. package/dist/reconnection-manager.js +229 -0
  86. package/dist/reconnection-manager.js.map +1 -0
  87. package/dist/reconnection-manager.test.d.ts +8 -0
  88. package/dist/reconnection-manager.test.d.ts.map +1 -0
  89. package/dist/reconnection-manager.test.js +151 -0
  90. package/dist/reconnection-manager.test.js.map +1 -0
  91. package/dist/remote-sync-handler.d.ts +61 -0
  92. package/dist/remote-sync-handler.d.ts.map +1 -0
  93. package/dist/remote-sync-handler.js +197 -0
  94. package/dist/remote-sync-handler.js.map +1 -0
  95. package/dist/remote-sync-handler.test.d.ts +7 -0
  96. package/dist/remote-sync-handler.test.d.ts.map +1 -0
  97. package/dist/remote-sync-handler.test.js +212 -0
  98. package/dist/remote-sync-handler.test.js.map +1 -0
  99. package/dist/retry.d.ts +35 -0
  100. package/dist/retry.d.ts.map +1 -0
  101. package/dist/retry.js +63 -0
  102. package/dist/retry.js.map +1 -0
  103. package/dist/retry.test.d.ts +5 -0
  104. package/dist/retry.test.d.ts.map +1 -0
  105. package/dist/retry.test.js +84 -0
  106. package/dist/retry.test.js.map +1 -0
  107. package/dist/storage-client.d.ts +69 -0
  108. package/dist/storage-client.d.ts.map +1 -0
  109. package/dist/storage-client.js +168 -0
  110. package/dist/storage-client.js.map +1 -0
  111. package/dist/storage-client.test.d.ts +7 -0
  112. package/dist/storage-client.test.d.ts.map +1 -0
  113. package/dist/storage-client.test.js +126 -0
  114. package/dist/storage-client.test.js.map +1 -0
  115. package/dist/supabase.d.ts +82 -0
  116. package/dist/supabase.d.ts.map +1 -0
  117. package/dist/supabase.js +341 -0
  118. package/dist/supabase.js.map +1 -0
  119. package/dist/supabase.test.d.ts +7 -0
  120. package/dist/supabase.test.d.ts.map +1 -0
  121. package/dist/supabase.test.js +273 -0
  122. package/dist/supabase.test.js.map +1 -0
  123. package/dist/sync-engine.d.ts +84 -0
  124. package/dist/sync-engine.d.ts.map +1 -0
  125. package/dist/sync-engine.js +251 -0
  126. package/dist/sync-engine.js.map +1 -0
  127. package/dist/sync-engine.test.d.ts +7 -0
  128. package/dist/sync-engine.test.d.ts.map +1 -0
  129. package/dist/sync-engine.test.js +241 -0
  130. package/dist/sync-engine.test.js.map +1 -0
  131. package/dist/sync-state.d.ts +82 -0
  132. package/dist/sync-state.d.ts.map +1 -0
  133. package/dist/sync-state.js +145 -0
  134. package/dist/sync-state.js.map +1 -0
  135. package/dist/sync-state.test.d.ts +7 -0
  136. package/dist/sync-state.test.d.ts.map +1 -0
  137. package/dist/sync-state.test.js +129 -0
  138. package/dist/sync-state.test.js.map +1 -0
  139. package/dist/types.d.ts +148 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +8 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/types.test.d.ts +7 -0
  144. package/dist/types.test.d.ts.map +1 -0
  145. package/dist/types.test.js +73 -0
  146. package/dist/types.test.js.map +1 -0
  147. package/dist/watcher.d.ts +55 -0
  148. package/dist/watcher.d.ts.map +1 -0
  149. package/dist/watcher.js +214 -0
  150. package/dist/watcher.js.map +1 -0
  151. package/dist/watcher.test.d.ts +8 -0
  152. package/dist/watcher.test.d.ts.map +1 -0
  153. package/dist/watcher.test.js +164 -0
  154. package/dist/watcher.test.js.map +1 -0
  155. package/package.json +58 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Device flow authentication for GSD Agent
3
+ *
4
+ * Implements device code flow similar to GitHub CLI:
5
+ * 1. Agent generates device code and user code
6
+ * 2. Agent displays instructions to visit web UI with user code
7
+ * 3. User enters code on web UI and authorizes the agent
8
+ * 4. Agent polls for authorization status
9
+ * 5. Agent exchanges device code for user tokens
10
+ * 6. Agent stores credentials and starts syncing
11
+ */
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import { createClient } from '@supabase/supabase-js';
15
+ import { loadConfig } from './config.js';
16
+ import { createLogger } from './logger.js';
17
+ import { getCredentialsPath } from './auth.js';
18
+ export class DeviceAuthFlow {
19
+ config;
20
+ logger;
21
+ supabase;
22
+ constructor() {
23
+ this.config = loadConfig();
24
+ this.logger = createLogger(this.config);
25
+ // Create temporary Supabase client for auth flow
26
+ this.supabase = createClient(this.config.supabase_url, this.config.supabase_anon_key || this.config.supabase_key, {
27
+ auth: {
28
+ autoRefreshToken: false,
29
+ persistSession: false
30
+ }
31
+ });
32
+ }
33
+ /**
34
+ * Generate a random device code (8 characters)
35
+ */
36
+ generateDeviceCode() {
37
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
38
+ let result = '';
39
+ for (let i = 0; i < 8; i++) {
40
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
41
+ }
42
+ return result;
43
+ }
44
+ /**
45
+ * Generate a user-friendly code (formatted as XXXX-YYYY)
46
+ */
47
+ generateUserCode() {
48
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
49
+ let result = '';
50
+ for (let i = 0; i < 8; i++) {
51
+ if (i === 4)
52
+ result += '-';
53
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
54
+ }
55
+ return result;
56
+ }
57
+ /**
58
+ * Start the device authorization flow
59
+ */
60
+ async startAuthFlow() {
61
+ try {
62
+ this.logger.info('Starting device authorization flow');
63
+ // Generate codes
64
+ const deviceCode = this.generateDeviceCode();
65
+ const userCode = this.generateUserCode();
66
+ // Calculate expiration
67
+ const expiresIn = 300; // 5 minutes
68
+ const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
69
+ // Create device code record in database
70
+ const { error: insertError } = await this.supabase
71
+ .from('device_codes')
72
+ .insert({
73
+ device_code: deviceCode,
74
+ user_code: userCode,
75
+ status: 'pending',
76
+ expires_at: expiresAt
77
+ });
78
+ if (insertError) {
79
+ this.logger.error('Failed to create device code', { error: insertError });
80
+ return false;
81
+ }
82
+ // Display instructions to user
83
+ console.log('\n=========================================');
84
+ console.log('GSD Agent Authentication Required');
85
+ console.log('=========================================');
86
+ console.log(`\nVisit: https://gsd.fahad.ink/agent-connect`);
87
+ console.log(`Enter this code: ${userCode}`);
88
+ console.log(`\nThis code will expire in 5 minutes.`);
89
+ console.log('=========================================\n');
90
+ // Poll for authorization
91
+ const tokenResult = await this.pollForAuthorization(deviceCode);
92
+ if (!tokenResult) {
93
+ this.logger.error('Device authorization failed or timed out');
94
+ return false;
95
+ }
96
+ // Save credentials
97
+ const credentialsPath = getCredentialsPath();
98
+ const credentialsDir = path.dirname(credentialsPath);
99
+ if (!fs.existsSync(credentialsDir)) {
100
+ fs.mkdirSync(credentialsDir, { recursive: true });
101
+ }
102
+ const credentials = {
103
+ user_id: tokenResult.user_id,
104
+ access_token: tokenResult.access_token,
105
+ refresh_token: tokenResult.refresh_token,
106
+ expires_at: tokenResult.expires_at,
107
+ supabase_url: this.config.supabase_url
108
+ };
109
+ fs.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), { mode: 0o600 });
110
+ this.logger.info('Device authorization successful, credentials saved');
111
+ // Update device code status to authorized
112
+ await this.supabase
113
+ .from('device_codes')
114
+ .update({
115
+ status: 'authorized',
116
+ authorized_at: new Date().toISOString(),
117
+ user_id: tokenResult.user_id
118
+ })
119
+ .eq('device_code', deviceCode);
120
+ return true;
121
+ }
122
+ catch (error) {
123
+ this.logger.error('Device auth flow failed', { error });
124
+ return false;
125
+ }
126
+ }
127
+ /**
128
+ * Poll for authorization status
129
+ */
130
+ async pollForAuthorization(deviceCode) {
131
+ const startTime = Date.now();
132
+ const timeout = 5 * 60 * 1000; // 5 minutes
133
+ const interval = 5000; // 5 seconds
134
+ while (Date.now() - startTime < timeout) {
135
+ try {
136
+ const { data, error } = await this.supabase
137
+ .from('device_codes')
138
+ .select('status, user_id')
139
+ .eq('device_code', deviceCode)
140
+ .single();
141
+ if (error) {
142
+ this.logger.error('Failed to check device code status', { error });
143
+ await new Promise(resolve => setTimeout(resolve, interval));
144
+ continue;
145
+ }
146
+ if (data.status === 'authorized' && data.user_id) {
147
+ // Generate tokens for the authorized user
148
+ return await this.generateUserTokens(data.user_id);
149
+ }
150
+ if (data.status === 'rejected') {
151
+ this.logger.warn('Device authorization was rejected by user');
152
+ return null;
153
+ }
154
+ // Wait before next poll
155
+ await new Promise(resolve => setTimeout(resolve, interval));
156
+ }
157
+ catch (error) {
158
+ this.logger.error('Polling error', { error });
159
+ await new Promise(resolve => setTimeout(resolve, interval));
160
+ }
161
+ }
162
+ this.logger.warn('Device authorization timed out');
163
+ return null;
164
+ }
165
+ /**
166
+ * Generate tokens for authorized user
167
+ */
168
+ async generateUserTokens(user_id) {
169
+ // In a real implementation, this would use Supabase Auth API to generate a session
170
+ // For now, we'll simulate a valid token exchange
171
+ // In a real scenario, we would:
172
+ // 1. Use Supabase's GoTrue API to create a session for the user
173
+ // 2. Or have the web UI return tokens that were generated server-side
174
+ // For simulation purposes:
175
+ const accessToken = `simulated_access_token_for_${user_id}`;
176
+ const refreshToken = `simulated_refresh_token_for_${user_id}`;
177
+ const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); // 24 hours
178
+ return {
179
+ access_token: accessToken,
180
+ refresh_token: refreshToken,
181
+ expires_at: expiresAt,
182
+ user_id: user_id
183
+ };
184
+ }
185
+ /**
186
+ * Check if agent is already authenticated
187
+ */
188
+ static isAuthenticated() {
189
+ try {
190
+ const credentialsPath = getCredentialsPath();
191
+ if (!fs.existsSync(credentialsPath)) {
192
+ return false;
193
+ }
194
+ const credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf-8'));
195
+ if (!credentials.access_token || !credentials.user_id) {
196
+ return false;
197
+ }
198
+ // Check if token is expired
199
+ const expiresAt = new Date(credentials.expires_at);
200
+ if (expiresAt < new Date()) {
201
+ return false;
202
+ }
203
+ return true;
204
+ }
205
+ catch (error) {
206
+ return false;
207
+ }
208
+ }
209
+ /**
210
+ * Clear authentication credentials
211
+ */
212
+ static async clearAuth() {
213
+ try {
214
+ const credentialsPath = getCredentialsPath();
215
+ if (fs.existsSync(credentialsPath)) {
216
+ fs.unlinkSync(credentialsPath);
217
+ }
218
+ return true;
219
+ }
220
+ catch (error) {
221
+ console.error('Failed to clear authentication:', error);
222
+ return false;
223
+ }
224
+ }
225
+ }
226
+ /**
227
+ * Main function to handle authentication command
228
+ */
229
+ export async function authenticateAgent() {
230
+ const authFlow = new DeviceAuthFlow();
231
+ return await authFlow.startAuthFlow();
232
+ }
233
+ //# sourceMappingURL=auth-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-flow.js","sourceRoot":"","sources":["../src/auth-flow.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Credential management for Supabase authentication
3
+ *
4
+ * Handles secure loading and storage of Supabase credentials.
5
+ * Priority order: 1) Environment variables, 2) ~/.gsd-agent/credentials.json
6
+ */
7
+ /**
8
+ * Credentials structure
9
+ */
10
+ export interface Credentials {
11
+ url: string;
12
+ key: string;
13
+ }
14
+ /**
15
+ * Get the path to the credentials file
16
+ * @returns Absolute path to ~/.gsd-agent/credentials.json
17
+ */
18
+ export declare function getCredentialsPath(): string;
19
+ /**
20
+ * Load credentials from environment variables or file
21
+ *
22
+ * Priority order:
23
+ * 1. Environment variables (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
24
+ * 2. ~/.gsd-agent/credentials.json
25
+ * 3. Throw error if neither exists
26
+ *
27
+ * @returns Credentials object with url and key
28
+ * @throws Error if credentials not found or invalid
29
+ */
30
+ export declare function loadCredentials(): Credentials;
31
+ /**
32
+ * Save credentials to ~/.gsd-agent/credentials.json
33
+ *
34
+ * Creates the directory if it doesn't exist.
35
+ * Sets file permissions to 0600 (owner read/write only).
36
+ *
37
+ * @param url - Supabase project URL
38
+ * @param key - Supabase service role key
39
+ * @throws Error if validation fails or file cannot be written
40
+ */
41
+ export declare function saveCredentials(url: string, key: string): void;
42
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AA2BD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,IAAI,WAAW,CAmD7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAmB9D"}
package/dist/auth.js ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Credential management for Supabase authentication
3
+ *
4
+ * Handles secure loading and storage of Supabase credentials.
5
+ * Priority order: 1) Environment variables, 2) ~/.gsd-agent/credentials.json
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ /**
11
+ * Get the path to the credentials file
12
+ * @returns Absolute path to ~/.gsd-agent/credentials.json
13
+ */
14
+ export function getCredentialsPath() {
15
+ return path.join(os.homedir(), '.gsd-agent', 'credentials.json');
16
+ }
17
+ /**
18
+ * Validate Supabase URL format
19
+ * @param url - URL to validate
20
+ * @throws Error if URL is invalid
21
+ */
22
+ function validateUrl(url) {
23
+ if (!url.startsWith('https://')) {
24
+ throw new Error('Supabase URL must start with https://');
25
+ }
26
+ if (!url.includes('.supabase.co')) {
27
+ throw new Error('Supabase URL must contain .supabase.co');
28
+ }
29
+ }
30
+ /**
31
+ * Validate Supabase service role key format
32
+ * @param key - Key to validate
33
+ * @throws Error if key is invalid
34
+ */
35
+ function validateKey(key) {
36
+ if (!key.startsWith('eyJ')) {
37
+ throw new Error('Supabase service role key must start with eyJ (JWT format)');
38
+ }
39
+ }
40
+ /**
41
+ * Load credentials from environment variables or file
42
+ *
43
+ * Priority order:
44
+ * 1. Environment variables (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
45
+ * 2. ~/.gsd-agent/credentials.json
46
+ * 3. Throw error if neither exists
47
+ *
48
+ * @returns Credentials object with url and key
49
+ * @throws Error if credentials not found or invalid
50
+ */
51
+ export function loadCredentials() {
52
+ // Try environment variables first
53
+ const envUrl = process.env.SUPABASE_URL;
54
+ const envKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
55
+ if (envUrl && envUrl.trim() && envKey && envKey.trim()) {
56
+ const url = envUrl.trim();
57
+ const key = envKey.trim();
58
+ validateUrl(url);
59
+ validateKey(key);
60
+ return { url, key };
61
+ }
62
+ // Try credentials file
63
+ const credPath = getCredentialsPath();
64
+ if (!fs.existsSync(credPath)) {
65
+ throw new Error('Supabase credentials not found. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY environment variables, ' +
66
+ `or create ${credPath} with credentials. See .env.example for template.`);
67
+ }
68
+ let credData;
69
+ try {
70
+ const fileContent = fs.readFileSync(credPath, 'utf-8');
71
+ credData = JSON.parse(fileContent);
72
+ }
73
+ catch (error) {
74
+ throw new Error(`Failed to parse credentials file ${credPath}: ${error}`);
75
+ }
76
+ // Validate required fields
77
+ const missingFields = [];
78
+ if (!credData.supabase_url)
79
+ missingFields.push('supabase_url');
80
+ if (!credData.supabase_service_role_key)
81
+ missingFields.push('supabase_service_role_key');
82
+ if (missingFields.length > 0) {
83
+ throw new Error(`Credentials file ${credPath} missing required fields: ${missingFields.join(', ')}`);
84
+ }
85
+ const url = credData.supabase_url.trim();
86
+ const key = credData.supabase_service_role_key.trim();
87
+ validateUrl(url);
88
+ validateKey(key);
89
+ return { url, key };
90
+ }
91
+ /**
92
+ * Save credentials to ~/.gsd-agent/credentials.json
93
+ *
94
+ * Creates the directory if it doesn't exist.
95
+ * Sets file permissions to 0600 (owner read/write only).
96
+ *
97
+ * @param url - Supabase project URL
98
+ * @param key - Supabase service role key
99
+ * @throws Error if validation fails or file cannot be written
100
+ */
101
+ export function saveCredentials(url, key) {
102
+ validateUrl(url);
103
+ validateKey(key);
104
+ const credPath = getCredentialsPath();
105
+ const credDir = path.dirname(credPath);
106
+ // Create directory if it doesn't exist
107
+ if (!fs.existsSync(credDir)) {
108
+ fs.mkdirSync(credDir, { recursive: true });
109
+ }
110
+ const credData = {
111
+ supabase_url: url,
112
+ supabase_service_role_key: key
113
+ };
114
+ // Write file with secure permissions
115
+ fs.writeFileSync(credPath, JSON.stringify(credData, null, 2), { mode: 0o600 });
116
+ }
117
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Command Executor with Security Whitelist
3
+ *
4
+ * Executes whitelisted GSD commands using spawn() with security controls.
5
+ * Streams output to database via OutputBatcher, updates command status.
6
+ */
7
+ import { ChildProcess } from 'child_process';
8
+ import type { SupabaseClient } from '@supabase/supabase-js';
9
+ /**
10
+ * Command definition with binary and argument builder
11
+ */
12
+ interface CommandDefinition {
13
+ bin: string;
14
+ args: (params: any) => string[];
15
+ }
16
+ /**
17
+ * Command record from database
18
+ */
19
+ interface Command {
20
+ id: string;
21
+ workspace_id: string;
22
+ command_name: string;
23
+ parameters: any;
24
+ status: string;
25
+ }
26
+ /**
27
+ * Whitelist of allowed commands
28
+ * SECURITY: Only these commands can be executed by the agent
29
+ *
30
+ * NOTE: GSD workflow commands (/gsd:progress, /gsd:status, etc.) are Claude Code
31
+ * skills that require AI interpretation. They cannot be executed as shell commands.
32
+ * Only gsd-tools.cjs commands are supported here.
33
+ */
34
+ export declare const ALLOWED_COMMANDS: Record<string, CommandDefinition>;
35
+ /**
36
+ * Execute a command with security validation and output streaming
37
+ *
38
+ * @param command - Command record from database
39
+ * @param supabase - Supabase client for status updates
40
+ * @returns Child process for tracking
41
+ */
42
+ export declare function executeCommand(command: Command, supabase: SupabaseClient): Promise<ChildProcess>;
43
+ export {};
44
+ //# sourceMappingURL=command-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-executor.d.ts","sourceRoot":"","sources":["../src/command-executor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAS,YAAY,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAG3D;;GAEG;AACH,UAAU,iBAAiB;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,MAAM,EAAE,CAAA;CAChC;AAED;;GAEG;AACH,UAAU,OAAO;IACf,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,GAAG,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAwC9D,CAAA;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,YAAY,CAAC,CAgJvB"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Command Executor with Security Whitelist
3
+ *
4
+ * Executes whitelisted GSD commands using spawn() with security controls.
5
+ * Streams output to database via OutputBatcher, updates command status.
6
+ */
7
+ import { spawn } from 'child_process';
8
+ import { OutputBatcher } from './output-streamer.js';
9
+ /**
10
+ * Whitelist of allowed commands
11
+ * SECURITY: Only these commands can be executed by the agent
12
+ *
13
+ * NOTE: GSD workflow commands (/gsd:progress, /gsd:status, etc.) are Claude Code
14
+ * skills that require AI interpretation. They cannot be executed as shell commands.
15
+ * Only gsd-tools.cjs commands are supported here.
16
+ */
17
+ export const ALLOWED_COMMANDS = {
18
+ // List todos
19
+ 'list-todos': {
20
+ bin: 'node',
21
+ args: () => [
22
+ `${process.env.HOME}/.claude/get-shit-done/bin/gsd-tools.cjs`,
23
+ 'list-todos'
24
+ ]
25
+ },
26
+ '/gsd:list-todos': {
27
+ bin: 'node',
28
+ args: () => [
29
+ `${process.env.HOME}/.claude/get-shit-done/bin/gsd-tools.cjs`,
30
+ 'list-todos'
31
+ ]
32
+ },
33
+ // Show project state
34
+ 'state': {
35
+ bin: 'node',
36
+ args: () => [
37
+ `${process.env.HOME}/.claude/get-shit-done/bin/gsd-tools.cjs`,
38
+ 'state'
39
+ ]
40
+ },
41
+ '/gsd:state': {
42
+ bin: 'node',
43
+ args: () => [
44
+ `${process.env.HOME}/.claude/get-shit-done/bin/gsd-tools.cjs`,
45
+ 'state'
46
+ ]
47
+ },
48
+ // Simple git status
49
+ 'git-status': {
50
+ bin: 'git',
51
+ args: () => ['status', '--short']
52
+ },
53
+ '/gsd:git-status': {
54
+ bin: 'git',
55
+ args: () => ['status', '--short']
56
+ }
57
+ };
58
+ /**
59
+ * Execute a command with security validation and output streaming
60
+ *
61
+ * @param command - Command record from database
62
+ * @param supabase - Supabase client for status updates
63
+ * @returns Child process for tracking
64
+ */
65
+ export async function executeCommand(command, supabase) {
66
+ // 1. Validate command against whitelist
67
+ const commandDef = ALLOWED_COMMANDS[command.command_name];
68
+ if (!commandDef) {
69
+ throw new Error(`Command not allowed: ${command.command_name}`);
70
+ }
71
+ // 2. Build args array (NEVER use shell: true)
72
+ const args = commandDef.args(command.parameters);
73
+ // 3. Get workspace path
74
+ const { data: workspace, error: wsError } = await supabase
75
+ .from('workspaces')
76
+ .select('root_path')
77
+ .eq('id', command.workspace_id)
78
+ .single();
79
+ if (wsError || !workspace) {
80
+ throw new Error(`Workspace not found: ${command.workspace_id}`);
81
+ }
82
+ // 4. Spawn process with stdio pipes (SECURITY: shell: false)
83
+ const childProcess = spawn(commandDef.bin, args, {
84
+ cwd: workspace.root_path,
85
+ stdio: ['ignore', 'pipe', 'pipe'],
86
+ shell: false // CRITICAL: Prevent command injection
87
+ });
88
+ // 5. Update command status to 'running' with PID
89
+ const startedAt = new Date().toISOString();
90
+ await supabase
91
+ .from('commands')
92
+ .update({
93
+ status: 'running',
94
+ pid: childProcess.pid,
95
+ started_at: startedAt
96
+ })
97
+ .eq('id', command.id);
98
+ // 6. Create OutputBatcher for this command
99
+ const batcher = new OutputBatcher(supabase);
100
+ let lineNumber = 1;
101
+ // 7. Pipe stdout to batcher
102
+ childProcess.stdout?.on('data', (data) => {
103
+ const lines = data.toString().split('\n');
104
+ for (const line of lines) {
105
+ if (line.trim()) {
106
+ batcher.add(command.id, lineNumber++, line, 'stdout');
107
+ }
108
+ }
109
+ });
110
+ // 8. Pipe stderr to batcher
111
+ childProcess.stderr?.on('data', (data) => {
112
+ const lines = data.toString().split('\n');
113
+ for (const line of lines) {
114
+ if (line.trim()) {
115
+ batcher.add(command.id, lineNumber++, line, 'stderr');
116
+ }
117
+ }
118
+ });
119
+ // 9. Handle process close/exit with synchronous completion tracking
120
+ let completed = false;
121
+ const handleCompletion = (code) => {
122
+ if (completed)
123
+ return;
124
+ completed = true;
125
+ console.log(`[CommandExecutor] Process completed for ${command.id}, exit code: ${code}`);
126
+ // Use setImmediate to ensure this runs after the event loop
127
+ setImmediate(async () => {
128
+ try {
129
+ // Flush remaining output
130
+ await batcher.flush();
131
+ const finishedAt = new Date().toISOString();
132
+ // Update status based on exit code
133
+ if (code === 0) {
134
+ console.log(`[CommandExecutor] Marking ${command.id} as complete`);
135
+ const { error } = await supabase
136
+ .from('commands')
137
+ .update({
138
+ status: 'complete',
139
+ exit_code: code,
140
+ completed_at: finishedAt
141
+ })
142
+ .eq('id', command.id);
143
+ if (error) {
144
+ console.error(`[CommandExecutor] Failed to update status:`, error);
145
+ }
146
+ else {
147
+ console.log(`[CommandExecutor] Successfully marked ${command.id} as complete`);
148
+ }
149
+ }
150
+ else {
151
+ console.log(`[CommandExecutor] Marking ${command.id} as error`);
152
+ const { error } = await supabase
153
+ .from('commands')
154
+ .update({
155
+ status: 'error',
156
+ exit_code: code,
157
+ error_message: `Process exited with code ${code}`,
158
+ completed_at: finishedAt
159
+ })
160
+ .eq('id', command.id);
161
+ if (error) {
162
+ console.error(`[CommandExecutor] Failed to update status:`, error);
163
+ }
164
+ }
165
+ }
166
+ catch (err) {
167
+ console.error(`[CommandExecutor] Error in completion handler:`, err);
168
+ }
169
+ });
170
+ };
171
+ childProcess.on('close', (code) => {
172
+ console.log(`[CommandExecutor] 'close' event fired for ${command.id}`);
173
+ handleCompletion(code);
174
+ });
175
+ childProcess.on('exit', (code) => {
176
+ console.log(`[CommandExecutor] 'exit' event fired for ${command.id}`);
177
+ handleCompletion(code);
178
+ });
179
+ // 10. Handle spawn errors
180
+ childProcess.on('error', async (error) => {
181
+ await batcher.flush();
182
+ await supabase
183
+ .from('commands')
184
+ .update({
185
+ status: 'error',
186
+ error_message: error.message,
187
+ finished_at: new Date().toISOString()
188
+ })
189
+ .eq('id', command.id);
190
+ });
191
+ return childProcess;
192
+ }
193
+ //# sourceMappingURL=command-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-executor.js","sourceRoot":"","sources":["../src/command-executor.ts"],"names":[],"mappings":"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"}