@minicor/mcp-server 2.0.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 (134) hide show
  1. package/README.md +169 -0
  2. package/dist/__tests__/diff.test.d.ts +2 -0
  3. package/dist/__tests__/diff.test.d.ts.map +1 -0
  4. package/dist/__tests__/diff.test.js +23 -0
  5. package/dist/__tests__/diff.test.js.map +1 -0
  6. package/dist/__tests__/helpers.test.d.ts +2 -0
  7. package/dist/__tests__/helpers.test.d.ts.map +1 -0
  8. package/dist/__tests__/helpers.test.js +70 -0
  9. package/dist/__tests__/helpers.test.js.map +1 -0
  10. package/dist/__tests__/inspect-scripts.test.d.ts +2 -0
  11. package/dist/__tests__/inspect-scripts.test.d.ts.map +1 -0
  12. package/dist/__tests__/inspect-scripts.test.js +66 -0
  13. package/dist/__tests__/inspect-scripts.test.js.map +1 -0
  14. package/dist/browser-client.d.ts +42 -0
  15. package/dist/browser-client.d.ts.map +1 -0
  16. package/dist/browser-client.js +66 -0
  17. package/dist/browser-client.js.map +1 -0
  18. package/dist/cli.d.ts +14 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +256 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/config.d.ts +21 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +57 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/diff.d.ts +7 -0
  27. package/dist/diff.d.ts.map +1 -0
  28. package/dist/diff.js +97 -0
  29. package/dist/diff.js.map +1 -0
  30. package/dist/helpers.d.ts +28 -0
  31. package/dist/helpers.d.ts.map +1 -0
  32. package/dist/helpers.js +91 -0
  33. package/dist/helpers.js.map +1 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +193 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/inspect-scripts.d.ts +18 -0
  39. package/dist/inspect-scripts.d.ts.map +1 -0
  40. package/dist/inspect-scripts.js +572 -0
  41. package/dist/inspect-scripts.js.map +1 -0
  42. package/dist/laminar-client.d.ts +160 -0
  43. package/dist/laminar-client.d.ts.map +1 -0
  44. package/dist/laminar-client.js +237 -0
  45. package/dist/laminar-client.js.map +1 -0
  46. package/dist/lds-client.d.ts +107 -0
  47. package/dist/lds-client.d.ts.map +1 -0
  48. package/dist/lds-client.js +81 -0
  49. package/dist/lds-client.js.map +1 -0
  50. package/dist/paths.d.ts +17 -0
  51. package/dist/paths.d.ts.map +1 -0
  52. package/dist/paths.js +46 -0
  53. package/dist/paths.js.map +1 -0
  54. package/dist/prompts/build-browser-rpa.d.ts +3 -0
  55. package/dist/prompts/build-browser-rpa.d.ts.map +1 -0
  56. package/dist/prompts/build-browser-rpa.js +90 -0
  57. package/dist/prompts/build-browser-rpa.js.map +1 -0
  58. package/dist/prompts/build-rpa.d.ts +3 -0
  59. package/dist/prompts/build-rpa.d.ts.map +1 -0
  60. package/dist/prompts/build-rpa.js +168 -0
  61. package/dist/prompts/build-rpa.js.map +1 -0
  62. package/dist/prompts/debug-execution.d.ts +3 -0
  63. package/dist/prompts/debug-execution.d.ts.map +1 -0
  64. package/dist/prompts/debug-execution.js +50 -0
  65. package/dist/prompts/debug-execution.js.map +1 -0
  66. package/dist/prompts/workflow-guide.d.ts +3 -0
  67. package/dist/prompts/workflow-guide.d.ts.map +1 -0
  68. package/dist/prompts/workflow-guide.js +175 -0
  69. package/dist/prompts/workflow-guide.js.map +1 -0
  70. package/dist/services.d.ts +61 -0
  71. package/dist/services.d.ts.map +1 -0
  72. package/dist/services.js +249 -0
  73. package/dist/services.js.map +1 -0
  74. package/dist/setup.d.ts +11 -0
  75. package/dist/setup.d.ts.map +1 -0
  76. package/dist/setup.js +790 -0
  77. package/dist/setup.js.map +1 -0
  78. package/dist/state.d.ts +23 -0
  79. package/dist/state.d.ts.map +1 -0
  80. package/dist/state.js +24 -0
  81. package/dist/state.js.map +1 -0
  82. package/dist/sync.d.ts +86 -0
  83. package/dist/sync.d.ts.map +1 -0
  84. package/dist/sync.js +394 -0
  85. package/dist/sync.js.map +1 -0
  86. package/dist/tools/browser.d.ts +3 -0
  87. package/dist/tools/browser.d.ts.map +1 -0
  88. package/dist/tools/browser.js +254 -0
  89. package/dist/tools/browser.js.map +1 -0
  90. package/dist/tools/config-stores.d.ts +3 -0
  91. package/dist/tools/config-stores.d.ts.map +1 -0
  92. package/dist/tools/config-stores.js +54 -0
  93. package/dist/tools/config-stores.js.map +1 -0
  94. package/dist/tools/core.d.ts +3 -0
  95. package/dist/tools/core.d.ts.map +1 -0
  96. package/dist/tools/core.js +202 -0
  97. package/dist/tools/core.js.map +1 -0
  98. package/dist/tools/cron.d.ts +3 -0
  99. package/dist/tools/cron.d.ts.map +1 -0
  100. package/dist/tools/cron.js +168 -0
  101. package/dist/tools/cron.js.map +1 -0
  102. package/dist/tools/elasticsearch.d.ts +3 -0
  103. package/dist/tools/elasticsearch.d.ts.map +1 -0
  104. package/dist/tools/elasticsearch.js +248 -0
  105. package/dist/tools/elasticsearch.js.map +1 -0
  106. package/dist/tools/issues.d.ts +3 -0
  107. package/dist/tools/issues.d.ts.map +1 -0
  108. package/dist/tools/issues.js +39 -0
  109. package/dist/tools/issues.js.map +1 -0
  110. package/dist/tools/stats.d.ts +3 -0
  111. package/dist/tools/stats.d.ts.map +1 -0
  112. package/dist/tools/stats.js +18 -0
  113. package/dist/tools/stats.js.map +1 -0
  114. package/dist/tools/sync-tools.d.ts +3 -0
  115. package/dist/tools/sync-tools.d.ts.map +1 -0
  116. package/dist/tools/sync-tools.js +121 -0
  117. package/dist/tools/sync-tools.js.map +1 -0
  118. package/dist/tools/vm-rpa.d.ts +3 -0
  119. package/dist/tools/vm-rpa.d.ts.map +1 -0
  120. package/dist/tools/vm-rpa.js +281 -0
  121. package/dist/tools/vm-rpa.js.map +1 -0
  122. package/dist/tools/vm.d.ts +3 -0
  123. package/dist/tools/vm.d.ts.map +1 -0
  124. package/dist/tools/vm.js +280 -0
  125. package/dist/tools/vm.js.map +1 -0
  126. package/dist/tools/workflow-ops.d.ts +3 -0
  127. package/dist/tools/workflow-ops.d.ts.map +1 -0
  128. package/dist/tools/workflow-ops.js +313 -0
  129. package/dist/tools/workflow-ops.js.map +1 -0
  130. package/dist/types.d.ts +14 -0
  131. package/dist/types.d.ts.map +1 -0
  132. package/dist/types.js +5 -0
  133. package/dist/types.js.map +1 -0
  134. package/package.json +58 -0
