@granular-software/sdk 0.4.10 → 0.4.12

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
@@ -48,6 +48,8 @@ CLI overrides:
48
48
  - `granular --prod <command>`
49
49
  - `granular --env local|production <command>`
50
50
 
51
+ When the resolved API URL points at `localhost` or `127.0.0.1`, the SDK and CLI automatically swap opaque `sk_...` WorkOS keys for the local dev key `gn_sk_tenant_default_principal_local_e2e_00000000` so local runs work without server-side WorkOS validation. Override with `GRANULAR_LOCAL_API_KEY=...` or disable with `GRANULAR_DISABLE_LOCAL_API_KEY_FALLBACK=true`.
52
+
51
53
  ## Agent documentation (CLI)
52
54
 
53
55
  Projects created with `granular init` can include Markdown for coding agents (optional `AGENTS.md` plus generated guides):
package/dist/cli/index.js CHANGED
@@ -5388,6 +5388,7 @@ var import_dotenv = __toESM(require_main());
5388
5388
  // src/endpoints.ts
5389
5389
  var LOCAL_API_URL = "ws://localhost:8787/granular";
5390
5390
  var PRODUCTION_API_URL = "wss://cf-api-gateway.arthur6084.workers.dev/granular";
5391
+ var DEFAULT_LOCAL_API_KEY = "gn_sk_tenant_default_principal_local_e2e_00000000";
5391
5392
  var LOCAL_AUTH_URL = "http://localhost:3000";
5392
5393
  var PRODUCTION_AUTH_URL = "https://app.granular.software";
