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
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();
|