package/dist/setup.js ADDED
@@ -0,0 +1,790 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Minicor MCP — Interactive Setup
4
+ *
5
+ * Browser mode (default): opens a local page with sign-in/sign-up + workspace creation
6
+ * CLI mode (--cli): terminal prompts for headless environments
7
+ *
8
+ * Tokens stored in ~/.minicor/tokens.json, MCP config written to ~/.cursor/mcp.json
9
+ */
10
+ import http from "node:http";
11
+ import { exec } from "node:child_process";
12
+ import readline from "node:readline";
13
+ import path from "node:path";
14
+ import fs from "node:fs";
15
+ import os from "node:os";
16
+ import { saveServiceConfig } from "./config.js";
17
+ import { getTokenPath, getWriteTokenPath, getWriteConfigPath, regionToApiBase, apiBaseToRegion, } from "./paths.js";
18
+ const MCP_ENTRY = path.resolve(path.dirname(new URL(import.meta.url).pathname), "index.js");
19
+ async function signIn(apiBase, email, password) {
20
+ const res = await fetch(`${apiBase}/auth/signin`, {
21
+ method: "POST",
22
+ headers: { "Content-Type": "application/json" },
23
+ body: JSON.stringify({ username: email, password }),
24
+ });
25
+ if (!res.ok) {
26
+ const body = await res.text().catch(() => "");
27
+ throw new Error(body || "Invalid email or password");
28
+ }
29
+ return (await res.json());
30
+ }
31
+ async function register(apiBase, data) {
32
+ const res = await fetch(`${apiBase}/auth/register`, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify(data),
36
+ });
37
+ if (!res.ok) {
38
+ const body = await res.text().catch(() => "");
39
+ throw new Error(body || "Registration failed");
40
+ }
41
+ }
42
+ async function listWorkspaces(apiBase, token) {
43
+ const res = await fetch(`${apiBase}/workspaces`, {
44
+ headers: { Authorization: `Bearer ${token}` },
45
+ });
46
+ if (!res.ok)
47
+ return [];
48
+ return (await res.json());
49
+ }
50
+ async function createWorkspace(apiBase, token, name) {
51
+ const res = await fetch(`${apiBase}/workspaces`, {
52
+ method: "POST",
53
+ headers: {
54
+ "Content-Type": "application/json",
55
+ Authorization: `Bearer ${token}`,
56
+ },
57
+ body: JSON.stringify({ name }),
58
+ });
59
+ if (!res.ok) {
60
+ const body = await res.text().catch(() => "");
61
+ throw new Error(body || "Failed to create workspace");
62
+ }
63
+ return (await res.json());
64
+ }
65
+ function storeTokens(loginData, region) {
66
+ const writePath = getWriteTokenPath();
67
+ const dir = path.dirname(writePath);
68
+ if (!fs.existsSync(dir))
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ fs.writeFileSync(writePath, JSON.stringify({
71
+ access_token: loginData.access_token,
72
+ refresh_token: loginData.refresh_token || null,
73
+ expires_at: Date.now() + loginData.expires_in * 1000,
74
+ api_base: regionToApiBase(region),
75
+ region,
76
+ }, null, 2) + "\n", { mode: 0o600 });
77
+ return writePath;
78
+ }
79
+ function writeMcpConfig() {
80
+ const dotCursor = path.join(os.homedir(), ".cursor");
81
+ if (!fs.existsSync(dotCursor))
82
+ fs.mkdirSync(dotCursor, { recursive: true });
83
+ const configPath = path.join(dotCursor, "mcp.json");
84
+ let existing = {};
85
+ if (fs.existsSync(configPath)) {
86
+ try {
87
+ existing = JSON.parse(fs.readFileSync(configPath, "utf-8"));
88
+ }
89
+ catch { }
90
+ }
91
+ if (!existing.mcpServers)
92
+ existing.mcpServers = {};
93
+ delete existing.mcpServers.laminar;
94
+ existing.mcpServers.minicor = {
95
+ command: "node",
96
+ args: [MCP_ENTRY],
97
+ };
98
+ fs.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
99
+ return configPath;
100
+ }
101
+ // ─── Helpers ─────────────────────────────────────────────────
102
+ function openBrowser(url) {
103
+ const cmd = process.platform === "darwin"
104
+ ? `open "${url}"`
105
+ : process.platform === "win32"
106
+ ? `start "" "${url}"`
107
+ : `xdg-open "${url}"`;
108
+ exec(cmd);
109
+ }
110
+ function jsonBody(req) {
111
+ return new Promise((resolve, reject) => {
112
+ const chunks = [];
113
+ req.on("data", (c) => chunks.push(c));
114
+ req.on("end", () => {
115
+ try {
116
+ resolve(JSON.parse(Buffer.concat(chunks).toString()));
117
+ }
118
+ catch {
119
+ reject(new Error("Invalid JSON"));
120
+ }
121
+ });
122
+ req.on("error", reject);
123
+ });
124
+ }
125
+ function send(res, status, body) {
126
+ res.writeHead(status, { "Content-Type": "application/json" });
127
+ res.end(JSON.stringify(body));
128
+ }
129
+ // ─── HTML ────────────────────────────────────────────────────
130
+ const HTML = `<!DOCTYPE html>
131
+ <html lang="en">
132
+ <head>
133
+ <meta charset="utf-8" />
134
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
135
+ <title>Minicor — Connect to Cursor</title>
136
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
137
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
138
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
139
+ <style>
140
+ *,:before,:after{box-sizing:border-box;margin:0;padding:0}
141
+ :root{
142
+ --brand:100 110 203;
143
+ --primary:hsl(231 48% 62%);--primary-hover:hsl(240 65% 60%);--primary-active:hsl(228 43% 59%);
144
+ --success:hsl(158 58% 45%);--danger:hsl(355 78% 60%);
145
+ --bg:#f8f9fc;--surface:#fff;--surface-2:#f3f4f8;--border:#e2e5f1;
146
+ --text:#111;--text-secondary:#4b5563;--text-muted:#9ca3af;
147
+ --radius:10px;--font:'Inter',system-ui,sans-serif;--mono:'SF Mono','Fira Code','Cascadia Code',monospace;
148
+ }
149
+ html{font-size:14px}
150
+ body{font-family:var(--font);background:var(--bg);color:var(--text);min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px;-webkit-font-smoothing:antialiased}
151
+ .wrapper{width:100%;max-width:440px}
152
+ .logo-row{display:flex;align-items:center;gap:10px;margin-bottom:24px}
153
+ .logo-row svg{flex-shrink:0}
154
+ .logo-row .wordmark{font-size:18px;font-weight:700;letter-spacing:-.3px}
155
+ .logo-row .badge{font-size:11px;font-weight:600;color:var(--primary);background:rgb(var(--brand)/.08);padding:2px 8px;border-radius:20px;margin-left:auto}
156
+ .card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px;margin-bottom:12px}
157
+ .card h2{font-size:16px;font-weight:600;margin-bottom:4px}
158
+ .card p.desc{font-size:13px;color:var(--text-secondary);margin-bottom:20px;line-height:1.45}
159
+ .field{margin-bottom:14px}
160
+ .field label{display:block;font-size:12px;font-weight:500;color:var(--text-secondary);margin-bottom:5px;letter-spacing:.01em}
161
+ .field input[type="email"],.field input[type="password"],.field input[type="text"]{
162
+ width:100%;height:36px;padding:0 10px;background:var(--surface-2);border:1px solid var(--border);border-radius:7px;
163
+ color:var(--text);font-size:13px;font-family:var(--font);outline:none;transition:border-color .15s,box-shadow .15s;
164
+ }
165
+ .field input:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgb(var(--brand)/.1)}
166
+ .field .input-wrap{position:relative}
167
+ .field input[type="password"]{padding-right:36px}
168
+ .toggle-pw{position:absolute;right:6px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;color:var(--text-muted);padding:4px;display:flex}
169
+ .toggle-pw:hover{color:var(--text-secondary)}
170
+ .seg{display:flex;background:var(--surface-2);border-radius:7px;padding:3px;gap:2px}
171
+ .seg label{flex:1;text-align:center;padding:6px 0;font-size:12px;font-weight:500;border-radius:5px;cursor:pointer;color:var(--text-secondary);transition:all .15s;user-select:none}
172
+ .seg input{display:none}
173
+ .seg input:checked+span{background:var(--surface);color:var(--text);box-shadow:0 1px 3px rgba(0,0,0,.08)}
174
+ .seg label span{display:block;padding:6px 0;border-radius:5px;transition:all .15s}
175
+ .tabs{display:flex;margin-bottom:20px;border-bottom:1px solid var(--border)}
176
+ .tabs button{flex:1;padding:10px 0;font-size:13px;font-weight:500;font-family:var(--font);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;color:var(--text-muted);transition:all .15s}
177
+ .tabs button.active{color:var(--primary);border-bottom-color:var(--primary)}
178
+ .tabs button:hover{color:var(--text)}
179
+ .tab-content{display:none}.tab-content.active{display:block}
180
+ .row{display:flex;gap:10px}
181
+ .row .field{flex:1}
182
+ .btn-primary{
183
+ width:100%;height:36px;margin-top:6px;background:var(--primary);color:#fff;border:none;border-radius:7px;
184
+ font-size:13px;font-weight:600;font-family:var(--font);cursor:pointer;transition:background .15s;
185
+ display:flex;align-items:center;justify-content:center;gap:6px;
186
+ }
187
+ .btn-primary:hover{background:var(--primary-hover)}
188
+ .btn-primary:active{background:var(--primary-active)}
189
+ .btn-primary:disabled{opacity:.55;cursor:not-allowed}
190
+ .btn-ghost{
191
+ width:100%;height:36px;background:none;border:1px solid var(--border);border-radius:7px;color:var(--text);
192
+ font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;transition:all .15s;
193
+ display:flex;align-items:center;justify-content:center;gap:6px;
194
+ }
195
+ .btn-ghost:hover{background:var(--surface-2);border-color:var(--text-muted)}
196
+ .btn-danger{color:var(--danger);border-color:hsl(355 78% 60%/.25)}
197
+ .btn-danger:hover{background:hsl(355 78% 60%/.06);border-color:var(--danger)}
198
+ .alert{padding:10px 12px;border-radius:7px;font-size:12px;line-height:1.4;display:none;margin-top:12px}
199
+ .alert.show{display:block}
200
+ .alert-error{background:hsl(355 78% 60%/.07);border:1px solid hsl(355 78% 60%/.18);color:var(--danger)}
201
+ .alert-success{background:hsl(158 58% 45%/.07);border:1px solid hsl(158 58% 45%/.18);color:hsl(158 58% 45%)}
202
+ .session{display:flex;align-items:center;gap:8px;padding:10px 12px;background:var(--surface-2);border:1px solid var(--border);border-radius:7px;font-size:12px;margin-bottom:12px}
203
+ .session .dot{width:7px;height:7px;border-radius:50%;background:var(--success);flex-shrink:0}
204
+ .session .info{flex:1;color:var(--text-secondary)}
205
+ .session .info strong{color:var(--text);font-weight:600}
206
+ .session button{background:none;border:none;color:var(--danger);font-size:12px;font-weight:500;cursor:pointer;padding:2px 6px;border-radius:4px}
207
+ .session button:hover{background:hsl(355 78% 60%/.08)}
208
+ .done-icon{width:48px;height:48px;border-radius:50%;background:hsl(158 58% 45%/.1);display:flex;align-items:center;justify-content:center;margin:0 auto 14px}
209
+ .done-icon svg{color:var(--success)}
210
+ .done h2{text-align:center;margin-bottom:4px}
211
+ .done p{text-align:center;color:var(--text-secondary);font-size:13px;margin-bottom:16px}
212
+ .code-block{
213
+ background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:12px;
214
+ font-family:var(--mono);font-size:11px;line-height:1.5;overflow-x:auto;white-space:pre;color:var(--text-secondary);
215
+ }
216
+ .spinner{width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .5s linear infinite}
217
+ @keyframes spin{to{transform:rotate(360deg)}}
218
+ .step{display:none}.step.active{display:block}
219
+ .footer{text-align:center;margin-top:16px;font-size:11px;color:var(--text-muted)}
220
+ .footer a{color:var(--primary);text-decoration:none}
221
+ .footer a:hover{text-decoration:underline}
222
+ .mt-12{margin-top:12px}.mb-12{margin-bottom:12px}
223
+ .divider{border:none;border-top:1px solid var(--border);margin:20px 0}
224
+ .section-label{font-size:13px;font-weight:600;color:var(--text);margin-bottom:12px;display:flex;align-items:center;gap:8px}
225
+ .section-label .tag{font-size:10px;font-weight:600;color:var(--text-muted);background:var(--surface-2);padding:2px 6px;border-radius:4px;text-transform:uppercase}
226
+ .hint{font-size:11px;color:var(--text-muted);margin-top:4px}
227
+ .svc-status{display:flex;align-items:center;gap:6px;font-size:12px;margin-top:8px;padding:6px 10px;border-radius:6px;background:var(--surface-2)}
228
+ .svc-status .dot-on{width:6px;height:6px;border-radius:50%;background:var(--success)}
229
+ .svc-status .dot-off{width:6px;height:6px;border-radius:50%;background:var(--text-muted)}
230
+ .ws-list{list-style:none;margin:12px 0}
231
+ .ws-list li{padding:8px 12px;border:1px solid var(--border);border-radius:7px;margin-bottom:6px;font-size:13px;display:flex;justify-content:space-between;align-items:center}
232
+ .ws-list li .id{color:var(--text-muted);font-size:11px}
233
+ </style>
234
+ </head>
235
+ <body>
236
+ <div class="wrapper">
237
+ <div class="logo-row">
238
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 118 98" width="36" height="30"><rect width="118" height="98" rx="10" ry="10" fill="#646ecb"/><g transform="translate(5.2,-4.6) scale(1.39)" fill="#fff"><path d="M27.4 59L10.6 38.5 27.4 17.7l4.6 3.7L18.2 38.4l13.8 16.9z"/><path d="M49.6 59l-4.6-3.7L58.9 38.3 45.1 21.4l4.6-3.7L66.5 38.3z"/></g></svg>
239
+ <span class="wordmark">Minicor</span>
240
+ <span class="badge">MCP</span>
241
+ </div>
242
+
243
+ <div id="session-banner" class="session" style="display:none">
244
+ <div class="dot"></div>
245
+ <div class="info">Connected to <strong id="s-region"></strong> &middot; expires <span id="s-expires"></span></div>
246
+ <button onclick="doLogout()">Sign out</button>
247
+ </div>
248
+
249
+ <!-- Step 1: Auth (Sign in / Sign up) -->
250
+ <div class="step active" id="step-auth">
251
+ <div class="card">
252
+ <h2>Connect to Cursor</h2>
253
+ <p class="desc">Sign in or create a Minicor account to enable the MCP integration.</p>
254
+
255
+ <div class="tabs">
256
+ <button class="active" onclick="switchTab('signin')">Sign in</button>
257
+ <button onclick="switchTab('signup')">Create account</button>
258
+ </div>
259
+
260
+ <!-- Sign In Tab -->
261
+ <div class="tab-content active" id="tab-signin">
262
+ <form id="login-form" autocomplete="on">
263
+ <div class="field">
264
+ <label for="login-email">Email</label>
265
+ <input id="login-email" type="email" placeholder="you@company.com" autocomplete="email" autofocus />
266
+ </div>
267
+ <div class="field">
268
+ <label for="login-password">Password</label>
269
+ <div class="input-wrap">
270
+ <input id="login-password" type="password" placeholder="\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022" autocomplete="current-password" />
271
+ </div>
272
+ </div>
273
+ <div class="field">
274
+ <label>Region</label>
275
+ <div class="seg">
276
+ <label><input type="radio" name="login-region" value="us" checked /><span>US</span></label>
277
+ <label><input type="radio" name="login-region" value="ca" /><span>Canada</span></label>
278
+ </div>
279
+ </div>
280
+ <button type="submit" class="btn-primary" id="btn-login">Sign in</button>
281
+ </form>
282
+ </div>
283
+
284
+ <!-- Sign Up Tab -->
285
+ <div class="tab-content" id="tab-signup">
286
+ <form id="signup-form" autocomplete="on">
287
+ <div class="row">
288
+ <div class="field">
289
+ <label for="signup-first">First name</label>
290
+ <input id="signup-first" type="text" placeholder="Jane" autocomplete="given-name" />
291
+ </div>
292
+ <div class="field">
293
+ <label for="signup-last">Last name</label>
294
+ <input id="signup-last" type="text" placeholder="Smith" autocomplete="family-name" />
295
+ </div>
296
+ </div>
297
+ <div class="field">
298
+ <label for="signup-email">Email</label>
299
+ <input id="signup-email" type="email" placeholder="you@company.com" autocomplete="email" />
300
+ </div>
301
+ <div class="field">
302
+ <label for="signup-password">Password</label>
303
+ <input id="signup-password" type="password" placeholder="Min 8 chars, uppercase + symbol" autocomplete="new-password" />
304
+ <div class="hint">At least 8 characters, one uppercase letter, one symbol</div>
305
+ </div>
306
+ <div class="field">
307
+ <label for="signup-confirm">Confirm password</label>
308
+ <input id="signup-confirm" type="password" placeholder="Re-enter password" autocomplete="new-password" />
309
+ </div>
310
+ <div class="field">
311
+ <label>Region</label>
312
+ <div class="seg">
313
+ <label><input type="radio" name="signup-region" value="us" checked /><span>US</span></label>
314
+ <label><input type="radio" name="signup-region" value="ca" /><span>Canada</span></label>
315
+ </div>
316
+ </div>
317
+ <button type="submit" class="btn-primary" id="btn-signup">Create account</button>
318
+ </form>
319
+ </div>
320
+
321
+ <div class="alert alert-error" id="auth-error"></div>
322
+ </div>
323
+ <div class="footer">
324
+ <a href="https://docs.laminar.run" target="_blank">Documentation</a>
325
+ </div>
326
+ </div>
327
+
328
+ <!-- Step 2: Workspace -->
329
+ <div class="step" id="step-workspace">
330
+ <div class="card">
331
+ <h2>Your Workspaces</h2>
332
+ <p class="desc" id="ws-desc">Loading...</p>
333
+ <ul class="ws-list" id="ws-list"></ul>
334
+ <div id="ws-create-section" style="display:none">
335
+ <div class="field">
336
+ <label for="ws-name">Workspace name</label>
337
+ <input id="ws-name" type="text" placeholder="My Workspace" />
338
+ </div>
339
+ <button class="btn-primary" id="btn-create-ws" onclick="doCreateWorkspace()">Create workspace</button>
340
+ </div>
341
+ <button class="btn-ghost mt-12" id="btn-skip-ws" onclick="goToAdvanced()" style="display:none">Continue</button>
342
+ <div class="alert alert-error" id="ws-error"></div>
343
+ </div>
344
+ </div>
345
+
346
+ <!-- Step 3: Advanced Settings -->
347
+ <div class="step" id="step-advanced">
348
+ <div class="card">
349
+ <h2>Advanced Settings</h2>
350
+ <p class="desc">Optional services that unlock extra MCP tools. Skip any you don't need.</p>
351
+ <div class="section-label">Elasticsearch <span class="tag">Log Search</span></div>
352
+ <p class="hint mb-12">Enables search_logs, search_across_workflows, and keyword-based incident investigation.</p>
353
+ <div class="field"><label for="es-endpoint">Elasticsearch Endpoint</label><input id="es-endpoint" type="text" placeholder="https://your-cluster.es.cloud.io" /></div>
354
+ <div class="field"><label for="es-api-key">Elasticsearch API Key</label><input id="es-api-key" type="text" placeholder="Base64-encoded API key" /></div>
355
+ <div class="field"><label for="es-index">Index Name <span style="color:var(--text-muted)">(optional)</span></label><input id="es-index" type="text" placeholder="search-workflow-executions" /></div>
356
+ <hr class="divider" />
357
+ <div class="section-label">CRON Service <span class="tag">Scheduling</span></div>
358
+ <p class="hint mb-12">Enables cron job management, retry scheduling, and automated triggers.</p>
359
+ <div class="field"><label for="cron-api-key">CRON API Key</label><input id="cron-api-key" type="text" placeholder="Your CRON service API key" /></div>
360
+ <div class="field"><label for="cron-base">CRON API Base <span style="color:var(--text-muted)">(optional)</span></label><input id="cron-base" type="text" placeholder="https://cron.laminar.run" /></div>
361
+ <button class="btn-primary" id="btn-save-advanced" onclick="saveAdvanced()">Save &amp; Continue</button>
362
+ <button class="btn-ghost mt-12" onclick="skipAdvanced()">Skip</button>
363
+ <div class="alert alert-error" id="advanced-error"></div>
364
+ </div>
365
+ </div>
366
+
367
+ <!-- Step 4: Done -->
368
+ <div class="step" id="step-done">
369
+ <div class="card done">
370
+ <div class="done-icon">
371
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
372
+ </div>
373
+ <h2>Connected</h2>
374
+ <p>Restart Cursor to activate the Minicor MCP server.</p>
375
+ <div id="svc-statuses" style="margin-bottom:14px"></div>
376
+ <div class="code-block" id="config-preview"></div>
377
+ <button class="btn-ghost mt-12" onclick="window.close()">Close</button>
378
+ </div>
379
+ </div>
380
+ </div>
381
+
382
+ <script>
383
+ let configPreview='';
384
+
385
+ function switchTab(tab){
386
+ document.querySelectorAll('.tabs button').forEach((b,i)=>b.classList.toggle('active',i===(tab==='signin'?0:1)));
387
+ document.getElementById('tab-signin').classList.toggle('active',tab==='signin');
388
+ document.getElementById('tab-signup').classList.toggle('active',tab==='signup');
389
+ clearAlerts(['auth-error']);
390
+ }
391
+
392
+ function showError(id,m){const e=document.getElementById(id);e.textContent=m;e.classList.add('show')}
393
+ function clearAlerts(ids){ids.forEach(id=>{const e=document.getElementById(id);if(e)e.classList.remove('show')})}
394
+
395
+ function goTo(step){
396
+ document.querySelectorAll('.step').forEach(s=>s.classList.remove('active'));
397
+ document.getElementById('step-'+step).classList.add('active');
398
+ }
399
+
400
+ (async()=>{try{
401
+ const r=await fetch('/api/status');const d=await r.json();
402
+ if(d.hasSession){
403
+ document.getElementById('session-banner').style.display='flex';
404
+ document.getElementById('s-region').textContent=d.region;
405
+ document.getElementById('s-expires').textContent=new Date(d.expiresAt).toLocaleString();
406
+ if(d.region==='Canada'||d.region==='ca'){
407
+ document.querySelector('input[name="login-region"][value="ca"]').checked=true;
408
+ document.querySelector('input[name="signup-region"][value="ca"]').checked=true;
409
+ }
410
+ }
411
+ if(d.serviceConfig){
412
+ if(d.serviceConfig.elasticsearch){
413
+ document.getElementById('es-endpoint').value=d.serviceConfig.elasticsearch.endpoint||'';
414
+ document.getElementById('es-api-key').value=d.serviceConfig.elasticsearch.apiKey||'';
415
+ document.getElementById('es-index').value=d.serviceConfig.elasticsearch.indexName||'';
416
+ }
417
+ if(d.serviceConfig.cron){
418
+ document.getElementById('cron-api-key').value=d.serviceConfig.cron.apiKey||'';
419
+ document.getElementById('cron-base').value=d.serviceConfig.cron.apiBase||'';
420
+ }
421
+ }
422
+ }catch{}})();
423
+
424
+ async function doLogout(){
425
+ try{await fetch('/api/logout',{method:'POST'})}catch{}
426
+ document.getElementById('session-banner').style.display='none';
427
+ }
428
+
429
+ async function doAuth(endpoint,body,btn,loadingText){
430
+ clearAlerts(['auth-error']);
431
+ btn.disabled=true;btn.innerHTML='<div class="spinner"></div>'+loadingText;
432
+ try{
433
+ const r=await fetch(endpoint,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
434
+ const d=await r.json();if(!r.ok)throw new Error(d.error||'Failed');
435
+ configPreview=d.configPreview||'';
436
+ await loadWorkspaces(d.accessToken);
437
+ }catch(err){showError('auth-error',err.message)}finally{btn.disabled=false;btn.textContent=btn.dataset.label}
438
+ }
439
+
440
+ document.getElementById('login-form').addEventListener('submit',async(e)=>{
441
+ e.preventDefault();
442
+ const email=document.getElementById('login-email').value.trim();
443
+ const password=document.getElementById('login-password').value;
444
+ const region=document.querySelector('input[name="login-region"]:checked').value;
445
+ if(!email||!password)return showError('auth-error','Email and password are required.');
446
+ const btn=document.getElementById('btn-login');btn.dataset.label='Sign in';
447
+ await doAuth('/api/connect',{email,password,region},btn,'Signing in\\u2026');
448
+ });
449
+
450
+ document.getElementById('signup-form').addEventListener('submit',async(e)=>{
451
+ e.preventDefault();
452
+ const firstName=document.getElementById('signup-first').value.trim();
453
+ const lastName=document.getElementById('signup-last').value.trim();
454
+ const email=document.getElementById('signup-email').value.trim();
455
+ const password=document.getElementById('signup-password').value;
456
+ const confirm=document.getElementById('signup-confirm').value;
457
+ const region=document.querySelector('input[name="signup-region"]:checked').value;
458
+ if(!firstName||!lastName||!email||!password)return showError('auth-error','All fields are required.');
459
+ if(password!==confirm)return showError('auth-error','Passwords do not match.');
460
+ if(password.length<8)return showError('auth-error','Password must be at least 8 characters.');
461
+ if(!/[A-Z]/.test(password))return showError('auth-error','Password must contain an uppercase letter.');
462
+ if(!/[^a-zA-Z0-9]/.test(password))return showError('auth-error','Password must contain a symbol.');
463
+ const btn=document.getElementById('btn-signup');btn.dataset.label='Create account';
464
+ await doAuth('/api/register',{firstName,lastName,email,password,region},btn,'Creating account\\u2026');
465
+ });
466
+
467
+ async function loadWorkspaces(token){
468
+ goTo('workspace');
469
+ try{
470
+ const r=await fetch('/api/workspaces',{headers:{'X-Token':token||''}});
471
+ const d=await r.json();
472
+ const list=d.workspaces||[];
473
+ const ul=document.getElementById('ws-list');
474
+ if(list.length===0){
475
+ document.getElementById('ws-desc').textContent='You don\\u2019t have any workspaces yet. Create one to get started.';
476
+ document.getElementById('ws-create-section').style.display='block';
477
+ } else {
478
+ document.getElementById('ws-desc').textContent='Your workspaces are ready. You can continue to advanced settings or create another.';
479
+ ul.innerHTML=list.map(w=>'<li>'+w.name+'<span class="id">ID: '+w.id+'</span></li>').join('');
480
+ document.getElementById('ws-create-section').style.display='block';
481
+ document.getElementById('btn-skip-ws').style.display='flex';
482
+ }
483
+ }catch(err){
484
+ document.getElementById('ws-desc').textContent='Could not load workspaces.';
485
+ document.getElementById('btn-skip-ws').style.display='flex';
486
+ }
487
+ }
488
+
489
+ async function doCreateWorkspace(){
490
+ clearAlerts(['ws-error']);
491
+ const name=document.getElementById('ws-name').value.trim();
492
+ if(!name||name.length<3)return showError('ws-error','Workspace name must be at least 3 characters.');
493
+ const btn=document.getElementById('btn-create-ws');
494
+ btn.disabled=true;btn.innerHTML='<div class="spinner"></div>Creating\\u2026';
495
+ try{
496
+ const r=await fetch('/api/create-workspace',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name})});
497
+ const d=await r.json();if(!r.ok)throw new Error(d.error||'Failed');
498
+ document.getElementById('ws-name').value='';
499
+ await loadWorkspaces('');
500
+ }catch(err){showError('ws-error',err.message)}finally{btn.disabled=false;btn.textContent='Create workspace'}
501
+ }
502
+
503
+ function goToAdvanced(){goTo('advanced')}
504
+
505
+ async function saveAdvanced(){
506
+ clearAlerts(['advanced-error']);
507
+ const config={};
508
+ const esEndpoint=document.getElementById('es-endpoint').value.trim();
509
+ const esApiKey=document.getElementById('es-api-key').value.trim();
510
+ const esIndex=document.getElementById('es-index').value.trim();
511
+ const cronApiKey=document.getElementById('cron-api-key').value.trim();
512
+ const cronBase=document.getElementById('cron-base').value.trim();
513
+ if(esEndpoint&&esApiKey){config.elasticsearch={endpoint:esEndpoint,apiKey:esApiKey};if(esIndex)config.elasticsearch.indexName=esIndex}
514
+ if(cronApiKey){config.cron={apiKey:cronApiKey};if(cronBase)config.cron.apiBase=cronBase}
515
+ const btn=document.getElementById('btn-save-advanced');
516
+ btn.disabled=true;btn.innerHTML='<div class="spinner"></div>Saving\\u2026';
517
+ try{
518
+ const r=await fetch('/api/advanced',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(config)});
519
+ const d=await r.json();if(!r.ok)throw new Error(d.error||'Save failed');
520
+ goToDone(config);
521
+ }catch(err){showError('advanced-error',err.message)}finally{btn.disabled=false;btn.textContent='Save & Continue'}
522
+ }
523
+
524
+ function skipAdvanced(){goToDone({})}
525
+
526
+ function goToDone(svcConfig){
527
+ goTo('done');
528
+ document.getElementById('config-preview').textContent=configPreview;
529
+ const statuses=document.getElementById('svc-statuses');
530
+ const items=[{name:'Elasticsearch (Log Search)',on:!!svcConfig.elasticsearch},{name:'CRON (Scheduling)',on:!!svcConfig.cron}];
531
+ statuses.innerHTML=items.map(i=>'<div class="svc-status"><div class="'+(i.on?'dot-on':'dot-off')+'"></div>'+i.name+': '+(i.on?'Configured':'Not configured')+'</div>').join('');
532
+ }
533
+ </script>
534
+ </body>
535
+ </html>`;
536
+ // ─── Browser server ──────────────────────────────────────────
537
+ let storedAccessToken = null;
538
+ async function startBrowserSetup() {
539
+ const srv = http.createServer(async (req, res) => {
540
+ res.setHeader("Access-Control-Allow-Origin", "*");
541
+ res.setHeader("Access-Control-Allow-Headers", "*");
542
+ res.setHeader("Access-Control-Allow-Methods", "*");
543
+ if (req.method === "OPTIONS") {
544
+ res.writeHead(204);
545
+ return res.end();
546
+ }
547
+ const url = new URL(req.url || "/", `http://localhost`);
548
+ try {
549
+ if (url.pathname === "/" && req.method === "GET") {
550
+ res.writeHead(200, { "Content-Type": "text/html" });
551
+ return res.end(HTML);
552
+ }
553
+ if (url.pathname === "/api/status" && req.method === "GET") {
554
+ let serviceConfig = null;
555
+ const cfgPath = getWriteConfigPath();
556
+ try {
557
+ if (fs.existsSync(cfgPath)) {
558
+ serviceConfig = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
559
+ }
560
+ }
561
+ catch { }
562
+ const tokenPath = getTokenPath();
563
+ if (fs.existsSync(tokenPath)) {
564
+ try {
565
+ const tokens = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
566
+ const region = tokens.region || apiBaseToRegion(tokens.api_base || "");
567
+ return send(res, 200, {
568
+ hasSession: true,
569
+ region: region === "ca" ? "Canada" : "US",
570
+ apiBase: tokens.api_base,
571
+ expiresAt: tokens.expires_at,
572
+ serviceConfig,
573
+ });
574
+ }
575
+ catch { }
576
+ }
577
+ return send(res, 200, { hasSession: false, serviceConfig });
578
+ }
579
+ if (url.pathname === "/api/logout" && req.method === "POST") {
580
+ const tokenPath = getTokenPath();
581
+ if (fs.existsSync(tokenPath))
582
+ fs.unlinkSync(tokenPath);
583
+ for (const base of [
584
+ path.join(os.homedir(), ".cursor"),
585
+ path.join(process.cwd(), ".cursor"),
586
+ ]) {
587
+ const mcpPath = path.join(base, "mcp.json");
588
+ if (fs.existsSync(mcpPath)) {
589
+ try {
590
+ const cfg = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
591
+ delete cfg.mcpServers?.minicor;
592
+ delete cfg.mcpServers?.laminar;
593
+ fs.writeFileSync(mcpPath, JSON.stringify(cfg, null, 2) + "\n");
594
+ }
595
+ catch { }
596
+ }
597
+ }
598
+ storedAccessToken = null;
599
+ console.log("Session cleared.");
600
+ return send(res, 200, { ok: true });
601
+ }
602
+ if (url.pathname === "/api/connect" && req.method === "POST") {
603
+ const { email, password, region } = await jsonBody(req);
604
+ const r = region === "ca" ? "ca" : "us";
605
+ const apiBase = regionToApiBase(r);
606
+ const loginData = await signIn(apiBase, email, password);
607
+ const tokenPath = storeTokens(loginData, r);
608
+ const configPath = writeMcpConfig();
609
+ storedAccessToken = loginData.access_token;
610
+ console.log(`\n✓ Tokens stored at ${tokenPath}`);
611
+ console.log(`✓ MCP config written to ${configPath}`);
612
+ return send(res, 200, {
613
+ accessToken: loginData.access_token,
614
+ configPath,
615
+ configPreview: JSON.stringify(JSON.parse(fs.readFileSync(configPath, "utf-8")), null, 2),
616
+ });
617
+ }
618
+ if (url.pathname === "/api/register" && req.method === "POST") {
619
+ const { firstName, lastName, email, password, region } = await jsonBody(req);
620
+ const r = region === "ca" ? "ca" : "us";
621
+ const apiBase = regionToApiBase(r);
622
+ await register(apiBase, { firstName, lastName, email, password });
623
+ const loginData = await signIn(apiBase, email, password);
624
+ const tokenPath = storeTokens(loginData, r);
625
+ const configPath = writeMcpConfig();
626
+ storedAccessToken = loginData.access_token;
627
+ console.log(`\n✓ Account created and signed in`);
628
+ console.log(`✓ Tokens stored at ${tokenPath}`);
629
+ console.log(`✓ MCP config written to ${configPath}`);
630
+ return send(res, 200, {
631
+ accessToken: loginData.access_token,
632
+ configPath,
633
+ configPreview: JSON.stringify(JSON.parse(fs.readFileSync(configPath, "utf-8")), null, 2),
634
+ });
635
+ }
636
+ if (url.pathname === "/api/workspaces" && req.method === "GET") {
637
+ const token = req.headers["x-token"] || storedAccessToken;
638
+ if (!token)
639
+ return send(res, 401, { error: "No token" });
640
+ const tokenPath = getTokenPath();
641
+ let apiBase = regionToApiBase("us");
642
+ if (fs.existsSync(tokenPath)) {
643
+ try {
644
+ const t = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
645
+ apiBase = regionToApiBase(t.region || apiBaseToRegion(t.api_base || ""));
646
+ }
647
+ catch { }
648
+ }
649
+ const workspaces = await listWorkspaces(apiBase, token);
650
+ return send(res, 200, { workspaces });
651
+ }
652
+ if (url.pathname === "/api/create-workspace" &&
653
+ req.method === "POST") {
654
+ const { name } = await jsonBody(req);
655
+ const token = storedAccessToken;
656
+ if (!token)
657
+ return send(res, 401, { error: "No token" });
658
+ const tokenPath = getTokenPath();
659
+ let apiBase = regionToApiBase("us");
660
+ if (fs.existsSync(tokenPath)) {
661
+ try {
662
+ const t = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
663
+ apiBase = regionToApiBase(t.region || apiBaseToRegion(t.api_base || ""));
664
+ }
665
+ catch { }
666
+ }
667
+ const ws = await createWorkspace(apiBase, token, name);
668
+ return send(res, 200, { workspace: ws });
669
+ }
670
+ if (url.pathname === "/api/advanced" && req.method === "POST") {
671
+ const body = await jsonBody(req);
672
+ saveServiceConfig(body);
673
+ const cfgPath = getWriteConfigPath();
674
+ console.log(`✓ Service config saved to ${cfgPath}`);
675
+ send(res, 200, { ok: true });
676
+ setTimeout(() => {
677
+ console.log("\nSetup complete. Restart Cursor to activate.\n");
678
+ process.exit(0);
679
+ }, 2000);
680
+ return;
681
+ }
682
+ send(res, 404, { error: "Not found" });
683
+ }
684
+ catch (e) {
685
+ send(res, 500, { error: e.message });
686
+ }
687
+ });
688
+ srv.listen(0, "127.0.0.1", () => {
689
+ const addr = srv.address();
690
+ if (!addr || typeof addr === "string") {
691
+ console.error("Failed to start server");
692
+ process.exit(1);
693
+ }
694
+ const url = `http://127.0.0.1:${addr.port}`;
695
+ console.log(`\n Minicor MCP Setup`);
696
+ console.log(` ─────────────────`);
697
+ console.log(` Opening browser → ${url}\n`);
698
+ openBrowser(url);
699
+ });
700
+ }
701
+ // ─── CLI mode ────────────────────────────────────────────────
702
+ function ask(rl, question) {
703
+ return new Promise((resolve) => rl.question(question, resolve));
704
+ }
705
+ async function startCliSetup() {
706
+ const rl = readline.createInterface({
707
+ input: process.stdin,
708
+ output: process.stdout,
709
+ });
710
+ console.log(`\n Minicor MCP Setup (CLI)`);
711
+ console.log(` ───────────────────────\n`);
712
+ const hasAccount = await ask(rl, "Do you have an account? (Y/n): ");
713
+ const isSignup = hasAccount.trim().toLowerCase() === "n";
714
+ const regionStr = await ask(rl, "Region (us/ca) [us]: ");
715
+ const region = regionStr.trim().toLowerCase() === "ca" ? "ca" : "us";
716
+ const apiBase = regionToApiBase(region);
717
+ let email;
718
+ let password;
719
+ if (isSignup) {
720
+ console.log("\nCreate your account:");
721
+ const firstName = await ask(rl, " First name: ");
722
+ const lastName = await ask(rl, " Last name: ");
723
+ email = await ask(rl, " Email: ");
724
+ password = await ask(rl, " Password: ");
725
+ const confirm = await ask(rl, " Confirm password: ");
726
+ if (password !== confirm) {
727
+ console.error("\nError: Passwords do not match.");
728
+ rl.close();
729
+ process.exit(1);
730
+ }
731
+ try {
732
+ await register(apiBase, {
733
+ firstName: firstName.trim(),
734
+ lastName: lastName.trim(),
735
+ email: email.trim(),
736
+ password,
737
+ });
738
+ console.log("\n✓ Account created. Signing in...");
739
+ }
740
+ catch (e) {
741
+ console.error(`\nError: ${e.message}`);
742
+ rl.close();
743
+ process.exit(1);
744
+ }
745
+ }
746
+ else {
747
+ console.log("\nSign in:");
748
+ email = await ask(rl, " Email: ");
749
+ password = await ask(rl, " Password: ");
750
+ }
751
+ try {
752
+ const loginData = await signIn(apiBase, email.trim(), password);
753
+ const tokenPath = storeTokens(loginData, region);
754
+ const configPath = writeMcpConfig();
755
+ console.log(`\n✓ Signed in.`);
756
+ console.log(`✓ Tokens stored at ${tokenPath}`);
757
+ console.log(`✓ MCP config written to ${configPath}`);
758
+ const workspaces = await listWorkspaces(apiBase, loginData.access_token);
759
+ if (workspaces.length === 0) {
760
+ const wsName = await ask(rl, "\nYou have no workspaces. Create one? Name: ");
761
+ if (wsName.trim().length >= 3) {
762
+ const ws = await createWorkspace(apiBase, loginData.access_token, wsName.trim());
763
+ console.log(`✓ Workspace "${ws.name}" created (ID: ${ws.id})`);
764
+ }
765
+ }
766
+ else {
767
+ console.log(`\nWorkspaces: ${workspaces.map((w) => `${w.name} (${w.id})`).join(", ")}`);
768
+ }
769
+ console.log("\nRestart Cursor to activate.\n");
770
+ }
771
+ catch (e) {
772
+ console.error(`\nError: ${e.message}`);
773
+ }
774
+ rl.close();
775
+ process.exit(0);
776
+ }
777
+ // ─── Entry point ─────────────────────────────────────────────
778
+ if (process.argv.includes("--cli")) {
779
+ startCliSetup().catch((err) => {
780
+ console.error("Setup failed:", err);
781
+ process.exit(1);
782
+ });
783
+ }
784
+ else {
785
+ startBrowserSetup().catch((err) => {
786
+ console.error("Setup failed:", err);
787
+ process.exit(1);
788
+ });
789
+ }
790
+ //# sourceMappingURL=setup.js.map