@capxul/sandbox 1.0.0-alpha.3 → 1.1.0

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.
Files changed (3) hide show
  1. package/README.md +9 -3
  2. package/dist/main.mjs +122 -294
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -15,10 +15,16 @@ npx @capxul/sandbox faucet send
15
15
  vp run --filter @capxul/sandbox dev -- --help
16
16
  ```
17
17
 
18
- `key create` uses the public quickstart endpoint and needs no operator secret.
18
+ `key create` uses the Little Sandpiper public staging quickstart endpoint and
19
+ needs no operator secret. `APP_ENV` does not retarget this public command; use
20
+ the returned key only with the staging tuple.
19
21
  `faucet send` invokes the canonical Convex deployment and requires operator
20
- configuration from `~/.config/capxul/secrets.env`; never print or commit those
21
- values.
22
+ access in the process environment: inside the infrastructure repository, run
23
+ it through the one env entry point
24
+ (`vp exec bash scripts/capxul-env.sh npx @capxul/sandbox faucet send`); outside
25
+ it, supply the deploy key directly
26
+ (`CONVEX_DEPLOY_KEY=… npx @capxul/sandbox faucet send`). Never print or commit
27
+ those values.
22
28
 
23
29
  ## Verify in the repository
24
30
 
package/dist/main.mjs CHANGED
@@ -1,307 +1,134 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
2
  import { cancel, confirm, intro, isCancel, note, outro, select, spinner, text } from "@clack/prompts";
4
3
  import { spawnSync } from "node:child_process";
5
- import { existsSync } from "node:fs";
6
4
  import { homedir, tmpdir } from "node:os";
7
- import { resolve } from "node:path";
8
- import { fileURLToPath } from "node:url";
5
+ import path, { resolve } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
9
7
  import { readFile, writeFile } from "node:fs/promises";
10
- //#region \0rolldown/runtime.js
11
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
12
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
13
- //#endregion
14
- //#region ../../packages/backend/scripts/publishable-key-minting.ts
15
- var import_main = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
16
- const fs = __require("fs");
17
- const path = __require("path");
18
- const os = __require("os");
19
- const crypto = __require("crypto");
20
- const TIPS = [
21
- " encrypted .env [www.dotenvx.com]",
22
- " secrets for agents [www.dotenvx.com]",
23
- " auth for agents [www.vestauth.com]",
24
- "⌘ custom filepath { path: '/custom/path/.env' }",
25
- "⌘ enable debugging { debug: true }",
26
- " override existing { override: true }",
27
- "⌘ suppress logs { quiet: true }",
28
- "⌘ multiple files { path: ['.env.local', '.env'] }"
29
- ];
30
- function _getRandomTip() {
31
- return TIPS[Math.floor(Math.random() * TIPS.length)];
32
- }
33
- function parseBoolean(value) {
34
- if (typeof value === "string") return ![
35
- "false",
36
- "0",
37
- "no",
38
- "off",
39
- ""
40
- ].includes(value.toLowerCase());
41
- return Boolean(value);
42
- }
43
- function supportsAnsi() {
44
- return process.stdout.isTTY;
45
- }
46
- function dim(text) {
47
- return supportsAnsi() ? `\x1b[2m${text}\x1b[0m` : text;
48
- }
49
- const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
50
- function parse(src) {
51
- const obj = {};
52
- let lines = src.toString();
53
- lines = lines.replace(/\r\n?/gm, "\n");
54
- let match;
55
- while ((match = LINE.exec(lines)) != null) {
56
- const key = match[1];
57
- let value = match[2] || "";
58
- value = value.trim();
59
- const maybeQuote = value[0];
60
- value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
61
- if (maybeQuote === "\"") {
62
- value = value.replace(/\\n/g, "\n");
63
- value = value.replace(/\\r/g, "\r");
64
- }
65
- obj[key] = value;
66
- }
67
- return obj;
68
- }
69
- function _parseVault(options) {
70
- options = options || {};
71
- const vaultPath = _vaultPath(options);
72
- options.path = vaultPath;
73
- const result = DotenvModule.configDotenv(options);
74
- if (!result.parsed) {
75
- const err = /* @__PURE__ */ new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
76
- err.code = "MISSING_DATA";
77
- throw err;
78
- }
79
- const keys = _dotenvKey(options).split(",");
80
- const length = keys.length;
81
- let decrypted;
82
- for (let i = 0; i < length; i++) try {
83
- const attrs = _instructions(result, keys[i].trim());
84
- decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
85
- break;
86
- } catch (error) {
87
- if (i + 1 >= length) throw error;
88
- }
89
- return DotenvModule.parse(decrypted);
90
- }
91
- function _warn(message) {
92
- console.error(`⚠ ${message}`);
93
- }
94
- function _debug(message) {
95
- console.log(`┆ ${message}`);
96
- }
97
- function _log(message) {
98
- console.log(`◇ ${message}`);
99
- }
100
- function _dotenvKey(options) {
101
- if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) return options.DOTENV_KEY;
102
- if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) return process.env.DOTENV_KEY;
103
- return "";
104
- }
105
- function _instructions(result, dotenvKey) {
106
- let uri;
107
- try {
108
- uri = new URL(dotenvKey);
109
- } catch (error) {
110
- if (error.code === "ERR_INVALID_URL") {
111
- const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
112
- err.code = "INVALID_DOTENV_KEY";
113
- throw err;
114
- }
115
- throw error;
116
- }
117
- const key = uri.password;
118
- if (!key) {
119
- const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing key part");
120
- err.code = "INVALID_DOTENV_KEY";
121
- throw err;
122
- }
123
- const environment = uri.searchParams.get("environment");
124
- if (!environment) {
125
- const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing environment part");
126
- err.code = "INVALID_DOTENV_KEY";
127
- throw err;
128
- }
129
- const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
130
- const ciphertext = result.parsed[environmentKey];
131
- if (!ciphertext) {
132
- const err = /* @__PURE__ */ new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
133
- err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
134
- throw err;
135
- }
136
- return {
137
- ciphertext,
138
- key
139
- };
140
- }
141
- function _vaultPath(options) {
142
- let possibleVaultPath = null;
143
- if (options && options.path && options.path.length > 0) if (Array.isArray(options.path)) {
144
- for (const filepath of options.path) if (fs.existsSync(filepath)) possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
145
- } else possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
146
- else possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
147
- if (fs.existsSync(possibleVaultPath)) return possibleVaultPath;
148
- return null;
149
- }
150
- function _resolveHome(envPath) {
151
- return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
152
- }
153
- function _configVault(options) {
154
- const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
155
- const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
156
- if (debug || !quiet) _log("loading env from encrypted .env.vault");
157
- const parsed = DotenvModule._parseVault(options);
158
- let processEnv = process.env;
159
- if (options && options.processEnv != null) processEnv = options.processEnv;
160
- DotenvModule.populate(processEnv, parsed, options);
161
- return { parsed };
162
- }
163
- function configDotenv(options) {
164
- const dotenvPath = path.resolve(process.cwd(), ".env");
165
- let encoding = "utf8";
166
- let processEnv = process.env;
167
- if (options && options.processEnv != null) processEnv = options.processEnv;
168
- let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
169
- let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
170
- if (options && options.encoding) encoding = options.encoding;
171
- else if (debug) _debug("no encoding is specified (UTF-8 is used by default)");
172
- let optionPaths = [dotenvPath];
173
- if (options && options.path) if (!Array.isArray(options.path)) optionPaths = [_resolveHome(options.path)];
174
- else {
175
- optionPaths = [];
176
- for (const filepath of options.path) optionPaths.push(_resolveHome(filepath));
177
- }
178
- let lastError;
179
- const parsedAll = {};
180
- for (const path of optionPaths) try {
181
- const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }));
182
- DotenvModule.populate(parsedAll, parsed, options);
183
- } catch (e) {
184
- if (debug) _debug(`failed to load ${path} ${e.message}`);
185
- lastError = e;
186
- }
187
- const populated = DotenvModule.populate(processEnv, parsedAll, options);
188
- debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
189
- quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
190
- if (debug || !quiet) {
191
- const keysCount = Object.keys(populated).length;
192
- const shortPaths = [];
193
- for (const filePath of optionPaths) try {
194
- const relative = path.relative(process.cwd(), filePath);
195
- shortPaths.push(relative);
196
- } catch (e) {
197
- if (debug) _debug(`failed to load ${filePath} ${e.message}`);
198
- lastError = e;
199
- }
200
- _log(`injected env (${keysCount}) from ${shortPaths.join(",")} ${dim(`// tip: ${_getRandomTip()}`)}`);
201
- }
202
- if (lastError) return {
203
- parsed: parsedAll,
204
- error: lastError
205
- };
206
- else return { parsed: parsedAll };
207
- }
208
- function config(options) {
209
- if (_dotenvKey(options).length === 0) return DotenvModule.configDotenv(options);
210
- const vaultPath = _vaultPath(options);
211
- if (!vaultPath) {
212
- _warn(`you set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}`);
213
- return DotenvModule.configDotenv(options);
8
+ //#region ../../config/deployment-topology.json
9
+ var deployment_topology_default = {
10
+ schemaVersion: 1,
11
+ product: "capxul-platform",
12
+ environments: {
13
+ "development": {
14
+ "appEnv": "development",
15
+ "productRole": "development",
16
+ "convexProject": "capxul-platform-dev",
17
+ "deployment": "dev:incredible-possum-990",
18
+ "deploymentReference": "dev/aaron-griffith",
19
+ "name": "incredible-possum-990",
20
+ "convexType": "dev",
21
+ "convexUrl": "https://incredible-possum-990.convex.cloud",
22
+ "convexSiteUrl": "https://incredible-possum-990.convex.site",
23
+ "chainId": 84532,
24
+ "expectedFingerprints": {
25
+ "applicationId": "a4b0c7e61ec45aec",
26
+ "openfortPublishableKey": "aab42edbcdc683e7",
27
+ "shieldPublishableKey": "a323489c9a8d8da2"
28
+ },
29
+ "proofCommand": "APP_ENV=development vp exec bash scripts/capxul-env.sh vp exec node scripts/prove-deployment-bootstrap.mjs"
30
+ },
31
+ "staging": {
32
+ "appEnv": "staging",
33
+ "productRole": "staging",
34
+ "convexProject": "capxul-platform-effect",
35
+ "deployment": "prod:little-sandpiper-974",
36
+ "deploymentReference": "production",
37
+ "name": "little-sandpiper-974",
38
+ "convexType": "prod",
39
+ "convexUrl": "https://little-sandpiper-974.convex.cloud",
40
+ "convexSiteUrl": "https://little-sandpiper-974.convex.site",
41
+ "chainId": 84532,
42
+ "expectedFingerprints": {
43
+ "applicationId": "4e2989ea295fc269",
44
+ "openfortPublishableKey": "aab42edbcdc683e7",
45
+ "shieldPublishableKey": "a323489c9a8d8da2"
46
+ },
47
+ "proofCommand": "APP_ENV=staging vp exec bash scripts/capxul-env.sh vp exec node scripts/prove-deployment-bootstrap.mjs"
214
48
  }
215
- return DotenvModule._configVault(options);
216
- }
217
- function decrypt(encrypted, keyStr) {
218
- const key = Buffer.from(keyStr.slice(-64), "hex");
219
- let ciphertext = Buffer.from(encrypted, "base64");
220
- const nonce = ciphertext.subarray(0, 12);
221
- const authTag = ciphertext.subarray(-16);
222
- ciphertext = ciphertext.subarray(12, -16);
49
+ },
50
+ production: null
51
+ };
52
+ //#endregion
53
+ //#region ../../scripts/deployment-topology.mjs
54
+ const SUPPORTED_APP_ENVS = ["development", "staging"];
55
+ function loadDeploymentTopology() {
56
+ const topology = structuredClone(deployment_topology_default);
57
+ const environmentNames = Object.keys(topology.environments ?? {}).sort();
58
+ if (topology.schemaVersion !== 1 || topology.product !== "capxul-platform" || topology.production !== null || JSON.stringify(environmentNames) !== JSON.stringify(SUPPORTED_APP_ENVS)) throw new Error("Invalid Capxul deployment topology: expected development + staging and no production tier");
59
+ return topology;
60
+ }
61
+ function resolveDeploymentTier(appEnv) {
62
+ const tier = loadDeploymentTopology().environments[appEnv];
63
+ if (tier === void 0) throw new Error(`Unsupported APP_ENV '${String(appEnv)}'; expected development or staging (production is not provisioned)`);
64
+ return tier;
65
+ }
66
+ function assertDeploymentTuple(appEnv, values) {
67
+ const tier = resolveDeploymentTier(appEnv);
68
+ const expected = {
69
+ CONVEX_DEPLOYMENT: tier.deployment,
70
+ CONVEX_URL: tier.convexUrl,
71
+ CONVEX_SITE_URL: tier.convexSiteUrl
72
+ };
73
+ const mismatches = Object.entries(expected).flatMap(([name, expectedValue]) => {
74
+ const actual = values[name]?.trim();
75
+ return actual === expectedValue ? [] : [`${name}: expected '${expectedValue}', received '${actual ?? "<missing>"}'`];
76
+ });
77
+ const deployKey = values.CONVEX_DEPLOY_KEY?.trim();
78
+ if (deployKey !== void 0 && deployKey.length > 0) {
79
+ const separatorIndex = deployKey.indexOf("|");
80
+ const candidateDeployment = separatorIndex > 0 && deployKey.slice(separatorIndex + 1).trim().length > 0 ? deployKey.slice(0, separatorIndex) : "";
81
+ const keyDeployment = /^(?:dev|prod):[a-z0-9-]+$/u.test(candidateDeployment) ? candidateDeployment : void 0;
82
+ if (keyDeployment !== tier.deployment) mismatches.push(`CONVEX_DEPLOY_KEY: expected a key bound to '${tier.deployment}', received a key for '${keyDeployment ?? "<invalid>"}'`);
83
+ }
84
+ for (const optionalUrlName of ["CAPXUL_E2E_BOOTSTRAP_URL", "CAPXUL_E2E_BOOTSTRAP_BASE_URL"]) {
85
+ const raw = values[optionalUrlName]?.trim();
86
+ if (raw === void 0 || raw.length === 0) continue;
87
+ let actualHost;
223
88
  try {
224
- const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
225
- aesgcm.setAuthTag(authTag);
226
- return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
227
- } catch (error) {
228
- const isRange = error instanceof RangeError;
229
- const invalidKeyLength = error.message === "Invalid key length";
230
- const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
231
- if (isRange || invalidKeyLength) {
232
- const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
233
- err.code = "INVALID_DOTENV_KEY";
234
- throw err;
235
- } else if (decryptionFailed) {
236
- const err = /* @__PURE__ */ new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
237
- err.code = "DECRYPTION_FAILED";
238
- throw err;
239
- } else throw error;
89
+ actualHost = new URL(raw).host;
90
+ } catch {
91
+ mismatches.push(`${optionalUrlName}: expected a URL for ${tier.name}, received an invalid URL`);
92
+ continue;
240
93
  }
94
+ if (actualHost !== new URL(tier.convexSiteUrl).host) mismatches.push(`${optionalUrlName}: expected host '${new URL(tier.convexSiteUrl).host}', received '${actualHost}'`);
241
95
  }
242
- function populate(processEnv, parsed, options = {}) {
243
- const debug = Boolean(options && options.debug);
244
- const override = Boolean(options && options.override);
245
- const populated = {};
246
- if (typeof parsed !== "object") {
247
- const err = /* @__PURE__ */ new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
248
- err.code = "OBJECT_REQUIRED";
249
- throw err;
250
- }
251
- for (const key of Object.keys(parsed)) if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
252
- if (override === true) {
253
- processEnv[key] = parsed[key];
254
- populated[key] = parsed[key];
255
- }
256
- if (debug) if (override === true) _debug(`"${key}" is already defined and WAS overwritten`);
257
- else _debug(`"${key}" is already defined and was NOT overwritten`);
258
- } else {
259
- processEnv[key] = parsed[key];
260
- populated[key] = parsed[key];
261
- }
262
- return populated;
263
- }
264
- const DotenvModule = {
265
- configDotenv,
266
- _configVault,
267
- _parseVault,
268
- config,
269
- decrypt,
270
- parse,
271
- populate
272
- };
273
- module.exports.configDotenv = DotenvModule.configDotenv;
274
- module.exports._configVault = DotenvModule._configVault;
275
- module.exports._parseVault = DotenvModule._parseVault;
276
- module.exports.config = DotenvModule.config;
277
- module.exports.decrypt = DotenvModule.decrypt;
278
- module.exports.parse = DotenvModule.parse;
279
- module.exports.populate = DotenvModule.populate;
280
- module.exports = DotenvModule;
281
- })))();
96
+ if (mismatches.length > 0) throw new Error(`Deployment tuple mismatch: expected ${appEnv} '${tier.name}'. Refusing to continue.\n${mismatches.join("\n")}`);
97
+ return tier;
98
+ }
99
+ function runCli() {
100
+ const command = process.argv[2] ?? "check";
101
+ const appEnvIndex = process.argv.indexOf("--app-env");
102
+ const appEnv = appEnvIndex === -1 ? process.env.APP_ENV ?? "development" : process.argv[appEnvIndex + 1];
103
+ if (command === "print") {
104
+ process.stdout.write(`${JSON.stringify(resolveDeploymentTier(appEnv), null, 2)}\n`);
105
+ return;
106
+ }
107
+ if (command === "field") {
108
+ const fieldIndex = process.argv.indexOf("--field");
109
+ const field = fieldIndex === -1 ? void 0 : process.argv[fieldIndex + 1];
110
+ const tier = resolveDeploymentTier(appEnv);
111
+ if (field === void 0 || !Object.hasOwn(tier, field)) throw new Error(`Unknown deployment topology field '${String(field)}'`);
112
+ process.stdout.write(`${String(tier[field])}\n`);
113
+ return;
114
+ }
115
+ if (command !== "check") throw new Error(`Unknown deployment-topology command '${command}'; expected check, print, or field`);
116
+ const tier = assertDeploymentTuple(appEnv, process.env);
117
+ process.stdout.write(`Capxul ${tier.productRole} tuple verified: ${tier.deployment} (${tier.convexProject})\n`);
118
+ }
119
+ if (process.argv[1] !== void 0 && path.basename(process.argv[1]) === "deployment-topology.mjs" && import.meta.url === pathToFileURL(process.argv[1]).href) try {
120
+ runCli();
121
+ } catch (error) {
122
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
123
+ process.exitCode = 1;
124
+ }
282
125
  resolve(resolve(fileURLToPath(new URL("..", import.meta.url))), "..", "..");
283
- const defaultOperatorSecretsPath = resolve(homedir(), ".config/capxul/secrets.env");
126
+ const standingStaging = resolveDeploymentTier("staging");
284
127
  const CANONICAL_QUICKSTART_DEPLOYMENT = {
285
- deployment: "prod:little-sandpiper-974",
286
- convexUrl: "https://little-sandpiper-974.convex.cloud",
287
- convexSiteUrl: "https://little-sandpiper-974.convex.site"
128
+ deployment: standingStaging.deployment,
129
+ convexUrl: standingStaging.convexUrl,
130
+ convexSiteUrl: standingStaging.convexSiteUrl
288
131
  };
289
- function loadOperatorEnv(sourceEnv = process.env) {
290
- const env = { ...sourceEnv };
291
- const envFiles = [defaultOperatorSecretsPath, env.CAPXUL_SECRETS_FILE ? resolve(env.CAPXUL_SECRETS_FILE) : void 0].filter((path) => path !== void 0);
292
- for (const path of envFiles) if (existsSync(path)) {
293
- const parsed = (0, import_main.config)({
294
- path,
295
- processEnv: env,
296
- override: true
297
- });
298
- if (parsed.error !== void 0) throw parsed.error;
299
- }
300
- env.CONVEX_URL ||= env.CAPXUL_E2E_CONVEX_URL;
301
- env.CONVEX_SITE_URL ||= env.CAPXUL_E2E_CONVEX_SITE_URL;
302
- env.CAPXUL_E2E_ORIGIN ||= env.CAPXUL_E2E_BOOTSTRAP_URL || env.SITE_URL;
303
- return env;
304
- }
305
132
  function normalizeHttpOrigin(raw) {
306
133
  const value = raw.trim();
307
134
  if (value.length === 0) throw new Error("Origin is required.");
@@ -513,7 +340,7 @@ async function runFaucetSend() {
513
340
  const s = spinner();
514
341
  s.start("Minting testnet funds");
515
342
  try {
516
- const runConvex = canonicalConvexRunner(loadOperatorEnv());
343
+ const runConvex = canonicalConvexRunner();
517
344
  const result = recipient.kind === "address" ? runConvex("", [
518
345
  "run",
519
346
  FAUCET_ADDRESS_FUNCTION,
@@ -545,7 +372,8 @@ async function runFaucetSend() {
545
372
  return 0;
546
373
  } catch (error) {
547
374
  s.stop("Faucet send failed");
548
- cancel(errorMessage(error));
375
+ const hint = process.env.CONVEX_DEPLOY_KEY === void 0 ? "\nHint: CONVEX_DEPLOY_KEY is not set — the faucet needs operator access to the canonical deployment. Supply it (CONVEX_DEPLOY_KEY=… npx @capxul/sandbox faucet send) or, inside the infrastructure repo, run through scripts/capxul-env.sh." : "";
376
+ cancel(errorMessage(error) + hint);
549
377
  return 1;
550
378
  }
551
379
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capxul/sandbox",
3
- "version": "1.0.0-alpha.3",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "Capxul sandbox CLI for canonical quickstart keys and testnet faucet funds.",
6
6
  "keywords": [