@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 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
@@ -1,4 +1,4 @@
1
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.mjs';
1
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.mjs';
2
2
 
3
3
  /**
4
4
  * Anthropic tool_use block from message response
@@ -1,4 +1,4 @@
1
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.js';
1
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.js';
2
2
 
3
3
  /**
4
4
  * Anthropic tool_use block from message response
@@ -1,5 +1,5 @@
1
1
  import { StructuredTool } from '@langchain/core/tools';
2
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.mjs';
2
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.mjs';
3
3
 
4
4
  /**
5
5
  * Helper to convert LangChain tools to Granular tools.
@@ -1,5 +1,5 @@
1
1
  import { StructuredTool } from '@langchain/core/tools';
2
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.js';
2
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.js';
3
3
 
4
4
  /**
5
5
  * Helper to convert LangChain tools to Granular tools.
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.mjs';
2
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.mjs';
3
3
 
4
4
  /**
5
5
  * Mastra tool definition interface
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.js';
2
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.js';
3
3
 
4
4
  /**
5
5
  * Mastra tool definition interface
@@ -1,4 +1,4 @@
1
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.mjs';
1
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.mjs';
2
2
 
3
3
  /**
4
4
  * OpenAI tool call from chat completion response
@@ -1,4 +1,4 @@
1
- import { T as ToolWithHandler } from '../types-CnX4jXYQ.js';
1
+ import { T as ToolWithHandler } from '../types-BOPsFZYi.js';
2
2
 
3
3
  /**
4
4
  * OpenAI tool call from chat completion response
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
- if (process.env.GRANULAR_API_URL) return process.env.GRANULAR_API_URL;
5459
- return "https://cf-api-gateway.arthur6084.workers.dev/granular";
5514
+ return resolveApiUrl();
5460
5515
  }
5461
5516
  function loadAuthUrl() {
5462
- if (process.env.GRANULAR_AUTH_URL) {
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 renderHtmlPage(title, message) {
5796
+ function escapeHtml(input) {
5797
+ return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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>${title}</title>
5895
+ <title>${safeTitle}</title>
5751
5896
  <style>
5752
- :root { color-scheme: light dark; }
5753
- body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 2rem; line-height: 1.5; }
5754
- h1 { margin-bottom: 0.5rem; }
5755
- p { margin-top: 0; color: #666; }
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
- <h1>${title}</h1>
5760
- <p>${message}</p>
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(renderHtmlPage("Granular login failed", "Authentication was canceled or failed. You can close this window."));
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-CnX4jXYQ.mjs';
3
- export { a2 as APIError, u as Assignment, z as Build, F as BuildListResponse, B as BuildPolicy, y as BuildStatus, v as EnvironmentListResponse, r as GranularAuth, H as JobStatus, K as JobSubmitResult, w as Manifest, a0 as ManifestImport, x as ManifestListResponse, $ as ManifestOperation, Z as ManifestPropertySpec, _ as ManifestRelationshipDef, a1 as ManifestVolume, t as PermissionProfileListResponse, s as PermissionRules, L as Prompt, N as RPCRequest, V as RPCRequestFromServer, O as RPCResponse, Q as SyncMessage, X as ToolInvokeParams, Y as ToolResultParams, q as ToolSchema } from './types-CnX4jXYQ.mjs';
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-CnX4jXYQ.js';
3
- export { a2 as APIError, u as Assignment, z as Build, F as BuildListResponse, B as BuildPolicy, y as BuildStatus, v as EnvironmentListResponse, r as GranularAuth, H as JobStatus, K as JobSubmitResult, w as Manifest, a0 as ManifestImport, x as ManifestListResponse, $ as ManifestOperation, Z as ManifestPropertySpec, _ as ManifestRelationshipDef, a1 as ManifestVolume, t as PermissionProfileListResponse, s as PermissionRules, L as Prompt, N as RPCRequest, V as RPCRequestFromServer, O as RPCResponse, Q as SyncMessage, X as ToolInvokeParams, Y as ToolResultParams, q as ToolSchema } from './types-CnX4jXYQ.js';
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[] */