@granular-software/sdk 0.2.0 → 0.3.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.
@@ -1,4 +1,4 @@
1
- import { T as ToolWithHandler } from '../types-D46q5WTh.mjs';
1
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.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-D46q5WTh.js';
1
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.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-D46q5WTh.mjs';
2
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.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-D46q5WTh.js';
2
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.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-D46q5WTh.mjs';
2
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.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-D46q5WTh.js';
2
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.js';
3
3
 
4
4
  /**
5
5
  * Mastra tool definition interface
@@ -1,4 +1,4 @@
1
- import { T as ToolWithHandler } from '../types-D46q5WTh.mjs';
1
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.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-D46q5WTh.js';
1
+ import { T as ToolWithHandler } from '../types-CnX4jXYQ.js';
2
2
 
3
3
  /**
4
4
  * OpenAI tool call from chat completion response
package/dist/cli/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ var fs = require('fs');
4
5
  var path = require('path');
5
6
  var readline = require('readline');
6
- var fs = require('fs');
7
7
  var process6 = require('process');
8
8
  var os = require('os');
9
9
  var tty = require('tty');
10
+ var child_process = require('child_process');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
13
 
@@ -28,9 +29,9 @@ function _interopNamespace(e) {
28
29
  return Object.freeze(n);
29
30
  }
30
31
 
32
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
31
33
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
32
34
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
33
- var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
34
35
  var process6__default = /*#__PURE__*/_interopDefault(process6);
35
36
  var os__default = /*#__PURE__*/_interopDefault(os);
36
37
  var tty__default = /*#__PURE__*/_interopDefault(tty);
