@granular-software/sdk 0.3.3 → 0.4.1
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/README.md +20 -0
- package/dist/adapters/anthropic.d.mts +1 -1
- package/dist/adapters/anthropic.d.ts +1 -1
- package/dist/adapters/langchain.d.mts +1 -1
- package/dist/adapters/langchain.d.ts +1 -1
- package/dist/adapters/mastra.d.mts +1 -1
- package/dist/adapters/mastra.d.ts +1 -1
- package/dist/adapters/openai.d.mts +1 -1
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/cli/index.js +306 -18
- package/dist/index.d.mts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +325 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +325 -21
- package/dist/index.mjs.map +1 -1
- package/dist/{types-CnX4jXYQ.d.mts → types-BOPsFZYi.d.mts} +34 -1
- package/dist/{types-CnX4jXYQ.d.ts → types-BOPsFZYi.d.ts} +34 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,26 @@ bun add @granular-software/sdk
|
|
|
27
27
|
npm install @granular-software/sdk
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
## Endpoint Modes
|
|
31
|
+
|
|
32
|
+
By default, the SDK resolves endpoints like this:
|
|
33
|
+
|
|
34
|
+
- Local mode (`NODE_ENV=development`): `ws://localhost:8787/granular`
|
|
35
|
+
- Production mode (default): `wss://api.granular.dev/v2/ws`
|
|
36
|
+
|
|
37
|
+
Overrides:
|
|
38
|
+
|
|
39
|
+
- SDK option: `endpointMode: 'local' | 'production'`
|
|
40
|
+
- SDK option: `apiUrl: 'ws://... | wss://...'` (highest priority)
|
|
41
|
+
- Env: `GRANULAR_ENDPOINT_MODE=local|production`
|
|
42
|
+
- Env: `GRANULAR_API_URL=...` (highest priority)
|
|
43
|
+
|
|
44
|
+
CLI overrides:
|
|
45
|
+
|
|
46
|
+
- `granular --local <command>`
|
|
47
|
+
- `granular --prod <command>`
|
|
48
|
+
- `granular --env local|production <command>`
|
|
49
|
+
|
|
30
50
|
## Quick Start
|
|
31
51
|
|
|
32
52
|
```typescript
|
package/dist/cli/index.js
CHANGED
|
@@ -5384,6 +5384,57 @@ var {
|
|
|
5384
5384
|
|
|
5385
5385
|
// src/cli/config.ts
|
|
5386
5386
|
var import_dotenv = __toESM(require_main());
|
|
5387
|
+
|
|
5388
|
+
// src/endpoints.ts
|
|
5389
|
+
var LOCAL_API_URL = "ws://localhost:8787/granular";
|
|
5390
|
+
var PRODUCTION_API_URL = "wss://api.granular.dev/v2/ws";
|
|
5391
|
+
var LOCAL_AUTH_URL = "http://localhost:3000";
|
|
5392
|
+
var PRODUCTION_AUTH_URL = "https://app.granular.software";
|
|
5393
|
+
function readEnv(name) {
|
|
5394
|
+
if (typeof process === "undefined" || !process.env) return void 0;
|
|
5395
|
+
return process.env[name];
|
|
5396
|
+
}
|
|
5397
|
+
function normalizeMode(value) {
|
|
5398
|
+
if (!value) return void 0;
|
|
5399
|
+
const normalized = value.trim().toLowerCase();
|
|
5400
|
+
if (normalized === "local") return "local";
|
|
5401
|
+
if (normalized === "prod" || normalized === "production") return "production";
|
|
5402
|
+
if (normalized === "auto") return "auto";
|
|
5403
|
+
return void 0;
|
|
5404
|
+
}
|
|
5405
|
+
function isTruthy(value) {
|
|
5406
|
+
if (!value) return false;
|
|
5407
|
+
const normalized = value.trim().toLowerCase();
|
|
5408
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
5409
|
+
}
|
|
5410
|
+
function resolveEndpointMode(explicitMode) {
|
|
5411
|
+
const explicit = normalizeMode(explicitMode);
|
|
5412
|
+
if (explicit === "local" || explicit === "production") {
|
|
5413
|
+
return explicit;
|
|
5414
|
+
}
|
|
5415
|
+
const envMode = normalizeMode(readEnv("GRANULAR_ENDPOINT_MODE") || readEnv("GRANULAR_ENV"));
|
|
5416
|
+
if (envMode === "local" || envMode === "production") {
|
|
5417
|
+
return envMode;
|
|
5418
|
+
}
|
|
5419
|
+
if (isTruthy(readEnv("GRANULAR_USE_LOCAL_ENDPOINTS")) || isTruthy(readEnv("GRANULAR_LOCAL"))) {
|
|
5420
|
+
return "local";
|
|
5421
|
+
}
|
|
5422
|
+
if (isTruthy(readEnv("GRANULAR_USE_PRODUCTION_ENDPOINTS")) || isTruthy(readEnv("GRANULAR_PROD"))) {
|
|
5423
|
+
return "production";
|
|
5424
|
+
}
|
|
5425
|
+
return readEnv("NODE_ENV") === "development" ? "local" : "production";
|
|
5426
|
+
}
|
|
5427
|
+
function resolveApiUrl(explicitApiUrl, mode) {
|
|
5428
|
+
return resolveEndpointMode(mode) === "local" ? LOCAL_API_URL : PRODUCTION_API_URL;
|
|
5429
|
+
}
|
|
5430
|
+
function resolveAuthUrl(explicitAuthUrl, mode) {
|
|
5431
|
+
if (explicitAuthUrl) {
|
|
5432
|
+
return explicitAuthUrl;
|
|
5433
|
+
}
|
|
5434
|
+
return resolveEndpointMode(mode) === "local" ? LOCAL_AUTH_URL : PRODUCTION_AUTH_URL;
|
|
5435
|
+
}
|
|
5436
|
+
|
|
5437
|
+
// src/cli/config.ts
|
|
5387
5438
|
var MANIFEST_FILE = "granular.json";
|
|
5388
5439
|
var RC_FILE = ".granularrc";
|
|
5389
5440
|
var ENV_LOCAL_FILE = ".env.local";
|
|
@@ -5453,16 +5504,17 @@ function loadApiKey() {
|
|
|
5453
5504
|
return void 0;
|
|
5454
5505
|
}
|
|
5455
5506
|
function loadApiUrl() {
|
|
5507
|
+
if (process.env.GRANULAR_API_URL) return process.env.GRANULAR_API_URL;
|
|
5508
|
+
const modeOverride = process.env.GRANULAR_ENDPOINT_MODE;
|
|
5509
|
+
if (modeOverride === "local" || modeOverride === "production" || modeOverride === "prod") {
|
|
5510
|
+
return resolveApiUrl(void 0, modeOverride === "prod" ? "production" : modeOverride);
|
|
5511
|
+
}
|
|
5456
5512
|
const rc = readRcFile();
|
|
5457
5513
|
if (rc.apiUrl) return rc.apiUrl;
|
|
5458
|
-
|
|
5459
|
-
return "https://cf-api-gateway.arthur6084.workers.dev/granular";
|
|
5514
|
+
return resolveApiUrl();
|
|
5460
5515
|
}
|
|
5461
5516
|
function loadAuthUrl() {
|
|
5462
|
-
|
|
5463
|
-
return process.env.GRANULAR_AUTH_URL;
|
|
5464
|
-
}
|
|
5465
|
-
return "https://app.granular.software";
|
|
5517
|
+
return resolveAuthUrl(process.env.GRANULAR_AUTH_URL);
|
|
5466
5518
|
}
|
|
5467
5519
|
function saveApiKey(apiKey) {
|
|
5468
5520
|
const envLocalPath = getEnvLocalPath();
|
|
@@ -5741,23 +5793,203 @@ function normalizeAuthBaseUrl(input) {
|
|
|
5741
5793
|
}
|
|
5742
5794
|
return url;
|
|
5743
5795
|
}
|
|
5744
|
-
function
|
|
5796
|
+
function escapeHtml(input) {
|
|
5797
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5798
|
+
}
|
|
5799
|
+
function generateSquirclePath(size = 128, radius = 56, exp = 4, steps = 72) {
|
|
5800
|
+
const center = size / 2;
|
|
5801
|
+
const points = [];
|
|
5802
|
+
for (let i = 0; i <= steps; i++) {
|
|
5803
|
+
const angle = i / steps * 2 * Math.PI;
|
|
5804
|
+
const cosA = Math.cos(angle);
|
|
5805
|
+
const sinA = Math.sin(angle);
|
|
5806
|
+
const r = 1 / Math.pow(
|
|
5807
|
+
Math.pow(Math.abs(cosA), exp) + Math.pow(Math.abs(sinA), exp),
|
|
5808
|
+
1 / exp
|
|
5809
|
+
);
|
|
5810
|
+
const x = center + r * cosA * radius;
|
|
5811
|
+
const y = center + r * sinA * radius;
|
|
5812
|
+
points.push(`${x.toFixed(3)} ${y.toFixed(3)}`);
|
|
5813
|
+
}
|
|
5814
|
+
return `M ${points.join(" L ")} Z`;
|
|
5815
|
+
}
|
|
5816
|
+
function generateHilbertPath(order = 4, size = 128, radius = 51) {
|
|
5817
|
+
const n = 1 << order;
|
|
5818
|
+
const center = size / 2;
|
|
5819
|
+
const squircleExp = 4;
|
|
5820
|
+
const points = [];
|
|
5821
|
+
function squareToSquircle(u, v) {
|
|
5822
|
+
if (Math.abs(u) < 1e-3 && Math.abs(v) < 1e-3) return [u, v];
|
|
5823
|
+
const angle = Math.atan2(v, u);
|
|
5824
|
+
const cosA = Math.cos(angle);
|
|
5825
|
+
const sinA = Math.sin(angle);
|
|
5826
|
+
const squircleR = 1 / Math.pow(
|
|
5827
|
+
Math.pow(Math.abs(cosA), squircleExp) + Math.pow(Math.abs(sinA), squircleExp),
|
|
5828
|
+
1 / squircleExp
|
|
5829
|
+
);
|
|
5830
|
+
const squareDist = Math.max(Math.abs(u), Math.abs(v));
|
|
5831
|
+
const newDist = squareDist * squircleR;
|
|
5832
|
+
return [newDist * cosA, newDist * sinA];
|
|
5833
|
+
}
|
|
5834
|
+
function d2xy(d) {
|
|
5835
|
+
let x = 0;
|
|
5836
|
+
let y = 0;
|
|
5837
|
+
let t = d;
|
|
5838
|
+
for (let s = 1; s < n; s *= 2) {
|
|
5839
|
+
const rx = 1 & Math.floor(t) / 2;
|
|
5840
|
+
const ry = 1 & (Math.floor(t) ^ rx);
|
|
5841
|
+
if (ry === 0) {
|
|
5842
|
+
if (rx === 1) {
|
|
5843
|
+
x = s - 1 - x;
|
|
5844
|
+
y = s - 1 - y;
|
|
5845
|
+
}
|
|
5846
|
+
[x, y] = [y, x];
|
|
5847
|
+
}
|
|
5848
|
+
x += s * rx;
|
|
5849
|
+
y += s * ry;
|
|
5850
|
+
t = Math.floor(t) / 4;
|
|
5851
|
+
}
|
|
5852
|
+
return [x, y];
|
|
5853
|
+
}
|
|
5854
|
+
for (let i = 0; i < n * n; i++) {
|
|
5855
|
+
const [x, y] = d2xy(i);
|
|
5856
|
+
const normX = x / (n - 1) * 2 - 1;
|
|
5857
|
+
const normY = y / (n - 1) * 2 - 1;
|
|
5858
|
+
const [sqX, sqY] = squareToSquircle(normX, normY);
|
|
5859
|
+
points.push([center + sqX * radius, center + sqY * radius]);
|
|
5860
|
+
}
|
|
5861
|
+
return points.map(([x, y], i) => `${i === 0 ? "M" : "L"} ${x.toFixed(3)} ${y.toFixed(3)}`).join(" ");
|
|
5862
|
+
}
|
|
5863
|
+
function renderLogoSvg() {
|
|
5864
|
+
const squirclePath = generateSquirclePath();
|
|
5865
|
+
const hilbertPath = generateHilbertPath();
|
|
5866
|
+
return `<svg class="logo" viewBox="0 0 128 128" role="img" aria-label="Granular logo" xmlns="http://www.w3.org/2000/svg">
|
|
5867
|
+
<defs>
|
|
5868
|
+
<linearGradient id="granular-logo-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5869
|
+
<stop offset="0%" stop-color="#ff8a54" />
|
|
5870
|
+
<stop offset="55%" stop-color="#ff6b35" />
|
|
5871
|
+
<stop offset="100%" stop-color="#e34b1f" />
|
|
5872
|
+
</linearGradient>
|
|
5873
|
+
<filter id="granular-logo-glow" x="-30%" y="-30%" width="160%" height="160%">
|
|
5874
|
+
<feGaussianBlur stdDeviation="1.6" result="blur" />
|
|
5875
|
+
<feMerge>
|
|
5876
|
+
<feMergeNode in="blur" />
|
|
5877
|
+
<feMergeNode in="SourceGraphic" />
|
|
5878
|
+
</feMerge>
|
|
5879
|
+
</filter>
|
|
5880
|
+
</defs>
|
|
5881
|
+
<path d="${squirclePath}" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.25)" stroke-width="1.2" />
|
|
5882
|
+
<path d="${hilbertPath}" fill="none" stroke="url(#granular-logo-gradient)" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round" filter="url(#granular-logo-glow)" />
|
|
5883
|
+
</svg>`;
|
|
5884
|
+
}
|
|
5885
|
+
function renderHtmlPage(title, message, variant) {
|
|
5886
|
+
const safeTitle = escapeHtml(title);
|
|
5887
|
+
const safeMessage = escapeHtml(message);
|
|
5888
|
+
const accent = variant === "success" ? "#ff6b35" : "#ff5c6b";
|
|
5889
|
+
const badge = variant === "success" ? "Success" : "Auth Error";
|
|
5745
5890
|
return `<!doctype html>
|
|
5746
5891
|
<html lang="en">
|
|
5747
5892
|
<head>
|
|
5748
5893
|
<meta charset="utf-8" />
|
|
5749
5894
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5750
|
-
<title>${
|
|
5895
|
+
<title>${safeTitle}</title>
|
|
5751
5896
|
<style>
|
|
5752
|
-
:root { color-scheme:
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5897
|
+
:root { color-scheme: dark; }
|
|
5898
|
+
* { box-sizing: border-box; }
|
|
5899
|
+
body {
|
|
5900
|
+
margin: 0;
|
|
5901
|
+
min-height: 100vh;
|
|
5902
|
+
display: grid;
|
|
5903
|
+
place-items: center;
|
|
5904
|
+
padding: 28px;
|
|
5905
|
+
font-family: "Avenir Next", "SF Pro Display", "Segoe UI", sans-serif;
|
|
5906
|
+
color: #f8f8f8;
|
|
5907
|
+
background:
|
|
5908
|
+
radial-gradient(1000px 480px at 12% -10%, rgba(255, 110, 58, 0.26), transparent 60%),
|
|
5909
|
+
radial-gradient(760px 480px at 100% 120%, rgba(99, 102, 241, 0.22), transparent 65%),
|
|
5910
|
+
linear-gradient(140deg, #0f0f12 0%, #131319 45%, #11131a 100%);
|
|
5911
|
+
}
|
|
5912
|
+
.panel {
|
|
5913
|
+
width: min(520px, 100%);
|
|
5914
|
+
border-radius: 24px;
|
|
5915
|
+
border: 1px solid rgba(255, 255, 255, 0.16);
|
|
5916
|
+
background: linear-gradient(160deg, rgba(255,255,255,0.08), rgba(255,255,255,0.03));
|
|
5917
|
+
backdrop-filter: blur(8px);
|
|
5918
|
+
box-shadow:
|
|
5919
|
+
0 18px 60px rgba(0, 0, 0, 0.45),
|
|
5920
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.18);
|
|
5921
|
+
padding: 26px 26px 24px;
|
|
5922
|
+
}
|
|
5923
|
+
.head {
|
|
5924
|
+
display: flex;
|
|
5925
|
+
align-items: center;
|
|
5926
|
+
gap: 14px;
|
|
5927
|
+
margin-bottom: 14px;
|
|
5928
|
+
}
|
|
5929
|
+
.logo-wrap {
|
|
5930
|
+
width: 62px;
|
|
5931
|
+
height: 62px;
|
|
5932
|
+
border-radius: 18px;
|
|
5933
|
+
border: 1px solid rgba(255,255,255,0.2);
|
|
5934
|
+
background: radial-gradient(circle at 25% 20%, rgba(255,255,255,0.17), rgba(255,255,255,0.02));
|
|
5935
|
+
display: grid;
|
|
5936
|
+
place-items: center;
|
|
5937
|
+
flex: 0 0 auto;
|
|
5938
|
+
}
|
|
5939
|
+
.logo {
|
|
5940
|
+
width: 46px;
|
|
5941
|
+
height: 46px;
|
|
5942
|
+
display: block;
|
|
5943
|
+
}
|
|
5944
|
+
.badge {
|
|
5945
|
+
display: inline-flex;
|
|
5946
|
+
align-items: center;
|
|
5947
|
+
border-radius: 999px;
|
|
5948
|
+
padding: 5px 11px;
|
|
5949
|
+
border: 1px solid ${accent}66;
|
|
5950
|
+
background: ${accent}26;
|
|
5951
|
+
color: ${accent};
|
|
5952
|
+
font-size: 12px;
|
|
5953
|
+
font-weight: 600;
|
|
5954
|
+
letter-spacing: 0.04em;
|
|
5955
|
+
text-transform: uppercase;
|
|
5956
|
+
margin-bottom: 8px;
|
|
5957
|
+
}
|
|
5958
|
+
h1 {
|
|
5959
|
+
margin: 0;
|
|
5960
|
+
font-size: 25px;
|
|
5961
|
+
line-height: 1.2;
|
|
5962
|
+
letter-spacing: 0.01em;
|
|
5963
|
+
color: #ffffff;
|
|
5964
|
+
}
|
|
5965
|
+
p {
|
|
5966
|
+
margin: 0;
|
|
5967
|
+
color: #d7d8df;
|
|
5968
|
+
font-size: 15px;
|
|
5969
|
+
line-height: 1.6;
|
|
5970
|
+
}
|
|
5971
|
+
.footer {
|
|
5972
|
+
margin-top: 18px;
|
|
5973
|
+
padding-top: 14px;
|
|
5974
|
+
border-top: 1px solid rgba(255, 255, 255, 0.11);
|
|
5975
|
+
font-size: 12px;
|
|
5976
|
+
color: #9ea3b2;
|
|
5977
|
+
letter-spacing: 0.02em;
|
|
5978
|
+
}
|
|
5756
5979
|
</style>
|
|
5757
5980
|
</head>
|
|
5758
5981
|
<body>
|
|
5759
|
-
<
|
|
5760
|
-
|
|
5982
|
+
<main class="panel">
|
|
5983
|
+
<div class="head">
|
|
5984
|
+
<div class="logo-wrap">${renderLogoSvg()}</div>
|
|
5985
|
+
<div>
|
|
5986
|
+
<div class="badge">${badge}</div>
|
|
5987
|
+
<h1>${safeTitle}</h1>
|
|
5988
|
+
</div>
|
|
5989
|
+
</div>
|
|
5990
|
+
<p>${safeMessage}</p>
|
|
5991
|
+
<div class="footer">Granular CLI authentication flow completed.</div>
|
|
5992
|
+
</main>
|
|
5761
5993
|
</body>
|
|
5762
5994
|
</html>`;
|
|
5763
5995
|
}
|
|
@@ -5841,7 +6073,7 @@ async function loginWithBrowser(options) {
|
|
|
5841
6073
|
if (!returnedState || returnedState !== state) {
|
|
5842
6074
|
res.statusCode = 400;
|
|
5843
6075
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
5844
|
-
res.end(renderHtmlPage("Granular login failed", "State mismatch. Please close this window and retry."));
|
|
6076
|
+
res.end(renderHtmlPage("Granular login failed", "State mismatch. Please close this window and retry.", "error"));
|
|
5845
6077
|
if (!settled) {
|
|
5846
6078
|
settled = true;
|
|
5847
6079
|
clearTimeout(timeout);
|
|
@@ -5852,7 +6084,13 @@ async function loginWithBrowser(options) {
|
|
|
5852
6084
|
if (error2) {
|
|
5853
6085
|
res.statusCode = 400;
|
|
5854
6086
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
5855
|
-
res.end(
|
|
6087
|
+
res.end(
|
|
6088
|
+
renderHtmlPage(
|
|
6089
|
+
"Granular login failed",
|
|
6090
|
+
`${describeAuthError(error2)} You can close this window and return to the CLI.`,
|
|
6091
|
+
"error"
|
|
6092
|
+
)
|
|
6093
|
+
);
|
|
5856
6094
|
if (!settled) {
|
|
5857
6095
|
settled = true;
|
|
5858
6096
|
clearTimeout(timeout);
|
|
@@ -5863,7 +6101,7 @@ async function loginWithBrowser(options) {
|
|
|
5863
6101
|
if (!apiKey2) {
|
|
5864
6102
|
res.statusCode = 400;
|
|
5865
6103
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
5866
|
-
res.end(renderHtmlPage("Granular login failed", "No API key was returned. Please retry."));
|
|
6104
|
+
res.end(renderHtmlPage("Granular login failed", "No API key was returned. Please retry.", "error"));
|
|
5867
6105
|
if (!settled) {
|
|
5868
6106
|
settled = true;
|
|
5869
6107
|
clearTimeout(timeout);
|
|
@@ -5873,7 +6111,7 @@ async function loginWithBrowser(options) {
|
|
|
5873
6111
|
}
|
|
5874
6112
|
res.statusCode = 200;
|
|
5875
6113
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
5876
|
-
res.end(renderHtmlPage("Granular login complete", "Authentication succeeded. You can close this window."));
|
|
6114
|
+
res.end(renderHtmlPage("Granular login complete", "Authentication succeeded. You can close this window.", "success"));
|
|
5877
6115
|
if (!settled) {
|
|
5878
6116
|
settled = true;
|
|
5879
6117
|
clearTimeout(timeout);
|
|
@@ -7475,6 +7713,18 @@ function prompt(question, defaultValue) {
|
|
|
7475
7713
|
});
|
|
7476
7714
|
});
|
|
7477
7715
|
}
|
|
7716
|
+
function waitForEnter(message) {
|
|
7717
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
7718
|
+
return Promise.resolve();
|
|
7719
|
+
}
|
|
7720
|
+
const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
|
|
7721
|
+
return new Promise((resolve) => {
|
|
7722
|
+
rl.question(` ${message}`, () => {
|
|
7723
|
+
rl.close();
|
|
7724
|
+
resolve();
|
|
7725
|
+
});
|
|
7726
|
+
});
|
|
7727
|
+
}
|
|
7478
7728
|
function confirm(question, defaultYes = true) {
|
|
7479
7729
|
const hint2 = defaultYes ? "Y/n" : "y/N";
|
|
7480
7730
|
return prompt(`${question} [${hint2}]`).then((answer) => {
|
|
@@ -7495,6 +7745,8 @@ async function initCommand(projectName, options) {
|
|
|
7495
7745
|
let apiKey = loadApiKey();
|
|
7496
7746
|
if (!apiKey) {
|
|
7497
7747
|
info("No API key found. Starting browser login...");
|
|
7748
|
+
await waitForEnter("Press Enter to open the login page in your browser...");
|
|
7749
|
+
console.log();
|
|
7498
7750
|
const waiting = spinner("Opening browser and waiting for authentication...");
|
|
7499
7751
|
try {
|
|
7500
7752
|
apiKey = await loginWithBrowser({
|
|
@@ -7615,6 +7867,18 @@ function promptSecret(question) {
|
|
|
7615
7867
|
});
|
|
7616
7868
|
});
|
|
7617
7869
|
}
|
|
7870
|
+
function waitForEnter2(message) {
|
|
7871
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
7872
|
+
return Promise.resolve();
|
|
7873
|
+
}
|
|
7874
|
+
const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
|
|
7875
|
+
return new Promise((resolve) => {
|
|
7876
|
+
rl.question(` ${message}`, () => {
|
|
7877
|
+
rl.close();
|
|
7878
|
+
resolve();
|
|
7879
|
+
});
|
|
7880
|
+
});
|
|
7881
|
+
}
|
|
7618
7882
|
function maskApiKey(apiKey) {
|
|
7619
7883
|
if (apiKey.length < 10) return `${apiKey}...`;
|
|
7620
7884
|
return `${apiKey.substring(0, 10)}...`;
|
|
@@ -7636,6 +7900,8 @@ async function loginCommand(options = {}) {
|
|
|
7636
7900
|
apiKey = await promptSecret("Enter your API key");
|
|
7637
7901
|
} else {
|
|
7638
7902
|
const timeoutMs = options.timeout && options.timeout > 0 ? options.timeout * 1e3 : void 0;
|
|
7903
|
+
await waitForEnter2("Press Enter to open the login page in your browser...");
|
|
7904
|
+
console.log();
|
|
7639
7905
|
const waiting = spinner("Opening browser and waiting for authentication...");
|
|
7640
7906
|
try {
|
|
7641
7907
|
apiKey = await loginWithBrowser({
|
|
@@ -8409,6 +8675,28 @@ async function simulateCommand(sandboxIdArg) {
|
|
|
8409
8675
|
var VERSION = "0.2.0";
|
|
8410
8676
|
var program2 = new Command();
|
|
8411
8677
|
program2.name("granular").description("Build and deploy AI sandboxes from code").version(VERSION, "-v, --version");
|
|
8678
|
+
program2.option("--local", "Use local endpoints (localhost)").option("--prod", "Use production endpoints").option("--env <target>", "Endpoint target: local|production");
|
|
8679
|
+
program2.hook("preAction", () => {
|
|
8680
|
+
const opts = program2.opts();
|
|
8681
|
+
const normalizedEnv = opts.env?.trim().toLowerCase();
|
|
8682
|
+
if (opts.local && opts.prod) {
|
|
8683
|
+
error("Cannot use --local and --prod at the same time.");
|
|
8684
|
+
process.exit(1);
|
|
8685
|
+
}
|
|
8686
|
+
if (normalizedEnv && !["local", "production", "prod"].includes(normalizedEnv)) {
|
|
8687
|
+
error('Invalid --env value. Use "local" or "production".');
|
|
8688
|
+
process.exit(1);
|
|
8689
|
+
}
|
|
8690
|
+
let mode;
|
|
8691
|
+
if (opts.local || normalizedEnv === "local") {
|
|
8692
|
+
mode = "local";
|
|
8693
|
+
} else if (opts.prod || normalizedEnv === "production" || normalizedEnv === "prod") {
|
|
8694
|
+
mode = "production";
|
|
8695
|
+
}
|
|
8696
|
+
if (mode) {
|
|
8697
|
+
process.env.GRANULAR_ENDPOINT_MODE = mode;
|
|
8698
|
+
}
|
|
8699
|
+
});
|
|
8412
8700
|
program2.command("init [project-name]").description("Initialize a new Granular project").option("--skip-build", "Skip the initial build step").action(async (projectName, opts) => {
|
|
8413
8701
|
try {
|
|
8414
8702
|
await initCommand(projectName, { skipBuild: opts.skipBuild });
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Automerge from '@automerge/automerge';
|
|
2
|
-
import { W as WSClientOptions, T as ToolWithHandler, P as PublishToolsResult, J as Job, a as ToolHandler, I as InstanceToolHandler, b as ToolInfo, c as ToolsChangedEvent, D as DomainState, G as GranularOptions, R as RecordUserOptions, U as User, C as ConnectOptions, E as EnvironmentData, d as GraphQLResult, e as DefineRelationshipOptions, f as RelationshipInfo, M as ModelRef, g as ManifestContent, h as RecordObjectOptions, i as RecordObjectResult, S as SandboxListResponse, j as Sandbox, k as CreateSandboxData, l as DeleteResponse, m as PermissionProfile, n as CreatePermissionProfileData, o as CreateEnvironmentData, p as Subject, A as AssignmentListResponse } from './types-
|
|
3
|
-
export {
|
|
2
|
+
import { W as WSClientOptions, T as ToolWithHandler, P as PublishToolsResult, J as Job, a as ToolHandler, I as InstanceToolHandler, b as ToolInfo, c as ToolsChangedEvent, D as DomainState, G as GranularOptions, R as RecordUserOptions, U as User, C as ConnectOptions, E as EnvironmentData, d as GraphQLResult, e as DefineRelationshipOptions, f as RelationshipInfo, M as ModelRef, g as ManifestContent, h as RecordObjectOptions, i as RecordObjectResult, S as SandboxListResponse, j as Sandbox, k as CreateSandboxData, l as DeleteResponse, m as PermissionProfile, n as CreatePermissionProfileData, o as CreateEnvironmentData, p as Subject, A as AssignmentListResponse } from './types-BOPsFZYi.mjs';
|
|
3
|
+
export { a6 as APIError, r as AccessTokenProvider, w as Assignment, H as Build, K as BuildListResponse, B as BuildPolicy, F as BuildStatus, s as EndpointMode, x as EnvironmentListResponse, t as GranularAuth, L as JobStatus, N as JobSubmitResult, y as Manifest, a4 as ManifestImport, z as ManifestListResponse, a3 as ManifestOperation, a1 as ManifestPropertySpec, a2 as ManifestRelationshipDef, a5 as ManifestVolume, v as PermissionProfileListResponse, u as PermissionRules, O as Prompt, X as RPCRequest, _ as RPCRequestFromServer, Y as RPCResponse, Z as SyncMessage, $ as ToolInvokeParams, a0 as ToolResultParams, q as ToolSchema, Q as WSDisconnectInfo, V as WSReconnectErrorInfo } from './types-BOPsFZYi.mjs';
|
|
4
4
|
import { Doc } from '@automerge/automerge/slim';
|
|
5
5
|
|
|
6
6
|
declare class WSClient {
|
|
@@ -16,14 +16,24 @@ declare class WSClient {
|
|
|
16
16
|
doc: Automerge.Doc<Record<string, unknown>>;
|
|
17
17
|
private syncState;
|
|
18
18
|
private reconnectTimer;
|
|
19
|
+
private tokenRefreshTimer;
|
|
19
20
|
private isExplicitlyDisconnected;
|
|
20
21
|
private options;
|
|
21
22
|
constructor(options: WSClientOptions);
|
|
23
|
+
private clearTokenRefreshTimer;
|
|
24
|
+
private decodeBase64Url;
|
|
25
|
+
private getTokenExpiryMs;
|
|
26
|
+
private scheduleTokenRefresh;
|
|
27
|
+
private refreshTokenInBackground;
|
|
28
|
+
private resolveTokenForConnect;
|
|
22
29
|
/**
|
|
23
30
|
* Connect to the WebSocket server
|
|
24
31
|
* @returns {Promise<void>} Resolves when connection is open
|
|
25
32
|
*/
|
|
26
33
|
connect(): Promise<void>;
|
|
34
|
+
private normalizeReason;
|
|
35
|
+
private rejectPending;
|
|
36
|
+
private buildDisconnectError;
|
|
27
37
|
private handleDisconnect;
|
|
28
38
|
private handleMessage;
|
|
29
39
|
/**
|
|
@@ -322,6 +332,15 @@ declare class Environment extends Session {
|
|
|
322
332
|
get permissionProfileId(): string;
|
|
323
333
|
/** The GraphQL API endpoint URL */
|
|
324
334
|
get apiEndpoint(): string;
|
|
335
|
+
private getRuntimeBaseUrl;
|
|
336
|
+
/**
|
|
337
|
+
* Close the session and disconnect from the sandbox.
|
|
338
|
+
*
|
|
339
|
+
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
340
|
+
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
341
|
+
* acknowledgement was observed.
|
|
342
|
+
*/
|
|
343
|
+
disconnect(): Promise<void>;
|
|
325
344
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
326
345
|
graphContainerStatus: {
|
|
327
346
|
lastKeepAliveAt: number;
|
|
@@ -637,7 +656,10 @@ declare class Granular {
|
|
|
637
656
|
private apiKey;
|
|
638
657
|
private apiUrl;
|
|
639
658
|
private httpUrl;
|
|
659
|
+
private tokenProvider?;
|
|
640
660
|
private WebSocketCtor?;
|
|
661
|
+
private onUnexpectedClose?;
|
|
662
|
+
private onReconnectError?;
|
|
641
663
|
/** Sandbox-level effect registry: sandboxId → (toolName → ToolWithHandler) */
|
|
642
664
|
private sandboxEffects;
|
|
643
665
|
/** Active environments tracker: sandboxId → Environment[] */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Automerge from '@automerge/automerge';
|
|
2
|
-
import { W as WSClientOptions, T as ToolWithHandler, P as PublishToolsResult, J as Job, a as ToolHandler, I as InstanceToolHandler, b as ToolInfo, c as ToolsChangedEvent, D as DomainState, G as GranularOptions, R as RecordUserOptions, U as User, C as ConnectOptions, E as EnvironmentData, d as GraphQLResult, e as DefineRelationshipOptions, f as RelationshipInfo, M as ModelRef, g as ManifestContent, h as RecordObjectOptions, i as RecordObjectResult, S as SandboxListResponse, j as Sandbox, k as CreateSandboxData, l as DeleteResponse, m as PermissionProfile, n as CreatePermissionProfileData, o as CreateEnvironmentData, p as Subject, A as AssignmentListResponse } from './types-
|
|
3
|
-
export {
|
|
2
|
+
import { W as WSClientOptions, T as ToolWithHandler, P as PublishToolsResult, J as Job, a as ToolHandler, I as InstanceToolHandler, b as ToolInfo, c as ToolsChangedEvent, D as DomainState, G as GranularOptions, R as RecordUserOptions, U as User, C as ConnectOptions, E as EnvironmentData, d as GraphQLResult, e as DefineRelationshipOptions, f as RelationshipInfo, M as ModelRef, g as ManifestContent, h as RecordObjectOptions, i as RecordObjectResult, S as SandboxListResponse, j as Sandbox, k as CreateSandboxData, l as DeleteResponse, m as PermissionProfile, n as CreatePermissionProfileData, o as CreateEnvironmentData, p as Subject, A as AssignmentListResponse } from './types-BOPsFZYi.js';
|
|
3
|
+
export { a6 as APIError, r as AccessTokenProvider, w as Assignment, H as Build, K as BuildListResponse, B as BuildPolicy, F as BuildStatus, s as EndpointMode, x as EnvironmentListResponse, t as GranularAuth, L as JobStatus, N as JobSubmitResult, y as Manifest, a4 as ManifestImport, z as ManifestListResponse, a3 as ManifestOperation, a1 as ManifestPropertySpec, a2 as ManifestRelationshipDef, a5 as ManifestVolume, v as PermissionProfileListResponse, u as PermissionRules, O as Prompt, X as RPCRequest, _ as RPCRequestFromServer, Y as RPCResponse, Z as SyncMessage, $ as ToolInvokeParams, a0 as ToolResultParams, q as ToolSchema, Q as WSDisconnectInfo, V as WSReconnectErrorInfo } from './types-BOPsFZYi.js';
|
|
4
4
|
import { Doc } from '@automerge/automerge/slim';
|
|
5
5
|
|
|
6
6
|
declare class WSClient {
|
|
@@ -16,14 +16,24 @@ declare class WSClient {
|
|
|
16
16
|
doc: Automerge.Doc<Record<string, unknown>>;
|
|
17
17
|
private syncState;
|
|
18
18
|
private reconnectTimer;
|
|
19
|
+
private tokenRefreshTimer;
|
|
19
20
|
private isExplicitlyDisconnected;
|
|
20
21
|
private options;
|
|
21
22
|
constructor(options: WSClientOptions);
|
|
23
|
+
private clearTokenRefreshTimer;
|
|
24
|
+
private decodeBase64Url;
|
|
25
|
+
private getTokenExpiryMs;
|
|
26
|
+
private scheduleTokenRefresh;
|
|
27
|
+
private refreshTokenInBackground;
|
|
28
|
+
private resolveTokenForConnect;
|
|
22
29
|
/**
|
|
23
30
|
* Connect to the WebSocket server
|
|
24
31
|
* @returns {Promise<void>} Resolves when connection is open
|
|
25
32
|
*/
|
|
26
33
|
connect(): Promise<void>;
|
|
34
|
+
private normalizeReason;
|
|
35
|
+
private rejectPending;
|
|
36
|
+
private buildDisconnectError;
|
|
27
37
|
private handleDisconnect;
|
|
28
38
|
private handleMessage;
|
|
29
39
|
/**
|
|
@@ -322,6 +332,15 @@ declare class Environment extends Session {
|
|
|
322
332
|
get permissionProfileId(): string;
|
|
323
333
|
/** The GraphQL API endpoint URL */
|
|
324
334
|
get apiEndpoint(): string;
|
|
335
|
+
private getRuntimeBaseUrl;
|
|
336
|
+
/**
|
|
337
|
+
* Close the session and disconnect from the sandbox.
|
|
338
|
+
*
|
|
339
|
+
* Sends `client.goodbye` over WebSocket first, then issues an HTTP fallback
|
|
340
|
+
* to the runtime goodbye endpoint if no definitive WS-side runtime notify
|
|
341
|
+
* acknowledgement was observed.
|
|
342
|
+
*/
|
|
343
|
+
disconnect(): Promise<void>;
|
|
325
344
|
/** The last known graph container status, updated by checkReadiness() or on heartbeat */
|
|
326
345
|
graphContainerStatus: {
|
|
327
346
|
lastKeepAliveAt: number;
|
|
@@ -637,7 +656,10 @@ declare class Granular {
|
|
|
637
656
|
private apiKey;
|
|
638
657
|
private apiUrl;
|
|
639
658
|
private httpUrl;
|
|
659
|
+
private tokenProvider?;
|
|
640
660
|
private WebSocketCtor?;
|
|
661
|
+
private onUnexpectedClose?;
|
|
662
|
+
private onReconnectError?;
|
|
641
663
|
/** Sandbox-level effect registry: sandboxId → (toolName → ToolWithHandler) */
|
|
642
664
|
private sandboxEffects;
|
|
643
665
|
/** Active environments tracker: sandboxId → Environment[] */
|