@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.
- 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 +231 -13
- package/dist/index.d.mts +200 -20
- package/dist/index.d.ts +200 -20
- package/dist/index.js +4433 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4428 -57
- package/dist/index.mjs.map +1 -1
- package/dist/{types-D46q5WTh.d.mts → types-CnX4jXYQ.d.mts} +47 -3
- package/dist/{types-D46q5WTh.d.ts → types-CnX4jXYQ.d.ts} +47 -3
- package/package.json +2 -3
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
|
|
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 (
|
|
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 (
|
|
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) =>
|
|
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 =
|
|
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
|
|
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 (
|
|
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 (
|
|
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(
|
|
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);
|