mstro-app 0.1.58 → 0.3.0

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 (161) hide show
  1. package/PRIVACY.md +126 -0
  2. package/README.md +24 -23
  3. package/bin/commands/login.js +85 -42
  4. package/bin/commands/logout.js +35 -1
  5. package/bin/commands/status.js +1 -1
  6. package/bin/mstro.js +231 -131
  7. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  8. package/dist/server/cli/headless/claude-invoker.js +550 -115
  9. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  10. package/dist/server/cli/headless/index.d.ts +2 -1
  11. package/dist/server/cli/headless/index.d.ts.map +1 -1
  12. package/dist/server/cli/headless/index.js +2 -0
  13. package/dist/server/cli/headless/index.js.map +1 -1
  14. package/dist/server/cli/headless/prompt-utils.d.ts +5 -8
  15. package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
  16. package/dist/server/cli/headless/prompt-utils.js +40 -5
  17. package/dist/server/cli/headless/prompt-utils.js.map +1 -1
  18. package/dist/server/cli/headless/runner.d.ts +1 -1
  19. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  20. package/dist/server/cli/headless/runner.js +52 -7
  21. package/dist/server/cli/headless/runner.js.map +1 -1
  22. package/dist/server/cli/headless/stall-assessor.d.ts +79 -1
  23. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  24. package/dist/server/cli/headless/stall-assessor.js +355 -20
  25. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  26. package/dist/server/cli/headless/tool-watchdog.d.ts +70 -0
  27. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -0
  28. package/dist/server/cli/headless/tool-watchdog.js +302 -0
  29. package/dist/server/cli/headless/tool-watchdog.js.map +1 -0
  30. package/dist/server/cli/headless/types.d.ts +98 -1
  31. package/dist/server/cli/headless/types.d.ts.map +1 -1
  32. package/dist/server/cli/improvisation-session-manager.d.ts +136 -2
  33. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  34. package/dist/server/cli/improvisation-session-manager.js +929 -132
  35. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  36. package/dist/server/index.js +5 -13
  37. package/dist/server/index.js.map +1 -1
  38. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  39. package/dist/server/mcp/bouncer-integration.js +18 -0
  40. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  41. package/dist/server/mcp/security-audit.d.ts +2 -2
  42. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  43. package/dist/server/mcp/security-audit.js +12 -8
  44. package/dist/server/mcp/security-audit.js.map +1 -1
  45. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  46. package/dist/server/mcp/security-patterns.js +9 -4
  47. package/dist/server/mcp/security-patterns.js.map +1 -1
  48. package/dist/server/routes/improvise.js +6 -6
  49. package/dist/server/routes/improvise.js.map +1 -1
  50. package/dist/server/services/analytics.d.ts +2 -0
  51. package/dist/server/services/analytics.d.ts.map +1 -1
  52. package/dist/server/services/analytics.js +26 -4
  53. package/dist/server/services/analytics.js.map +1 -1
  54. package/dist/server/services/platform.d.ts.map +1 -1
  55. package/dist/server/services/platform.js +17 -10
  56. package/dist/server/services/platform.js.map +1 -1
  57. package/dist/server/services/sandbox-utils.d.ts +6 -0
  58. package/dist/server/services/sandbox-utils.d.ts.map +1 -0
  59. package/dist/server/services/sandbox-utils.js +72 -0
  60. package/dist/server/services/sandbox-utils.js.map +1 -0
  61. package/dist/server/services/settings.d.ts +6 -0
  62. package/dist/server/services/settings.d.ts.map +1 -1
  63. package/dist/server/services/settings.js +21 -0
  64. package/dist/server/services/settings.js.map +1 -1
  65. package/dist/server/services/terminal/pty-manager.d.ts +5 -51
  66. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  67. package/dist/server/services/terminal/pty-manager.js +63 -102
  68. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  69. package/dist/server/services/websocket/file-explorer-handlers.d.ts +5 -0
  70. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -0
  71. package/dist/server/services/websocket/file-explorer-handlers.js +518 -0
  72. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -0
  73. package/dist/server/services/websocket/git-handlers.d.ts +36 -0
  74. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -0
  75. package/dist/server/services/websocket/git-handlers.js +797 -0
  76. package/dist/server/services/websocket/git-handlers.js.map +1 -0
  77. package/dist/server/services/websocket/git-pr-handlers.d.ts +4 -0
  78. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -0
  79. package/dist/server/services/websocket/git-pr-handlers.js +299 -0
  80. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -0
  81. package/dist/server/services/websocket/git-worktree-handlers.d.ts +4 -0
  82. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -0
  83. package/dist/server/services/websocket/git-worktree-handlers.js +353 -0
  84. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -0
  85. package/dist/server/services/websocket/handler-context.d.ts +32 -0
  86. package/dist/server/services/websocket/handler-context.d.ts.map +1 -0
  87. package/dist/server/services/websocket/handler-context.js +4 -0
  88. package/dist/server/services/websocket/handler-context.js.map +1 -0
  89. package/dist/server/services/websocket/handler.d.ts +27 -338
  90. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  91. package/dist/server/services/websocket/handler.js +74 -2106
  92. package/dist/server/services/websocket/handler.js.map +1 -1
  93. package/dist/server/services/websocket/index.d.ts +1 -1
  94. package/dist/server/services/websocket/index.d.ts.map +1 -1
  95. package/dist/server/services/websocket/index.js.map +1 -1
  96. package/dist/server/services/websocket/session-handlers.d.ts +10 -0
  97. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -0
  98. package/dist/server/services/websocket/session-handlers.js +507 -0
  99. package/dist/server/services/websocket/session-handlers.js.map +1 -0
  100. package/dist/server/services/websocket/settings-handlers.d.ts +6 -0
  101. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -0
  102. package/dist/server/services/websocket/settings-handlers.js +125 -0
  103. package/dist/server/services/websocket/settings-handlers.js.map +1 -0
  104. package/dist/server/services/websocket/tab-handlers.d.ts +10 -0
  105. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -0
  106. package/dist/server/services/websocket/tab-handlers.js +131 -0
  107. package/dist/server/services/websocket/tab-handlers.js.map +1 -0
  108. package/dist/server/services/websocket/terminal-handlers.d.ts +9 -0
  109. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -0
  110. package/dist/server/services/websocket/terminal-handlers.js +220 -0
  111. package/dist/server/services/websocket/terminal-handlers.js.map +1 -0
  112. package/dist/server/services/websocket/types.d.ts +67 -2
  113. package/dist/server/services/websocket/types.d.ts.map +1 -1
  114. package/hooks/bouncer.sh +11 -4
  115. package/package.json +7 -2
  116. package/server/README.md +176 -159
  117. package/server/cli/headless/claude-invoker.ts +740 -133
  118. package/server/cli/headless/index.ts +7 -1
  119. package/server/cli/headless/output-utils.test.ts +225 -0
  120. package/server/cli/headless/prompt-utils.ts +37 -5
  121. package/server/cli/headless/runner.ts +55 -8
  122. package/server/cli/headless/stall-assessor.test.ts +165 -0
  123. package/server/cli/headless/stall-assessor.ts +478 -22
  124. package/server/cli/headless/tool-watchdog.test.ts +429 -0
  125. package/server/cli/headless/tool-watchdog.ts +398 -0
  126. package/server/cli/headless/types.ts +93 -1
  127. package/server/cli/improvisation-session-manager.ts +1133 -145
  128. package/server/index.ts +5 -14
  129. package/server/mcp/README.md +59 -67
  130. package/server/mcp/bouncer-integration.test.ts +161 -0
  131. package/server/mcp/bouncer-integration.ts +28 -0
  132. package/server/mcp/security-audit.ts +12 -8
  133. package/server/mcp/security-patterns.test.ts +258 -0
  134. package/server/mcp/security-patterns.ts +8 -2
  135. package/server/routes/improvise.ts +6 -6
  136. package/server/services/analytics.ts +26 -4
  137. package/server/services/platform.test.ts +0 -10
  138. package/server/services/platform.ts +16 -11
  139. package/server/services/sandbox-utils.ts +78 -0
  140. package/server/services/settings.ts +25 -0
  141. package/server/services/terminal/pty-manager.ts +68 -129
  142. package/server/services/websocket/autocomplete.test.ts +194 -0
  143. package/server/services/websocket/file-explorer-handlers.ts +587 -0
  144. package/server/services/websocket/git-handlers.ts +924 -0
  145. package/server/services/websocket/git-pr-handlers.ts +363 -0
  146. package/server/services/websocket/git-worktree-handlers.ts +403 -0
  147. package/server/services/websocket/handler-context.ts +44 -0
  148. package/server/services/websocket/handler.test.ts +1 -1
  149. package/server/services/websocket/handler.ts +90 -2421
  150. package/server/services/websocket/index.ts +1 -1
  151. package/server/services/websocket/session-handlers.ts +574 -0
  152. package/server/services/websocket/settings-handlers.ts +150 -0
  153. package/server/services/websocket/tab-handlers.ts +150 -0
  154. package/server/services/websocket/terminal-handlers.ts +277 -0
  155. package/server/services/websocket/types.ts +145 -4
  156. package/bin/release.sh +0 -110
  157. package/dist/server/services/terminal/tmux-manager.d.ts +0 -82
  158. package/dist/server/services/terminal/tmux-manager.d.ts.map +0 -1
  159. package/dist/server/services/terminal/tmux-manager.js +0 -352
  160. package/dist/server/services/terminal/tmux-manager.js.map +0 -1
  161. package/server/services/terminal/tmux-manager.ts +0 -426
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
 
