mstro-app 0.2.0 → 0.3.1

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 (153) hide show
  1. package/PRIVACY.md +126 -0
  2. package/README.md +24 -23
  3. package/bin/commands/login.js +79 -49
  4. package/bin/mstro.js +305 -39
  5. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  6. package/dist/server/cli/headless/claude-invoker.js +137 -30
  7. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  8. package/dist/server/cli/headless/mcp-config.js +2 -2
  9. package/dist/server/cli/headless/mcp-config.js.map +1 -1
  10. package/dist/server/cli/headless/runner.d.ts +6 -1
  11. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  12. package/dist/server/cli/headless/runner.js +59 -4
  13. package/dist/server/cli/headless/runner.js.map +1 -1
  14. package/dist/server/cli/headless/stall-assessor.d.ts +3 -1
  15. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  16. package/dist/server/cli/headless/stall-assessor.js +20 -1
  17. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  18. package/dist/server/cli/headless/tool-watchdog.d.ts +4 -1
  19. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  20. package/dist/server/cli/headless/tool-watchdog.js +30 -24
  21. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  22. package/dist/server/cli/headless/types.d.ts +20 -2
  23. package/dist/server/cli/headless/types.d.ts.map +1 -1
  24. package/dist/server/cli/improvisation-session-manager.d.ts +30 -3
  25. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  26. package/dist/server/cli/improvisation-session-manager.js +224 -31
  27. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  28. package/dist/server/index.js +6 -4
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/mcp/bouncer-cli.js +53 -14
  31. package/dist/server/mcp/bouncer-cli.js.map +1 -1
  32. package/dist/server/mcp/bouncer-integration.d.ts +1 -1
  33. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  34. package/dist/server/mcp/bouncer-integration.js +70 -7
  35. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  36. package/dist/server/mcp/security-audit.d.ts +3 -3
  37. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  38. package/dist/server/mcp/security-audit.js.map +1 -1
  39. package/dist/server/mcp/server.js +3 -2
  40. package/dist/server/mcp/server.js.map +1 -1
  41. package/dist/server/services/analytics.d.ts +2 -2
  42. package/dist/server/services/analytics.d.ts.map +1 -1
  43. package/dist/server/services/analytics.js +13 -1
  44. package/dist/server/services/analytics.js.map +1 -1
  45. package/dist/server/services/files.js +7 -7
  46. package/dist/server/services/files.js.map +1 -1
  47. package/dist/server/services/pathUtils.js +1 -1
  48. package/dist/server/services/pathUtils.js.map +1 -1
  49. package/dist/server/services/platform.d.ts +2 -2
  50. package/dist/server/services/platform.d.ts.map +1 -1
  51. package/dist/server/services/platform.js +13 -1
  52. package/dist/server/services/platform.js.map +1 -1
  53. package/dist/server/services/sentry.d.ts +1 -1
  54. package/dist/server/services/sentry.d.ts.map +1 -1
  55. package/dist/server/services/sentry.js.map +1 -1
  56. package/dist/server/services/terminal/pty-manager.d.ts +12 -0
  57. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  58. package/dist/server/services/terminal/pty-manager.js +81 -6
  59. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  60. package/dist/server/services/websocket/file-explorer-handlers.d.ts +5 -0
  61. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -0
  62. package/dist/server/services/websocket/file-explorer-handlers.js +518 -0
  63. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -0
  64. package/dist/server/services/websocket/file-utils.d.ts +4 -0
  65. package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
  66. package/dist/server/services/websocket/file-utils.js +27 -8
  67. package/dist/server/services/websocket/file-utils.js.map +1 -1
  68. package/dist/server/services/websocket/git-handlers.d.ts +36 -0
  69. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -0
  70. package/dist/server/services/websocket/git-handlers.js +797 -0
  71. package/dist/server/services/websocket/git-handlers.js.map +1 -0
  72. package/dist/server/services/websocket/git-pr-handlers.d.ts +4 -0
  73. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -0
  74. package/dist/server/services/websocket/git-pr-handlers.js +299 -0
  75. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -0
  76. package/dist/server/services/websocket/git-worktree-handlers.d.ts +4 -0
  77. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -0
  78. package/dist/server/services/websocket/git-worktree-handlers.js +353 -0
  79. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -0
  80. package/dist/server/services/websocket/handler-context.d.ts +32 -0
  81. package/dist/server/services/websocket/handler-context.d.ts.map +1 -0
  82. package/dist/server/services/websocket/handler-context.js +4 -0
  83. package/dist/server/services/websocket/handler-context.js.map +1 -0
  84. package/dist/server/services/websocket/handler.d.ts +27 -359
  85. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  86. package/dist/server/services/websocket/handler.js +68 -2329
  87. package/dist/server/services/websocket/handler.js.map +1 -1
  88. package/dist/server/services/websocket/index.d.ts +1 -1
  89. package/dist/server/services/websocket/index.d.ts.map +1 -1
  90. package/dist/server/services/websocket/index.js.map +1 -1
  91. package/dist/server/services/websocket/session-handlers.d.ts +10 -0
  92. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -0
  93. package/dist/server/services/websocket/session-handlers.js +508 -0
  94. package/dist/server/services/websocket/session-handlers.js.map +1 -0
  95. package/dist/server/services/websocket/settings-handlers.d.ts +6 -0
  96. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -0
  97. package/dist/server/services/websocket/settings-handlers.js +125 -0
  98. package/dist/server/services/websocket/settings-handlers.js.map +1 -0
  99. package/dist/server/services/websocket/tab-handlers.d.ts +10 -0
  100. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -0
  101. package/dist/server/services/websocket/tab-handlers.js +131 -0
  102. package/dist/server/services/websocket/tab-handlers.js.map +1 -0
  103. package/dist/server/services/websocket/terminal-handlers.d.ts +9 -0
  104. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -0
  105. package/dist/server/services/websocket/terminal-handlers.js +220 -0
  106. package/dist/server/services/websocket/terminal-handlers.js.map +1 -0
  107. package/dist/server/services/websocket/types.d.ts +63 -2
  108. package/dist/server/services/websocket/types.d.ts.map +1 -1
  109. package/dist/server/utils/agent-manager.d.ts +22 -2
  110. package/dist/server/utils/agent-manager.d.ts.map +1 -1
  111. package/dist/server/utils/agent-manager.js +2 -2
  112. package/dist/server/utils/agent-manager.js.map +1 -1
  113. package/dist/server/utils/port-manager.js.map +1 -1
  114. package/hooks/bouncer.sh +17 -3
  115. package/package.json +7 -3
  116. package/server/README.md +176 -159
  117. package/server/cli/headless/claude-invoker.ts +172 -43
  118. package/server/cli/headless/mcp-config.ts +8 -8
  119. package/server/cli/headless/runner.ts +57 -4
  120. package/server/cli/headless/stall-assessor.ts +25 -0
  121. package/server/cli/headless/tool-watchdog.ts +33 -25
  122. package/server/cli/headless/types.ts +11 -2
  123. package/server/cli/improvisation-session-manager.ts +285 -37
  124. package/server/index.ts +15 -13
  125. package/server/mcp/README.md +59 -67
  126. package/server/mcp/bouncer-cli.ts +73 -20
  127. package/server/mcp/bouncer-integration.ts +99 -16
  128. package/server/mcp/security-audit.ts +4 -4
  129. package/server/mcp/server.ts +6 -5
  130. package/server/services/analytics.ts +16 -4
  131. package/server/services/files.ts +13 -13
  132. package/server/services/pathUtils.ts +2 -2
  133. package/server/services/platform.ts +17 -6
  134. package/server/services/sentry.ts +1 -1
  135. package/server/services/terminal/pty-manager.ts +88 -11
  136. package/server/services/websocket/file-explorer-handlers.ts +587 -0
  137. package/server/services/websocket/file-utils.ts +28 -9
  138. package/server/services/websocket/git-handlers.ts +924 -0
  139. package/server/services/websocket/git-pr-handlers.ts +363 -0
  140. package/server/services/websocket/git-worktree-handlers.ts +403 -0
  141. package/server/services/websocket/handler-context.ts +44 -0
  142. package/server/services/websocket/handler.ts +85 -2680
  143. package/server/services/websocket/index.ts +1 -1
  144. package/server/services/websocket/session-handlers.ts +575 -0
  145. package/server/services/websocket/settings-handlers.ts +150 -0
  146. package/server/services/websocket/tab-handlers.ts +150 -0
  147. package/server/services/websocket/terminal-handlers.ts +277 -0
  148. package/server/services/websocket/types.ts +137 -0
  149. package/server/utils/agent-manager.ts +6 -6
  150. package/server/utils/port-manager.ts +1 -1
  151. package/bin/release.sh +0 -110
  152. package/server/services/platform.test.ts +0 -1304
  153. package/server/services/websocket/handler.test.ts +0 -20
