estatehelm 1.0.1 → 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,6 +1314,21 @@ 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
+ var KRATOS_URL = process.env.ESTATEHELM_KRATOS_URL || "https://pauth.estatehelm.com";
1318
+ function setServerUrls(apiUrl, appUrl, kratosUrl) {
1319
+ if (apiUrl) {
1320
+ API_BASE_URL = apiUrl;
1321
+ console.error(`[Config] Using API: ${apiUrl}`);
1322
+ }
1323
+ if (appUrl) {
1324
+ APP_URL = appUrl;
1325
+ console.error(`[Config] Using App: ${appUrl}`);
1326
+ }
1327
+ if (kratosUrl) {
1328
+ KRATOS_URL = kratosUrl;
1329
+ console.error(`[Config] Using Kratos: ${kratosUrl}`);
1330
+ }
1331
+ }
1317
1332
  var DEFAULT_CONFIG = {
1318
1333
  defaultMode: "full"
1319
1334
  };
@@ -1439,21 +1454,25 @@ function prompt(question) {
1439
1454
  });
1440
1455
  });
1441
1456
  }
1442
- async function findAvailablePort(startPort = 19847) {
1443
- 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) => {
1444
1471
  const server = http.createServer();
1445
- server.listen(startPort, "127.0.0.1", () => {
1446
- const address = server.address();
1447
- const port = typeof address === "object" && address ? address.port : startPort;
1448
- server.close(() => resolve(port));
1449
- });
1450
- server.on("error", (err) => {
1451
- if (err.code === "EADDRINUSE") {
1452
- resolve(findAvailablePort(startPort + 1));
1453
- } else {
1454
- reject(err);
1455
- }
1472
+ server.listen(port, "127.0.0.1", () => {
1473
+ server.close(() => resolve(true));
1456
1474
  });
1475
+ server.on("error", () => resolve(false));
1457
1476
  });
1458
1477
  }
1459
1478
  function waitForCallback(port) {
@@ -1461,6 +1480,36 @@ function waitForCallback(port) {
1461
1480
  const server = http.createServer((req, res) => {
1462
1481
  const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
1463
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
+ }
1464
1513
  if (sessionToken) {
1465
1514
  res.writeHead(200, { "Content-Type": "text/html" });
1466
1515
  res.end(`
@@ -1486,17 +1535,8 @@ function waitForCallback(port) {
1486
1535
  server.close();
1487
1536
  resolve(sessionToken);
1488
1537
  } else {
1489
- res.writeHead(400, { "Content-Type": "text/html" });
1490
- res.end(`
1491
- <!DOCTYPE html>
1492
- <html>
1493
- <head><title>Authentication Failed</title></head>
1494
- <body>
1495
- <h1>Authentication Failed</h1>
1496
- <p>No session token received. Please try again.</p>
1497
- </body>
1498
- </html>
1499
- `);
1538
+ res.writeHead(404, { "Content-Type": "text/plain" });
1539
+ res.end("Not found");
1500
1540
  }
1501
1541
  });
1502
1542
  server.listen(port, "127.0.0.1", () => {
@@ -1578,19 +1618,70 @@ async function decryptDeviceCredentials(encryptedPayload) {
1578
1618
  );
1579
1619
  return new Uint8Array(plaintext);
1580
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
+ }
1581
1669
  async function login() {
1582
1670
  console.log("\nEstateHelm Login");
1583
1671
  console.log("================\n");
1584
1672
  console.log("Starting authentication server...");
1585
1673
  const port = await findAvailablePort();
1586
1674
  const callbackUrl = `http://127.0.0.1:${port}/callback`;
1587
- const loginUrl = `${APP_URL}/signin?redirect=/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");
1588
1679
  console.log(`
1589
- Opening browser for authentication...`);
1590
- 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}
1591
1682
  `);
1592
1683
  const callbackPromise = waitForCallback(port);
1593
- await (0, import_open.default)(loginUrl);
1684
+ await (0, import_open.default)(oauthUrl);
1594
1685
  console.log("Waiting for authentication...");
1595
1686
  const sessionToken = await callbackPromise;
1596
1687
  console.log("Authentication successful!");
@@ -2865,7 +2956,18 @@ function searchEntity(entity, query) {
2865
2956
 
2866
2957
  // src/index.ts
2867
2958
  var program = new import_commander.Command();
2868
- program.name("estatehelm").description("EstateHelm CLI - MCP server for AI assistants").version("1.0.0");
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) => {
2960
+ const opts = thisCommand.opts();
2961
+ if (opts.staging) {
2962
+ setServerUrls(
2963
+ "https://previewapi.estatehelm.com",
2964
+ "https://previewapp.estatehelm.com",
2965
+ "https://stauth.estatehelm.com"
2966
+ );
2967
+ } else if (opts.apiUrl || opts.appUrl) {
2968
+ setServerUrls(opts.apiUrl, opts.appUrl);
2969
+ }
2970
+ });
2869
2971
  program.command("login").description("Authenticate with EstateHelm").action(async () => {
2870
2972
  try {
2871
2973
  await login();