mstro-app 0.1.47

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/bin/commands/config.js +145 -0
  4. package/bin/commands/login.js +313 -0
  5. package/bin/commands/logout.js +75 -0
  6. package/bin/commands/status.js +197 -0
  7. package/bin/commands/whoami.js +161 -0
  8. package/bin/configure-claude.js +298 -0
  9. package/bin/mstro.js +581 -0
  10. package/bin/postinstall.js +45 -0
  11. package/bin/release.sh +110 -0
  12. package/dist/server/cli/headless/claude-invoker.d.ts +17 -0
  13. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -0
  14. package/dist/server/cli/headless/claude-invoker.js +311 -0
  15. package/dist/server/cli/headless/claude-invoker.js.map +1 -0
  16. package/dist/server/cli/headless/index.d.ts +13 -0
  17. package/dist/server/cli/headless/index.d.ts.map +1 -0
  18. package/dist/server/cli/headless/index.js +10 -0
  19. package/dist/server/cli/headless/index.js.map +1 -0
  20. package/dist/server/cli/headless/mcp-config.d.ts +11 -0
  21. package/dist/server/cli/headless/mcp-config.d.ts.map +1 -0
  22. package/dist/server/cli/headless/mcp-config.js +76 -0
  23. package/dist/server/cli/headless/mcp-config.js.map +1 -0
  24. package/dist/server/cli/headless/output-utils.d.ts +33 -0
  25. package/dist/server/cli/headless/output-utils.d.ts.map +1 -0
  26. package/dist/server/cli/headless/output-utils.js +101 -0
  27. package/dist/server/cli/headless/output-utils.js.map +1 -0
  28. package/dist/server/cli/headless/prompt-utils.d.ts +21 -0
  29. package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -0
  30. package/dist/server/cli/headless/prompt-utils.js +84 -0
  31. package/dist/server/cli/headless/prompt-utils.js.map +1 -0
  32. package/dist/server/cli/headless/runner.d.ts +24 -0
  33. package/dist/server/cli/headless/runner.d.ts.map +1 -0
  34. package/dist/server/cli/headless/runner.js +99 -0
  35. package/dist/server/cli/headless/runner.js.map +1 -0
  36. package/dist/server/cli/headless/types.d.ts +106 -0
  37. package/dist/server/cli/headless/types.d.ts.map +1 -0
  38. package/dist/server/cli/headless/types.js +4 -0
  39. package/dist/server/cli/headless/types.js.map +1 -0
  40. package/dist/server/cli/improvisation-session-manager.d.ts +155 -0
  41. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -0
  42. package/dist/server/cli/improvisation-session-manager.js +415 -0
  43. package/dist/server/cli/improvisation-session-manager.js.map +1 -0
  44. package/dist/server/index.d.ts +2 -0
  45. package/dist/server/index.d.ts.map +1 -0
  46. package/dist/server/index.js +386 -0
  47. package/dist/server/index.js.map +1 -0
  48. package/dist/server/mcp/bouncer-cli.d.ts +3 -0
  49. package/dist/server/mcp/bouncer-cli.d.ts.map +1 -0
  50. package/dist/server/mcp/bouncer-cli.js +99 -0
  51. package/dist/server/mcp/bouncer-cli.js.map +1 -0
  52. package/dist/server/mcp/bouncer-integration.d.ts +36 -0
  53. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -0
  54. package/dist/server/mcp/bouncer-integration.js +301 -0
  55. package/dist/server/mcp/bouncer-integration.js.map +1 -0
  56. package/dist/server/mcp/security-audit.d.ts +52 -0
  57. package/dist/server/mcp/security-audit.d.ts.map +1 -0
  58. package/dist/server/mcp/security-audit.js +118 -0
  59. package/dist/server/mcp/security-audit.js.map +1 -0
  60. package/dist/server/mcp/security-patterns.d.ts +73 -0
  61. package/dist/server/mcp/security-patterns.d.ts.map +1 -0
  62. package/dist/server/mcp/security-patterns.js +247 -0
  63. package/dist/server/mcp/security-patterns.js.map +1 -0
  64. package/dist/server/mcp/server.d.ts +3 -0
  65. package/dist/server/mcp/server.d.ts.map +1 -0
  66. package/dist/server/mcp/server.js +146 -0
  67. package/dist/server/mcp/server.js.map +1 -0
  68. package/dist/server/routes/files.d.ts +9 -0
  69. package/dist/server/routes/files.d.ts.map +1 -0
  70. package/dist/server/routes/files.js +24 -0
  71. package/dist/server/routes/files.js.map +1 -0
  72. package/dist/server/routes/improvise.d.ts +3 -0
  73. package/dist/server/routes/improvise.d.ts.map +1 -0
  74. package/dist/server/routes/improvise.js +72 -0
  75. package/dist/server/routes/improvise.js.map +1 -0
  76. package/dist/server/routes/index.d.ts +10 -0
  77. package/dist/server/routes/index.d.ts.map +1 -0
  78. package/dist/server/routes/index.js +12 -0
  79. package/dist/server/routes/index.js.map +1 -0
  80. package/dist/server/routes/instances.d.ts +10 -0
  81. package/dist/server/routes/instances.d.ts.map +1 -0
  82. package/dist/server/routes/instances.js +47 -0
  83. package/dist/server/routes/instances.js.map +1 -0
  84. package/dist/server/routes/notifications.d.ts +3 -0
  85. package/dist/server/routes/notifications.d.ts.map +1 -0
  86. package/dist/server/routes/notifications.js +136 -0
  87. package/dist/server/routes/notifications.js.map +1 -0
  88. package/dist/server/services/analytics.d.ts +56 -0
  89. package/dist/server/services/analytics.d.ts.map +1 -0
  90. package/dist/server/services/analytics.js +240 -0
  91. package/dist/server/services/analytics.js.map +1 -0
  92. package/dist/server/services/auth.d.ts +26 -0
  93. package/dist/server/services/auth.d.ts.map +1 -0
  94. package/dist/server/services/auth.js +71 -0
  95. package/dist/server/services/auth.js.map +1 -0
  96. package/dist/server/services/client-id.d.ts +10 -0
  97. package/dist/server/services/client-id.d.ts.map +1 -0
  98. package/dist/server/services/client-id.js +61 -0
  99. package/dist/server/services/client-id.js.map +1 -0
  100. package/dist/server/services/credentials.d.ts +39 -0
  101. package/dist/server/services/credentials.d.ts.map +1 -0
  102. package/dist/server/services/credentials.js +110 -0
  103. package/dist/server/services/credentials.js.map +1 -0
  104. package/dist/server/services/files.d.ts +119 -0
  105. package/dist/server/services/files.d.ts.map +1 -0
  106. package/dist/server/services/files.js +560 -0
  107. package/dist/server/services/files.js.map +1 -0
  108. package/dist/server/services/instances.d.ts +52 -0
  109. package/dist/server/services/instances.d.ts.map +1 -0
  110. package/dist/server/services/instances.js +241 -0
  111. package/dist/server/services/instances.js.map +1 -0
  112. package/dist/server/services/pathUtils.d.ts +47 -0
  113. package/dist/server/services/pathUtils.d.ts.map +1 -0
  114. package/dist/server/services/pathUtils.js +124 -0
  115. package/dist/server/services/pathUtils.js.map +1 -0
  116. package/dist/server/services/platform.d.ts +72 -0
  117. package/dist/server/services/platform.d.ts.map +1 -0
  118. package/dist/server/services/platform.js +368 -0
  119. package/dist/server/services/platform.js.map +1 -0
  120. package/dist/server/services/sentry.d.ts +5 -0
  121. package/dist/server/services/sentry.d.ts.map +1 -0
  122. package/dist/server/services/sentry.js +71 -0
  123. package/dist/server/services/sentry.js.map +1 -0
  124. package/dist/server/services/terminal/pty-manager.d.ts +149 -0
  125. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -0
  126. package/dist/server/services/terminal/pty-manager.js +377 -0
  127. package/dist/server/services/terminal/pty-manager.js.map +1 -0
  128. package/dist/server/services/terminal/tmux-manager.d.ts +82 -0
  129. package/dist/server/services/terminal/tmux-manager.d.ts.map +1 -0
  130. package/dist/server/services/terminal/tmux-manager.js +352 -0
  131. package/dist/server/services/terminal/tmux-manager.js.map +1 -0
  132. package/dist/server/services/websocket/autocomplete.d.ts +50 -0
  133. package/dist/server/services/websocket/autocomplete.d.ts.map +1 -0
  134. package/dist/server/services/websocket/autocomplete.js +361 -0
  135. package/dist/server/services/websocket/autocomplete.js.map +1 -0
  136. package/dist/server/services/websocket/file-utils.d.ts +44 -0
  137. package/dist/server/services/websocket/file-utils.d.ts.map +1 -0
  138. package/dist/server/services/websocket/file-utils.js +272 -0
  139. package/dist/server/services/websocket/file-utils.js.map +1 -0
  140. package/dist/server/services/websocket/handler.d.ts +246 -0
  141. package/dist/server/services/websocket/handler.d.ts.map +1 -0
  142. package/dist/server/services/websocket/handler.js +1771 -0
  143. package/dist/server/services/websocket/handler.js.map +1 -0
  144. package/dist/server/services/websocket/index.d.ts +11 -0
  145. package/dist/server/services/websocket/index.d.ts.map +1 -0
  146. package/dist/server/services/websocket/index.js +14 -0
  147. package/dist/server/services/websocket/index.js.map +1 -0
  148. package/dist/server/services/websocket/types.d.ts +214 -0
  149. package/dist/server/services/websocket/types.d.ts.map +1 -0
  150. package/dist/server/services/websocket/types.js +4 -0
  151. package/dist/server/services/websocket/types.js.map +1 -0
  152. package/dist/server/utils/agent-manager.d.ts +69 -0
  153. package/dist/server/utils/agent-manager.d.ts.map +1 -0
  154. package/dist/server/utils/agent-manager.js +269 -0
  155. package/dist/server/utils/agent-manager.js.map +1 -0
  156. package/dist/server/utils/paths.d.ts +25 -0
  157. package/dist/server/utils/paths.d.ts.map +1 -0
  158. package/dist/server/utils/paths.js +38 -0
  159. package/dist/server/utils/paths.js.map +1 -0
  160. package/dist/server/utils/port-manager.d.ts +10 -0
  161. package/dist/server/utils/port-manager.d.ts.map +1 -0
  162. package/dist/server/utils/port-manager.js +60 -0
  163. package/dist/server/utils/port-manager.js.map +1 -0
  164. package/dist/server/utils/port.d.ts +26 -0
  165. package/dist/server/utils/port.d.ts.map +1 -0
  166. package/dist/server/utils/port.js +83 -0
  167. package/dist/server/utils/port.js.map +1 -0
  168. package/hooks/bouncer.sh +138 -0
  169. package/package.json +74 -0
  170. package/server/README.md +191 -0
  171. package/server/cli/headless/claude-invoker.ts +415 -0
  172. package/server/cli/headless/index.ts +39 -0
  173. package/server/cli/headless/mcp-config.ts +87 -0
  174. package/server/cli/headless/output-utils.ts +109 -0
  175. package/server/cli/headless/prompt-utils.ts +108 -0
  176. package/server/cli/headless/runner.ts +133 -0
  177. package/server/cli/headless/types.ts +118 -0
  178. package/server/cli/improvisation-session-manager.ts +531 -0
  179. package/server/index.ts +456 -0
  180. package/server/mcp/README.md +122 -0
  181. package/server/mcp/bouncer-cli.ts +127 -0
  182. package/server/mcp/bouncer-integration.ts +430 -0
  183. package/server/mcp/security-audit.ts +180 -0
  184. package/server/mcp/security-patterns.ts +290 -0
  185. package/server/mcp/server.ts +174 -0
  186. package/server/routes/files.ts +29 -0
  187. package/server/routes/improvise.ts +82 -0
  188. package/server/routes/index.ts +13 -0
  189. package/server/routes/instances.ts +54 -0
  190. package/server/routes/notifications.ts +158 -0
  191. package/server/services/analytics.ts +277 -0
  192. package/server/services/auth.ts +80 -0
  193. package/server/services/client-id.ts +68 -0
  194. package/server/services/credentials.ts +134 -0
  195. package/server/services/files.ts +710 -0
  196. package/server/services/instances.ts +275 -0
  197. package/server/services/pathUtils.ts +158 -0
  198. package/server/services/platform.test.ts +1314 -0
  199. package/server/services/platform.ts +435 -0
  200. package/server/services/sentry.ts +81 -0
  201. package/server/services/terminal/pty-manager.ts +464 -0
  202. package/server/services/terminal/tmux-manager.ts +426 -0
  203. package/server/services/websocket/autocomplete.ts +438 -0
  204. package/server/services/websocket/file-utils.ts +305 -0
  205. package/server/services/websocket/handler.test.ts +20 -0
  206. package/server/services/websocket/handler.ts +2047 -0
  207. package/server/services/websocket/index.ts +40 -0
  208. package/server/services/websocket/types.ts +339 -0
  209. package/server/tsconfig.json +19 -0
  210. package/server/utils/agent-manager.ts +323 -0
  211. package/server/utils/paths.ts +45 -0
  212. package/server/utils/port-manager.ts +70 -0
  213. package/server/utils/port.ts +102 -0