package/PRIVACY.md ADDED
@@ -0,0 +1,126 @@
1
+ # Mstro Privacy Policy
2
+
3
+ **Effective Date:** February 2026
4
+
5
+ ## Overview
6
+
7
+ Mstro, Inc. ("we", "us", "our") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, and safeguard information when you use the Mstro CLI and related services.
8
+
9
+ ## Information We Collect
10
+
11
+ ### 1. Account Information
12
+ When you create an account, we collect:
13
+ - Email address
14
+ - Name (optional)
15
+ - Device identifiers (for authentication)
16
+
17
+ ### 2. Telemetry Data (Anonymous)
18
+ By default, Mstro collects anonymous telemetry to improve the software:
19
+
20
+ **Error Reporting (via Sentry):**
21
+ - Stack traces and error messages
22
+ - Mstro version and environment
23
+ - Operating system and architecture
24
+ - Node.js version
25
+
26
+ **Usage Analytics (via PostHog):**
27
+ - Feature usage patterns
28
+ - Session duration
29
+ - Command frequency
30
+
31
+ **What we DO NOT collect:**
32
+ - File contents or source code
33
+ - API keys or credentials
34
+ - Personal identifying information in error reports
35
+ - IP addresses (stripped before storage)
36
+
37
+ ### 3. Session Data
38
+ When using mstro.app:
39
+ - Orchestra configurations
40
+ - Session metadata
41
+ - Connection timestamps
42
+
43
+ ## How to Opt Out of Telemetry
44
+
45
+ You can disable all telemetry at any time:
46
+
47
+ ```bash
48
+ mstro telemetry off
49
+
50
+ # Or via environment variable
51
+ export MSTRO_TELEMETRY=0
52
+ ```
53
+
54
+ When telemetry is disabled:
55
+ - No error reports are sent to Sentry
56
+ - No usage analytics are sent to PostHog
57
+ - Core functionality remains unchanged
58
+
59
+ ## How We Use Your Information
60
+
61
+ We use collected information to:
62
+ - Provide and maintain the Mstro service
63
+ - Identify and fix bugs and crashes
64
+ - Improve software performance and features
65
+ - Communicate service updates and changes
66
+ - Ensure security and prevent abuse
67
+
68
+ ## Data Sharing
69
+
70
+ We do not sell your personal information. We may share data with:
71
+
72
+ - **Service Providers:** Third-party services that help operate Mstro (e.g., Sentry for error tracking, PostHog for analytics)
73
+ - **Legal Requirements:** When required by law or to protect our rights
74
+ - **Business Transfers:** In connection with a merger or acquisition
75
+
76
+ ## Data Retention
77
+
78
+ - **Account Data:** Retained while your account is active
79
+ - **Telemetry Data:** Retained for up to 90 days
80
+ - **Error Reports:** Retained for up to 30 days
81
+
82
+ ## Data Security
83
+
84
+ We implement appropriate security measures including:
85
+ - Encryption in transit (TLS/HTTPS)
86
+ - Secure credential storage (chmod 600)
87
+ - Regular security audits
88
+ - Minimal data collection principles
89
+
90
+ ## Your Rights
91
+
92
+ Depending on your jurisdiction, you may have rights to:
93
+ - Access your personal data
94
+ - Correct inaccurate data
95
+ - Delete your data
96
+ - Export your data
97
+ - Opt out of data collection
98
+
99
+ To exercise these rights, contact us at the information below.
100
+
101
+ ## Children's Privacy
102
+
103
+ Mstro is not intended for users under 13 years of age. We do not knowingly collect information from children.
104
+
105
+ ## International Users
106
+
107
+ Mstro is operated from the United States. By using the service, you consent to the transfer of your information to the United States.
108
+
109
+ ## Changes to This Policy
110
+
111
+ We may update this Privacy Policy periodically. We will notify users of significant changes through the CLI or our website.
112
+
113
+ ## Open Source
114
+
115
+ Mstro CLI is open source under the MIT License. You can review the code at:
116
+ https://github.com/mstro-app/mstro
117
+
118
+ ## Contact Us
119
+
120
+ For privacy-related questions or concerns:
121
+ - Website: https://mstro.app
122
+ - GitHub: https://github.com/mstro-app/mstro/issues
123
+
124
+ ---
125
+
126
+ Copyright (c) 2025-present Mstro, Inc. All rights reserved.
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # mstro
2
2
 
