estatehelm 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1314,7 +1314,8 @@ var KEYTAR_ACCOUNTS = {
1314
1314
  };
1315
1315
  var API_BASE_URL = process.env.ESTATEHELM_API_URL || "https://api.estatehelm.com";
1316
1316
  var APP_URL = process.env.ESTATEHELM_APP_URL || "https://app.estatehelm.com";
1317
- function setServerUrls(apiUrl, appUrl) {
1317
+ var KRATOS_URL = process.env.ESTATEHELM_KRATOS_URL || "https://pauth.estatehelm.com";
1318
+ function setServerUrls(apiUrl, appUrl, kratosUrl) {
1318
1319
  if (apiUrl) {
1319
1320
  API_BASE_URL = apiUrl;
1320
1321
  console.error(`[Config] Using API: ${apiUrl}`);
@@ -1323,6 +1324,10 @@ function setServerUrls(apiUrl, appUrl) {
1323
1324
  APP_URL = appUrl;
1324
1325
  console.error(`[Config] Using App: ${appUrl}`);
1325
1326
  }
1327
+ if (kratosUrl) {
1328
+ KRATOS_URL = kratosUrl;
1329
+ console.error(`[Config] Using Kratos: ${kratosUrl}`);
1330
+ }
1326
1331
  }
1327
1332
  var DEFAULT_CONFIG = {
1328
1333
  defaultMode: "full"
@@ -1449,21 +1454,25 @@ function prompt(question) {
1449
1454
  });
1450
1455
  });
1451
1456
  }
