farseer-cli 1.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 (120) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +741 -0
  3. package/dist/commands/app.d.ts +2 -0
  4. package/dist/commands/app.js +349 -0
  5. package/dist/commands/app.js.map +7 -0
  6. package/dist/commands/apps.d.ts +2 -0
  7. package/dist/commands/apps.js +111 -0
  8. package/dist/commands/apps.js.map +7 -0
  9. package/dist/commands/checkout.d.ts +2 -0
  10. package/dist/commands/checkout.js +166 -0
  11. package/dist/commands/checkout.js.map +7 -0
  12. package/dist/commands/config.d.ts +2 -0
  13. package/dist/commands/config.js +139 -0
  14. package/dist/commands/config.js.map +7 -0
  15. package/dist/commands/diff.d.ts +2 -0
  16. package/dist/commands/diff.js +183 -0
  17. package/dist/commands/diff.js.map +7 -0
  18. package/dist/commands/files.js +99 -0
  19. package/dist/commands/files.js.map +7 -0
  20. package/dist/commands/install.d.ts +2 -0
  21. package/dist/commands/install.js +79 -0
  22. package/dist/commands/install.js.map +7 -0
  23. package/dist/commands/list.d.ts +2 -0
  24. package/dist/commands/list.js +92 -0
  25. package/dist/commands/list.js.map +7 -0
  26. package/dist/commands/login.d.ts +2 -0
  27. package/dist/commands/login.js +134 -0
  28. package/dist/commands/login.js.map +7 -0
  29. package/dist/commands/logout.d.ts +2 -0
  30. package/dist/commands/logout.js +59 -0
  31. package/dist/commands/logout.js.map +7 -0
  32. package/dist/commands/mcp-server.d.ts +8 -0
  33. package/dist/commands/mcp-server.js +41 -0
  34. package/dist/commands/mcp-server.js.map +7 -0
  35. package/dist/commands/model.d.ts +2 -0
  36. package/dist/commands/model.js +189 -0
  37. package/dist/commands/model.js.map +7 -0
  38. package/dist/commands/pull.d.ts +2 -0
  39. package/dist/commands/pull.js +287 -0
  40. package/dist/commands/pull.js.map +7 -0
  41. package/dist/commands/push.d.ts +2 -0
  42. package/dist/commands/push.js +251 -0
  43. package/dist/commands/push.js.map +7 -0
  44. package/dist/commands/run.d.ts +2 -0
  45. package/dist/commands/run.js +246 -0
  46. package/dist/commands/run.js.map +7 -0
  47. package/dist/commands/setup.d.ts +2 -0
  48. package/dist/commands/setup.js +137 -0
  49. package/dist/commands/status.d.ts +2 -0
  50. package/dist/commands/status.js +145 -0
  51. package/dist/commands/status.js.map +7 -0
  52. package/dist/commands/unsetup.d.ts +2 -0
  53. package/dist/commands/unsetup.js +122 -0
  54. package/dist/commands/whoami.d.ts +2 -0
  55. package/dist/commands/whoami.js +63 -0
  56. package/dist/commands/whoami.js.map +7 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +135 -0
  59. package/dist/index.js.map +7 -0
  60. package/dist/mcp/index.d.ts +7 -0
  61. package/dist/mcp/index.js +35 -0
  62. package/dist/mcp/index.js.map +7 -0
  63. package/dist/mcp/prompts/workflows.d.ts +7 -0
  64. package/dist/mcp/prompts/workflows.js +374 -0
  65. package/dist/mcp/prompts/workflows.js.map +7 -0
  66. package/dist/mcp/resources/documentation.d.ts +8 -0
  67. package/dist/mcp/resources/documentation.js +167 -0
  68. package/dist/mcp/resources/documentation.js.map +7 -0
  69. package/dist/mcp/server.d.ts +7 -0
  70. package/dist/mcp/server.js +49 -0
  71. package/dist/mcp/server.js.map +7 -0
  72. package/dist/mcp/tools/appTools.d.ts +7 -0
  73. package/dist/mcp/tools/appTools.js +377 -0
  74. package/dist/mcp/tools/appTools.js.map +7 -0
  75. package/dist/mcp/tools/authTools.d.ts +7 -0
  76. package/dist/mcp/tools/authTools.js +158 -0
  77. package/dist/mcp/tools/authTools.js.map +7 -0
  78. package/dist/mcp/tools/modelTools.d.ts +7 -0
  79. package/dist/mcp/tools/modelTools.js +331 -0
  80. package/dist/mcp/tools/modelTools.js.map +7 -0
  81. package/dist/mcp/tools/runTools.d.ts +7 -0
  82. package/dist/mcp/tools/runTools.js +231 -0
  83. package/dist/mcp/tools/runTools.js.map +7 -0
  84. package/dist/mcp/tools/syncTools.d.ts +7 -0
  85. package/dist/mcp/tools/syncTools.js +382 -0
  86. package/dist/mcp/tools/syncTools.js.map +7 -0
  87. package/dist/mcp/utils/helpers.d.ts +69 -0
  88. package/dist/mcp/utils/helpers.js +113 -0
  89. package/dist/mcp/utils/helpers.js.map +7 -0
  90. package/dist/services/appSyncService.d.ts +75 -0
  91. package/dist/services/appSyncService.js +370 -0
  92. package/dist/services/appSyncService.js.map +7 -0
  93. package/dist/services/configService.d.ts +39 -0
  94. package/dist/services/configService.js +196 -0
  95. package/dist/services/configService.js.map +7 -0
  96. package/dist/services/farseerApi.d.ts +166 -0
  97. package/dist/services/farseerApi.js +378 -0
  98. package/dist/services/farseerApi.js.map +7 -0
  99. package/dist/services/farseerFactory.d.ts +88 -0
  100. package/dist/services/farseerFactory.js +179 -0
  101. package/dist/services/farseerFactory.js.map +7 -0
  102. package/dist/services/farseerService.d.ts +96 -0
  103. package/dist/services/farseerService.js +614 -0
  104. package/dist/services/farseerService.js.map +7 -0
  105. package/dist/services/gitService.d.ts +31 -0
  106. package/dist/services/gitService.js +134 -0
  107. package/dist/services/gitService.js.map +7 -0
  108. package/dist/services/syncService.d.ts +44 -0
  109. package/dist/services/syncService.js +320 -0
  110. package/dist/services/syncService.js.map +7 -0
  111. package/dist/utils/constants.d.ts +7 -0
  112. package/dist/utils/constants.js +46 -0
  113. package/dist/utils/constants.js.map +7 -0
  114. package/dist/utils/helpers.d.ts +69 -0
  115. package/dist/utils/helpers.js +413 -0
  116. package/dist/utils/helpers.js.map +7 -0
  117. package/dist/utils/logger.d.ts +14 -0
  118. package/dist/utils/logger.js +76 -0
  119. package/dist/utils/logger.js.map +7 -0
  120. package/package.json +62 -0
