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.
- package/LICENSE +21 -0
- package/README.md +177 -0
- package/bin/commands/config.js +145 -0
- package/bin/commands/login.js +313 -0
- package/bin/commands/logout.js +75 -0
- package/bin/commands/status.js +197 -0
- package/bin/commands/whoami.js +161 -0
- package/bin/configure-claude.js +298 -0
- package/bin/mstro.js +581 -0
- package/bin/postinstall.js +45 -0
- package/bin/release.sh +110 -0
- package/dist/server/cli/headless/claude-invoker.d.ts +17 -0
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker.js +311 -0
- package/dist/server/cli/headless/claude-invoker.js.map +1 -0
- package/dist/server/cli/headless/index.d.ts +13 -0
- package/dist/server/cli/headless/index.d.ts.map +1 -0
- package/dist/server/cli/headless/index.js +10 -0
- package/dist/server/cli/headless/index.js.map +1 -0
- package/dist/server/cli/headless/mcp-config.d.ts +11 -0
- package/dist/server/cli/headless/mcp-config.d.ts.map +1 -0
- package/dist/server/cli/headless/mcp-config.js +76 -0
- package/dist/server/cli/headless/mcp-config.js.map +1 -0
- package/dist/server/cli/headless/output-utils.d.ts +33 -0
- package/dist/server/cli/headless/output-utils.d.ts.map +1 -0
- package/dist/server/cli/headless/output-utils.js +101 -0
- package/dist/server/cli/headless/output-utils.js.map +1 -0
- package/dist/server/cli/headless/prompt-utils.d.ts +21 -0
- package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -0
- package/dist/server/cli/headless/prompt-utils.js +84 -0
- package/dist/server/cli/headless/prompt-utils.js.map +1 -0
- package/dist/server/cli/headless/runner.d.ts +24 -0
- package/dist/server/cli/headless/runner.d.ts.map +1 -0
- package/dist/server/cli/headless/runner.js +99 -0
- package/dist/server/cli/headless/runner.js.map +1 -0
- package/dist/server/cli/headless/types.d.ts +106 -0
- package/dist/server/cli/headless/types.d.ts.map +1 -0
- package/dist/server/cli/headless/types.js +4 -0
- package/dist/server/cli/headless/types.js.map +1 -0
- package/dist/server/cli/improvisation-session-manager.d.ts +155 -0
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -0
- package/dist/server/cli/improvisation-session-manager.js +415 -0
- package/dist/server/cli/improvisation-session-manager.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +386 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/mcp/bouncer-cli.d.ts +3 -0
- package/dist/server/mcp/bouncer-cli.d.ts.map +1 -0
- package/dist/server/mcp/bouncer-cli.js +99 -0
- package/dist/server/mcp/bouncer-cli.js.map +1 -0
- package/dist/server/mcp/bouncer-integration.d.ts +36 -0
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -0
- package/dist/server/mcp/bouncer-integration.js +301 -0
- package/dist/server/mcp/bouncer-integration.js.map +1 -0
- package/dist/server/mcp/security-audit.d.ts +52 -0
- package/dist/server/mcp/security-audit.d.ts.map +1 -0
- package/dist/server/mcp/security-audit.js +118 -0
- package/dist/server/mcp/security-audit.js.map +1 -0
- package/dist/server/mcp/security-patterns.d.ts +73 -0
- package/dist/server/mcp/security-patterns.d.ts.map +1 -0
- package/dist/server/mcp/security-patterns.js +247 -0
- package/dist/server/mcp/security-patterns.js.map +1 -0
- package/dist/server/mcp/server.d.ts +3 -0
- package/dist/server/mcp/server.d.ts.map +1 -0
- package/dist/server/mcp/server.js +146 -0
- package/dist/server/mcp/server.js.map +1 -0
- package/dist/server/routes/files.d.ts +9 -0
- package/dist/server/routes/files.d.ts.map +1 -0
- package/dist/server/routes/files.js +24 -0
- package/dist/server/routes/files.js.map +1 -0
- package/dist/server/routes/improvise.d.ts +3 -0
- package/dist/server/routes/improvise.d.ts.map +1 -0
- package/dist/server/routes/improvise.js +72 -0
- package/dist/server/routes/improvise.js.map +1 -0
- package/dist/server/routes/index.d.ts +10 -0
- package/dist/server/routes/index.d.ts.map +1 -0
- package/dist/server/routes/index.js +12 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/server/routes/instances.d.ts +10 -0
- package/dist/server/routes/instances.d.ts.map +1 -0
- package/dist/server/routes/instances.js +47 -0
- package/dist/server/routes/instances.js.map +1 -0
- package/dist/server/routes/notifications.d.ts +3 -0
- package/dist/server/routes/notifications.d.ts.map +1 -0
- package/dist/server/routes/notifications.js +136 -0
- package/dist/server/routes/notifications.js.map +1 -0
- package/dist/server/services/analytics.d.ts +56 -0
- package/dist/server/services/analytics.d.ts.map +1 -0
- package/dist/server/services/analytics.js +240 -0
- package/dist/server/services/analytics.js.map +1 -0
- package/dist/server/services/auth.d.ts +26 -0
- package/dist/server/services/auth.d.ts.map +1 -0
- package/dist/server/services/auth.js +71 -0
- package/dist/server/services/auth.js.map +1 -0
- package/dist/server/services/client-id.d.ts +10 -0
- package/dist/server/services/client-id.d.ts.map +1 -0
- package/dist/server/services/client-id.js +61 -0
- package/dist/server/services/client-id.js.map +1 -0
- package/dist/server/services/credentials.d.ts +39 -0
- package/dist/server/services/credentials.d.ts.map +1 -0
- package/dist/server/services/credentials.js +110 -0
- package/dist/server/services/credentials.js.map +1 -0
- package/dist/server/services/files.d.ts +119 -0
- package/dist/server/services/files.d.ts.map +1 -0
- package/dist/server/services/files.js +560 -0
- package/dist/server/services/files.js.map +1 -0
- package/dist/server/services/instances.d.ts +52 -0
- package/dist/server/services/instances.d.ts.map +1 -0
- package/dist/server/services/instances.js +241 -0
- package/dist/server/services/instances.js.map +1 -0
- package/dist/server/services/pathUtils.d.ts +47 -0
- package/dist/server/services/pathUtils.d.ts.map +1 -0
- package/dist/server/services/pathUtils.js +124 -0
- package/dist/server/services/pathUtils.js.map +1 -0
- package/dist/server/services/platform.d.ts +72 -0
- package/dist/server/services/platform.d.ts.map +1 -0
- package/dist/server/services/platform.js +368 -0
- package/dist/server/services/platform.js.map +1 -0
- package/dist/server/services/sentry.d.ts +5 -0
- package/dist/server/services/sentry.d.ts.map +1 -0
- package/dist/server/services/sentry.js +71 -0
- package/dist/server/services/sentry.js.map +1 -0
- package/dist/server/services/terminal/pty-manager.d.ts +149 -0
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -0
- package/dist/server/services/terminal/pty-manager.js +377 -0
- package/dist/server/services/terminal/pty-manager.js.map +1 -0
- package/dist/server/services/terminal/tmux-manager.d.ts +82 -0
- package/dist/server/services/terminal/tmux-manager.d.ts.map +1 -0
- package/dist/server/services/terminal/tmux-manager.js +352 -0
- package/dist/server/services/terminal/tmux-manager.js.map +1 -0
- package/dist/server/services/websocket/autocomplete.d.ts +50 -0
- package/dist/server/services/websocket/autocomplete.d.ts.map +1 -0
- package/dist/server/services/websocket/autocomplete.js +361 -0
- package/dist/server/services/websocket/autocomplete.js.map +1 -0
- package/dist/server/services/websocket/file-utils.d.ts +44 -0
- package/dist/server/services/websocket/file-utils.d.ts.map +1 -0
- package/dist/server/services/websocket/file-utils.js +272 -0
- package/dist/server/services/websocket/file-utils.js.map +1 -0
- package/dist/server/services/websocket/handler.d.ts +246 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -0
- package/dist/server/services/websocket/handler.js +1771 -0
- package/dist/server/services/websocket/handler.js.map +1 -0
- package/dist/server/services/websocket/index.d.ts +11 -0
- package/dist/server/services/websocket/index.d.ts.map +1 -0
- package/dist/server/services/websocket/index.js +14 -0
- package/dist/server/services/websocket/index.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +214 -0
- package/dist/server/services/websocket/types.d.ts.map +1 -0
- package/dist/server/services/websocket/types.js +4 -0
- package/dist/server/services/websocket/types.js.map +1 -0
- package/dist/server/utils/agent-manager.d.ts +69 -0
- package/dist/server/utils/agent-manager.d.ts.map +1 -0
- package/dist/server/utils/agent-manager.js +269 -0
- package/dist/server/utils/agent-manager.js.map +1 -0
- package/dist/server/utils/paths.d.ts +25 -0
- package/dist/server/utils/paths.d.ts.map +1 -0
- package/dist/server/utils/paths.js +38 -0
- package/dist/server/utils/paths.js.map +1 -0
- package/dist/server/utils/port-manager.d.ts +10 -0
- package/dist/server/utils/port-manager.d.ts.map +1 -0
- package/dist/server/utils/port-manager.js +60 -0
- package/dist/server/utils/port-manager.js.map +1 -0
- package/dist/server/utils/port.d.ts +26 -0
- package/dist/server/utils/port.d.ts.map +1 -0
- package/dist/server/utils/port.js +83 -0
- package/dist/server/utils/port.js.map +1 -0
- package/hooks/bouncer.sh +138 -0
- package/package.json +74 -0
- package/server/README.md +191 -0
- package/server/cli/headless/claude-invoker.ts +415 -0
- package/server/cli/headless/index.ts +39 -0
- package/server/cli/headless/mcp-config.ts +87 -0
- package/server/cli/headless/output-utils.ts +109 -0
- package/server/cli/headless/prompt-utils.ts +108 -0
- package/server/cli/headless/runner.ts +133 -0
- package/server/cli/headless/types.ts +118 -0
- package/server/cli/improvisation-session-manager.ts +531 -0
- package/server/index.ts +456 -0
- package/server/mcp/README.md +122 -0
- package/server/mcp/bouncer-cli.ts +127 -0
- package/server/mcp/bouncer-integration.ts +430 -0
- package/server/mcp/security-audit.ts +180 -0
- package/server/mcp/security-patterns.ts +290 -0
- package/server/mcp/server.ts +174 -0
- package/server/routes/files.ts +29 -0
- package/server/routes/improvise.ts +82 -0
- package/server/routes/index.ts +13 -0
- package/server/routes/instances.ts +54 -0
- package/server/routes/notifications.ts +158 -0
- package/server/services/analytics.ts +277 -0
- package/server/services/auth.ts +80 -0
- package/server/services/client-id.ts +68 -0
- package/server/services/credentials.ts +134 -0
- package/server/services/files.ts +710 -0
- package/server/services/instances.ts +275 -0
- package/server/services/pathUtils.ts +158 -0
- package/server/services/platform.test.ts +1314 -0
- package/server/services/platform.ts +435 -0
- package/server/services/sentry.ts +81 -0
- package/server/services/terminal/pty-manager.ts +464 -0
- package/server/services/terminal/tmux-manager.ts +426 -0
- package/server/services/websocket/autocomplete.ts +438 -0
- package/server/services/websocket/file-utils.ts +305 -0
- package/server/services/websocket/handler.test.ts +20 -0
- package/server/services/websocket/handler.ts +2047 -0
- package/server/services/websocket/index.ts +40 -0
- package/server/services/websocket/types.ts +339 -0
- package/server/tsconfig.json +19 -0
- package/server/utils/agent-manager.ts +323 -0
- package/server/utils/paths.ts +45 -0
- package/server/utils/port-manager.ts +70 -0
- 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();
|