@@ -1185,7 +1186,7 @@ var require_command = __commonJS({
1185
1186
  var EventEmitter = __require("events").EventEmitter;
1186
1187
  var childProcess = __require("child_process");
1187
1188
  var path4 = __require("path");
1188
- var fs4 = __require("fs");
1189
+ var fs5 = __require("fs");
1189
1190
  var process10 = __require("process");
1190
1191
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1191
1192
  var { CommanderError: CommanderError2 } = require_error();
@@ -2165,7 +2166,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2165
2166
  * @param {string} subcommandName
2166
2167
  */
2167
2168
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2168
- if (fs4.existsSync(executableFile)) return;
2169
+ if (fs5.existsSync(executableFile)) return;
2169
2170
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2170
2171
  const executableMissing = `'${executableFile}' does not exist
2171
2172
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2184,10 +2185,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2184
2185
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2185
2186
  function findFile(baseDir, baseName) {
2186
2187
  const localBin = path4.resolve(baseDir, baseName);
2187
- if (fs4.existsSync(localBin)) return localBin;
2188
+ if (fs5.existsSync(localBin)) return localBin;
2188
2189
  if (sourceExt.includes(path4.extname(baseName))) return void 0;
2189
2190
  const foundExt = sourceExt.find(
2190
- (ext) => fs4.existsSync(`${localBin}${ext}`)
2191
+ (ext) => fs5.existsSync(`${localBin}${ext}`)
2191
2192
  );
2192
2193
  if (foundExt) return `${localBin}${foundExt}`;
2193
2194
  return void 0;
@@ -2199,7 +2200,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2199
2200
  if (this._scriptPath) {
2200
2201
  let resolvedScriptPath;
2201
2202
  try {
2202
- resolvedScriptPath = fs4.realpathSync(this._scriptPath);
2203
+ resolvedScriptPath = fs5.realpathSync(this._scriptPath);
2203
2204
  } catch {
2204
2205
  resolvedScriptPath = this._scriptPath;
2205
2206
  }
@@ -3430,7 +3431,7 @@ var require_package = __commonJS({
3430
3431
  // ../../node_modules/.bun/dotenv@16.6.1/node_modules/dotenv/lib/main.js
3431
3432
  var require_main = __commonJS({
3432
3433
  "../../node_modules/.bun/dotenv@16.6.1/node_modules/dotenv/lib/main.js"(exports, module) {
3433
- var fs4 = __require("fs");
3434
+ var fs5 = __require("fs");
3434
3435
  var path4 = __require("path");
3435
3436
  var os2 = __require("os");
3436
3437
  var crypto = __require("crypto");
@@ -3539,7 +3540,7 @@ var require_main = __commonJS({
3539
3540
  if (options && options.path && options.path.length > 0) {
3540
3541
  if (Array.isArray(options.path)) {
3541
3542
  for (const filepath of options.path) {
3542
- if (fs4.existsSync(filepath)) {
3543
+ if (fs5.existsSync(filepath)) {
3543
3544
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
3544
3545
  }
3545
3546
  }
@@ -3549,7 +3550,7 @@ var require_main = __commonJS({
3549
3550
  } else {
3550
3551
  possibleVaultPath = path4.resolve(process.cwd(), ".env.vault");
3551
3552
  }
3552
- if (fs4.existsSync(possibleVaultPath)) {
3553
+ if (fs5.existsSync(possibleVaultPath)) {
3553
3554
  return possibleVaultPath;
3554
3555
  }
3555
3556
  return null;
@@ -3598,7 +3599,7 @@ var require_main = __commonJS({
3598
3599
  const parsedAll = {};
3599
3600
  for (const path5 of optionPaths) {
3600
3601
  try {
3601
- const parsed = DotenvModule.parse(fs4.readFileSync(path5, { encoding }));
3602
+ const parsed = DotenvModule.parse(fs5.readFileSync(path5, { encoding }));
3602
3603
  DotenvModule.populate(parsedAll, parsed, options);
3603
3604
  } catch (e) {
3604
3605
  if (debug) {
@@ -5535,6 +5536,18 @@ function createDefaultManifest(name) {
5535
5536
  }
5536
5537
  };
5537
5538
  }
5539
+ var STD_CLASS_NAMES = /* @__PURE__ */ new Set(["entity", "class", "user", "company", "string", "number", "boolean", "tool_parameter"]);
5540
+ function getClassNamesFromManifest(manifest) {
5541
+ const names = /* @__PURE__ */ new Set();
5542
+ for (const vol of manifest.volumes ?? []) {
5543
+ for (const op of vol.operations ?? []) {
5544
+ if (op.create && !STD_CLASS_NAMES.has(op.create)) {
5545
+ names.add(op.create);
5546
+ }
5547
+ }
5548
+ }
5549
+ return Array.from(names);
5550
+ }
5538
5551
  function resolveConfig(options) {
5539
5552
  const apiKey = loadApiKey();
5540
5553
  const apiUrl = loadApiUrl();
@@ -7119,6 +7132,175 @@ function buildStatus(status) {
7119
7132
  }
7120
7133
 
7121
7134
  // src/cli/commands/init.ts
7135
+ var EFFECTS_SCRIPT_NAME = "granular-effects.ts";
7136
+ function toWsUrl(apiUrl) {
7137
+ return apiUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://");
7138
+ }
7139
+ function generateEffectsScript(sandboxId, apiUrl, classNames) {
7140
+ const wsUrl = toWsUrl(apiUrl);
7141
+ classNames.length > 0;
7142
+ const mockDataEntries = classNames.map(
7143
+ (c) => ` ${c}: [
7144
+ { id: '${c}_001', name: 'Sample ${c}' },
7145
+ { id: '${c}_002', name: 'Another ${c}' },
7146
+ ]`
7147
+ );
7148
+ const mockDataBlock = mockDataEntries.length > 0 ? `const MOCK_DATA: Record<string, any[]> = {
7149
+ ${mockDataEntries.join(",\n")},
7150
+ };` : "const MOCK_DATA: Record<string, any[]> = {};";
7151
+ const instanceEffects = classNames.map((className) => {
7152
+ className.charAt(0).toUpperCase() + className.slice(1);
7153
+ return ` {
7154
+ name: 'get_info',
7155
+ description: \`Get details of a ${className} by ID\`,
7156
+ className: '${className}',
7157
+ inputSchema: {
7158
+ type: 'object',
7159
+ properties: { include_related: { type: 'boolean', description: 'Include related items' } },
7160
+ },
7161
+ handler: async (id: string, params: any) => {
7162
+ logEffect('get_info', '${className}', id, params);
7163
+ const item = (MOCK_DATA['${className}'] || []).find((x: any) => x.id === id);
7164
+ if (!item) return { error: \`${className} "\${id}" not found\` };
7165
+ return { ...item, ...(params?.include_related ? { related: [] } : {}) };
7166
+ },
7167
+ }`;
7168
+ });
7169
+ const staticEffects = classNames.map((className) => {
7170
+ const searchName = "search_" + className + "s";
7171
+ return ` {
7172
+ name: '${searchName}',
7173
+ description: \`Search ${className}s by keyword\`,
7174
+ className: '${className}',
7175
+ static: true,
7176
+ inputSchema: {
7177
+ type: 'object',
7178
+ properties: {
7179
+ query: { type: 'string', description: 'Search keyword' },
7180
+ limit: { type: 'number', description: 'Max results' },
7181
+ },
7182
+ required: ['query'],
7183
+ },
7184
+ handler: async (params: any) => {
7185
+ logEffect('${searchName}', null, null, params);
7186
+ const q = ((params?.query) || '').toLowerCase();
7187
+ const limit = params?.limit ?? 10;
7188
+ const list = (MOCK_DATA['${className}'] || []).filter((x: any) =>
7189
+ String(x.name || x.id || '').toLowerCase().includes(q)
7190
+ ).slice(0, limit);
7191
+ return { query: params?.query, results: list, total: list.length };
7192
+ },
7193
+ }`;
7194
+ });
7195
+ const allEffects = [...instanceEffects, ...staticEffects];
7196
+ if (allEffects.length > 0) {
7197
+ allEffects.push(` {
7198
+ name: 'full_text_search',
7199
+ description: 'Search across all types',
7200
+ inputSchema: {
7201
+ type: 'object',
7202
+ properties: {
7203
+ query: { type: 'string', description: 'Search query' },
7204
+ types: { type: 'array', items: { type: 'string' }, description: 'Filter by type' },
7205
+ },
7206
+ required: ['query'],
7207
+ },
7208
+ handler: async (params: any) => {
7209
+ logEffect('full_text_search', null, null, params);
7210
+ const q = ((params?.query) || '').toLowerCase();
7211
+ const types: string[] = params?.types || [${classNames.map((c) => `'${c}'`).join(", ")}];
7212
+ const matches: any[] = [];
7213
+ for (const type of types) {
7214
+ const items = MOCK_DATA[type] || [];
7215
+ for (const item of items) {
7216
+ const text = Object.values(item).join(' ').toLowerCase();
7217
+ if (!q || text.includes(q)) {
7218
+ matches.push({ type, id: item.id, label: item.name || item.id, snippet: text.slice(0, 60) });
7219
+ }
7220
+ }
7221
+ }
7222
+ return { query: params?.query, types, matches, total: matches.length };
7223
+ },
7224
+ }`);
7225
+ }
7226
+ const effectsArray = allEffects.length > 0 ? `[
7227
+ ${allEffects.join(",\n")}
7228
+ ]` : "[]";
7229
+ return `/**
7230
+ * Granular effects script for sandbox ${sandboxId}
7231
+ * Run with: npx tsx ${EFFECTS_SCRIPT_NAME}
7232
+ * Requires: GRANULAR_API_KEY in env or .env.local, and optional "ws" package for Node.
7233
+ *
7234
+ * Keeps the process alive so the simulator can invoke these effect handlers.
7235
+ */
7236
+
7237
+ import { Granular } from '@granular-software/sdk';
7238
+
7239
+ const SANDBOX_ID = '${sandboxId}';
7240
+ const API_URL = '${wsUrl}';
7241
+
7242
+ function log(msg: string) {
7243
+ console.log(\`[\${new Date().toISOString()}] [Effects] \${msg}\`);
7244
+ }
7245
+
7246
+ function logEffect(name: string, className: string | null, id: string | null, params: any) {
7247
+ const target = className ? (id ? \`\${className}(\${id})\` : \`\${className}\`) : 'global';
7248
+ console.log(\` [EFFECT] \${name}(\${target}) \${JSON.stringify(params || {})}\`);
7249
+ }
7250
+
7251
+ async function main() {
7252
+ const auth = process.env.GRANULAR_TOKEN ?? process.env.GRANULAR_API_KEY;
7253
+ if (!auth) {
7254
+ console.error('[Effects] Set GRANULAR_API_KEY or GRANULAR_TOKEN (e.g. from simulator "Copy CLI env").');
7255
+ process.exit(1);
7256
+ }
7257
+
7258
+ log('Initializing client...');
7259
+ const granular = new Granular({
7260
+ ...(auth.startsWith('eyJ') ? { token: auth } : { apiKey: auth }),
7261
+ apiUrl: process.env.GRANULAR_API_URL ?? API_URL,
7262
+ });
7263
+
7264
+ const userId = 'effects_user';
7265
+ log(\`Recording user \${userId}\`);
7266
+ const user = await granular.recordUser({
7267
+ userId,
7268
+ name: 'Effects Host',
7269
+ permissions: ['default'],
7270
+ });
7271
+
7272
+ log(\`Connecting to sandbox \${SANDBOX_ID}\`);
7273
+ const env = await granular.connect({ sandbox: SANDBOX_ID, user, clientId: 'effects-host' });
7274
+
7275
+ log('Waiting for graph container to be ready...');
7276
+ for (let i = 0; i < 6; i++) {
7277
+ await new Promise((r) => setTimeout(r, 5000));
7278
+ try {
7279
+ const probe = await env.graphql(\`query { model(path: "class") { path } }\`);
7280
+ if ((probe as any)?.data?.model?.path) {
7281
+ log(\`Graph ready after \${(i + 1) * 5}s\`);
7282
+ break;
7283
+ }
7284
+ } catch {
7285
+ log(\`Not ready yet (\${(i + 1) * 5}s)...\`);
7286
+ }
7287
+ }
7288
+
7289
+ log('Publishing effects...');
7290
+ ${mockDataBlock}
7291
+
7292
+ await env.publishEffects(${effectsArray});
7293
+
7294
+ log('Effects published. Process kept alive for simulator. Press Ctrl+C to exit.');
7295
+ await new Promise(() => {});
7296
+ }
7297
+
7298
+ main().catch((err) => {
7299
+ console.error('[Effects] Failed:', err);
7300
+ process.exit(1);
7301
+ });
7302
+ `;
7303
+ }
7122
7304
  function prompt(question, defaultValue) {
7123
7305
  const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
7124
7306
  const suffix = defaultValue ? ` ${brand.muted(`(${defaultValue})`)}` : "";
@@ -7228,6 +7410,11 @@ async function initCommand(projectName) {
7228
7410
  dim("You can retry with `granular build`");
7229
7411
  }
7230
7412
  }