@@ -0,0 +1,614 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var farseerService_exports = {};
29
+ __export(farseerService_exports, {
30
+ FarseerService: () => FarseerService,
31
+ loginWithBrowser: () => loginWithBrowser,
32
+ loginWithPassword: () => loginWithPassword,
33
+ refreshAccessToken: () => refreshAccessToken
34
+ });
35
+ module.exports = __toCommonJS(farseerService_exports);
36
+ var farseer = __toESM(require("farseer-client"));
37
+ var import_configService = require("./configService");
38
+ class FarseerService {
39
+ constructor(credential) {
40
+ this.credential = credential;
41
+ this.client = new farseer.FarseerClient({
42
+ basePath: credential.basePath,
43
+ headers: {
44
+ "X-TENANT-ID": credential.tenantId,
45
+ "X-API-KEY": credential.apiKey
46
+ }
47
+ });
48
+ }
49
+ static fromUserAuth(tenant, basePath) {
50
+ const auth = (0, import_configService.getUserAuth)();
51
+ if (!auth) return null;
52
+ const service = new FarseerService({
53
+ type: "apiKey",
54
+ tenantId: tenant,
55
+ apiKey: "",
56
+ basePath
57
+ });
58
+ service.client = new farseer.FarseerClient({
59
+ basePath,
60
+ headers: {
61
+ "X-TENANT-ID": tenant
62
+ },
63
+ accessToken: auth.accessToken
64
+ });
65
+ return service;
66
+ }
67
+ async testConnection() {
68
+ try {
69
+ await this.client.getItemByPath(["Files"]);
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ async getFilesFolder() {
76
+ try {
77
+ const item = await this.client.getItemByPath(["Files"]);
78
+ return {
79
+ id: item.id,
80
+ name: item.name,
81
+ type: item.type
82
+ };
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+ async listFiles(folderPath = ["Files"]) {
88
+ const files = [];
89
+ try {
90
+ const folder = await this.client.getItemByPath(folderPath);
91
+ const folderContent = await this.client.folders.listItemsBatch([{ id: folder.id }]);
92
+ for (const item of folderContent[0]?.items || []) {
93
+ const itemPath = [...folderPath.slice(1), item.name].join("/");
94
+ if (item.type === farseer.FolderItemType.Folder) {
95
+ const subFiles = await this.listFiles([...folderPath, item.name]);
96
+ files.push(...subFiles);
97
+ } else if (item.type === farseer.FolderItemType.FarseerFile && "reference" in item) {
98
+ files.push({
99
+ name: item.name,
100
+ path: itemPath,
101
+ reference: item.reference
102
+ });
103
+ }
104
+ }
105
+ } catch (error) {
106
+ return [];
107
+ }
108
+ return files;
109
+ }
110
+ async getFileContent(reference) {
111
+ const blob = await this.client.farseerFiles.get(reference);
112
+ return await blob.text();
113
+ }
114
+ async createFile(content, fileName, folderPath = ["Files"]) {
115
+ await this.ensureFolderPath(folderPath);
116
+ const folder = await this.client.getItemByPath(folderPath);
117
+ const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);
118
+ const file = new farseer.WebFile([blob], fileName);
119
+ await this.client.farseerFiles.create(file, "GENERAL", folder.id.toString());
120
+ }
121
+ async updateFile(reference, content, fileName) {
122
+ const blob = content instanceof Buffer ? new Blob([content]) : new Blob([content]);
123
+ const file = new farseer.WebFile([blob], fileName);
124
+ await this.client.farseerFiles.update(reference, file);
125
+ }
126
+ async deleteFile(reference) {
127
+ await this.client.farseerFiles.remove(reference);
128
+ }
129
+ async ensureFolderPath(folderPath) {
130
+ let currentPath = [];
131
+ for (const segment of folderPath) {
132
+ currentPath.push(segment);
133
+ try {
134
+ await this.client.getItemByPath(currentPath);
135
+ } catch {
136
+ if (currentPath.length > 1) {
137
+ const parentPath = currentPath.slice(0, -1);
138
+ const parent = await this.client.getItemByPath(parentPath);
139
+ await this.client.folders.create({
140
+ parentId: parent.id,
141
+ name: segment,
142
+ allowedTypes: []
143
+ });
144
+ }
145
+ }
146
+ }
147
+ }
148
+ async getFileByPath(filePath) {
149
+ const pathParts = ["Files", ...filePath.split("/")];
150
+ const fileName = pathParts.pop();
151
+ const folderPath = pathParts;
152
+ try {
153
+ const folder = await this.client.getItemByPath(folderPath);
154
+ const folderContent = await this.client.folders.listItemsBatch([{ id: folder.id }]);
155
+ for (const item of folderContent[0]?.items || []) {
156
+ if (item.name === fileName && item.type === farseer.FolderItemType.FarseerFile && "reference" in item) {
157
+ return {
158
+ name: item.name,
159
+ path: filePath,
160
+ reference: item.reference
161
+ };
162
+ }
163
+ }
164
+ } catch {
165
+ return null;
166
+ }
167
+ return null;
168
+ }
169
+ async getFileMetadata(reference) {
170
+ try {
171
+ const metadata = await this.client.farseerFiles.getMetadata(reference);
172
+ return {
173
+ uploadTime: metadata.uploadTime?.toISOString(),
174
+ uploader: metadata.uploader ? {
175
+ id: metadata.uploader.id,
176
+ email: metadata.uploader.email || "",
177
+ firstName: metadata.uploader.firstName || "",
178
+ lastName: metadata.uploader.lastName || ""
179
+ } : void 0,
180
+ size: metadata.size
181
+ };
182
+ } catch (error) {
183
+ return null;
184
+ }
185
+ }
186
+ async getFileContentAsBuffer(reference) {
187
+ const blob = await this.client.farseerFiles.get(reference);
188
+ const arrayBuffer = await blob.arrayBuffer();
189
+ return Buffer.from(arrayBuffer);
190
+ }
191
+ isTextFile(filename) {
192
+ const ext = filename.toLowerCase().split(".").pop() || "";
193
+ return ["ts", "js", "mjs", "cjs", "json", "txt", "md", "csv", "xml", "html", "css", "yaml", "yml"].includes(ext);
194
+ }
195
+ isBinaryFile(filename) {
196
+ return !this.isTextFile(filename);
197
+ }
198
+ // ==================== Apps (Remote Jobs) API ====================
199
+ /**
200
+ * List all apps (Remote Jobs) on the tenant
201
+ */
202
+ async listApps() {
203
+ const response = await this.apiRequest("/remoteJobs", "GET");
204
+ return response.map((app) => ({
205
+ id: app.id,
206
+ name: app.name,
207
+ type: "REMOTE_JOB",
208
+ reference: String(app.id)
209
+ }));
210
+ }
211
+ /**
212
+ * Get app details by reference ID
213
+ */
214
+ async getApp(referenceId) {
215
+ try {
216
+ return await this.apiRequest(`/remoteJobs/${referenceId}`, "GET");
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ /**
222
+ * Get app by name
223
+ */
224
+ async getAppByName(name) {
225
+ const apps = await this.listApps();
226
+ const app = apps.find((a) => a.name.toLowerCase() === name.toLowerCase());
227
+ if (!app) {
228
+ return null;
229
+ }
230
+ return this.getApp(app.reference);
231
+ }
232
+ /**
233
+ * Create a new app
234
+ */
235
+ async createApp(name) {
236
+ const appsFolderId = await this.getAppsFolderId();
237
+ if (!appsFolderId) {
238
+ throw new Error("Apps folder not found");
239
+ }
240
+ return await this.apiRequest("/remoteJobs", "POST", {
241
+ folderId: appsFolderId,
242
+ name
243
+ });
244
+ }
245
+ /**
246
+ * Update an app
247
+ */
248
+ async updateApp(referenceId, update) {
249
+ await this.apiRequest(`/remoteJobs/${referenceId}`, "PUT", {
250
+ ...update,
251
+ schedule: null,
252
+ runnerName: null,
253
+ predefinedActions: []
254
+ });
255
+ const app = await this.getApp(referenceId);
256
+ if (!app) {
257
+ throw new Error("Failed to fetch updated app");
258
+ }
259
+ return app;
260
+ }
261
+ /**
262
+ * Delete an app
263
+ */
264
+ async deleteApp(referenceId) {
265
+ await this.apiRequest(`/remoteJobs/${referenceId}`, "DELETE");
266
+ }
267
+ /**
268
+ * Get script files available for apps
269
+ */
270
+ async getScriptFiles() {
271
+ const allFiles = await this.listFiles(["Files"]);
272
+ return allFiles.filter((f) => f.name.endsWith(".ts") || f.name.endsWith(".js")).map((f) => ({
273
+ id: f.reference,
274
+ name: f.name
275
+ }));
276
+ }
277
+ /**
278
+ * Export the entire model structure
279
+ */
280
+ async exportModel() {
281
+ return this.apiRequest("/model/export", "POST");
282
+ }
283
+ /**
284
+ * Get the Apps folder ID
285
+ */
286
+ async getAppsFolderId() {
287
+ try {
288
+ const item = await this.client.getItemByPath(["Apps"]);
289
+ return item.id;
290
+ } catch {
291
+ return null;
292
+ }
293
+ }
294
+ /**
295
+ * Make a direct API request (for endpoints not in farseer-client)
296
+ */
297
+ async apiRequest(endpoint, method, body) {
298
+ const url = `${this.credential.basePath}${endpoint}`;
299
+ const headers = {
300
+ "Content-Type": "application/json",
301
+ "X-TENANT-ID": this.credential.tenantId,
302
+ "X-API-KEY": this.credential.apiKey,
303
+ "x-api-version": "3.3.0"
304
+ };
305
+ const response = await fetch(url, {
306
+ method,
307
+ headers,
308
+ body: body ? JSON.stringify(body) : void 0
309
+ });
310
+ if (!response.ok) {
311
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
312
+ }
313
+ if (response.status === 204) {
314
+ return void 0;
315
+ }
316
+ return response.json();
317
+ }
318
+ }
319
+ async function loginWithPassword(email, password) {
320
+ const tokenUrl = "https://login.farseer.io/auth/realms/master/protocol/openid-connect/token";
321
+ try {
322
+ const response = await fetch(tokenUrl, {
323
+ method: "POST",
324
+ headers: {
325
+ "Content-Type": "application/x-www-form-urlencoded"
326
+ },
327
+ body: new URLSearchParams({
328
+ grant_type: "password",
329
+ client_id: "security-admin-console",
330
+ username: email,
331
+ password
332
+ })
333
+ });
334
+ if (!response.ok) {
335
+ return null;
336
+ }
337
+ const data = await response.json();
338
+ return {
339
+ accessToken: data.access_token,
340
+ refreshToken: data.refresh_token,
341
+ expiresIn: data.expires_in
342
+ };
343
+ } catch {
344
+ return null;
345
+ }
346
+ }
347
+ function getErrorPage(title, message) {
348
+ return `
349
+ <html>
350
+ <head>
351
+ <title>Farseer CLI - Error</title>
352
+ <style>
353
+ * { margin: 0; padding: 0; box-sizing: border-box; }
354
+ body {
355
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
356
+ background: #fafafa;
357
+ min-height: 100vh;
358
+ display: flex;
359
+ align-items: center;
360
+ justify-content: center;
361
+ }
362
+ .container {
363
+ text-align: center;
364
+ padding: 48px;
365
+ }
366
+ .error-icon {
367
+ width: 80px;
368
+ height: 80px;
369
+ border-radius: 50%;
370
+ background: #ef4444;
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: center;
374
+ margin: 0 auto 24px;
375
+ }
376
+ .error-icon svg {
377
+ width: 40px;
378
+ height: 40px;
379
+ stroke: white;
380
+ stroke-width: 3;
381
+ fill: none;
382
+ }
383
+ h1 {
384
+ color: #18181b;
385
+ font-size: 24px;
386
+ font-weight: 600;
387
+ margin-bottom: 8px;
388
+ }
389
+ p {
390
+ color: #71717a;
391
+ font-size: 15px;
392
+ }
393
+ </style>
394
+ </head>
395
+ <body>
396
+ <div class="container">
397
+ <div class="error-icon">
398
+ <svg viewBox="0 0 24 24">
399
+ <line x1="18" y1="6" x2="6" y2="18"></line>
400
+ <line x1="6" y1="6" x2="18" y2="18"></line>
401
+ </svg>
402
+ </div>
403
+ <h1>${title}</h1>
404
+ <p>${message}</p>
405
+ <p style="margin-top: 16px;">You can close this window.</p>
406
+ </div>
407
+ </body>
408
+ </html>
409
+ `;
410
+ }
411
+ async function loginWithBrowser(realm = "master") {
412
+ const http = await import("http");
413
+ const crypto = await import("crypto");
414
+ const { exec } = await import("child_process");
415
+ const { promisify } = await import("util");
416
+ const execAsync = promisify(exec);
417
+ const KEYCLOAK_BASE = `https://login.farseer.io/auth/realms/${realm}/protocol/openid-connect`;
418
+ const CLIENT_ID = "auth";
419
+ const REDIRECT_PORT = 8787;
420
+ const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
421
+ const codeVerifier = crypto.randomBytes(32).toString("base64url");
422
+ const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
423
+ const state = crypto.randomBytes(16).toString("hex");
424
+ return new Promise((resolve) => {
425
+ const server = http.createServer(async (req, res) => {
426
+ const url = new URL(req.url || "", `http://localhost:${REDIRECT_PORT}`);
427
+ if (url.pathname === "/callback") {
428
+ const code = url.searchParams.get("code");
429
+ const returnedState = url.searchParams.get("state");
430
+ const error = url.searchParams.get("error");
431
+ if (error) {
432
+ res.writeHead(200, { "Content-Type": "text/html" });
433
+ res.end(getErrorPage("Login Failed", `Error: ${error}`));
434
+ server.close();
435
+ resolve(null);
436
+ return;
437
+ }
438
+ if (!code || returnedState !== state) {
439
+ res.writeHead(400, { "Content-Type": "text/html" });
440
+ res.end(getErrorPage("Invalid Response", "Missing code or state mismatch."));
441
+ server.close();
442
+ resolve(null);
443
+ return;
444
+ }
445
+ try {
446
+ const tokenResponse = await fetch(`${KEYCLOAK_BASE}/token`, {
447
+ method: "POST",
448
+ headers: {
449
+ "Content-Type": "application/x-www-form-urlencoded"
450
+ },
451
+ body: new URLSearchParams({
452
+ grant_type: "authorization_code",
453
+ client_id: CLIENT_ID,
454
+ code,
455
+ redirect_uri: REDIRECT_URI,
456
+ code_verifier: codeVerifier
457
+ })
458
+ });
459
+ if (!tokenResponse.ok) {
460
+ throw new Error("Token exchange failed");
461
+ }
462
+ const data = await tokenResponse.json();
463
+ res.writeHead(200, { "Content-Type": "text/html" });
464
+ res.end(`
465
+ <html>
466
+ <head>
467
+ <title>Farseer CLI - Login</title>
468
+ <style>
469
+ * { margin: 0; padding: 0; box-sizing: border-box; }
470
+ body {
471
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
472
+ background: #fafafa;
473
+ min-height: 100vh;
474
+ display: flex;
475
+ align-items: center;
476
+ justify-content: center;
477
+ }
478
+ .container {
479
+ text-align: center;
480
+ padding: 48px;
481
+ }
482
+ .checkmark {
483
+ width: 80px;
484
+ height: 80px;
485
+ border-radius: 50%;
486
+ background: #22c55e;
487
+ display: flex;
488
+ align-items: center;
489
+ justify-content: center;
490
+ margin: 0 auto 24px;
491
+ animation: scale-in 0.3s ease-out;
492
+ }
493
+ .checkmark svg {
494
+ width: 40px;
495
+ height: 40px;
496
+ stroke: white;
497
+ stroke-width: 3;
498
+ fill: none;
499
+ animation: draw 0.4s ease-out 0.2s forwards;
500
+ stroke-dasharray: 50;
501
+ stroke-dashoffset: 50;
502
+ }
503
+ @keyframes scale-in {
504
+ from { transform: scale(0); }
505
+ to { transform: scale(1); }
506
+ }
507
+ @keyframes draw {
508
+ to { stroke-dashoffset: 0; }
509
+ }
510
+ h1 {
511
+ color: #18181b;
512
+ font-size: 24px;
513
+ font-weight: 600;
514
+ margin-bottom: 8px;
515
+ }
516
+ p {
517
+ color: #71717a;
518
+ font-size: 15px;
519
+ }
520
+ </style>
521
+ </head>
522
+ <body>
523
+ <div class="container">
524
+ <div class="checkmark">
525
+ <svg viewBox="0 0 24 24">
526
+ <polyline points="20 6 9 17 4 12"></polyline>
527
+ </svg>
528
+ </div>
529
+ <h1>Login Successful</h1>
530
+ <p>You can close this window and return to the terminal.</p>
531
+ </div>
532
+ </body>
533
+ </html>
534
+ `);
535
+ server.close();
536
+ resolve({
537
+ accessToken: data.access_token,
538
+ refreshToken: data.refresh_token,
539
+ expiresIn: data.expires_in
540
+ });
541
+ } catch {
542
+ res.writeHead(500, { "Content-Type": "text/html" });
543
+ res.end(getErrorPage("Token Exchange Failed", "Could not complete login."));
544
+ server.close();
545
+ resolve(null);
546
+ }
547
+ }
548
+ });
549
+ server.listen(REDIRECT_PORT, async () => {
550
+ const authUrl = new URL(`${KEYCLOAK_BASE}/auth`);
551
+ authUrl.searchParams.set("client_id", CLIENT_ID);
552
+ authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
553
+ authUrl.searchParams.set("response_type", "code");
554
+ authUrl.searchParams.set("scope", "openid offline_access");
555
+ authUrl.searchParams.set("state", state);
556
+ authUrl.searchParams.set("code_challenge", codeChallenge);
557
+ authUrl.searchParams.set("code_challenge_method", "S256");
558
+ const url = authUrl.toString();
559
+ try {
560
+ if (process.platform === "darwin") {
561
+ await execAsync(`open "${url}"`);
562
+ } else if (process.platform === "win32") {
563
+ await execAsync(`start "${url}"`);
564
+ } else {
565
+ await execAsync(`xdg-open "${url}"`);
566
+ }
567
+ } catch {
568
+ console.log(`
569
+ Open this URL in your browser:
570
+ ${url}
571
+ `);
572
+ }
573
+ });
574
+ setTimeout(() => {
575
+ server.close();
576
+ resolve(null);
577
+ }, 5 * 60 * 1e3);
578
+ });
579
+ }
580
+ async function refreshAccessToken(refreshToken, realm = "master") {
581
+ const tokenUrl = `https://login.farseer.io/auth/realms/${realm}/protocol/openid-connect/token`;
582
+ try {
583
+ const response = await fetch(tokenUrl, {
584
+ method: "POST",
585
+ headers: {
586
+ "Content-Type": "application/x-www-form-urlencoded"
587
+ },
588
+ body: new URLSearchParams({
589
+ grant_type: "refresh_token",
590
+ client_id: "auth",
591
+ refresh_token: refreshToken
592
+ })
593
+ });
594
+ if (!response.ok) {
595
+ return null;
596
+ }
597
+ const data = await response.json();
598
+ return {
599
+ accessToken: data.access_token,
600
+ refreshToken: data.refresh_token,
601
+ expiresIn: data.expires_in
602
+ };
603
+ } catch {
604
+ return null;
605
+ }
606
+ }
607
+ // Annotate the CommonJS export names for ESM import in node:
608
+ 0 && (module.exports = {
609
+ FarseerService,
610
+ loginWithBrowser,
611
+ loginWithPassword,
612
+ refreshAccessToken
613
+ });
614
+ //# sourceMappingURL=farseerService.js.map