@@ -143,7 +143,7 @@ function openBrowser(url) {
143
143
  /**
144
144
  * Request device code from platform
145
145
  */
146
- async function requestDeviceCode(clientId, platformUrl) {
146
+ async function requestDeviceCode(clientId, platformUrl, force = false) {
147
147
  const machineHostname = hostname();
148
148
  const osType = type().toLowerCase();
149
149
  const cpuArch = arch();
@@ -158,6 +158,7 @@ async function requestDeviceCode(clientId, platformUrl) {
158
158
  osType,
159
159
  cpuArch,
160
160
  nodeVersion,
161
+ ...(force ? { force: true } : {}),
161
162
  }),
162
163
  });
163
164
 
@@ -235,22 +236,78 @@ async function pollForAuth(deviceCode, interval, platformUrl, maxAttempts = 180)
235
236
  throw new Error('Authorization timed out. Please try again.');
236
237
  }
237
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
+
238
290
  /**
239
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).
240
298
  */
241
- export async function login(args = []) {
299
+ export async function login(args = [], options = {}) {
300
+ const { inline = false } = options;
242
301
  const forceReauth = args.includes('--force') || args.includes('-f');
243
302
  const devMode = args.includes('--dev');
244
303
  const platformUrl = devMode ? DEV_PLATFORM_URL : PROD_PLATFORM_URL;
245
304
 
246
- log('\n Mstro Login\n', colors.bold + colors.cyan);
247
-
248
305
  if (devMode) {
249
- log(` [DEV MODE] Using ${platformUrl}\n`, colors.yellow);
306
+ log(`[DEV MODE] Using ${platformUrl}\n`, colors.yellow);
250
307
  }
251
308
 
252
- // Check if already logged in
253
- if (isLoggedIn() && !forceReauth) {
309
+ // Check if already logged in (skip when called inline — caller already checked)
310
+ if (!inline && isLoggedIn() && !forceReauth) {
254
311
  const creds = getCredentials();
255
312
  log(` Already logged in as ${creds.email}`, colors.green);
256
313
  log(` Use "mstro logout" to sign out, or "mstro login --force" to re-authenticate.\n`, colors.dim);
@@ -259,50 +316,36 @@ export async function login(args = []) {
259
316
 
260
317
  const clientId = getClientId();
261
318
 
262
- log(' Requesting authorization...', colors.dim);
319
+ if (forceReauth) {
320
+ await deregisterOldDevice(platformUrl);
321
+ }
322
+
323
+ log('Requesting authorization...', colors.dim);
263
324
 
264
325
  try {
265
- // Step 1: Request device code
266
- const { deviceCode, userCode, verificationUrlComplete, interval } = await requestDeviceCode(clientId, platformUrl);
326
+ const result = await runDeviceCodeFlow(clientId, platformUrl, forceReauth);
267
327
 
268
- // Step 2: Show code and open browser
269
328
  log('');
270
- log(` Your authorization code: ${userCode}`, colors.bold);
271
329
  log('');
272
- log(' Opening browser to complete login...', colors.dim);
273
- log(` If browser doesn't open, visit: ${verificationUrlComplete}`, colors.dim);
330
+ log(` Logged in as ${result.user.email}`, colors.bold + colors.green);
274
331
  log('');
275
332
 
276
- openBrowser(verificationUrlComplete);
277
-
278
- // Step 3: Poll for result
279
- log(' Waiting for authorization', colors.dim);
280
- process.stdout.write(' ');
281
-
282
- const result = await pollForAuth(deviceCode, interval, platformUrl);
333
+ if (!inline) {
334
+ log(' Run "mstro" to start a machine.', colors.cyan);
335
+ log('');
283
336
 
284
- // Step 4: Save credentials
285
- const credentials = {
286
- token: result.accessToken,
287
- userId: result.user.id,
288
- email: result.user.email,
289
- name: result.user.name,
290
- clientId,
291
- createdAt: new Date().toISOString(),
292
- };
293
-
294
- saveCredentials(credentials);
295
-
296
- log('');
297
- log('');
298
- log(` Logged in as ${result.user.email}`, colors.bold + colors.green);
299
- log('');
300
- log(' This device is now connected to your mstro.app account.', colors.dim);
301
- log(' Any "mstro" commands will sync with your web dashboard.', colors.dim);
302
- log('');
303
- log(' Run "mstro" to start an orchestra.', colors.cyan);
304
- log('');
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
+ }
305
345
  } catch (err) {
346
+ if (inline) {
347
+ throw err;
348
+ }
306
349
  log('');
307
350
  log(` Login failed: ${err.message}`, colors.red);
308
351
  log('');
@@ -8,6 +8,9 @@ import { existsSync, readFileSync, unlinkSync } from 'node:fs';
8
8
  import { homedir } from 'node:os';
9
9
  import { join } from 'node:path';
10
10
 
11
+ const PROD_PLATFORM_URL = 'https://api.mstro.app';
12
+ const DEV_PLATFORM_URL = 'http://localhost:4102';
13
+
11
14
  const colors = {
12
15
  reset: '\x1b[0m',
13
16
  bold: '\x1b[1m',
@@ -39,10 +42,36 @@ function getCredentials() {
39
42
  }
40
43
  }
41
44
 
45
+ /**
46
+ * Deregister device from the platform server
47
+ */
48
+ async function deregisterDevice(token, platformUrl) {
49
+ try {
50
+ const response = await fetch(`${platformUrl}/api/auth/device/deregister`, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ 'Authorization': `Bearer ${token}`,
55
+ },
56
+ });
57
+
58
+ if (!response.ok) {
59
+ const data = await response.json().catch(() => ({}));
60
+ log(` Warning: Could not deregister device from server: ${data.error || response.statusText}`, colors.yellow);
61
+ }
62
+ } catch {
63
+ // Network error - proceed with local logout anyway
64
+ log(' Warning: Could not reach server to deregister device. Local credentials will still be removed.', colors.yellow);
65
+ }
66
+ }
67
+
42
68
  /**
43
69
  * Main logout command
44
70
  */
45
- export async function logout() {
71
+ export async function logout(args = []) {
72
+ const devMode = Array.isArray(args) && args.includes('--dev');
73
+ const platformUrl = devMode ? DEV_PLATFORM_URL : PROD_PLATFORM_URL;
74
+
46
75
  log('\n Mstro Logout\n', colors.bold + colors.cyan);
47
76
 
48
77
  const creds = getCredentials();
@@ -56,6 +85,11 @@ export async function logout() {
56
85
  const email = creds.email;
57
86
 
58
87
  try {
88
+ // Deregister device from server so it can be re-registered later
89
+ if (creds.token) {
90
+ await deregisterDevice(creds.token, platformUrl);
91
+ }
92
+
59
93
  // Delete credentials file
60
94
  if (existsSync(CREDENTIALS_FILE)) {
61
95
  unlinkSync(CREDENTIALS_FILE);
@@ -187,7 +187,7 @@ export async function status() {
187
187
 
188
188
  // Quick commands
189
189
  log(' Commands', colors.bold);
190
- log(' mstro Start an orchestra', colors.dim);
190
+ log(' mstro Start a machine', colors.dim);
191
191
  log(' mstro login Sign in to your account', colors.dim);
192
192
  log(' mstro logout Sign out', colors.dim);
193
193
  log(' mstro whoami Show account details', colors.dim);