7413
+ const classNames = getClassNamesFromManifest(project.manifest);
7414
+ const effectsPath = path__namespace.join(getProjectRoot(), EFFECTS_SCRIPT_NAME);
7415
+ const effectsContent = generateEffectsScript(sandbox.sandboxId, apiUrl, classNames);
7416
+ fs__namespace.writeFileSync(effectsPath, effectsContent, "utf-8");
7417
+ success(`Created ${brand.bold(EFFECTS_SCRIPT_NAME)} (effects script for simulator)`);
7231
7418
  console.log();
7232
7419
  divider();
7233
7420
  console.log();
@@ -7237,13 +7424,20 @@ async function initCommand(projectName) {
7237
7424
  "Sandbox": sandbox.sandboxId,
7238
7425
  "Manifest": "granular.json",
7239
7426
  "Config": ".granularrc",
7240
- "API Key": ".env.local"
7427
+ "API Key": ".env.local",
7428
+ "Effects script": EFFECTS_SCRIPT_NAME
7241
7429
  });
7242
7430
  nextSteps([
7243
7431
  { command: "granular add class <name>", description: "Add a class to your schema" },
7244
7432
  { command: "granular build", description: "Build your sandbox" },
7245
7433
  { command: "granular status", description: "Check build status" }
7246
7434
  ]);
