tthr 0.3.11 → 0.3.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.
@@ -0,0 +1,721 @@
1
+ // src/commands/generate.ts
2
+ import chalk2 from "chalk";
3
+ import ora from "ora";
4
+ import fs3 from "fs-extra";
5
+ import path3 from "path";
6
+
7
+ // src/utils/auth.ts
8
+ import chalk from "chalk";
9
+ import fs from "fs-extra";
10
+ import path from "path";
11
+ var CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || "", ".tether");
12
+ var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
13
+ var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
14
+ var API_URL = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
15
+ var REFRESH_THRESHOLD_DAYS = 7;
16
+ async function getCredentials() {
17
+ try {
18
+ if (await fs.pathExists(CREDENTIALS_FILE)) {
19
+ return await fs.readJSON(CREDENTIALS_FILE);
20
+ }
21
+ } catch {
22
+ }
23
+ return null;
24
+ }
25
+ async function refreshSession(credentials) {
26
+ try {
27
+ const response = await fetch(`${API_URL}/auth/session/refresh`, {
28
+ method: "POST",
29
+ headers: {
30
+ "Content-Type": "application/json",
31
+ "Authorization": `Bearer ${credentials.accessToken}`
32
+ }
33
+ });
34
+ if (!response.ok) {
35
+ return null;
36
+ }
37
+ const data = await response.json();
38
+ const updated = {
39
+ ...credentials,
40
+ expiresAt: data.expiresAt
41
+ };
42
+ await saveCredentials(updated);
43
+ return updated;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ async function requireAuth() {
49
+ const credentials = await getCredentials();
50
+ if (!credentials) {
51
+ console.error(chalk.red("\n\u2717 Not logged in\n"));
52
+ console.log(chalk.dim("Run `tthr login` to authenticate\n"));
53
+ process.exit(1);
54
+ }
55
+ if (credentials.expiresAt) {
56
+ const expiresAt = new Date(credentials.expiresAt);
57
+ const now = /* @__PURE__ */ new Date();
58
+ if (now > expiresAt) {
59
+ await clearCredentials();
60
+ console.error(chalk.red("\n\u2717 Session expired \u2014 you have been signed out\n"));
61
+ console.log(chalk.dim("Run `tthr login` to authenticate\n"));
62
+ process.exit(1);
63
+ }
64
+ const daysUntilExpiry = (expiresAt.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24);
65
+ if (daysUntilExpiry <= REFRESH_THRESHOLD_DAYS) {
66
+ const refreshed = await refreshSession(credentials);
67
+ if (refreshed) {
68
+ return refreshed;
69
+ }
70
+ }
71
+ }
72
+ return credentials;
73
+ }
74
+ async function saveCredentials(credentials) {
75
+ await fs.ensureDir(CONFIG_DIR);
76
+ await fs.writeJSON(CREDENTIALS_FILE, credentials, { spaces: 2, mode: 384 });
77
+ }
78
+ async function clearCredentials() {
79
+ try {
80
+ await fs.remove(CREDENTIALS_FILE);
81
+ } catch {
82
+ }
83
+ }
84
+
85
+ // src/utils/config.ts
86
+ import fs2 from "fs-extra";
87
+ import path2 from "path";
88
+ var DEFAULT_CONFIG = {
89
+ schema: "./tether/schema.ts",
90
+ functions: "./tether/functions",
91
+ output: "./tether/_generated",
92
+ dev: {
93
+ port: 3001,
94
+ host: "localhost"
95
+ },
96
+ database: {
97
+ walMode: true
98
+ }
99
+ };
100
+ async function loadConfig(cwd = process.cwd()) {
101
+ const configPath = path2.resolve(cwd, "tether.config.ts");
102
+ if (!await fs2.pathExists(configPath)) {
103
+ return DEFAULT_CONFIG;
104
+ }
105
+ const configSource = await fs2.readFile(configPath, "utf-8");
106
+ const config = {};
107
+ const schemaMatch = configSource.match(/schema\s*:\s*['"]([^'"]+)['"]/);
108
+ if (schemaMatch) {
109
+ config.schema = schemaMatch[1];
110
+ }
111
+ const functionsMatch = configSource.match(/functions\s*:\s*['"]([^'"]+)['"]/);
112
+ if (functionsMatch) {
113
+ config.functions = functionsMatch[1];
114
+ }
115
+ const outputMatch = configSource.match(/output\s*:\s*['"]([^'"]+)['"]/);
116
+ if (outputMatch) {
117
+ config.output = outputMatch[1];
118
+ }
119
+ const projectIdMatch = configSource.match(/projectId\s*:\s*['"]([^'"]+)['"]/);
120
+ if (projectIdMatch) {
121
+ config.projectId = projectIdMatch[1];
122
+ }
123
+ const urlMatch = configSource.match(/(?:^|\n)\s*url\s*:\s*['"]([^'"]+)['"]/);
124
+ if (urlMatch) {
125
+ config.url = urlMatch[1];
126
+ }
127
+ const envMatch = configSource.match(/environment\s*:\s*['"]([^'"]+)['"]/);
128
+ if (envMatch) {
129
+ config.environment = envMatch[1];
130
+ }
131
+ const portMatch = configSource.match(/port\s*:\s*(\d+)/);
132
+ if (portMatch) {
133
+ config.dev = { ...config.dev, port: parseInt(portMatch[1], 10) };
134
+ }
135
+ const hostMatch = configSource.match(/host\s*:\s*['"]([^'"]+)['"]/);
136
+ if (hostMatch) {
137
+ config.dev = { ...config.dev, host: hostMatch[1] };
138
+ }
139
+ return {
140
+ ...DEFAULT_CONFIG,
141
+ ...config,
142
+ dev: { ...DEFAULT_CONFIG.dev, ...config.dev },
143
+ database: { ...DEFAULT_CONFIG.database, ...config.database }
144
+ };
145
+ }
146
+ async function resolveEnvironmentFromApiKey(cwd = process.cwd()) {
147
+ const envPath = path2.resolve(cwd, ".env");
148
+ if (!await fs2.pathExists(envPath)) return void 0;
149
+ const envContent = await fs2.readFile(envPath, "utf-8");
150
+ const match = envContent.match(/TETHER_API_KEY=\s*(tthr_([^_]+)_[0-9a-f]+)/);
151
+ if (!match) return void 0;
152
+ const prefix = match[2];
153
+ switch (prefix) {
154
+ case "dev":
155
+ return "development";
156
+ case "prod":
157
+ return "production";
158
+ default:
159
+ return prefix;
160
+ }
161
+ }
162
+ var DEFAULT_API_URL = "https://tether-api.strands.gg";
163
+ function getApiUrl(config) {
164
+ if (process.env.TETHER_API_URL) {
165
+ return process.env.TETHER_API_URL.replace(/\/+$/, "");
166
+ }
167
+ if (config?.url) {
168
+ return config.url.replace(/\/+$/, "");
169
+ }
170
+ const isDev3 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
171
+ return isDev3 ? "http://localhost:3001" : DEFAULT_API_URL;
172
+ }
173
+ function resolvePath(configPath, cwd = process.cwd()) {
174
+ const normalised = configPath.replace(/^\.\//, "");
175
+ return path2.resolve(cwd, normalised);
176
+ }
177
+ async function detectFramework(cwd = process.cwd()) {
178
+ const packageJsonPath = path2.resolve(cwd, "package.json");
179
+ if (!await fs2.pathExists(packageJsonPath)) {
180
+ return "unknown";
181
+ }
182
+ try {
183
+ const packageJson = await fs2.readJson(packageJsonPath);
184
+ const deps = {
185
+ ...packageJson.dependencies,
186
+ ...packageJson.devDependencies
187
+ };
188
+ if (deps.nuxt || deps["@nuxt/kit"]) {
189
+ return "nuxt";
190
+ }
191
+ if (deps.next) {
192
+ return "next";
193
+ }
194
+ if (deps["@sveltejs/kit"]) {
195
+ return "sveltekit";
196
+ }
197
+ if (deps.vite && !deps.nuxt && !deps.next && !deps["@sveltejs/kit"]) {
198
+ return "vite";
199
+ }
200
+ return "vanilla";
201
+ } catch {
202
+ return "unknown";
203
+ }
204
+ }
205
+ function getFrameworkDevCommand(framework) {
206
+ switch (framework) {
207
+ case "nuxt":
208
+ return "nuxt dev";
209
+ case "next":
210
+ return "next dev";
211
+ case "sveltekit":
212
+ return "vite dev";
213
+ case "vite":
214
+ return "vite dev";
215
+ case "vanilla":
216
+ return null;
217
+ // No default dev server for vanilla
218
+ default:
219
+ return null;
220
+ }
221
+ }
222
+
223
+ // src/commands/generate.ts
224
+ var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
225
+ var API_URL2 = isDev2 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
226
+ function parseSchemaFile(source) {
227
+ const tables = [];
228
+ const schemaMatch = source.match(/defineSchema\s*\(\s*\{([\s\S]*)\}\s*\)/);
229
+ if (!schemaMatch) return tables;
230
+ const schemaContent = schemaMatch[1];
231
+ const tableStartRegex = /(\w+)\s*:\s*\{/g;
232
+ let match;
233
+ while ((match = tableStartRegex.exec(schemaContent)) !== null) {
234
+ const tableName = match[1];
235
+ const startOffset = match.index + match[0].length;
236
+ let braceCount = 1;
237
+ let endOffset = startOffset;
238
+ for (let i = startOffset; i < schemaContent.length && braceCount > 0; i++) {
239
+ const char = schemaContent[i];
240
+ if (char === "{") braceCount++;
241
+ else if (char === "}") braceCount--;
242
+ endOffset = i;
243
+ }
244
+ const columnsContent = schemaContent.slice(startOffset, endOffset);
245
+ const columns = parseColumns(columnsContent);
246
+ tables.push({ name: tableName, columns });
247
+ }
248
+ return tables;
249
+ }
250
+ function parseColumns(content) {
251
+ const columns = [];
252
+ const columnRegex = /(\w+)\s*:\s*(\w+)(?:<([^>]+)>)?\s*\(\s*\)((?:\[.*?\]|[^,\n}])*)/g;
253
+ let match;
254
+ while ((match = columnRegex.exec(content)) !== null) {
255
+ const name = match[1];
256
+ const schemaType = match[2];
257
+ const genericType = match[3];
258
+ const modifiers = match[4] || "";
259
+ const nullable = !modifiers.includes(".notNull()");
260
+ const oneOfMatch = modifiers.match(/\.oneOf\s*\(\s*\[(.*?)\]\s*\)/);
261
+ let oneOf;
262
+ if (oneOfMatch) {
263
+ oneOf = [...oneOfMatch[1].matchAll(/['"]([^'"]+)['"]/g)].map((m) => m[1]);
264
+ }
265
+ columns.push({
266
+ name,
267
+ type: schemaTypeToTS(schemaType),
268
+ nullable,
269
+ jsonType: genericType,
270
+ // Store the generic type for json columns
271
+ oneOf
272
+ });
273
+ }
274
+ return columns;
275
+ }
276
+ function schemaTypeToTS(schemaType) {
277
+ switch (schemaType) {
278
+ case "text":
279
+ return "string";
280
+ case "integer":
281
+ return "number";
282
+ case "real":
283
+ return "number";
284
+ case "boolean":
285
+ return "boolean";
286
+ case "timestamp":
287
+ return "string";
288
+ case "json":
289
+ return "unknown";
290
+ case "blob":
291
+ return "Uint8Array";
292
+ case "asset":
293
+ return "TetherAsset";
294
+ // Asset object returned by API
295
+ default:
296
+ return "unknown";
297
+ }
298
+ }
299
+ function extractSingleType(source, typeName) {
300
+ const interfaceStart = new RegExp(`(?:export\\s+)?interface\\s+${typeName}\\s*\\{`);
301
+ const interfaceMatch = interfaceStart.exec(source);
302
+ if (interfaceMatch) {
303
+ const braceStart = interfaceMatch.index + interfaceMatch[0].length;
304
+ let braceCount = 1;
305
+ let endIdx = braceStart;
306
+ for (let i = braceStart; i < source.length && braceCount > 0; i++) {
307
+ if (source[i] === "{") braceCount++;
308
+ else if (source[i] === "}") braceCount--;
309
+ endIdx = i;
310
+ }
311
+ return source.slice(interfaceMatch.index, endIdx + 1).replace(/^export\s+/, "");
312
+ }
313
+ const typeStart = new RegExp(`(?:export\\s+)?type\\s+${typeName}\\s*=`);
314
+ const typeMatch = typeStart.exec(source);
315
+ if (typeMatch) {
316
+ const semiIdx = source.indexOf(";", typeMatch.index);
317
+ if (semiIdx !== -1) {
318
+ return source.slice(typeMatch.index, semiIdx + 1).replace(/^export\s+/, "");
319
+ }
320
+ }
321
+ return null;
322
+ }
323
+ function extractTypeDefinitions(source, typeNames) {
324
+ if (typeNames.size === 0) return [];
325
+ const allDefinedTypes = /* @__PURE__ */ new Set();
326
+ const typeDefRegex = /(?:export\s+)?(?:interface|type)\s+(\w+)/g;
327
+ let m;
328
+ while ((m = typeDefRegex.exec(source)) !== null) {
329
+ allDefinedTypes.add(m[1]);
330
+ }
331
+ const resolved = /* @__PURE__ */ new Map();
332
+ const queue = [...typeNames];
333
+ while (queue.length > 0) {
334
+ const name = queue.shift();
335
+ if (resolved.has(name)) continue;
336
+ const block = extractSingleType(source, name);
337
+ if (!block) continue;
338
+ resolved.set(name, block);
339
+ for (const definedType of allDefinedTypes) {
340
+ if (!resolved.has(definedType) && !queue.includes(definedType)) {
341
+ const refRegex = new RegExp(`\\b${definedType}\\b`);
342
+ if (refRegex.test(block)) {
343
+ queue.push(definedType);
344
+ }
345
+ }
346
+ }
347
+ }
348
+ const ordered = [];
349
+ const visited = /* @__PURE__ */ new Set();
350
+ function visit(name) {
351
+ if (visited.has(name)) return;
352
+ visited.add(name);
353
+ const block = resolved.get(name);
354
+ if (!block) return;
355
+ for (const dep of resolved.keys()) {
356
+ if (dep !== name && new RegExp(`\\b${dep}\\b`).test(block)) {
357
+ visit(dep);
358
+ }
359
+ }
360
+ ordered.push(name);
361
+ }
362
+ for (const name of resolved.keys()) {
363
+ visit(name);
364
+ }
365
+ return ordered.map((name) => resolved.get(name));
366
+ }
367
+ function tableNameToInterface(tableName) {
368
+ let name = tableName;
369
+ if (name.endsWith("ies")) {
370
+ name = name.slice(0, -3) + "y";
371
+ } else if (name.endsWith("s") && !name.endsWith("ss")) {
372
+ name = name.slice(0, -1);
373
+ }
374
+ return name.charAt(0).toUpperCase() + name.slice(1);
375
+ }
376
+ function generateDbFile(tables, schemaSource) {
377
+ const TS_PRIMITIVES = /* @__PURE__ */ new Set(["number", "string", "boolean", "unknown", "any", "void", "never", "undefined", "null", "object"]);
378
+ const jsonTypes = /* @__PURE__ */ new Set();
379
+ for (const table of tables) {
380
+ for (const col of table.columns) {
381
+ if (col.jsonType) {
382
+ const baseType = col.jsonType.replace(/\s*\|\s*null/g, "").replace(/\[\]/g, "").trim();
383
+ if (!TS_PRIMITIVES.has(baseType)) {
384
+ jsonTypes.add(baseType);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ const lines = [
390
+ "// Auto-generated by Tether CLI - do not edit manually",
391
+ `// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
392
+ "",
393
+ "import { createDatabaseProxy, type TetherDatabase } from '@tthr/client';",
394
+ "import {",
395
+ " type QueryDefinition,",
396
+ " type MutationDefinition,",
397
+ " z,",
398
+ "} from '@tthr/server';",
399
+ "import type { TetherEnv } from './env';"
400
+ ];
401
+ if (jsonTypes.size > 0) {
402
+ const typeBlocks = extractTypeDefinitions(schemaSource, jsonTypes);
403
+ if (typeBlocks.length > 0) {
404
+ lines.push("");
405
+ lines.push("// Schema types (inlined from schema.ts)");
406
+ for (const block of typeBlocks) {
407
+ lines.push(block);
408
+ lines.push("");
409
+ }
410
+ }
411
+ }
412
+ lines.push("");
413
+ lines.push("// Asset type returned by the API for asset columns");
414
+ lines.push("export interface TetherAsset {");
415
+ lines.push(" id: string;");
416
+ lines.push(" filename: string;");
417
+ lines.push(" contentType: string;");
418
+ lines.push(" size: number;");
419
+ lines.push(" url: string;");
420
+ lines.push(" createdAt: string;");
421
+ lines.push("}");
422
+ lines.push("");
423
+ for (const table of tables) {
424
+ const interfaceName = tableNameToInterface(table.name);
425
+ lines.push(`export interface ${interfaceName} {`);
426
+ lines.push(" _id: string;");
427
+ for (const col of table.columns) {
428
+ let colType;
429
+ if (col.oneOf && col.oneOf.length > 0) {
430
+ colType = col.oneOf.map((v) => `'${v}'`).join(" | ");
431
+ } else {
432
+ colType = col.jsonType || col.type;
433
+ }
434
+ const typeStr = col.nullable ? `${colType} | null` : colType;
435
+ const optional = col.nullable ? "?" : "";
436
+ lines.push(` ${col.name}${optional}: ${typeStr};`);
437
+ }
438
+ lines.push(" _createdAt: string;");
439
+ lines.push(" _updatedAt?: string | null;");
440
+ lines.push(" _deletedAt?: string | null;");
441
+ lines.push("}");
442
+ lines.push("");
443
+ }
444
+ lines.push("export interface Schema {");
445
+ for (const table of tables) {
446
+ const interfaceName = tableNameToInterface(table.name);
447
+ lines.push(` ${table.name}: ${interfaceName};`);
448
+ }
449
+ lines.push("}");
450
+ lines.push("");
451
+ lines.push("// Database client with typed tables");
452
+ lines.push("// This is a proxy that will be populated by the Tether runtime");
453
+ lines.push("export const db: TetherDatabase<Schema> = createDatabaseProxy<Schema>();");
454
+ lines.push("");
455
+ lines.push("// ============================================================================");
456
+ lines.push("// Typed function wrappers - use these instead of importing from @tthr/server");
457
+ lines.push("// This ensures the `db` and `env` parameters in handlers are properly typed");
458
+ lines.push("// ============================================================================");
459
+ lines.push("");
460
+ lines.push("/**");
461
+ lines.push(" * Define a query function with typed database and environment variable access.");
462
+ lines.push(" * The `db` and `env` parameters in the handler will have full type safety.");
463
+ lines.push(" */");
464
+ lines.push("export function query<TArgs = void, TResult = unknown>(");
465
+ lines.push(" definition: QueryDefinition<TArgs, TResult, Schema, TetherEnv>");
466
+ lines.push("): QueryDefinition<TArgs, TResult, Schema, TetherEnv> {");
467
+ lines.push(" return definition;");
468
+ lines.push("}");
469
+ lines.push("");
470
+ lines.push("/**");
471
+ lines.push(" * Define a mutation function with typed database and environment variable access.");
472
+ lines.push(" * The `db` and `env` parameters in the handler will have full type safety.");
473
+ lines.push(" */");
474
+ lines.push("export function mutation<TArgs = void, TResult = unknown>(");
475
+ lines.push(" definition: MutationDefinition<TArgs, TResult, Schema, TetherEnv>");
476
+ lines.push("): MutationDefinition<TArgs, TResult, Schema, TetherEnv> {");
477
+ lines.push(" return definition;");
478
+ lines.push("}");
479
+ lines.push("");
480
+ lines.push("// Re-export z for convenience");
481
+ lines.push("export { z };");
482
+ lines.push("");
483
+ return lines.join("\n");
484
+ }
485
+ async function parseFunctionsDir(functionsDir) {
486
+ const functions = [];
487
+ if (!await fs3.pathExists(functionsDir)) {
488
+ return functions;
489
+ }
490
+ const files = await fs3.readdir(functionsDir);
491
+ for (const file of files) {
492
+ if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
493
+ const filePath = path3.join(functionsDir, file);
494
+ const stat = await fs3.stat(filePath);
495
+ if (!stat.isFile()) continue;
496
+ const moduleName = file.replace(/\.(ts|js)$/, "");
497
+ const source = await fs3.readFile(filePath, "utf-8");
498
+ const exportRegex = /export\s+const\s+(\w+)\s*=\s*(query|mutation)\s*\(/g;
499
+ let match;
500
+ while ((match = exportRegex.exec(source)) !== null) {
501
+ functions.push({
502
+ name: match[1],
503
+ moduleName
504
+ });
505
+ }
506
+ }
507
+ return functions;
508
+ }
509
+ async function generateApiFile(functionsDir) {
510
+ const functions = await parseFunctionsDir(functionsDir);
511
+ const moduleMap = /* @__PURE__ */ new Map();
512
+ for (const fn of functions) {
513
+ if (!moduleMap.has(fn.moduleName)) {
514
+ moduleMap.set(fn.moduleName, []);
515
+ }
516
+ moduleMap.get(fn.moduleName).push(fn.name);
517
+ }
518
+ const lines = [
519
+ "// Auto-generated by Tether CLI - do not edit manually",
520
+ `// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
521
+ "",
522
+ "import { createApiProxy } from '@tthr/client';",
523
+ "",
524
+ "/**",
525
+ " * API function reference type for useQuery/useMutation.",
526
+ ' * The _name property contains the function path (e.g., "users.list").',
527
+ " */",
528
+ "export interface ApiFunction<TArgs = unknown, TResult = unknown> {",
529
+ " _name: string;",
530
+ " _args?: TArgs;",
531
+ " _result?: TResult;",
532
+ "}",
533
+ ""
534
+ ];
535
+ if (moduleMap.size > 0) {
536
+ for (const [moduleName, fnNames] of moduleMap) {
537
+ const interfaceName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1) + "Api";
538
+ lines.push(`export interface ${interfaceName} {`);
539
+ for (const fnName of fnNames) {
540
+ lines.push(` ${fnName}: ApiFunction;`);
541
+ }
542
+ lines.push("}");
543
+ lines.push("");
544
+ }
545
+ lines.push("export interface Api {");
546
+ for (const moduleName of moduleMap.keys()) {
547
+ const interfaceName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1) + "Api";
548
+ lines.push(` ${moduleName}: ${interfaceName};`);
549
+ }
550
+ lines.push("}");
551
+ } else {
552
+ lines.push("/**");
553
+ lines.push(" * Flexible API type that allows access to any function path.");
554
+ lines.push(" * Functions are accessed as api.moduleName.functionName");
555
+ lines.push(" * The actual types depend on your function definitions.");
556
+ lines.push(" */");
557
+ lines.push("export type Api = {");
558
+ lines.push(" [module: string]: {");
559
+ lines.push(" [fn: string]: ApiFunction;");
560
+ lines.push(" };");
561
+ lines.push("};");
562
+ }
563
+ lines.push("");
564
+ lines.push("// API client proxy - provides typed access to your functions");
565
+ lines.push("// On the client: returns { _name } references for useQuery/useMutation");
566
+ lines.push("// In Tether functions: calls the actual function implementation");
567
+ lines.push("export const api = createApiProxy<Api>();");
568
+ lines.push("");
569
+ return lines.join("\n");
570
+ }
571
+ async function fetchEnvVarKeys(projectId, environment) {
572
+ const credentials = await getCredentials();
573
+ if (!credentials) return [];
574
+ const envPath = environment !== "production" ? `/projects/${projectId}/env/${environment}/env-vars` : `/projects/${projectId}/env-vars`;
575
+ try {
576
+ const response = await fetch(`${API_URL2}${envPath}`, {
577
+ headers: {
578
+ "Authorization": `Bearer ${credentials.accessToken}`
579
+ }
580
+ });
581
+ if (!response.ok) return [];
582
+ const data = await response.json();
583
+ return (data.envVars || []).map((v) => v.key).sort();
584
+ } catch {
585
+ return [];
586
+ }
587
+ }
588
+ function generateEnvFile(keys) {
589
+ const lines = [
590
+ "// Auto-generated by Tether CLI - do not edit manually",
591
+ `// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
592
+ "",
593
+ "/**",
594
+ " * Typed environment variables for this project.",
595
+ " * Keys are fetched from the Tether API \u2014 values are never exposed.",
596
+ ' * Use `env.get("KEY")` in your functions to access them.',
597
+ " */",
598
+ ""
599
+ ];
600
+ if (keys.length > 0) {
601
+ lines.push("export type EnvVarKey =");
602
+ for (let i = 0; i < keys.length; i++) {
603
+ const sep = i === keys.length - 1 ? ";" : "";
604
+ lines.push(` | '${keys[i]}'${sep}`);
605
+ }
606
+ lines.push("");
607
+ lines.push("export interface TetherEnv {");
608
+ for (const key of keys) {
609
+ lines.push(` ${key}: string;`);
610
+ }
611
+ lines.push("}");
612
+ } else {
613
+ lines.push("export type EnvVarKey = string;");
614
+ lines.push("");
615
+ lines.push("export interface TetherEnv {");
616
+ lines.push(" [key: string]: string;");
617
+ lines.push("}");
618
+ }
619
+ lines.push("");
620
+ return lines.join("\n");
621
+ }
622
+ async function generateTypes(options = {}) {
623
+ const config = await loadConfig();
624
+ const schemaPath = resolvePath(config.schema);
625
+ const outputDir = resolvePath(config.output);
626
+ const functionsDir = resolvePath(config.functions);
627
+ if (!await fs3.pathExists(schemaPath)) {
628
+ throw new Error(`Schema file not found: ${schemaPath}`);
629
+ }
630
+ const schemaSource = await fs3.readFile(schemaPath, "utf-8");
631
+ const tables = parseSchemaFile(schemaSource);
632
+ const environment = config.environment || await resolveEnvironmentFromApiKey() || "development";
633
+ let projectId;
634
+ const envFilePath = path3.resolve(process.cwd(), ".env");
635
+ if (await fs3.pathExists(envFilePath)) {
636
+ const envContent = await fs3.readFile(envFilePath, "utf-8");
637
+ const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
638
+ projectId = match?.[1]?.trim();
639
+ }
640
+ const envVarKeys = projectId ? await fetchEnvVarKeys(projectId, environment) : [];
641
+ await fs3.ensureDir(outputDir);
642
+ await fs3.writeFile(
643
+ path3.join(outputDir, "db.ts"),
644
+ generateDbFile(tables, schemaSource)
645
+ );
646
+ await fs3.writeFile(
647
+ path3.join(outputDir, "api.ts"),
648
+ await generateApiFile(functionsDir)
649
+ );
650
+ await fs3.writeFile(
651
+ path3.join(outputDir, "env.ts"),
652
+ generateEnvFile(envVarKeys)
653
+ );
654
+ await fs3.writeFile(
655
+ path3.join(outputDir, "index.ts"),
656
+ `// Auto-generated by Tether CLI - do not edit manually
657
+ export * from './db';
658
+ export * from './api';
659
+ export * from './env';
660
+ `
661
+ );
662
+ return { tables, envVarKeys, outputDir };
663
+ }
664
+ async function generateCommand() {
665
+ await requireAuth();
666
+ const configPath = path3.resolve(process.cwd(), "tether.config.ts");
667
+ if (!await fs3.pathExists(configPath)) {
668
+ console.log(chalk2.red("\nError: Not a Tether project"));
669
+ console.log(chalk2.dim("Run `tthr init` to create a new project\n"));
670
+ process.exit(1);
671
+ }
672
+ console.log(chalk2.bold("\n\u26A1 Generating types from schema\n"));
673
+ const spinner = ora("Reading schema...").start();
674
+ try {
675
+ spinner.text = "Generating types...";
676
+ const { tables, envVarKeys, outputDir } = await generateTypes();
677
+ if (tables.length === 0) {
678
+ spinner.warn("No tables found in schema");
679
+ console.log(chalk2.dim(" Make sure your schema uses defineSchema({ ... })\n"));
680
+ return;
681
+ }
682
+ spinner.succeed(`Types generated for ${tables.length} table(s)`);
683
+ console.log("\n" + chalk2.green("\u2713") + " Tables:");
684
+ for (const table of tables) {
685
+ console.log(chalk2.dim(` - ${table.name} (${table.columns.length} columns)`));
686
+ }
687
+ if (envVarKeys.length > 0) {
688
+ console.log("\n" + chalk2.green("\u2713") + ` Environment variables: ${envVarKeys.length} key(s)`);
689
+ for (const key of envVarKeys) {
690
+ console.log(chalk2.dim(` - ${key}`));
691
+ }
692
+ }
693
+ const relativeOutput = path3.relative(process.cwd(), outputDir);
694
+ console.log("\n" + chalk2.green("\u2713") + " Generated files:");
695
+ console.log(chalk2.dim(` ${relativeOutput}/db.ts`));
696
+ console.log(chalk2.dim(` ${relativeOutput}/api.ts`));
697
+ console.log(chalk2.dim(` ${relativeOutput}/env.ts`));
698
+ console.log(chalk2.dim(` ${relativeOutput}/index.ts
699
+ `));
700
+ } catch (error) {
701
+ spinner.fail("Failed to generate types");
702
+ console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
703
+ process.exit(1);
704
+ }
705
+ }
706
+
707
+ export {
708
+ API_URL,
709
+ getCredentials,
710
+ requireAuth,
711
+ saveCredentials,
712
+ clearCredentials,
713
+ loadConfig,
714
+ resolveEnvironmentFromApiKey,
715
+ getApiUrl,
716
+ resolvePath,
717
+ detectFramework,
718
+ getFrameworkDevCommand,
719
+ generateTypes,
720
+ generateCommand
721
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ generateCommand,
3
+ generateTypes
4
+ } from "./chunk-2QPN6NDY.js";
5
+ export {
6
+ generateCommand,
7
+ generateTypes
8
+ };
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  detectFramework,
6
6
  generateCommand,
7
7
  generateTypes,
8
+ getApiUrl,
8
9
  getCredentials,
9
10
  getFrameworkDevCommand,
10
11
  loadConfig,
@@ -12,7 +13,7 @@ import {
12
13
  resolveEnvironmentFromApiKey,
13
14
  resolvePath,
14
15
  saveCredentials
15
- } from "./chunk-QNUE3MEY.js";
16
+ } from "./chunk-2QPN6NDY.js";
16
17
 
17
18
  // src/index.ts
18
19
  import { Command } from "commander";
@@ -34,8 +35,6 @@ import ora from "ora";
34
35
  import fs from "fs-extra";
35
36
  import path from "path";
36
37
  import * as esbuild from "esbuild";
37
- var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
38
- var API_URL2 = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
39
38
  async function deployCommand(options) {
40
39
  const credentials = await requireAuth();
41
40
  const configPath = path.resolve(process.cwd(), "tether.config.ts");
@@ -60,10 +59,11 @@ async function deployCommand(options) {
60
59
  process.exit(1);
61
60
  }
62
61
  const environment = options.env || config.environment || await resolveEnvironmentFromApiKey() || "development";
62
+ const API_URL4 = `${getApiUrl(config)}/api/v1`;
63
63
  console.log(chalk.bold("\n\u26A1 Deploying to Tether\n"));
64
64
  console.log(chalk.dim(` Project: ${projectId}`));
65
65
  console.log(chalk.dim(` Environment: ${environment}`));
66
- console.log(chalk.dim(` API: ${API_URL2}
66
+ console.log(chalk.dim(` API: ${API_URL4}
67
67
  `));
68
68
  console.log(chalk.dim(" Generating types..."));
69
69
  try {
@@ -84,7 +84,8 @@ async function deployCommand(options) {
84
84
  }
85
85
  console.log(chalk.green("\n\u2713 Deployment complete\n"));
86
86
  }
87
- async function deploySchemaToServer(projectId, token, schemaPath, environment, dryRun) {
87
+ async function deploySchemaToServer(projectId, token, schemaPath, environment, dryRun, apiUrl) {
88
+ const API_URL4 = apiUrl || `${getApiUrl()}/api/v1`;
88
89
  const spinner = ora("Reading schema...").start();
89
90
  try {
90
91
  if (!await fs.pathExists(schemaPath)) {
@@ -114,7 +115,7 @@ async function deploySchemaToServer(projectId, token, schemaPath, environment, d
114
115
  return;
115
116
  }
116
117
  spinner.text = "Deploying schema...";
117
- const schemaUrl = `${API_URL2}/projects/${projectId}/env/${environment}/deploy/schema`;
118
+ const schemaUrl = `${API_URL4}/projects/${projectId}/env/${environment}/deploy/schema`;
118
119
  console.log(chalk.dim(`
119
120
  URL: ${schemaUrl}`));
120
121
  const rlsConfigs = tables.filter((t) => t.rls).map((t) => ({
@@ -188,7 +189,8 @@ ${messages.join("\n")}`);
188
189
  }
189
190
  return result.outputFiles[0].text;
190
191
  }
191
- async function deployFunctionsToServer(projectId, token, functionsDir, environment, dryRun) {
192
+ async function deployFunctionsToServer(projectId, token, functionsDir, environment, dryRun, apiUrl) {
193
+ const API_URL4 = apiUrl || `${getApiUrl()}/api/v1`;
192
194
  const spinner = ora("Reading functions...").start();
193
195
  try {
194
196
  if (!await fs.pathExists(functionsDir)) {
@@ -245,7 +247,7 @@ ${helperSource}`;
245
247
  return;
246
248
  }
247
249
  spinner.text = "Deploying functions...";
248
- const functionsUrl = `${API_URL2}/projects/${projectId}/env/${environment}/deploy/functions`;
250
+ const functionsUrl = `${API_URL4}/projects/${projectId}/env/${environment}/deploy/functions`;
249
251
  console.log(chalk.dim(`
250
252
  URL: ${functionsUrl}`));
251
253
  const response = await fetch(functionsUrl, {
@@ -583,8 +585,8 @@ function parseFunctions(moduleName, source) {
583
585
  }
584
586
 
585
587
  // src/commands/init.ts
586
- var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
587
- var API_URL3 = isDev2 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
588
+ var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
589
+ var API_URL2 = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
588
590
  async function getLatestVersion(packageName) {
589
591
  try {
590
592
  const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
@@ -686,7 +688,7 @@ ${packageManager} is not installed on your system.`));
686
688
  await scaffoldVanillaProject(projectName, projectPath, spinner);
687
689
  }
688
690
  spinner.text = "Creating project on Tether...";
689
- const response = await fetch(`${API_URL3}/projects`, {
691
+ const response = await fetch(`${API_URL2}/projects`, {
690
692
  method: "POST",
691
693
  headers: {
692
694
  "Content-Type": "application/json",
@@ -1243,8 +1245,8 @@ PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
1243
1245
  }
1244
1246
  }
1245
1247
  }
1246
- function getInstallCommand(pm, isDev7 = false) {
1247
- const devFlag = isDev7 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
1248
+ function getInstallCommand(pm, isDev3 = false) {
1249
+ const devFlag = isDev3 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
1248
1250
  return `${pm} ${pm === "npm" ? "install" : "add"} ${devFlag}`.trim();
1249
1251
  }
1250
1252
  async function installTetherPackages(projectPath, template, packageManager) {
@@ -1689,8 +1691,6 @@ import fs3 from "fs-extra";
1689
1691
  import path3 from "path";
1690
1692
  import { spawn } from "child_process";
1691
1693
  import { watch } from "chokidar";
1692
- var isDev3 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
1693
- var API_BASE = isDev3 ? "http://localhost:3001" : "https://tether-api.strands.gg";
1694
1694
  var frameworkProcess = null;
1695
1695
  var schemaWatcher = null;
1696
1696
  var functionsWatcher = null;
@@ -1706,7 +1706,7 @@ async function runGenerate(spinner) {
1706
1706
  }
1707
1707
  isGenerating = true;
1708
1708
  try {
1709
- const { generateTypes: generateTypes2 } = await import("./generate-DYQ67MHG.js");
1709
+ const { generateTypes: generateTypes2 } = await import("./generate-KAPPABOL.js");
1710
1710
  spinner.text = "Regenerating types...";
1711
1711
  await generateTypes2({ silent: true });
1712
1712
  spinner.succeed("Types regenerated");
@@ -1813,8 +1813,9 @@ async function validateFunctions(functionsDir) {
1813
1813
  }
1814
1814
  return { valid: errors.length === 0, errors };
1815
1815
  }
1816
- function connectToEnvironment(projectId, environment, token, spinner) {
1817
- const wsBase = API_BASE.replace("https://", "wss://").replace("http://", "ws://");
1816
+ function connectToEnvironment(projectId, environment, token, spinner, apiBase) {
1817
+ const base = apiBase || getApiUrl();
1818
+ const wsBase = base.replace("https://", "wss://").replace("http://", "ws://");
1818
1819
  const wsUrl = environment && environment !== "production" ? `${wsBase}/ws/${projectId}/${environment}?token=${encodeURIComponent(token)}` : `${wsBase}/ws/${projectId}?token=${encodeURIComponent(token)}`;
1819
1820
  let reconnectAttempts = 0;
1820
1821
  const maxReconnectAttempts = 10;
@@ -1976,7 +1977,7 @@ async function devCommand(options) {
1976
1977
  }
1977
1978
  }
1978
1979
  spinner.text = "Generating types...";
1979
- const { generateTypes: generateTypes2 } = await import("./generate-DYQ67MHG.js");
1980
+ const { generateTypes: generateTypes2 } = await import("./generate-KAPPABOL.js");
1980
1981
  await generateTypes2({ silent: true });
1981
1982
  spinner.succeed("Types generated");
1982
1983
  if (config.projectId) {
@@ -2033,7 +2034,7 @@ async function devCommand(options) {
2033
2034
  }
2034
2035
  spinner.succeed("File watchers ready");
2035
2036
  if (config.projectId) {
2036
- connectToEnvironment(config.projectId, environment, credentials.accessToken, spinner);
2037
+ connectToEnvironment(config.projectId, environment, credentials.accessToken, spinner, getApiUrl(config));
2037
2038
  }
2038
2039
  if (devCmd && !options.skipFramework) {
2039
2040
  spinner.start(`Starting ${framework} dev server...`);
@@ -2061,9 +2062,9 @@ import ora4 from "ora";
2061
2062
  import os from "os";
2062
2063
  import { exec } from "child_process";
2063
2064
  import readline from "readline";
2064
- var isDev4 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
2065
- var API_URL4 = isDev4 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
2066
- var AUTH_URL = isDev4 ? "http://localhost:3000/cli" : "https://tthr.io/cli";
2065
+ var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
2066
+ var API_URL3 = `${getApiUrl()}/api/v1`;
2067
+ var AUTH_URL = isDev2 ? "http://localhost:3000/cli" : "https://tthr.io/cli";
2067
2068
  async function loginCommand() {
2068
2069
  console.log(chalk4.bold("\u26A1 Login to Tether\n"));
2069
2070
  const existing = await getCredentials();
@@ -2152,7 +2153,7 @@ async function requestDeviceCode() {
2152
2153
  const userCode = generateUserCode();
2153
2154
  const deviceCode = crypto.randomUUID();
2154
2155
  const deviceName = os.hostname();
2155
- const response = await fetch(`${API_URL4}/auth/device`, {
2156
+ const response = await fetch(`${API_URL3}/auth/device`, {
2156
2157
  method: "POST",
2157
2158
  headers: { "Content-Type": "application/json" },
2158
2159
  body: JSON.stringify({ userCode, deviceCode, deviceName })
@@ -2186,7 +2187,7 @@ async function pollForApproval(deviceCode, interval, expiresIn) {
2186
2187
  while (Date.now() < expiresAt) {
2187
2188
  await sleep(interval * 1e3);
2188
2189
  spinner.text = `Checking... ${chalk4.dim(`(${Math.ceil((expiresAt - Date.now()) / 1e3)}s remaining)`)}`;
2189
- const response = await fetch(`${API_URL4}/auth/device/${deviceCode}`, {
2190
+ const response = await fetch(`${API_URL3}/auth/device/${deviceCode}`, {
2190
2191
  method: "GET"
2191
2192
  }).catch(() => null);
2192
2193
  updateCountdown();
@@ -2278,18 +2279,18 @@ function detectPackageManager() {
2278
2279
  }
2279
2280
  return "npm";
2280
2281
  }
2281
- function getInstallCommand2(pm, packages, isDev7) {
2282
+ function getInstallCommand2(pm, packages, isDev3) {
2282
2283
  const packagesStr = packages.join(" ");
2283
2284
  switch (pm) {
2284
2285
  case "bun":
2285
- return isDev7 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
2286
+ return isDev3 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
2286
2287
  case "pnpm":
2287
- return isDev7 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
2288
+ return isDev3 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
2288
2289
  case "yarn":
2289
- return isDev7 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
2290
+ return isDev3 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
2290
2291
  case "npm":
2291
2292
  default:
2292
- return isDev7 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
2293
+ return isDev3 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
2293
2294
  }
2294
2295
  }
2295
2296
  async function getLatestVersion2(packageName) {
@@ -2398,8 +2399,6 @@ import chalk6 from "chalk";
2398
2399
  import ora6 from "ora";
2399
2400
  import fs5 from "fs-extra";
2400
2401
  import path5 from "path";
2401
- var isDev5 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
2402
- var API_URL5 = isDev5 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
2403
2402
  async function execCommand(sql) {
2404
2403
  if (!sql || sql.trim() === "") {
2405
2404
  console.log(chalk6.red("\nError: SQL query required"));
@@ -2419,9 +2418,11 @@ async function execCommand(sql) {
2419
2418
  console.log(chalk6.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
2420
2419
  process.exit(1);
2421
2420
  }
2421
+ const config = await loadConfig();
2422
+ const API_URL4 = `${getApiUrl(config)}/api/v1`;
2422
2423
  const spinner = ora6("Executing query...").start();
2423
2424
  try {
2424
- const response = await fetch(`${API_URL5}/projects/${projectId}/exec`, {
2425
+ const response = await fetch(`${API_URL4}/projects/${projectId}/exec`, {
2425
2426
  method: "POST",
2426
2427
  headers: {
2427
2428
  "Content-Type": "application/json",
@@ -2471,8 +2472,10 @@ import chalk7 from "chalk";
2471
2472
  import ora7 from "ora";
2472
2473
  import fs6 from "fs-extra";
2473
2474
  import path6 from "path";
2474
- var isDev6 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
2475
- var API_URL6 = isDev6 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
2475
+ async function resolveApiUrl() {
2476
+ const config = await loadConfig();
2477
+ return `${getApiUrl(config)}/api/v1`;
2478
+ }
2476
2479
  async function getProjectId() {
2477
2480
  const envPath = path6.resolve(process.cwd(), ".env");
2478
2481
  let projectId;
@@ -2491,9 +2494,10 @@ async function getProjectId() {
2491
2494
  async function envListCommand() {
2492
2495
  const credentials = await requireAuth();
2493
2496
  const projectId = await getProjectId();
2497
+ const API_URL4 = await resolveApiUrl();
2494
2498
  const spinner = ora7("Fetching environments...").start();
2495
2499
  try {
2496
- const response = await fetch(`${API_URL6}/projects/${projectId}/environments`, {
2500
+ const response = await fetch(`${API_URL4}/projects/${projectId}/environments`, {
2497
2501
  headers: {
2498
2502
  "Authorization": `Bearer ${credentials.accessToken}`
2499
2503
  }
@@ -2523,6 +2527,7 @@ async function envListCommand() {
2523
2527
  async function envCreateCommand(name, options) {
2524
2528
  const credentials = await requireAuth();
2525
2529
  const projectId = await getProjectId();
2530
+ const API_URL4 = await resolveApiUrl();
2526
2531
  const normalizedName = name.toLowerCase();
2527
2532
  if (!/^[a-z][a-z0-9-_]*$/.test(normalizedName)) {
2528
2533
  console.log(chalk7.red("\nError: Invalid environment name"));
@@ -2536,7 +2541,7 @@ async function envCreateCommand(name, options) {
2536
2541
  body.cloneFrom = options.from;
2537
2542
  spinner.text = `Creating environment '${normalizedName}' from '${options.from}'...`;
2538
2543
  }
2539
- const response = await fetch(`${API_URL6}/projects/${projectId}/environments`, {
2544
+ const response = await fetch(`${API_URL4}/projects/${projectId}/environments`, {
2540
2545
  method: "POST",
2541
2546
  headers: {
2542
2547
  "Content-Type": "application/json",
@@ -2565,9 +2570,10 @@ async function envCreateCommand(name, options) {
2565
2570
  async function envDeleteCommand(name) {
2566
2571
  const credentials = await requireAuth();
2567
2572
  const projectId = await getProjectId();
2573
+ const API_URL4 = await resolveApiUrl();
2568
2574
  const spinner = ora7(`Deleting environment '${name}'...`).start();
2569
2575
  try {
2570
- const response = await fetch(`${API_URL6}/projects/${projectId}/environments/${name}`, {
2576
+ const response = await fetch(`${API_URL4}/projects/${projectId}/environments/${name}`, {
2571
2577
  method: "DELETE",
2572
2578
  headers: {
2573
2579
  "Authorization": `Bearer ${credentials.accessToken}`
@@ -2587,9 +2593,10 @@ async function envDeleteCommand(name) {
2587
2593
  async function envDefaultCommand(name) {
2588
2594
  const credentials = await requireAuth();
2589
2595
  const projectId = await getProjectId();
2596
+ const API_URL4 = await resolveApiUrl();
2590
2597
  const spinner = ora7(`Setting '${name}' as default environment...`).start();
2591
2598
  try {
2592
- const response = await fetch(`${API_URL6}/projects/${projectId}/environments/default`, {
2599
+ const response = await fetch(`${API_URL4}/projects/${projectId}/environments/default`, {
2593
2600
  method: "PATCH",
2594
2601
  headers: {
2595
2602
  "Content-Type": "application/json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tthr",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Tether CLI - project scaffolding and deployment",
5
5
  "type": "module",
6
6
  "bin": {