1452
- async function findAvailablePort(startPort = 19847) {
1453
- return new Promise((resolve, reject) => {
1457
+ var ALLOWED_CALLBACK_PORTS = [11033, 11034, 11035];
1458
+ async function findAvailablePort() {
1459
+ for (const port of ALLOWED_CALLBACK_PORTS) {
1460
+ const available = await isPortAvailable(port);
1461
+ if (available) {
1462
+ return port;
1463
+ }
1464
+ }
1465
+ throw new Error(
1466
+ `No available ports for CLI callback. Ports ${ALLOWED_CALLBACK_PORTS.join(", ")} are all in use.`
1467
+ );
1468
+ }
1469
+ function isPortAvailable(port) {
1470
+ return new Promise((resolve) => {
1454
1471
  const server = http.createServer();
1455
- server.listen(startPort, "127.0.0.1", () => {
1456
- const address = server.address();
1457
- const port = typeof address === "object" && address ? address.port : startPort;
1458
- server.close(() => resolve(port));
1459
- });
1460
- server.on("error", (err) => {
1461
- if (err.code === "EADDRINUSE") {
1462
- resolve(findAvailablePort(startPort + 1));
1463
- } else {
1464
- reject(err);
1465
- }
1472
+ server.listen(port, "127.0.0.1", () => {
1473
+ server.close(() => resolve(true));
1466
1474
  });
1475
+ server.on("error", () => resolve(false));
1467
1476
  });
1468
1477
  }
1469
1478
  function waitForCallback(port) {
@@ -1471,6 +1480,36 @@ function waitForCallback(port) {
1471
1480
  const server = http.createServer((req, res) => {
1472
1481
  const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
1473
1482
  const sessionToken = url.searchParams.get("session_token");
1483
+ const error = url.searchParams.get("error");
1484
+ const errorDescription = url.searchParams.get("error_description");
1485
+ if (error) {
1486
+ res.writeHead(400, { "Content-Type": "text/html" });
1487
+ res.end(`
1488
+ <!DOCTYPE html>
1489
+ <html>
1490
+ <head>
1491
+ <title>Authentication Failed</title>
1492
+ <style>
1493
+ body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #fef2f2; }
1494
+ .card { background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 10px 25px rgba(0,0,0,0.1); text-align: center; max-width: 400px; }
1495
+ h1 { color: #dc2626; margin: 0 0 0.5rem; }
1496
+ p { color: #6b7280; margin: 0.5rem 0; }
1497
+ .error { font-family: monospace; background: #f3f4f6; padding: 0.5rem; border-radius: 0.25rem; font-size: 0.875rem; }
1498
+ </style>
1499
+ </head>
1500
+ <body>
1501
+ <div class="card">
1502
+ <h1>Authentication Failed</h1>
1503
+ <p>${errorDescription || error || "An error occurred during authentication."}</p>
1504
+ <p class="error">${error}</p>
1505
+ </div>
1506
+ </body>
1507
+ </html>
1508
+ `);
1509
+ server.close();
1510
+ reject(new Error(errorDescription || error || "Authentication failed"));
1511
+ return;
1512
+ }
1474
1513
  if (sessionToken) {
1475
1514
  res.writeHead(200, { "Content-Type": "text/html" });
1476
1515
  res.end(`
@@ -1496,17 +1535,8 @@ function waitForCallback(port) {
1496
1535
  server.close();
1497
1536
  resolve(sessionToken);
1498
1537
  } else {
1499
- res.writeHead(400, { "Content-Type": "text/html" });
1500
- res.end(`
1501
- <!DOCTYPE html>
1502
- <html>
1503
- <head><title>Authentication Failed</title></head>
1504
- <body>
1505
- <h1>Authentication Failed</h1>
1506
- <p>No session token received. Please try again.</p>
1507
- </body>
1508
- </html>
1509
- `);
1538
+ res.writeHead(404, { "Content-Type": "text/plain" });
1539
+ res.end("Not found");
1510
1540
  }
1511
1541
  });
1512
1542
  server.listen(port, "127.0.0.1", () => {
@@ -1588,19 +1618,70 @@ async function decryptDeviceCredentials(encryptedPayload) {
1588
1618
  );
1589
1619
  return new Uint8Array(plaintext);
1590
1620
  }
1621
+ async function createNativeLoginFlow(returnTo) {
1622
+ const url = `${KRATOS_URL}/self-service/login/api?return_to=${encodeURIComponent(returnTo)}`;
1623
+ const response = await fetch(url, {
1624
+ method: "GET",
1625
+ headers: { Accept: "application/json" }
1626
+ });
1627
+ if (!response.ok) {
1628
+ const error = await response.text();
1629
+ throw new Error(`Failed to create login flow: ${response.status} - ${error}`);
1630
+ }
1631
+ const flow = await response.json();
1632
+ return {
1633
+ flowId: flow.id,
1634
+ flowUrl: flow.request_url || `${KRATOS_URL}/self-service/login?flow=${flow.id}`
1635
+ };
1636
+ }
1637
+ async function initiateOidcFlow(flowId, provider = "google") {
1638
+ const url = `${KRATOS_URL}/self-service/login?flow=${flowId}`;
1639
+ const response = await fetch(url, {
1640
+ method: "POST",
1641
+ headers: {
1642
+ "Content-Type": "application/json",
1643
+ Accept: "application/json"
1644
+ },
1645
+ body: JSON.stringify({
1646
+ method: "oidc",
1647
+ provider
1648
+ }),
1649
+ redirect: "manual"
1650
+ // Don't follow redirects
1651
+ });
1652
+ if (response.status === 422) {
1653
+ const data = await response.json();
1654
+ const redirectUrl = data.redirect_browser_to;
1655
+ if (redirectUrl) {
1656
+ return redirectUrl;
1657
+ }
1658
+ throw new Error("No redirect URL in OIDC response");
1659
+ }
1660
+ if (response.ok) {
1661
+ const data = await response.json();
1662
+ if (data.session_token) {
1663
+ return data.session_token;
1664
+ }
1665
+ }
1666
+ const error = await response.text();
1667
+ throw new Error(`Failed to initiate OIDC flow: ${response.status} - ${error}`);
1668
+ }
1591
1669
  async function login() {
1592
1670
  console.log("\nEstateHelm Login");
1593
1671
  console.log("================\n");
1594
1672
  console.log("Starting authentication server...");
1595
1673
  const port = await findAvailablePort();
1596
1674
  const callbackUrl = `http://127.0.0.1:${port}/callback`;
1597
- const loginUrl = `${APP_URL}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
1675
+ console.log("Creating login flow...");
1676
+ const { flowId } = await createNativeLoginFlow(callbackUrl);
1677
+ console.log("Initiating OAuth flow...");
1678
+ const oauthUrl = await initiateOidcFlow(flowId, "google");
1598
1679
  console.log(`
1599
- Opening browser for authentication...`);
1600
- console.log(`If the browser doesn't open, visit: ${loginUrl}
1680
+ Opening browser for Google authentication...`);
1681
+ console.log(`If the browser doesn't open, visit: ${oauthUrl}
1601
1682
  `);
1602
1683
  const callbackPromise = waitForCallback(port);
1603
- await (0, import_open.default)(loginUrl);
1684
+ await (0, import_open.default)(oauthUrl);
1604
1685
  console.log("Waiting for authentication...");
1605
1686
  const sessionToken = await callbackPromise;
1606
1687
  console.log("Authentication successful!");
@@ -2878,7 +2959,11 @@ var program = new import_commander.Command();
2878
2959
  program.name("estatehelm").description("EstateHelm CLI - MCP server for AI assistants").version("1.0.0").option("--staging", "Use staging environment (previewapi/previewapp.estatehelm.com)").option("--api-url <url>", "API server URL (default: https://api.estatehelm.com)").option("--app-url <url>", "App server URL (default: https://app.estatehelm.com)").hook("preAction", (thisCommand) => {
2879
2960
  const opts = thisCommand.opts();
2880
2961
  if (opts.staging) {
2881
- setServerUrls("https://previewapi.estatehelm.com", "https://previewapp.estatehelm.com");
2962
+ setServerUrls(
2963
+ "https://previewapi.estatehelm.com",
2964
+ "https://previewapp.estatehelm.com",
2965
+ "https://stauth.estatehelm.com"
2966
+ );
2882
2967
  } else if (opts.apiUrl || opts.appUrl) {
2883
2968
  setServerUrls(opts.apiUrl, opts.appUrl);
2884
2969
  }