7435
+ console.log();
7436
+ dim("Run the effects script (keeps process alive for simulator):");
7437
+ hint(`npx tsx ${EFFECTS_SCRIPT_NAME}`, "requires tsx and GRANULAR_API_KEY in .env.local");
7438
+ dim("Open the simulator in your browser:");
7439
+ hint("granular simulate", "opens app.granular.software/simulator for this sandbox");
7440
+ console.log();
7247
7441
  }
7248
7442
  function promptSecret2(question) {
7249
7443
  const rl = readline__namespace.createInterface({ input: process.stdin, output: process.stdout });
@@ -7847,7 +8041,7 @@ ${manifest.description}`);
7847
8041
  lines.push(`import { Granular } from '@granular-software/sdk';`);
7848
8042
  lines.push(`
7849
8043
  const granular = new Granular({`);
7850
- lines.push(` apiKey: process.env.GRANULAR_API_KEY!,`);
8044
+ lines.push(` ...(process.env.GRANULAR_TOKEN ? { token: process.env.GRANULAR_TOKEN } : { apiKey: process.env.GRANULAR_API_KEY! }),`);
7851
8045
  lines.push(`});`);
7852
8046
  lines.push(`
7853
8047
  // 1. Record the user (creates or updates identity)`);
@@ -7994,6 +8188,22 @@ console.log('Connected to:', env.environmentId);`);
7994
8188
  spinner2.succeed(`Generated ${brand.bold("GRANULAR.md")}`);
7995
8189
  info(`Open GRANULAR.md to see your project documentation.`);
7996
8190
  }
8191
+ var SIMULATOR_BASE = "https://app.granular.software/simulator";
8192
+ function openUrl(url) {
8193
+ const platform = process.platform;
8194
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
8195
+ child_process.spawn(cmd, [url], { stdio: "ignore", shell: platform === "win32" });
8196
+ }
8197
+ async function simulateCommand(sandboxIdArg) {
8198
+ const sandboxId = sandboxIdArg ?? resolveConfig().sandboxId;
8199
+ if (!sandboxId) {
8200
+ error("No sandbox ID. Run from a project with `granular init` or pass a sandbox ID: granular simulate <sandbox-id>");
8201
+ process.exit(1);
8202
+ }
8203
+ const url = `${SIMULATOR_BASE}?sandboxId=${encodeURIComponent(sandboxId)}`;
8204
+ info(`Opening simulator: ${url}`);
8205
+ openUrl(url);
8206
+ }
7997
8207
 
7998
8208
  // src/cli/index.ts
7999
8209
  var VERSION = "0.2.0";
@@ -8096,4 +8306,12 @@ program2.command("document").description("Generate GRANULAR.md documentation fro
8096
8306
  process.exit(1);
8097
8307
  }
8098
8308
  });
8309
+ program2.command("simulate [sandbox-id]").description("Open the Granular simulator in the browser for the current (or given) sandbox").action(async (sandboxId) => {
8310
+ try {
8311
+ await simulateCommand(sandboxId);
8312
+ } catch (err) {
8313
+ error(err.message);
8314
+ process.exit(1);
8315
+ }
8316
+ });
8099
8317
  program2.parse(process.argv);