5393
5394
  function readEnv(name) {
@@ -5407,6 +5408,21 @@ function isTruthy(value) {
5407
5408
  const normalized = value.trim().toLowerCase();
5408
5409
  return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
5409
5410
  }
5411
+ function isLocalApiUrl(url) {
5412
+ try {
5413
+ const parsed = new URL(url);
5414
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
5415
+ } catch {
5416
+ return false;
5417
+ }
5418
+ }
5419
+ function resolveAuthTokenForApiUrl(authToken, apiUrl) {
5420
+ if (!authToken.startsWith("sk_") || !isLocalApiUrl(apiUrl) || isTruthy(readEnv("GRANULAR_DISABLE_LOCAL_API_KEY_FALLBACK"))) {
5421
+ return authToken;
5422
+ }
5423
+ const override = readEnv("GRANULAR_LOCAL_API_KEY")?.trim();
5424
+ return override || DEFAULT_LOCAL_API_KEY;
5425
+ }
5410
5426
  function resolveEndpointMode(explicitMode) {
5411
5427
  const explicit = normalizeMode(explicitMode);
5412
5428
  if (explicit === "local" || explicit === "production") {
@@ -6762,7 +6778,7 @@ var ApiClient = class {
6762
6778
  apiKey;
6763
6779
  baseUrl;
6764
6780
  constructor(apiKey, apiUrl) {
6765
- this.apiKey = apiKey;
6781
+ this.apiKey = resolveAuthTokenForApiUrl(apiKey, apiUrl);
6766
6782
  this.baseUrl = apiUrl.replace("wss://", "https://").replace("ws://", "http://").replace(/\/ws$/, "");
6767
6783
  }
6768
6784
  async request(path4, options = {}) {
@@ -9722,7 +9738,7 @@ async function initCommand(projectName, options) {
9722
9738
  dim(" The seed script shows how records enter the graph. The effects script shows how external systems plug in.");
9723
9739
  console.log();
9724
9740
  }
9725
- var DEFAULT_LOCAL_API_KEY = "gn_sk_tenant_default_principal_local_e2e_00000000";
9741
+ var DEFAULT_LOCAL_API_KEY2 = "gn_sk_tenant_default_principal_local_e2e_00000000";
9726
9742
  function promptSecret(question) {
9727
9743
  const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
9728
9744
  return new Promise((resolve) => {
@@ -9748,7 +9764,7 @@ function maskApiKey(apiKey) {
9748
9764
  if (apiKey.length < 10) return `${apiKey}...`;
9749
9765
  return `${apiKey.substring(0, 10)}...`;
9750
9766
  }
9751
- function isLocalApiUrl(apiUrl) {
9767
+ function isLocalApiUrl2(apiUrl) {
9752
9768
  return apiUrl.startsWith("ws://localhost:") || apiUrl.startsWith("wss://localhost:") || apiUrl.startsWith("ws://127.0.0.1:") || apiUrl.startsWith("wss://127.0.0.1:") || apiUrl.startsWith("http://localhost:") || apiUrl.startsWith("https://localhost:") || apiUrl.startsWith("http://127.0.0.1:") || apiUrl.startsWith("https://127.0.0.1:");
9753
9769
  }
9754
9770
  async function loginCommand(options = {}) {
@@ -9762,8 +9778,8 @@ async function loginCommand(options = {}) {
9762
9778
  const apiUrl = loadApiUrl();
9763
9779
  let apiKey = options.apiKey?.trim();
9764
9780
  if (!apiKey) {
9765
- if (!options.manual && isLocalApiUrl(apiUrl)) {
9766
- apiKey = existing?.trim() || process.env.GRANULAR_LOCAL_API_KEY?.trim() || DEFAULT_LOCAL_API_KEY;
9781
+ if (!options.manual && isLocalApiUrl2(apiUrl)) {
9782
+ apiKey = existing?.trim() || process.env.GRANULAR_LOCAL_API_KEY?.trim() || DEFAULT_LOCAL_API_KEY2;
9767
9783
  info("Using local Granular API key for localhost development.");
9768
9784
  } else if (options.manual) {
9769
9785
  dim("Get your API key at https://app.granular.software/w/default/api-keys");
@@ -10324,13 +10340,19 @@ function openUrl(url) {
10324
10340
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
10325
10341
  child_process.spawn(cmd, [url], { stdio: "ignore", shell: platform === "win32" });
10326
10342
  }
10327
- async function simulateCommand(sandboxIdArg) {
10343
+ async function simulateCommand(sandboxIdArg, options) {
10328
10344
  const sandboxId = sandboxIdArg ?? resolveConfig().sandboxId;
10329
10345
  if (!sandboxId) {
10330
10346
  error("No sandbox ID. Run from a project with `granular init` or pass a sandbox ID: granular simulate <sandbox-id>");
10331
10347
  process.exit(1);
10332
10348
  }
10333
- const url = `${SIMULATOR_BASE}?sandboxId=${encodeURIComponent(sandboxId)}`;
10349
+ const params = new URLSearchParams({
10350
+ sandboxId
10351
+ });
10352
+ if (options?.subjectId) {
10353
+ params.set("subject_id", options.subjectId);
10354
+ }
10355
+ const url = `${SIMULATOR_BASE}?${params.toString()}`;
10334
10356
  info(`Opening simulator: ${url}`);
10335
10357
  openUrl(url);
10336
10358
  }
@@ -10470,9 +10492,9 @@ program2.command("document").description("Generate GRANULAR_SANDBOX.md (agent re
10470
10492
  process.exit(1);
10471
10493
  }
10472
10494
  });
10473
- program2.command("simulate [sandbox-id]").description("Open the Granular simulator in the browser for the current (or given) sandbox").action(async (sandboxId) => {
10495
+ program2.command("simulate [sandbox-id]").description("Open the Granular simulator in the browser for the current (or given) sandbox").option("--subject-id <subjectId>", "Open the simulator preloaded for a specific subject").action(async (sandboxId, options) => {
10474
10496
  try {
10475
- await simulateCommand(sandboxId);
10497
+ await simulateCommand(sandboxId, options);
10476
10498
  } catch (err) {
10477
10499
  error(err.message);
10478
10500
  process.exit(1);
package/dist/index.d.mts CHANGED
@@ -457,7 +457,7 @@ interface EffectsChangedEvent extends ToolsChangedEvent {
457
457
  }
458
458
  type EffectHandler = ToolHandler;
459
459
  type InstanceEffectHandler = InstanceToolHandler;
460
- type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'timeout' | 'canceled';
460
+ type JobStatus = 'queued' | 'running' | 'awaitingTool' | 'awaitingHuman' | 'succeeded' | 'failed' | 'timeout' | 'canceled';
461
461
  type JobFeedbackSentiment = 'good' | 'bad';
462
462
  interface JobFeedbackToolCall {
463
463
  callId?: string;
@@ -529,8 +529,15 @@ interface Prompt {
529
529
  type: 'confirm' | 'choice' | 'input';
530
530
  title: string;
531
531
  message: string;
532
- options?: string[];
532
+ options?: Array<string | {
533
+ value: string;
534
+ label: string;
535
+ description?: string;
536
+ }>;
533
537
  defaultValue?: unknown;
538
+ placeholder?: string;
539
+ allowEmpty?: boolean;
540
+ metadata?: Record<string, unknown>;
534
541
  }
535
542
  type SessionHeapFieldType = 'string' | 'number' | 'boolean' | 'null' | 'unknown';
536
543
  interface SessionHeapFieldValue {
package/dist/index.d.ts CHANGED
@@ -457,7 +457,7 @@ interface EffectsChangedEvent extends ToolsChangedEvent {
457
457
  }
458
458
  type EffectHandler = ToolHandler;
459
459
  type InstanceEffectHandler = InstanceToolHandler;
460
- type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'timeout' | 'canceled';
460
+ type JobStatus = 'queued' | 'running' | 'awaitingTool' | 'awaitingHuman' | 'succeeded' | 'failed' | 'timeout' | 'canceled';
461
461
  type JobFeedbackSentiment = 'good' | 'bad';
462
462
  interface JobFeedbackToolCall {
463
463
  callId?: string;
@@ -529,8 +529,15 @@ interface Prompt {
529
529
  type: 'confirm' | 'choice' | 'input';
530
530
  title: string;
531
531
  message: string;
532
- options?: string[];
532
+ options?: Array<string | {
533
+ value: string;
534
+ label: string;
535
+ description?: string;
536
+ }>;
533
537
  defaultValue?: unknown;
538
+ placeholder?: string;
539
+ allowEmpty?: boolean;
540
+ metadata?: Record<string, unknown>;
534
541
  }
535
542
  type SessionHeapFieldType = 'string' | 'number' | 'boolean' | 'null' | 'unknown';
536
543
  interface SessionHeapFieldValue {
package/dist/index.js CHANGED
@@ -4650,7 +4650,7 @@ var Session = class {
4650
4650
  * Respond to a prompt request from the sandbox
4651
4651
  */
4652
4652
  async answerPrompt(promptId, answer) {
4653
- await this.client.call("prompt.answer", { promptId, value: answer });
4653
+ await this.client.call("prompt.answer", { promptId, answer, value: answer });
4654
4654
  }
4655
4655
  /**
4656
4656
  * Get the current list of available effects.
@@ -4967,13 +4967,31 @@ import { ${allImports} } from "./sandbox-tools";
4967
4967
  });
4968
4968
  }
4969
4969
  setupEventHandlers() {
4970
+ this.client.on("open", (payload) => this.emit("open", payload || {}));
4970
4971
  this.client.on("sync", (doc) => {
4971
4972
  this.currentDomainRevision = this.extractDomainRevisionFromDoc(doc);
4972
4973
  this.emit("sync", doc);
4973
4974
  this.checkForToolChanges();
4974
4975
  });
4975
- this.client.on("prompt", (prompt) => this.emit("prompt", prompt));
4976
+ const emitPrompt = (payload) => {
4977
+ const raw = payload;
4978
+ const prompt = raw.prompt || {
4979
+ id: typeof raw.id === "string" ? raw.id : String(raw.promptId || ""),
4980
+ type: raw.type === "confirm" || raw.type === "choice" || raw.type === "input" ? raw.type : raw.promptType === "confirm" || raw.promptType === "choice" ? raw.promptType : "input",
4981
+ title: typeof raw.title === "string" ? raw.title : "Input required",
4982
+ message: typeof raw.message === "string" ? raw.message : "",
4983
+ options: raw.options,
4984
+ defaultValue: raw.defaultValue,
4985
+ placeholder: raw.placeholder,
4986
+ allowEmpty: raw.allowEmpty,
4987
+ metadata: raw.metadata
4988
+ };
4989
+ this.emit("prompt", prompt);
4990
+ };
4991
+ this.client.on("prompt", emitPrompt);
4992
+ this.client.on("prompt.request", emitPrompt);
4976
4993
  this.client.on("disconnect", (payload) => this.emit("disconnect", payload || {}));
4994
+ this.client.on("reconnect_error", (payload) => this.emit("reconnect_error", payload || {}));
4977
4995
  this.client.on("job.status", (data) => {
4978
4996
  this.emit("job:status", data);
4979
4997
  });
@@ -5045,6 +5063,8 @@ function normalizeJobStatus(status) {
5045
5063
  case "failed":
5046
5064
  case "running":
5047
5065
  case "queued":
5066
+ case "awaitingTool":
5067
+ case "awaitingHuman":
5048
5068
  case "succeeded":
5049
5069
  case "timeout":
5050
5070
  case "canceled":
@@ -5179,6 +5199,19 @@ var JobImplementation = class {
5179
5199
  this.client.on(`job.${id}.error`, (error) => {
5180
5200
  this.finalize("failed", void 0, error);
5181
5201
  });
5202
+ this.client.on("job.status", (data) => {
5203
+ const jobData = data;
5204
+ if (jobData.jobId !== id) {
5205
+ return;
5206
+ }
5207
+ const normalizedStatus = normalizeJobStatus(jobData.status);
5208
+ this.status = normalizedStatus;
5209
+ this.metadata.status = normalizedStatus;
5210
+ if (normalizedStatus === "running") {
5211
+ this.markStarted();
5212
+ }
5213
+ this.emit("status", normalizedStatus);
5214
+ });
5182
5215
  this.client.on("job.completed", (data) => {
5183
5216
  const jobData = data;
5184
5217
  if (jobData.jobId === id) {
@@ -5316,6 +5349,7 @@ var JobImplementation = class {
5316
5349
  // src/endpoints.ts
5317
5350
  var LOCAL_API_URL = "ws://localhost:8787/granular";
5318
5351
  var PRODUCTION_API_URL = "wss://cf-api-gateway.arthur6084.workers.dev/granular";
5352
+ var DEFAULT_LOCAL_API_KEY = "gn_sk_tenant_default_principal_local_e2e_00000000";
5319
5353
  function readEnv(name) {
5320
5354
  if (typeof process === "undefined" || !process.env) return void 0;
5321
5355
  return process.env[name];
@@ -5333,6 +5367,21 @@ function isTruthy(value) {
5333
5367
  const normalized = value.trim().toLowerCase();
5334
5368
  return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
5335
5369
  }
5370
+ function isLocalApiUrl(url) {
5371
+ try {
5372
+ const parsed = new URL(url);
5373
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
5374
+ } catch {
5375
+ return false;
5376
+ }
5377
+ }
5378
+ function resolveAuthTokenForApiUrl(authToken, apiUrl) {
5379
+ if (!authToken.startsWith("sk_") || !isLocalApiUrl(apiUrl) || isTruthy(readEnv("GRANULAR_DISABLE_LOCAL_API_KEY_FALLBACK"))) {
5380
+ return authToken;
5381
+ }
5382
+ const override = readEnv("GRANULAR_LOCAL_API_KEY")?.trim();
5383
+ return override || DEFAULT_LOCAL_API_KEY;
5384
+ }
5336
5385
  function resolveEndpointMode(explicitMode) {
5337
5386
  const explicit = normalizeMode(explicitMode);
5338
5387
  if (explicit === "local" || explicit === "production") {
@@ -6251,8 +6300,8 @@ var Granular = class {
6251
6300
  if (!auth) {
6252
6301
  throw new Error("Granular client requires either apiKey or token. Set GRANULAR_API_KEY or GRANULAR_TOKEN, or pass one in options.");
6253
6302
  }
6254
- this.apiKey = auth;
6255
6303
  this.apiUrl = resolveApiUrl(options.apiUrl, options.endpointMode);
6304
+ this.apiKey = resolveAuthTokenForApiUrl(auth, this.apiUrl);
6256
6305
  this.tokenProvider = options.tokenProvider;
6257
6306
  this.WebSocketCtor = options.WebSocketCtor;
6258
6307
  this.onUnexpectedClose = options.onUnexpectedClose;