3
- Luxurious remote workspace for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Run AI-powered coding sessions from any browser while Claude executes locally on any of your machines.
3
+ Run [Claude Code](https://docs.anthropic.com/en/docs/claude-code) from any browser. The CLI runs locally on your machine and streams live sessions to [mstro.app](https://mstro.app) via a secure WebSocket relay.
4
4
 
5
- **mstro** is the CLI client for [mstro.app](https://mstro.app). It runs on your machine (laptop, cloud VM, CI server) and connects to the mstro.app web interface via a secure relay. You write prompts in the browser, Claude Code runs in your terminal.
6
-
7
- **Get started at [mstro.app](https://mstro.app)** — create an account, then install this CLI to connect your machine.
5
+ **mstro** runs on your laptop, cloud VM, or CI server and connects to the mstro.app web interface. You write prompts in the browser, Claude Code runs in your terminal.
8
6
 
9
7
  ## How It Works
10
8
 
@@ -14,7 +12,7 @@ Browser (mstro.app) <--WebSocket--> Platform Server (relay) <--WebSocket-->
14
12
  Claude Code CLI
15
13
  ```
16
14
 
17
- 1. `mstro` starts a local server and connects to the mstro.app platform server
15
+ 1. `mstro` starts a local server and connects to the mstro.app platform
18
16
  2. You open [mstro.app](https://mstro.app) in any browser and see your connected machine
19
17
  3. Prompts you send in the browser are relayed to your machine
20
18
  4. Claude Code runs locally with full access to your project files
@@ -33,19 +31,25 @@ Requires [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed
33
31
  ## Quick Start
34
32
 
35
33
  ```bash
36
- mstro login # Authenticate this device with your mstro.app account
37
- mstro # Start mstro in your project directory
34
+ mstro # Logs in automatically on first run, then starts the server
38
35
  ```
39
36
 
40
- On first run, mstro will offer to set up the **Security Bouncer** - a tool permission manager that protects against dangerous operations. Say yes.
37
+ Or without installing globally:
41
38
 
42
- Then open [mstro.app](https://mstro.app) in your browser. Your machine appears as a connected "orchestra." Start prompting.
39
+ ```bash
40
+ npx mstro-app # Same thing — login + launch in one command
41
+ ```
43
42
 
44
- ## Security Bouncer
43
+ On first run, mstro will:
44
+ 1. Open your browser to authenticate with your mstro.app account
45
+ 2. Offer to set up the **Security Bouncer** — say yes
46
+ 3. Connect to the platform
45
47
 
46
- The Bouncer replaces the default human-in-the-loop approval model with an agent-in-the-loop approach. An AI reviewer is better suited to evaluate tool calls than a human — it has full context on what should and shouldn't run, responds in milliseconds instead of interrupting your flow, and frees you up to focus on higher-level work while Claude Code executes autonomously. The result is faster, safer workflows without the constant approval prompts.
48
+ Then open [mstro.app](https://mstro.app) in your browser. Your machine appears as a connected workspace. Start prompting.
47
49
 
48
- The bouncer hook is installed globally at `~/.claude/hooks/bouncer.sh` and applies to all Claude Code sessions, but the level of protection depends on how Claude Code is running:
50
+ ## Security Bouncer
51
+
52
+ The Bouncer automatically approves or blocks Claude Code tool calls so you don't have to. It installs as a hook at `~/.claude/hooks/bouncer.sh` and applies to all Claude Code sessions.
49
53
 
50
54
  **Mstro sessions (headless)** get the full 2-layer system:
51
55
 
@@ -64,22 +68,19 @@ The bouncer is set up automatically on first run. To reconfigure or install manu
64
68
  mstro configure-hooks
65
69
  ```
66
70
 
67
- This installs a hook at `~/.claude/hooks/bouncer.sh` and registers it in `~/.claude/settings.json`.
68
-
69
- Set `BOUNCER_USE_AI=false` to disable the AI analysis layer (pattern matching only).
70
-
71
71
  ## CLI Reference
72
72
 
73
73
  ### Commands
74
74
 
75
75
  ```bash
76
- mstro # Start the client server
77
- mstro login # Authenticate this device with mstro.app
76
+ mstro # Start mstro (logs in automatically if needed)
77
+ mstro login # Re-authenticate or switch accounts
78
78
  mstro logout # Sign out
79
79
  mstro whoami # Show current user and device info
80
80
  mstro status # Show connection and auth status
81
81
  mstro setup-terminal # Enable web terminal (compiles native module)
82
82
  mstro configure-hooks # Install/reconfigure Security Bouncer
83
+ mstro telemetry [on|off] # Show/toggle anonymous telemetry
83
84
  ```
84
85
 
85
86
  ### Options
@@ -89,7 +90,6 @@ mstro configure-hooks # Install/reconfigure Security Bouncer
89
90
  | `-p, --port <port>` | Start on a specific port (default: 4101, auto-increments if busy) |
90
91
  | `-w, --working-dir <dir>` | Set working directory |
91
92
  | `-v, --verbose` | Verbose output |
92
- | `--dev` | Connect to local platform at localhost:4102 |
93
93
  | `--version` | Show version |
94
94
  | `--help` | Show help |
95
95
 
@@ -98,11 +98,11 @@ mstro configure-hooks # Install/reconfigure Security Bouncer
98
98
  Run multiple mstro instances for different projects. Each auto-selects an available port:
99
99
 
100
100
  ```
101
- $ mstro # Project A → port 4101
102
- $ mstro # Project B → port 4102
101
+ $ mstro # Project A
102
+ $ mstro # Project B
103
103
  ```
104
104
 
105
- Each instance appears as a separate orchestra in the web interface.
105
+ Each instance appears as a separate workspace in the web interface.
106
106
 
107
107
  ## Environment Variables
108
108
 
@@ -110,6 +110,7 @@ Each instance appears as a separate orchestra in the web interface.
110
110
  |----------|-------------|
111
111
  | `PORT` | Override server port |
112
112
  | `BOUNCER_USE_AI` | Set to `false` to disable AI analysis layer |
113
+ | `MSTRO_TELEMETRY` | Set to `0` to disable telemetry |
113
114
  | `PLATFORM_URL` | Platform server URL (default: `https://api.mstro.app`) |
114
115
 
115
116
  ## Config Files
@@ -129,7 +130,7 @@ mstro stores config in `~/.mstro/`:
129
130
 
130
131
  ### Optional: Web Terminal
131
132
 
132
- The web terminal feature requires a native module (`node-pty`). mstro works without it - you just won't have the terminal tab in the browser.
133
+ The web terminal feature requires a native module (`node-pty`). mstro works without it you just won't have the terminal tab in the browser.
133
134
 
134
135
  On first run, mstro will automatically attempt to compile `node-pty`. If your system has build tools installed, it just works. If not, mstro will let you know what to install:
135
136
 
@@ -236,10 +236,68 @@ async function pollForAuth(deviceCode, interval, platformUrl, maxAttempts = 180)
236
236
  throw new Error('Authorization timed out. Please try again.');
237
237
  }
238
238
 
239
+ /**
240
+ * Deregister old device before force re-auth
241
+ */
242
+ async function deregisterOldDevice(platformUrl) {
243
+ const existingCreds = getCredentials();
244
+ if (!existingCreds?.token) return;
245
+ try {
246
+ await fetch(`${platformUrl}/api/auth/device/deregister`, {
247
+ method: 'POST',
248
+ headers: {
249
+ 'Content-Type': 'application/json',
250
+ 'Authorization': `Bearer ${existingCreds.token}`,
251
+ },
252
+ });
253
+ } catch {
254
+ // Deregister failed (e.g. network issue), force flag on request will handle it
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Run the device code authorization flow. Throws on failure.
260
+ */
261
+ async function runDeviceCodeFlow(clientId, platformUrl, forceReauth) {
262
+ const { deviceCode, userCode, verificationUrlComplete, interval } = await requestDeviceCode(clientId, platformUrl, forceReauth);
263
+
264
+ log(`Your authorization code: ${userCode}`, colors.bold);
265
+ log('');
266
+ log('Opening browser to complete login...', colors.dim);
267
+ log(` If browser doesn't open, visit: ${verificationUrlComplete}`, colors.dim);
268
+ log('');
269
+
270
+ openBrowser(verificationUrlComplete);
271
+
272
+ log(' Waiting for authorization', colors.dim);
273
+ process.stdout.write(' ');
274
+
275
+ const result = await pollForAuth(deviceCode, interval, platformUrl);
276
+
277
+ const credentials = {
278
+ token: result.accessToken,
279
+ userId: result.user.id,
280
+ email: result.user.email,
281
+ name: result.user.name,
282
+ clientId,
283
+ createdAt: new Date().toISOString(),
284
+ };
285
+
286
+ saveCredentials(credentials);
287
+ return result;
288
+ }
289
+
239
290
  /**
240
291
  * Main login command
292
+ *
293
+ * @param {string[]} args - CLI arguments
294
+ * @param {object} options
295
+ * @param {boolean} options.inline - When true, called from startServer() auto-login:
296
+ * skips "already logged in" check, omits post-login tips, and throws on failure
297
+ * instead of calling process.exit(1).
241
298
  */
242
- export async function login(args = []) {
299
+ export async function login(args = [], options = {}) {
300
+ const { inline = false } = options;
243
301
  const forceReauth = args.includes('--force') || args.includes('-f');
244
302
  const devMode = args.includes('--dev');
245
303
  const platformUrl = devMode ? DEV_PLATFORM_URL : PROD_PLATFORM_URL;
@@ -248,8 +306,8 @@ export async function login(args = []) {
248
306
  log(`[DEV MODE] Using ${platformUrl}\n`, colors.yellow);
249
307
  }
250
308
 
251
- // Check if already logged in
252
- if (isLoggedIn() && !forceReauth) {
309
+ // Check if already logged in (skip when called inline — caller already checked)
310
+ if (!inline && isLoggedIn() && !forceReauth) {
253
311
  const creds = getCredentials();
254
312
  log(` Already logged in as ${creds.email}`, colors.green);
255
313
  log(` Use "mstro logout" to sign out, or "mstro login --force" to re-authenticate.\n`, colors.dim);
@@ -258,64 +316,36 @@ export async function login(args = []) {
258
316
 
259
317
  const clientId = getClientId();
260
318
 
261
- // If force re-auth and we have existing credentials, deregister the old device first
262
319
  if (forceReauth) {
263
- const existingCreds = getCredentials();
264
- if (existingCreds?.token) {
265
- try {
266
- await fetch(`${platformUrl}/api/auth/device/deregister`, {
267
- method: 'POST',
268
- headers: {
269
- 'Content-Type': 'application/json',
270
- 'Authorization': `Bearer ${existingCreds.token}`,
271
- },
272
- });
273
- } catch {
274
- // Deregister failed (e.g. network issue), force flag on request will handle it
275
- }
276
- }
320
+ await deregisterOldDevice(platformUrl);
277
321
  }
278
322
 
279
323
  log('Requesting authorization...', colors.dim);
280
324
 
281
325
  try {
282
- // Step 1: Request device code
283
- const { deviceCode, userCode, verificationUrlComplete, interval } = await requestDeviceCode(clientId, platformUrl, forceReauth);
284
-
285
- // Step 2: Show code and open browser
286
- log(`Your authorization code: ${userCode}`, colors.bold);
287
- log('');
288
- log('Opening browser to complete login...', colors.dim);
289
- log(` If browser doesn't open, visit: ${verificationUrlComplete}`, colors.dim);
290
- log('');
291
-
292
- openBrowser(verificationUrlComplete);
293
-
294
- // Step 3: Poll for result
295
- log(' Waiting for authorization', colors.dim);
296
- process.stdout.write(' ');
297
-
298
- const result = await pollForAuth(deviceCode, interval, platformUrl);
299
-
300
- // Step 4: Save credentials
301
- const credentials = {
302
- token: result.accessToken,
303
- userId: result.user.id,
304
- email: result.user.email,
305
- name: result.user.name,
306
- clientId,
307
- createdAt: new Date().toISOString(),
308
- };
309
-
310
- saveCredentials(credentials);
326
+ const result = await runDeviceCodeFlow(clientId, platformUrl, forceReauth);
311
327
 
312
328
  log('');
313
329
  log('');
314
330
  log(` Logged in as ${result.user.email}`, colors.bold + colors.green);
315
331
  log('');
316
- log(' Run "mstro" to start a machine.', colors.cyan);
317
- log('');
332
+
333
+ if (!inline) {
334
+ log(' Run "mstro" to start a machine.', colors.cyan);
335
+ log('');
336
+
337
+ // Check if node-pty is available, show tip if not
338
+ try {
339
+ await import('node-pty');
340
+ } catch {
341
+ log(' Tip: Terminal support requires native compilation.', colors.dim);
342
+ log(' Run "mstro setup-terminal" to enable web terminal.\n', colors.dim);
343
+ }
344
+ }
318
345
  } catch (err) {
346
+ if (inline) {
347
+ throw err;
348
+ }
319
349
  log('');
320
350
  log(` Login failed: ${err.message}`, colors.red);
321
351
  log('');