@@ -0,0 +1,40 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * WebSocket Improvise Module
6
+ *
7
+ * Re-exports all WebSocket handler components for backward compatibility.
8
+ */
9
+
10
+
11
+ // Services
12
+ export { AutocompleteService } from './autocomplete.js';
13
+ // Utilities
14
+ export {
15
+ CACHE_TTL_MS,
16
+ directoryCache,
17
+ FILE_TYPE_MAP,
18
+ getFileType,
19
+ isIgnored,
20
+ isImageFile,
21
+ isPathInSafeLocation,
22
+ parseGitignore,
23
+ readFileContent,
24
+ scanDirectoryRecursiveWithDepth
25
+ } from './file-utils.js';
26
+ export type { UsageReport, UsageReporter } from './handler.js';
27
+ // Main handler class
28
+ export { WebSocketImproviseHandler } from './handler.js';
29
+ // Types
30
+ export type {
31
+ AutocompleteResult,
32
+ CacheEntry,
33
+ ConnectionData,
34
+ FileMetadata,
35
+ FileReadResult,
36
+ FrecencyData,
37
+ FrecencyEntry,
38
+ WebSocketMessage,
39
+ WebSocketResponse
40
+ } from './types.js';
@@ -0,0 +1,339 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * WebSocket Improvise Types
6
+ *
7
+ * Type definitions for WebSocket improvisation sessions.
8
+ */
9
+
10
+ /**
11
+ * Runtime-agnostic WebSocket context interface
12
+ * Works with both Bun's ServerWebSocket and Node.js ws library
13
+ */
14
+ export interface WSContext {
15
+ send(data: string | Buffer): void
16
+ close(): void
17
+ readyState: number
18
+ // Internal properties for tracking
19
+ _workingDir?: string
20
+ _ws?: unknown
21
+ }
22
+
23
+ export interface WebSocketMessage {
24
+ type:
25
+ | 'execute'
26
+ | 'cancel'
27
+ | 'getHistory'
28
+ | 'getSessions'
29
+ | 'getSessionsCount'
30
+ | 'deleteSession'
31
+ | 'getSessionById'
32
+ | 'clearHistory'
33
+ | 'searchHistory'
34
+ | 'new'
35
+ | 'autocomplete'
36
+ | 'readFile'
37
+ | 'ping'
38
+ | 'initTab'
39
+ | 'resumeSession'
40
+ | 'approve'
41
+ | 'reject'
42
+ | 'recordSelection'
43
+ | 'requestNotificationSummary'
44
+ | 'terminalInit'
45
+ | 'terminalReconnect'
46
+ | 'terminalList'
47
+ | 'terminalInitPersistent'
48
+ | 'terminalListPersistent'
49
+ | 'terminalInput'
50
+ | 'terminalResize'
51
+ | 'terminalClose'
52
+ // File explorer message types
53
+ | 'listDirectory'
54
+ | 'writeFile'
55
+ | 'createFile'
56
+ | 'createDirectory'
57
+ | 'deleteFile'
58
+ | 'renameFile'
59
+ // Git message types
60
+ | 'gitStatus'
61
+ | 'gitStage'
62
+ | 'gitUnstage'
63
+ | 'gitCommit'
64
+ | 'gitCommitWithAI'
65
+ | 'gitPush'
66
+ | 'gitLog'
67
+ | 'gitDiscoverRepos'
68
+ | 'gitSetDirectory';
69
+ tabId?: string;
70
+ terminalId?: string;
71
+ data?: any;
72
+ }
73
+
74
+ export interface WebSocketResponse {
75
+ type:
76
+ | 'output'
77
+ | 'thinking'
78
+ | 'movementStart'
79
+ | 'movementComplete'
80
+ | 'movementError'
81
+ | 'sessionUpdate'
82
+ | 'history'
83
+ | 'sessions'
84
+ | 'sessionsCount'
85
+ | 'sessionDeleted'
86
+ | 'sessionData'
87
+ | 'historyCleared'
88
+ | 'searchResults'
89
+ | 'newSession'
90
+ | 'autocomplete'
91
+ | 'fileContent'
92
+ | 'error'
93
+ | 'pong'
94
+ | 'tabInitialized'
95
+ | 'approvalRequired'
96
+ | 'toolUse'
97
+ | 'notificationSummary'
98
+ | 'terminalOutput'
99
+ | 'terminalReady'
100
+ | 'terminalExit'
101
+ | 'terminalError'
102
+ | 'terminalScrollback'
103
+ | 'terminalList'
104
+ | 'terminalListPersistent'
105
+ // File explorer response types
106
+ | 'directoryListing'
107
+ | 'fileWritten'
108
+ | 'fileCreated'
109
+ | 'directoryCreated'
110
+ | 'fileDeleted'
111
+ | 'fileRenamed'
112
+ // Git response types
113
+ | 'gitStatus'
114
+ | 'gitStaged'
115
+ | 'gitUnstaged'
116
+ | 'gitCommitted'
117
+ | 'gitCommitMessage'
118
+ | 'gitPushed'
119
+ | 'gitLog'
120
+ | 'gitError'
121
+ | 'gitReposDiscovered'
122
+ | 'gitDirectorySet';
123
+ tabId?: string;
124
+ terminalId?: string;
125
+ data?: any;
126
+ }
127
+
128
+ export interface ConnectionData {
129
+ tabId: string;
130
+ workingDir: string;
131
+ }
132
+
133
+ // Extended autocomplete option with metadata
134
+ export interface AutocompleteResult {
135
+ value: string;
136
+ label: string;
137
+ isDirectory: boolean;
138
+ isRecent: boolean;
139
+ fileType: string;
140
+ matchedIndices: Array<[number, number]>;
141
+ }
142
+
143
+ // Frecency data structure - tracks completion usage
144
+ export interface FrecencyEntry {
145
+ count: number; // Total number of times selected
146
+ lastUsed: number; // Timestamp of last use
147
+ }
148
+
149
+ export interface FrecencyData {
150
+ [filePath: string]: FrecencyEntry;
151
+ }
152
+
153
+ // Directory cache for performance
154
+ export interface CacheEntry {
155
+ files: Array<{ relativePath: string; isDirectory: boolean; fileName: string; depth: number }>;
156
+ timestamp: number;
157
+ }
158
+
159
+ export interface FileMetadata {
160
+ relativePath: string;
161
+ isDirectory: boolean;
162
+ fileName: string;
163
+ depth: number;
164
+ }
165
+
166
+ export interface FileReadResult {
167
+ filePath: string;
168
+ fileName: string;
169
+ content: string;
170
+ isImage?: boolean;
171
+ mimeType?: string;
172
+ error?: string;
173
+ }
174
+
175
+ // ============================================================================
176
+ // File Explorer Types
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Directory entry for file explorer listing
181
+ */
182
+ export interface DirectoryEntry {
183
+ name: string;
184
+ path: string;
185
+ type: 'file' | 'directory';
186
+ size?: number;
187
+ modifiedAt?: string;
188
+ }
189
+
190
+ /**
191
+ * Message data for listDirectory request
192
+ */
193
+ export interface ListDirectoryData {
194
+ dirPath: string;
195
+ showHidden?: boolean;
196
+ }
197
+
198
+ /**
199
+ * Message data for writeFile request
200
+ */
201
+ export interface WriteFileData {
202
+ filePath: string;
203
+ content: string;
204
+ }
205
+
206
+ /**
207
+ * Message data for createFile request
208
+ */
209
+ export interface CreateFileData {
210
+ filePath: string;
211
+ }
212
+
213
+ /**
214
+ * Message data for createDirectory request
215
+ */
216
+ export interface CreateDirectoryData {
217
+ dirPath: string;
218
+ }
219
+
220
+ /**
221
+ * Message data for deleteFile request
222
+ */
223
+ export interface DeleteFileData {
224
+ filePath: string;
225
+ }
226
+
227
+ /**
228
+ * Message data for renameFile request
229
+ */
230
+ export interface RenameFileData {
231
+ oldPath: string;
232
+ newPath: string;
233
+ }
234
+
235
+ /**
236
+ * Response data for directoryListing
237
+ */
238
+ export interface DirectoryListingResponse {
239
+ success: boolean;
240
+ entries?: DirectoryEntry[];
241
+ error?: string;
242
+ }
243
+
244
+ /**
245
+ * Response data for file operations (write, create, delete, rename)
246
+ */
247
+ export interface FileOperationResponse {
248
+ success: boolean;
249
+ path?: string;
250
+ error?: string;
251
+ }
252
+
253
+ // ============================================================================
254
+ // Git Types
255
+ // ============================================================================
256
+
257
+ /**
258
+ * Git file status entry
259
+ */
260
+ export interface GitFileStatus {
261
+ /** File path relative to working directory */
262
+ path: string;
263
+ /** Status code (M=modified, A=added, D=deleted, ?=untracked, R=renamed) */
264
+ status: 'M' | 'A' | 'D' | '?' | 'R' | 'C' | 'U';
265
+ /** Whether the file is staged */
266
+ staged: boolean;
267
+ /** Original path (for renamed files) */
268
+ originalPath?: string;
269
+ }
270
+
271
+ /**
272
+ * Git status response
273
+ */
274
+ export interface GitStatusResponse {
275
+ /** Current branch name */
276
+ branch: string;
277
+ /** Whether the repository has uncommitted changes */
278
+ isDirty: boolean;
279
+ /** Staged files */
280
+ staged: GitFileStatus[];
281
+ /** Unstaged/modified files */
282
+ unstaged: GitFileStatus[];
283
+ /** Untracked files */
284
+ untracked: GitFileStatus[];
285
+ /** Number of commits ahead of remote */
286
+ ahead: number;
287
+ /** Number of commits behind remote */
288
+ behind: number;
289
+ }
290
+
291
+ /**
292
+ * Git commit log entry
293
+ */
294
+ export interface GitLogEntry {
295
+ /** Commit hash */
296
+ hash: string;
297
+ /** Short hash */
298
+ shortHash: string;
299
+ /** Commit message subject */
300
+ subject: string;
301
+ /** Author name */
302
+ author: string;
303
+ /** Commit date (ISO string) */
304
+ date: string;
305
+ }
306
+
307
+ /**
308
+ * Discovered git repository info
309
+ */
310
+ export interface GitRepoInfo {
311
+ /** Path to the git repository (directory containing .git) */
312
+ path: string;
313
+ /** Repository name (directory name) */
314
+ name: string;
315
+ /** Current branch if available */
316
+ branch?: string;
317
+ }
318
+
319
+ /**
320
+ * Git repos discovered response
321
+ */
322
+ export interface GitReposDiscoveredResponse {
323
+ /** List of discovered git repositories */
324
+ repos: GitRepoInfo[];
325
+ /** Whether the root working directory is a git repo */
326
+ rootIsGitRepo: boolean;
327
+ /** Currently selected git directory (if any) */
328
+ selectedDirectory: string | null;
329
+ }
330
+
331
+ /**
332
+ * Git directory set response
333
+ */
334
+ export interface GitDirectorySetResponse {
335
+ /** The directory that was set */
336
+ directory: string;
337
+ /** Whether the directory is valid (contains .git) */
338
+ isValid: boolean;
339
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "isolatedModules": true,
14
+ "outDir": "../dist/server",
15
+ "rootDir": "."
16
+ },
17
+ "include": ["./**/*"],
18
+ "exclude": ["node_modules"]
19
+ }
@@ -0,0 +1,323 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Agent Manager
6
+ *
7
+ * Handles agent discovery, installation, and availability checking.
8
+ * Implements pre-flight file copy approach for agent hot-loading.
9
+ */
10
+
11
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
12
+ import { homedir } from 'node:os';
13
+ import { basename, dirname, join } from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ export interface AgentInfo {
17
+ name: string;
18
+ path: string;
19
+ location: 'bundled' | 'global' | 'project';
20
+ description?: string;
21
+ }
22
+
23
+ export class AgentManager {
24
+ private bundledAgentsPath: string;
25
+ private globalAgentsPath: string;
26
+
27
+ constructor() {
28
+ // Path to bundled agents in mstro installation
29
+ // In ES modules, we need to get __dirname equivalent using import.meta.url
30
+ const __filename = fileURLToPath(import.meta.url);
31
+ const __dirname = dirname(__filename);
32
+ this.bundledAgentsPath = join(dirname(dirname(__dirname)), '.claude', 'agents');
33
+
34
+ // Global user agents directory
35
+ this.globalAgentsPath = join(homedir(), '.claude', 'agents');
36
+ }
37
+
38
+ /**
39
+ * Get path to project agents directory
40
+ */
41
+ private getProjectAgentsPath(workingDir: string): string {
42
+ return join(workingDir, '.claude', 'agents');
43
+ }
44
+
45
+ /**
46
+ * List all available bundled agents
47
+ */
48
+ listBundledAgents(): AgentInfo[] {
49
+ if (!existsSync(this.bundledAgentsPath)) {
50
+ return [];
51
+ }
52
+
53
+ const files = readdirSync(this.bundledAgentsPath)
54
+ .filter(f => f.endsWith('.md') && f !== 'README.md');
55
+
56
+ return files.map(file => ({
57
+ name: basename(file, '.md'),
58
+ path: join(this.bundledAgentsPath, file),
59
+ location: 'bundled' as const,
60
+ description: this.extractDescription(join(this.bundledAgentsPath, file))
61
+ }));
62
+ }
63
+
64
+ /**
65
+ * List all globally installed agents
66
+ */
67
+ listGlobalAgents(): AgentInfo[] {
68
+ if (!existsSync(this.globalAgentsPath)) {
69
+ return [];
70
+ }
71
+
72
+ const files = readdirSync(this.globalAgentsPath)
73
+ .filter(f => f.endsWith('.md'));
74
+
75
+ return files.map(file => ({
76
+ name: basename(file, '.md'),
77
+ path: join(this.globalAgentsPath, file),
78
+ location: 'global' as const,
79
+ description: this.extractDescription(join(this.globalAgentsPath, file))
80
+ }));
81
+ }
82
+
83
+ /**
84
+ * List all project-level agents
85
+ */
86
+ listProjectAgents(workingDir: string): AgentInfo[] {
87
+ const projectPath = this.getProjectAgentsPath(workingDir);
88
+ if (!existsSync(projectPath)) {
89
+ return [];
90
+ }
91
+
92
+ const files = readdirSync(projectPath)
93
+ .filter(f => f.endsWith('.md'));
94
+
95
+ return files.map(file => ({
96
+ name: basename(file, '.md'),
97
+ path: join(projectPath, file),
98
+ location: 'project' as const,
99
+ description: this.extractDescription(join(projectPath, file))
100
+ }));
101
+ }
102
+
103
+ /**
104
+ * Extract description from agent markdown file (first line after title)
105
+ */
106
+ private extractDescription(filePath: string): string | undefined {
107
+ try {
108
+ const content = readFileSync(filePath, 'utf-8');
109
+ const lines = content.split('\n').filter(l => l.trim());
110
+ // Find first non-header line
111
+ for (const line of lines) {
112
+ if (!line.startsWith('#')) {
113
+ return line.trim();
114
+ }
115
+ }
116
+ } catch {
117
+ // Ignore errors
118
+ }
119
+ return undefined;
120
+ }
121
+
122
+ /**
123
+ * Check if an agent is available (exists in project, global, or bundled)
124
+ */
125
+ findAgent(agentName: string, workingDir?: string): AgentInfo | null {
126
+ const agentFile = `${agentName}.md`;
127
+
128
+ // 1. Check project-level (if working dir provided)
129
+ if (workingDir) {
130
+ const projectPath = join(this.getProjectAgentsPath(workingDir), agentFile);
131
+ if (existsSync(projectPath)) {
132
+ return {
133
+ name: agentName,
134
+ path: projectPath,
135
+ location: 'project',
136
+ description: this.extractDescription(projectPath)
137
+ };
138
+ }
139
+ }
140
+
141
+ // 2. Check global
142
+ const globalPath = join(this.globalAgentsPath, agentFile);
143
+ if (existsSync(globalPath)) {
144
+ return {
145
+ name: agentName,
146
+ path: globalPath,
147
+ location: 'global',
148
+ description: this.extractDescription(globalPath)
149
+ };
150
+ }
151
+
152
+ // 3. Check bundled
153
+ const bundledPath = join(this.bundledAgentsPath, agentFile);
154
+ if (existsSync(bundledPath)) {
155
+ return {
156
+ name: agentName,
157
+ path: bundledPath,
158
+ location: 'bundled',
159
+ description: this.extractDescription(bundledPath)
160
+ };
161
+ }
162
+
163
+ return null;
164
+ }
165
+
166
+ /**
167
+ * Ensure an agent is available for use
168
+ * If not in project or global, copy from bundled to project
169
+ */
170
+ async ensureAgentAvailable(agentName: string, workingDir: string): Promise<AgentInfo> {
171
+ const agentFile = `${agentName}.md`;
172
+ const projectPath = join(this.getProjectAgentsPath(workingDir), agentFile);
173
+ const globalPath = join(this.globalAgentsPath, agentFile);
174
+ const bundledPath = join(this.bundledAgentsPath, agentFile);
175
+
176
+ // If already in project or global, we're done
177
+ if (existsSync(projectPath)) {
178
+ return {
179
+ name: agentName,
180
+ path: projectPath,
181
+ location: 'project',
182
+ description: this.extractDescription(projectPath)
183
+ };
184
+ }
185
+
186
+ if (existsSync(globalPath)) {
187
+ return {
188
+ name: agentName,
189
+ path: globalPath,
190
+ location: 'global',
191
+ description: this.extractDescription(globalPath)
192
+ };
193
+ }
194
+
195
+ // Check if bundled agent exists
196
+ if (!existsSync(bundledPath)) {
197
+ throw new Error(`Agent not found: ${agentName}\nNot available in bundled, global, or project agents.`);
198
+ }
199
+
200
+ // Copy bundled agent to project
201
+ const targetDir = dirname(projectPath);
202
+ mkdirSync(targetDir, { recursive: true });
203
+ copyFileSync(bundledPath, projectPath);
204
+
205
+ return {
206
+ name: agentName,
207
+ path: projectPath,
208
+ location: 'project',
209
+ description: this.extractDescription(projectPath)
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Install a specific bundled agent to global directory
215
+ */
216
+ installAgentGlobally(agentName: string): void {
217
+ const agentFile = `${agentName}.md`;
218
+ const bundledPath = join(this.bundledAgentsPath, agentFile);
219
+ const globalPath = join(this.globalAgentsPath, agentFile);
220
+
221
+ if (!existsSync(bundledPath)) {
222
+ throw new Error(`Bundled agent not found: ${agentName}`);
223
+ }
224
+
225
+ // Create global agents directory if needed
226
+ mkdirSync(this.globalAgentsPath, { recursive: true });
227
+
228
+ // Copy to global
229
+ copyFileSync(bundledPath, globalPath);
230
+ }
231
+
232
+ /**
233
+ * Install all bundled agents to global directory
234
+ */
235
+ installAllAgentsGlobally(): string[] {
236
+ const bundled = this.listBundledAgents();
237
+ const installed: string[] = [];
238
+
239
+ mkdirSync(this.globalAgentsPath, { recursive: true });
240
+
241
+ for (const agent of bundled) {
242
+ const targetPath = join(this.globalAgentsPath, `${agent.name}.md`);
243
+ copyFileSync(agent.path, targetPath);
244
+ installed.push(agent.name);
245
+ }
246
+
247
+ return installed;
248
+ }
249
+
250
+ /**
251
+ * Install a specific bundled agent to project directory
252
+ */
253
+ installAgentToProject(agentName: string, workingDir: string): void {
254
+ const agentFile = `${agentName}.md`;
255
+ const bundledPath = join(this.bundledAgentsPath, agentFile);
256
+ const projectPath = join(this.getProjectAgentsPath(workingDir), agentFile);
257
+
258
+ if (!existsSync(bundledPath)) {
259
+ throw new Error(`Bundled agent not found: ${agentName}`);
260
+ }
261
+
262
+ // Create project agents directory if needed
263
+ const targetDir = dirname(projectPath);
264
+ mkdirSync(targetDir, { recursive: true });
265
+
266
+ // Copy to project
267
+ copyFileSync(bundledPath, projectPath);
268
+ }
269
+
270
+ /**
271
+ * Install all bundled agents to project directory
272
+ */
273
+ installAllAgentsToProject(workingDir: string): string[] {
274
+ const bundled = this.listBundledAgents();
275
+ const installed: string[] = [];
276
+
277
+ const projectAgentsPath = this.getProjectAgentsPath(workingDir);
278
+ mkdirSync(projectAgentsPath, { recursive: true });
279
+
280
+ for (const agent of bundled) {
281
+ const targetPath = join(projectAgentsPath, `${agent.name}.md`);
282
+ copyFileSync(agent.path, targetPath);
283
+ installed.push(agent.name);
284
+ }
285
+
286
+ return installed;
287
+ }
288
+
289
+ /**
290
+ * Extract agent names from a score object
291
+ */
292
+ extractAgentNamesFromScore(score: any): string[] {
293
+ if (!Array.isArray(score.movements)) return [];
294
+
295
+ const names = (score.movements as any[])
296
+ .flatMap(m => Array.isArray(m.musicians) ? m.musicians : [])
297
+ .filter((m: any) => m.type === 'custom')
298
+ .map((m: any) => m.config?.agent || m.role)
299
+ .filter(Boolean);
300
+
301
+ return [...new Set<string>(names)];
302
+ }
303
+
304
+ /**
305
+ * Ensure all agents required by a score are available
306
+ */
307
+ async ensureScoreAgentsAvailable(score: any, workingDir: string): Promise<Map<string, AgentInfo>> {
308
+ const agentNames = this.extractAgentNamesFromScore(score);
309
+ const results = new Map<string, AgentInfo>();
310
+
311
+ for (const agentName of agentNames) {
312
+ const info = await this.ensureAgentAvailable(agentName, workingDir);
313
+ results.set(agentName, info);
314
+ }
315
+
316
+ return results;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Singleton instance
322
+ */
323
+ export const agentManager = new AgentManager();