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 +115 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
1453
|
-
|
|
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(
|
|
1456
|
-
|
|
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(
|
|
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
|
-
|
|
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: ${
|
|
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)(
|
|
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(
|
|
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
|
}
|