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
package/bin/mstro.js ADDED
@@ -0,0 +1,581 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
3
+ // Licensed under the MIT License. See LICENSE file for details.
4
+
5
+ /**
6
+ * Mstro CLI
7
+ *
8
+ * Main entry point for the Mstro AI assistant.
9
+ *
10
+ * Usage:
11
+ * mstro # Start Mstro (auto-finds available port)
12
+ * mstro login # Authenticate this device
13
+ * mstro logout # Sign out
14
+ * mstro whoami # Show current user
15
+ * mstro status # Show connection status
16
+ * mstro setup-terminal # Enable web terminal
17
+ * mstro -p 4105 # Start on specific port (overrides auto port)
18
+ * mstro configure-hooks # Configure Claude Code security hooks
19
+ * mstro --help # Show help
20
+ */
21
+
22
+ import { execSync, spawn } from 'node:child_process';
23
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
24
+ import { homedir, platform as osPlatform } from 'node:os';
25
+ import { dirname, join, resolve } from 'node:path';
26
+ import { createInterface } from 'node:readline';
27
+ import { fileURLToPath } from 'node:url';
28
+ import updateNotifier from 'update-notifier';
29
+
30
+ const __filename = fileURLToPath(import.meta.url);
31
+ const __dirname = dirname(__filename);
32
+ const CLIENT_ROOT = resolve(__dirname, '..');
33
+
34
+ // Read package.json for update-notifier
35
+ const pkg = JSON.parse(readFileSync(join(CLIENT_ROOT, 'package.json'), 'utf-8'));
36
+
37
+ // Check for updates (runs async in background, notifies on next run)
38
+ const notifier = updateNotifier({
39
+ pkg,
40
+ updateCheckInterval: 1000 * 60 * 60 * 24 // Check daily
41
+ });
42
+
43
+ // Capture the user's original working directory before any cwd changes
44
+ const USER_CWD = process.cwd();
45
+
46
+ // First-run detection paths
47
+ const MSTRO_CONFIG_DIR = join(homedir(), '.mstro');
48
+ const MSTRO_FIRST_RUN_FLAG = join(MSTRO_CONFIG_DIR, '.configured');
49
+ const MSTRO_TERMINAL_CHECKED_FLAG = join(MSTRO_CONFIG_DIR, '.terminal-checked');
50
+ const MSTRO_TELEMETRY_NOTICE_FLAG = join(MSTRO_CONFIG_DIR, '.telemetry-notice-shown');
51
+ const CLAUDE_SETTINGS_FILE = join(homedir(), '.claude', 'settings.json');
52
+ const CLAUDE_HOOKS_DIR = join(homedir(), '.claude', 'hooks');
53
+ const BOUNCER_HOOK_FILE = join(CLAUDE_HOOKS_DIR, 'bouncer.sh');
54
+
55
+ /**
56
+ * Mark Mstro as configured by writing the first-run flag file
57
+ */
58
+ function markConfigured() {
59
+ try {
60
+ mkdirSync(MSTRO_CONFIG_DIR, { recursive: true, mode: 0o700 });
61
+ writeFileSync(MSTRO_FIRST_RUN_FLAG, new Date().toISOString());
62
+ } catch (_) {
63
+ // Ignore errors — non-critical
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Set the terminal tab title
69
+ * Format: "mstro: directory_name"
70
+ * Uses ANSI escape sequence: ESC ] 0 ; title BEL
71
+ */
72
+ function setTerminalTitle(directory) {
73
+ const dirName = directory.split('/').pop() || directory;
74
+ const title = `mstro: ${dirName}`;
75
+ // ESC ] 0 ; title BEL - sets both window title and tab title
76
+ process.stdout.write(`\x1b]0;${title}\x07`);
77
+ }
78
+
79
+ // Set terminal title on startup
80
+ setTerminalTitle(process.cwd());
81
+
82
+ // ANSI colors
83
+ const colors = {
84
+ reset: '\x1b[0m',
85
+ bold: '\x1b[1m',
86
+ green: '\x1b[32m',
87
+ yellow: '\x1b[33m',
88
+ blue: '\x1b[34m',
89
+ red: '\x1b[31m',
90
+ dim: '\x1b[2m',
91
+ cyan: '\x1b[36m',
92
+ };
93
+
94
+ function log(msg, color = '') {
95
+ console.log(`${color}${msg}${colors.reset}`);
96
+ }
97
+
98
+ function prompt(question) {
99
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
100
+ return new Promise((resolve) => {
101
+ rl.question(question, (answer) => {
102
+ rl.close();
103
+ resolve(answer.trim().toLowerCase());
104
+ });
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Check if bouncer hooks are properly configured
110
+ * Returns true if both settings.json has the hook AND the hook file exists
111
+ */
112
+ function isBouncerConfigured() {
113
+ if (!existsSync(BOUNCER_HOOK_FILE) || !existsSync(CLAUDE_SETTINGS_FILE)) {
114
+ return false;
115
+ }
116
+
117
+ try {
118
+ const settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8'));
119
+ const preToolUseHooks = settings.hooks?.PreToolUse;
120
+ return Array.isArray(preToolUseHooks) &&
121
+ preToolUseHooks.some(matcher =>
122
+ Array.isArray(matcher.hooks) &&
123
+ matcher.hooks.some(hook => hook.command?.includes('bouncer.sh'))
124
+ );
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Check if user has dismissed the bouncer setup prompt
132
+ */
133
+ function hasUserDismissedSetup() {
134
+ return existsSync(MSTRO_FIRST_RUN_FLAG);
135
+ }
136
+
137
+ /**
138
+ * Mark bouncer setup as dismissed by user
139
+ */
140
+ function markSetupDismissed() {
141
+ if (!existsSync(MSTRO_CONFIG_DIR)) {
142
+ mkdirSync(MSTRO_CONFIG_DIR, { recursive: true, mode: 0o700 });
143
+ }
144
+ writeFileSync(MSTRO_FIRST_RUN_FLAG, new Date().toISOString());
145
+ }
146
+
147
+ /**
148
+ * Show a one-line warning that bouncer is not configured
149
+ */
150
+ function showBouncerWarning() {
151
+ log(' Security Bouncer not configured. Run: mstro configure-hooks', colors.dim);
152
+ }
153
+
154
+ /**
155
+ * Show telemetry notice on first run (one-time)
156
+ */
157
+ function showTelemetryNotice() {
158
+ if (existsSync(MSTRO_TELEMETRY_NOTICE_FLAG)) {
159
+ return; // Already shown
160
+ }
161
+
162
+ log('');
163
+ log(' Telemetry Notice', colors.bold);
164
+ log(' Mstro collects anonymous error reports and usage data to improve', colors.dim);
165
+ log(' the software. No personal data or code is collected.', colors.dim);
166
+ log('');
167
+ log(' To disable: mstro telemetry off', colors.dim);
168
+ log(' Privacy policy: https://github.com/mstro-app/mstro/blob/main/cli/PRIVACY.md', colors.dim);
169
+ log('');
170
+
171
+ // Mark as shown
172
+ try {
173
+ if (!existsSync(MSTRO_CONFIG_DIR)) {
174
+ mkdirSync(MSTRO_CONFIG_DIR, { recursive: true, mode: 0o700 });
175
+ }
176
+ writeFileSync(MSTRO_TELEMETRY_NOTICE_FLAG, new Date().toISOString());
177
+ } catch {
178
+ // Ignore errors - non-critical
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Prompt user to configure hooks
184
+ * Returns: 'configure' | 'skip' | 'never'
185
+ */
186
+ async function promptBouncerSetup() {
187
+ log('\n Welcome to Mstro!\n', colors.bold + colors.cyan);
188
+ log(' Mstro includes a Security Bouncer that automatically manages', colors.dim);
189
+ log(' tool permissions for Claude Code - keeping you safe while you work.\n', colors.dim);
190
+
191
+ log(' The Security Bouncer:', colors.bold);
192
+ log(' - Blocks dangerous commands automatically', colors.dim);
193
+ log(' - Allows normal development work without interruption', colors.dim);
194
+ log(' - Uses AI to analyze ambiguous operations\n', colors.dim);
195
+
196
+ const isInteractive = process.stdin.isTTY;
197
+
198
+ if (!isInteractive) {
199
+ log(' Non-interactive mode: skipping bouncer setup.', colors.yellow);
200
+ log(' Run "mstro configure-hooks" to set up the Security Bouncer.\n', colors.dim);
201
+ return 'skip';
202
+ }
203
+
204
+ log(' Configure Security Bouncer now?', colors.bold);
205
+ log(' [Y] Yes, configure now', colors.dim);
206
+ log(' [n] Not now (ask again next time)', colors.dim);
207
+ log(' [d] Don\'t show this again\n', colors.dim);
208
+
209
+ const answer = await prompt(' Your choice [Y/n/d]: ');
210
+ const choice = answer.toLowerCase();
211
+
212
+ if (choice === '' || choice === 'y' || choice === 'yes') {
213
+ log('');
214
+ return 'configure';
215
+ } else if (choice === 'd' || choice === 'dont' || choice === "don't") {
216
+ log('\n Got it! You can configure later with: mstro configure-hooks\n', colors.dim);
217
+ markSetupDismissed(); // Don't show full prompt again
218
+ return 'never';
219
+ } else {
220
+ log('\n Skipping for now. Will ask again next time.', colors.yellow);
221
+ log(' You can also configure with: mstro configure-hooks\n', colors.dim);
222
+ return 'skip';
223
+ }
224
+ }
225
+
226
+ function showHelp() {
227
+ log('\n Mstro - No-code AI Assistant\n', colors.bold + colors.cyan);
228
+ log(' Run Claude Code workflows from your laptop, cloud VM, or any machine.\n', colors.dim);
229
+ log(' Usage:', colors.bold);
230
+ log(' mstro Start Mstro (auto-finds available port)', colors.dim);
231
+ log(' mstro login Authenticate this device with mstro.app', colors.dim);
232
+ log(' mstro logout Sign out of mstro.app', colors.dim);
233
+ log(' mstro whoami Show current user and device info', colors.dim);
234
+ log(' mstro status Show connection and auth status', colors.dim);
235
+ log(' mstro telemetry [on|off] Enable/disable anonymous telemetry', colors.dim);
236
+ log(' mstro setup-terminal Enable web terminal (compiles native module)', colors.dim);
237
+ log(' mstro -p 4105 Start on specific port (overrides auto port)', colors.dim);
238
+ log(' mstro configure-hooks Configure Claude Code security hooks', colors.dim);
239
+ log(' mstro --version Show version number', colors.dim);
240
+ log(' mstro --help Show this help message', colors.dim);
241
+ log('');
242
+ log(' Options:', colors.bold);
243
+ log(' --port, -p <port> Override automatic port selection', colors.dim);
244
+ log(' --working-dir, -w <dir> Set working directory', colors.dim);
245
+ log(' --verbose, -v Enable verbose output', colors.dim);
246
+ log('');
247
+ log(' Authentication:', colors.bold);
248
+ log(' Run "mstro login" to connect this device to your mstro.app account.', colors.dim);
249
+ log(' Once logged in, orchestras sync automatically with your web dashboard.', colors.dim);
250
+ log('');
251
+ log(' Security:', colors.bold);
252
+ log(' Mstro includes a Security Bouncer that automatically manages', colors.dim);
253
+ log(' tool permissions for Claude Code. It blocks dangerous operations', colors.dim);
254
+ log(' while allowing normal development work to proceed smoothly.', colors.dim);
255
+ log('');
256
+ }
257
+
258
+ function runNpmScript(script, args = [], envOverrides = {}) {
259
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
260
+ const child = spawn(npmCmd, ['run', script, ...args], {
261
+ cwd: CLIENT_ROOT,
262
+ stdio: 'inherit',
263
+ env: { ...process.env, MSTRO_WORKING_DIR: USER_CWD, ...envOverrides },
264
+ });
265
+
266
+ let isShuttingDown = false;
267
+
268
+ // Handle Ctrl+C: kill child process and wait for it to exit
269
+ process.on('SIGINT', () => {
270
+ if (isShuttingDown) return;
271
+ isShuttingDown = true;
272
+ child.kill('SIGTERM');
273
+ // Don't exit here - let the child's 'exit' event handle process.exit()
274
+ });
275
+
276
+ // Change the parent process's working directory back to where the user ran mstro.
277
+ // This ensures that when the terminal opens a new tab based on the active process's
278
+ // cwd, it opens in the user's original directory instead of the npm-linked mstro dir.
279
+ try {
280
+ process.chdir(USER_CWD);
281
+ } catch (_e) {
282
+ // Ignore if directory no longer exists
283
+ }
284
+
285
+ child.on('error', (err) => {
286
+ log(`Error: ${err.message}`, colors.red);
287
+ process.exit(1);
288
+ });
289
+
290
+ child.on('exit', (code) => {
291
+ // Print a newline to ensure clean prompt after shutdown messages
292
+ if (isShuttingDown) {
293
+ process.stdout.write('\n');
294
+ }
295
+ process.exit(code || 0);
296
+ });
297
+ }
298
+
299
+ function runConfigureHooks(andThenStart = false) {
300
+ const configScript = join(__dirname, 'configure-claude.js');
301
+ const child = spawn('node', [configScript, ...process.argv.slice(3)], {
302
+ cwd: CLIENT_ROOT,
303
+ stdio: 'inherit',
304
+ });
305
+
306
+ // Handle Ctrl+C: kill child process and exit immediately
307
+ process.on('SIGINT', () => {
308
+ child.kill('SIGTERM');
309
+ process.exit(0);
310
+ });
311
+
312
+ child.on('error', (err) => {
313
+ log(`Error: ${err.message}`, colors.red);
314
+ process.exit(1);
315
+ });
316
+
317
+ child.on('exit', (code) => {
318
+ if (code === 0) {
319
+ markConfigured();
320
+ if (andThenStart) {
321
+ // After configuring, start the server
322
+ log('\nStarting Mstro client...', colors.bold + colors.cyan);
323
+ const requestedPort = parsePort(process.argv.slice(2));
324
+ const envOverrides = requestedPort ? { PORT: String(requestedPort) } : {};
325
+ runNpmScript('start', [], envOverrides);
326
+ } else {
327
+ process.exit(0);
328
+ }
329
+ } else {
330
+ process.exit(code || 0);
331
+ }
332
+ });
333
+ }
334
+
335
+ // Parse arguments
336
+ const args = process.argv.slice(2);
337
+
338
+ // Extract --port / -p value
339
+ function parsePort(args) {
340
+ const portIndex = args.findIndex(a => a === '--port' || a === '-p');
341
+ if (portIndex !== -1 && args[portIndex + 1]) {
342
+ const port = parseInt(args[portIndex + 1], 10);
343
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
344
+ return port;
345
+ }
346
+ log(`Invalid port: ${args[portIndex + 1]}`, colors.red);
347
+ process.exit(1);
348
+ }
349
+ return null;
350
+ }
351
+
352
+ /**
353
+ * Show update notification if available
354
+ */
355
+ function showUpdateNotification() {
356
+ if (notifier.update) {
357
+ const { current, latest, type } = notifier.update;
358
+ const updateCmd = 'npm i -g mstro@latest';
359
+
360
+ log('');
361
+ log(` ${colors.yellow}Update available:${colors.reset} ${colors.dim}${current}${colors.reset} → ${colors.green}${latest}${colors.reset} ${colors.dim}(${type})${colors.reset}`);
362
+ log(` Run: ${colors.cyan}${updateCmd}${colors.reset}`);
363
+ log('');
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Check if user is logged in
369
+ */
370
+ function isLoggedIn() {
371
+ const credentialsFile = join(MSTRO_CONFIG_DIR, 'credentials.json');
372
+ if (!existsSync(credentialsFile)) {
373
+ return false;
374
+ }
375
+ try {
376
+ const creds = JSON.parse(readFileSync(credentialsFile, 'utf-8'));
377
+ return !!(creds.token && creds.userId && creds.email);
378
+ } catch {
379
+ return false;
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Show login required message
385
+ */
386
+ function showLoginRequired() {
387
+ log('\n Authentication required', colors.bold + colors.yellow);
388
+ log('');
389
+ log(' You must be logged in to use mstro.', colors.dim);
390
+ log(' Run "mstro login" to authenticate this device.', colors.dim);
391
+ log('');
392
+ }
393
+
394
+ /**
395
+ * Check if node-pty is loadable (native module compiled correctly)
396
+ */
397
+ async function isNodePtyAvailable() {
398
+ try {
399
+ const pty = await import('node-pty');
400
+ // Verify the native module actually works, not just that it imports
401
+ const test = pty.spawn('/bin/echo', ['test'], { name: 'xterm', cols: 80, rows: 24 });
402
+ test.kill();
403
+ return true;
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Try to rebuild node-pty silently. Returns true on success.
411
+ */
412
+ function tryRebuildNodePty() {
413
+ try {
414
+ execSync('npm rebuild node-pty', { cwd: CLIENT_ROOT, stdio: 'pipe' });
415
+ return true;
416
+ } catch {
417
+ return false;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Get platform-specific build tool install instructions
423
+ */
424
+ function getBuildToolInstructions() {
425
+ const os = osPlatform();
426
+ if (os === 'darwin') {
427
+ return ' xcode-select --install';
428
+ } else if (os === 'win32') {
429
+ return ' npm install -g windows-build-tools';
430
+ } else {
431
+ return ' sudo apt install build-essential python3 # Debian/Ubuntu\n sudo dnf install gcc-c++ make python3 # Fedora/RHEL';
432
+ }
433
+ }
434
+
435
+ /**
436
+ * First-run terminal setup check (runs after bouncer setup).
437
+ * Tries to rebuild node-pty automatically. If that fails, shows instructions.
438
+ */
439
+ async function checkTerminalSetup() {
440
+ if (await isNodePtyAvailable()) {
441
+ return; // Already working
442
+ }
443
+
444
+ log('\n Web Terminal', colors.bold + colors.cyan);
445
+ log(' mstro includes a browser-based terminal (optional).\n', colors.dim);
446
+ log(' Attempting to compile native module...', colors.dim);
447
+
448
+ if (tryRebuildNodePty()) {
449
+ log(' Terminal support enabled!\n', colors.green);
450
+ return;
451
+ }
452
+
453
+ log(' Could not compile terminal module.\n', colors.yellow);
454
+ log(' To enable the web terminal later:', colors.dim);
455
+ log(' 1. Install build tools:', colors.dim);
456
+ log(getBuildToolInstructions(), colors.dim);
457
+ log(' 2. Run:', colors.dim);
458
+ log(' mstro setup-terminal\n', colors.dim);
459
+ }
460
+
461
+ /**
462
+ * Explicit setup-terminal command
463
+ */
464
+ async function setupTerminal() {
465
+ log('\n Setting up terminal support...\n', colors.bold + colors.cyan);
466
+
467
+ if (await isNodePtyAvailable()) {
468
+ log(' Terminal support is already enabled.\n', colors.green);
469
+ return;
470
+ }
471
+
472
+ log(' Rebuilding node-pty native module...', colors.dim);
473
+
474
+ if (tryRebuildNodePty()) {
475
+ log('\n Terminal support enabled! Restart mstro to use it.\n', colors.green + colors.bold);
476
+ return;
477
+ }
478
+
479
+ log('\n Failed to build node-pty.\n', colors.red);
480
+ log(' Install build tools first:', colors.dim);
481
+ log(getBuildToolInstructions(), colors.dim);
482
+ log('\n Then re-run: mstro setup-terminal\n', colors.dim);
483
+ process.exit(1);
484
+ }
485
+
486
+ async function startServer(envOverrides) {
487
+ if (!isLoggedIn()) {
488
+ showLoginRequired();
489
+ process.exit(1);
490
+ }
491
+
492
+ if (!isBouncerConfigured()) {
493
+ if (hasUserDismissedSetup()) {
494
+ showBouncerWarning();
495
+ } else {
496
+ const choice = await promptBouncerSetup();
497
+ if (choice === 'configure') {
498
+ runConfigureHooks(true);
499
+ return;
500
+ }
501
+ }
502
+ }
503
+
504
+ if (!existsSync(MSTRO_TERMINAL_CHECKED_FLAG)) {
505
+ await checkTerminalSetup();
506
+ if (!existsSync(MSTRO_CONFIG_DIR)) {
507
+ mkdirSync(MSTRO_CONFIG_DIR, { recursive: true, mode: 0o700 });
508
+ }
509
+ writeFileSync(MSTRO_TERMINAL_CHECKED_FLAG, new Date().toISOString());
510
+ }
511
+
512
+ log('\nStarting Mstro client...', colors.bold + colors.cyan);
513
+ runNpmScript('start', [], envOverrides);
514
+ }
515
+
516
+ async function main() {
517
+ const requestedPort = parsePort(args);
518
+ const isDevMode = args.includes('--dev');
519
+ const envOverrides = {
520
+ ...(requestedPort ? { PORT: String(requestedPort) } : {}),
521
+ ...(isDevMode ? { PLATFORM_URL: 'http://localhost:4102' } : {}),
522
+ };
523
+
524
+ const subcommand = args.find(arg => !arg.startsWith('-') && !arg.startsWith('--'));
525
+
526
+ // Command dispatch table
527
+ const commands = new Map([
528
+ ['login', async () => {
529
+ const { login } = await import('./commands/login.js');
530
+ await login(args.slice(args.indexOf('login') + 1));
531
+ }],
532
+ ['logout', async () => {
533
+ const { logout } = await import('./commands/logout.js');
534
+ await logout();
535
+ }],
536
+ ['whoami', async () => {
537
+ const { whoami } = await import('./commands/whoami.js');
538
+ await whoami(args.slice(args.indexOf('whoami') + 1));
539
+ }],
540
+ ['status', async () => {
541
+ const { status } = await import('./commands/status.js');
542
+ await status();
543
+ }],
544
+ ['telemetry', async () => {
545
+ const { telemetry } = await import('./commands/config.js');
546
+ await telemetry(args.slice(args.indexOf('telemetry') + 1));
547
+ }],
548
+ ['setup-terminal', () => setupTerminal()],
549
+ ['configure-hooks', () => runConfigureHooks(false)],
550
+ ]);
551
+
552
+ // Flag-based commands
553
+ if (args.includes('--version') || args.includes('-V')) {
554
+ log(`mstro v${pkg.version}`);
555
+ showUpdateNotification();
556
+ return;
557
+ }
558
+
559
+ if (args.includes('--help') || args.includes('-h')) {
560
+ showHelp();
561
+ showUpdateNotification();
562
+ return;
563
+ }
564
+
565
+ if (args.includes('--configure-hooks') || args.includes('-c')) {
566
+ runConfigureHooks(false);
567
+ return;
568
+ }
569
+
570
+ // Subcommand dispatch
571
+ const handler = subcommand ? commands.get(subcommand) : undefined;
572
+ if (handler) {
573
+ await handler();
574
+ return;
575
+ }
576
+
577
+ // Default: start server
578
+ await startServer(envOverrides);
579
+ }
580
+
581
+ main();
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
4
+ // Licensed under the MIT License. See LICENSE file for details.
5
+
6
+ /**
7
+ * Postinstall script for Mstro
8
+ *
9
+ * Fixes permissions for native dependencies after npm install.
10
+ */
11
+
12
+ import { chmodSync, existsSync } from 'node:fs';
13
+ import { platform } from 'node:os';
14
+ import { dirname, join, resolve } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = dirname(__filename);
19
+ const MSTRO_ROOT = resolve(__dirname, '..');
20
+
21
+ /**
22
+ * Ensures node-pty's spawn-helper binary has execute permission.
23
+ * npm pack / monorepo extraction can strip the +x bit, causing
24
+ * "posix_spawnp failed" at runtime.
25
+ */
26
+ function fixNodePtyPermissions() {
27
+ const os = platform();
28
+ if (os === 'win32') return;
29
+
30
+ const arch = process.arch; // 'arm64' or 'x64'
31
+ const spawnHelper = join(
32
+ MSTRO_ROOT, 'node_modules', 'node-pty', 'prebuilds',
33
+ `darwin-${arch}`, 'spawn-helper'
34
+ );
35
+
36
+ if (existsSync(spawnHelper)) {
37
+ try {
38
+ chmodSync(spawnHelper, 0o755);
39
+ } catch (_) {
40
+ // Non-fatal — user may not have node-pty installed
41
+ }
42
+ }
43
+ }
44
+
45
+ fixNodePtyPermissions();