wawesome 0.0.5 → 0.0.6

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 +12 -0
  2. package/dist/index.mjs +353 -25
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,6 +32,15 @@ mkdir my-wasm-app && cd my-wasm-app
32
32
  npx wawesome init
33
33
  ```
34
34
 
35
+ `init` asks for the function name and for the **App slug** — the App groups the Functions of one
36
+ project, and its slug is part of the public URL your client sees. Both default to the directory
37
+ name, so naming is usually a matter of pressing enter.
38
+
39
+ An App slug must be a legal hostname label: lowercase letters, numbers, and single hyphens between
40
+ them (no leading or trailing hyphen, 63 characters at most). Type something else — `My Client` —
41
+ and the CLI shows you the slug it would become (`my-client`) and asks again, rather than rewriting
42
+ your answer behind your back.
43
+
35
44
  ### 4. Build & Deploy
36
45
 
37
46
  Deploy your serverless function to Wawesome Cloud instantly:
@@ -146,6 +155,9 @@ Every project directory includes a `wawesome-function.json` file generated durin
146
155
  }
147
156
  ```
148
157
 
158
+ `app` is the App this Function is deployed into, and it is client-facing — every deploy from this
159
+ directory is scoped to it.
160
+
149
161
  ### Local Development / Gateway Overrides
150
162
 
151
163
  If you are running a local gateway or self-hosted instance, you can configure your CLI Gateway URL using any of the
package/dist/index.mjs CHANGED
@@ -148,8 +148,166 @@ async function buildJs(entryInput, options) {
148
148
  }
149
149
  }
150
150
  //#endregion
151
+ //#region src/prompt.ts
152
+ /**
153
+ * Open a prompt session on stdin, queueing lines as they arrive.
154
+ *
155
+ * Reading one `rl.question` at a time drops piped input: it lands as a single
156
+ * chunk, so every line after the first is emitted with no question pending.
157
+ * End of input answers the remaining questions with their defaults.
158
+ */
159
+ function openPromptSession() {
160
+ const rl = readline.createInterface({
161
+ input: process.stdin,
162
+ output: process.stdout
163
+ });
164
+ const queued = [];
165
+ const waiting = [];
166
+ let ended = false;
167
+ rl.on("line", (line) => {
168
+ const next = waiting.shift();
169
+ if (next) next(line);
170
+ else queued.push(line);
171
+ });
172
+ rl.on("close", () => {
173
+ ended = true;
174
+ while (waiting.length > 0) waiting.shift()?.(null);
175
+ });
176
+ function nextLine() {
177
+ if (queued.length > 0) return Promise.resolve(queued.shift());
178
+ if (ended) return Promise.resolve(null);
179
+ return new Promise((resolve) => waiting.push(resolve));
180
+ }
181
+ return {
182
+ async ask(question, defaultVal) {
183
+ const offered = defaultVal ? ` (${defaultVal})` : "";
184
+ process.stdout.write(`${question}${offered}: `);
185
+ const line = await nextLine();
186
+ if (line === null) {
187
+ process.stdout.write("\n");
188
+ return defaultVal;
189
+ }
190
+ return line.trim() || defaultVal;
191
+ },
192
+ close() {
193
+ rl.close();
194
+ }
195
+ };
196
+ }
197
+ /**
198
+ * Whether a question can actually be put to somebody. A piped or redirected
199
+ * stdin still answers — with end-of-input — so this only tells a caller whether
200
+ * a *default* is going to be what the user gets.
201
+ */
202
+ function isInteractive() {
203
+ return Boolean(process.stdin.isTTY);
204
+ }
205
+ //#endregion
206
+ //#region src/tenant.ts
207
+ /** A rejection carrying the gateway's own prose and, where given, its reason. */
208
+ var GatewayError = class extends Error {
209
+ status;
210
+ reason;
211
+ constructor(message, status, reason) {
212
+ super(message);
213
+ this.name = "GatewayError";
214
+ this.status = status;
215
+ this.reason = reason;
216
+ }
217
+ };
218
+ async function asGatewayError(res, fallback) {
219
+ const body = await res.text();
220
+ try {
221
+ const parsed = JSON.parse(body);
222
+ return new GatewayError(parsed.error || fallback, res.status, parsed.reason);
223
+ } catch {
224
+ return new GatewayError(fallback, res.status);
225
+ }
226
+ }
227
+ async function fetchTenantDetails(creds) {
228
+ const res = await fetch(`${creds.gateway_url}/v1/tenant`, { headers: { Authorization: `Bearer ${creds.tenant_jwt}` } });
229
+ if (!res.ok) throw await asGatewayError(res, `Failed to read workspace (HTTP ${res.status}).`);
230
+ return await res.json();
231
+ }
232
+ /**
233
+ * The workspace's name and slug, preferring what login already stored.
234
+ *
235
+ * Credentials written before the slug was stored have neither, so this fetches
236
+ * and writes them back rather than making every later command ask again.
237
+ */
238
+ async function resolveWorkspace(creds) {
239
+ if (creds.tenant_slug && creds.tenant_name) return {
240
+ name: creds.tenant_name,
241
+ slug: creds.tenant_slug
242
+ };
243
+ const tenant = await fetchTenantDetails(creds);
244
+ writeCredentials({
245
+ ...creds,
246
+ tenant_slug: tenant.tenant_slug,
247
+ tenant_name: tenant.name
248
+ });
249
+ return {
250
+ name: tenant.name,
251
+ slug: tenant.tenant_slug
252
+ };
253
+ }
254
+ /**
255
+ * Ask for the workspace's public address to become `slug`.
256
+ *
257
+ * The slug is sent exactly as typed. Sanitizing it the way an App slug is
258
+ * sanitized would hand the user a public address they never chose, so an
259
+ * unusable name comes back as a `malformed` rejection instead.
260
+ */
261
+ async function renameTenantSlug(creds, slug) {
262
+ const res = await fetch(`${creds.gateway_url}/v1/tenant/slug`, {
263
+ method: "PUT",
264
+ headers: {
265
+ Authorization: `Bearer ${creds.tenant_jwt}`,
266
+ "Content-Type": "application/json"
267
+ },
268
+ body: JSON.stringify({ slug })
269
+ });
270
+ if (!res.ok) throw await asGatewayError(res, `Rename failed (HTTP ${res.status}).`);
271
+ const result = await res.json();
272
+ const stored = readCredentials();
273
+ if (stored) writeCredentials({
274
+ ...stored,
275
+ tenant_slug: result.tenant_slug
276
+ });
277
+ return result;
278
+ }
279
+ /** The address a deployed Function answers on, for a workspace and app. */
280
+ function publicInvokeUrl(gatewayUrl, tenantSlug, appSlug, functionName) {
281
+ return `${gatewayUrl}/v1/s/${encodeURIComponent(tenantSlug)}/apps/${encodeURIComponent(appSlug)}/functions/${encodeURIComponent(functionName)}/invoke`;
282
+ }
283
+ //#endregion
151
284
  //#region src/auth.ts
152
285
  /**
286
+ * Ask what to call the workspace being created.
287
+ *
288
+ * There is no default worth offering: a name nobody typed is exactly what this
289
+ * replaced. A non-interactive login has nobody to ask, so it says what flag to
290
+ * pass rather than inventing one and minting a workspace under it.
291
+ */
292
+ async function promptForWorkspaceName(options) {
293
+ const supplied = options.workspace?.trim();
294
+ if (supplied) return supplied;
295
+ if (!isInteractive()) throw new Error("No workspace found, and no name to create one with. Re-run with --workspace <name>.");
296
+ console.log("\n[wawesome] You don't have a workspace yet — let's create one.");
297
+ console.log(" (this is the name you'll see in the dashboard; its public address is set separately)");
298
+ const session = openPromptSession();
299
+ try {
300
+ for (let attempt = 0; attempt < 3; attempt++) {
301
+ const answer = await session.ask(" Workspace name?", "");
302
+ if (answer.trim()) return answer.trim();
303
+ console.log("[wawesome] A workspace needs a name.");
304
+ }
305
+ } finally {
306
+ session.close();
307
+ }
308
+ throw new Error("No workspace name given. Nothing was created.");
309
+ }
310
+ /**
153
311
  * OAuth login flow:
154
312
  * 1. Build Supabase OAuth URL
155
313
  * 2. Open browser
@@ -217,18 +375,24 @@ async function login(options) {
217
375
  if (!tenantsRes.ok) throw new Error(`Failed to fetch tenants (HTTP ${tenantsRes.status}). Have you completed onboarding?`);
218
376
  const tenants = await tenantsRes.json();
219
377
  let primaryTenantId;
378
+ let workspaceName;
379
+ let workspaceSlug;
220
380
  if (tenants.length === 0) {
221
- console.log("[wawesome] No workspaces found. Initializing default workspace...");
381
+ const chosenName = await promptForWorkspaceName(options);
382
+ console.log(`[wawesome] Creating workspace "${chosenName}"...`);
222
383
  const initRes = await fetch(`${gatewayUrl}/api/v1/onboarding/init`, {
223
384
  method: "POST",
224
385
  headers: {
225
386
  Authorization: `Bearer ${accessToken}`,
226
387
  "Content-Type": "application/json"
227
388
  },
228
- body: JSON.stringify({ tenant_name: "Personal Workspace" })
389
+ body: JSON.stringify({ tenant_name: chosenName })
229
390
  });
230
391
  if (!initRes.ok) throw new Error(`Failed to initialize workspace (HTTP ${initRes.status}).`);
231
- primaryTenantId = (await initRes.json()).tenant_id;
392
+ const initData = await initRes.json();
393
+ primaryTenantId = initData.tenant_id;
394
+ workspaceSlug = initData.tenant_slug;
395
+ workspaceName = initData.tenant_name || chosenName;
232
396
  } else primaryTenantId = tenants[0].tenant_id;
233
397
  if (isVerbose) console.log(`[wawesome:verbose] Using tenant: ${primaryTenantId}`);
234
398
  console.log("[wawesome] Exchanging for tenant-scoped credentials...");
@@ -242,6 +406,8 @@ async function login(options) {
242
406
  });
243
407
  if (!exchangeRes.ok) throw new Error(`Token exchange failed (HTTP ${exchangeRes.status}).`);
244
408
  const exchangeData = await exchangeRes.json();
409
+ workspaceSlug = exchangeData.tenant_slug || workspaceSlug;
410
+ workspaceName = exchangeData.tenant_name || workspaceName;
245
411
  let userEmail = exchangeData.email || "unknown";
246
412
  try {
247
413
  const payloadPart = exchangeData.tenant_jwt.split(".")[1];
@@ -252,14 +418,18 @@ async function login(options) {
252
418
  gateway_url: gatewayUrl,
253
419
  tenant_jwt: exchangeData.tenant_jwt,
254
420
  tenant_id: primaryTenantId,
255
- user_email: userEmail
421
+ user_email: userEmail,
422
+ tenant_slug: workspaceSlug,
423
+ tenant_name: workspaceName
256
424
  });
257
425
  console.log("\n======================================================");
258
426
  console.log("🎉 \x1B[32mLOGIN SUCCESSFUL!\x1B[0m");
259
427
  console.log("======================================================");
260
- console.log(`\n Tenant: ${primaryTenantId}`);
261
- console.log(` Gateway: ${gatewayUrl}`);
262
- console.log(` Email: ${userEmail}`);
428
+ if (workspaceName) console.log(`\n Workspace: ${workspaceName}`);
429
+ if (workspaceSlug) console.log(` Address: ${workspaceSlug}`);
430
+ console.log(` Tenant: ${primaryTenantId}`);
431
+ console.log(` Gateway: ${gatewayUrl}`);
432
+ console.log(` Email: ${userEmail}`);
263
433
  console.log("\n Credentials saved to ~/.wawesome/credentials.json");
264
434
  console.log("======================================================\n");
265
435
  server.close();
@@ -297,16 +467,26 @@ function logout() {
297
467
  /**
298
468
  * Show current login status.
299
469
  */
300
- function whoami() {
470
+ async function whoami() {
301
471
  const creds = readCredentials();
302
472
  if (!creds) {
303
473
  console.log("[wawesome] Not logged in. Run 'wawesome login' to authenticate.");
304
474
  return;
305
475
  }
476
+ let workspace = null;
477
+ try {
478
+ workspace = await resolveWorkspace(creds);
479
+ } catch {
480
+ workspace = null;
481
+ }
306
482
  console.log("\n[wawesome] Current session:");
307
- console.log(` Email: ${creds.user_email}`);
308
- console.log(` Tenant: ${creds.tenant_id}`);
309
- console.log(` Gateway: ${creds.gateway_url}\n`);
483
+ console.log(` Email: ${creds.user_email}`);
484
+ if (workspace) {
485
+ console.log(` Workspace: ${workspace.name}`);
486
+ console.log(` Address: ${workspace.slug}`);
487
+ }
488
+ console.log(` Tenant: ${creds.tenant_id}`);
489
+ console.log(` Gateway: ${creds.gateway_url}\n`);
310
490
  }
311
491
  //#endregion
312
492
  //#region src/deploy.ts
@@ -405,27 +585,67 @@ async function deploy(entryInput, options) {
405
585
  }
406
586
  process.exit(1);
407
587
  }
588
+ let invokeUrl = null;
589
+ try {
590
+ const { slug } = await resolveWorkspace(creds);
591
+ invokeUrl = publicInvokeUrl(creds.gateway_url, slug, app, funcName);
592
+ } catch (err) {
593
+ if (isVerbose) console.log(`[wawesome:verbose] Could not resolve the workspace address: ${err instanceof Error ? err.message : err}`);
594
+ }
408
595
  console.log("\n======================================================");
409
596
  console.log("🚀 \x1B[32mDEPLOYED SUCCESSFULLY!\x1B[0m");
410
597
  console.log("======================================================");
411
598
  console.log(`\n App: ${app}`);
412
599
  console.log(` Function: ${funcName}`);
413
600
  if (version !== void 0) console.log(` Version: ${version}`);
601
+ if (invokeUrl) console.log(`\n URL: \x1b[36m${invokeUrl}\x1b[0m`);
414
602
  console.log("======================================================\n");
415
603
  }
604
+ /**
605
+ * Normalise arbitrary input into a legal DNS label, returning an empty string
606
+ * when nothing legal survives — callers decide whether that is an error.
607
+ */
608
+ function sanitizeSlug(raw) {
609
+ let label = "";
610
+ for (const c of raw.trim().toLowerCase()) if (c >= "a" && c <= "z") label += c;
611
+ else if (c >= "0" && c <= "9") label += c;
612
+ else if (label.length > 0 && !label.endsWith("-")) label += "-";
613
+ label = label.slice(0, 63);
614
+ while (label.endsWith("-")) label = label.slice(0, -1);
615
+ return label;
616
+ }
617
+ /**
618
+ * Whether `candidate` is already a legal DNS label — i.e. whether
619
+ * {@link sanitizeSlug} would leave it untouched.
620
+ */
621
+ function isLegalSlug(candidate) {
622
+ return candidate.length > 0 && candidate.length <= 63 && !candidate.startsWith("-") && !candidate.endsWith("-") && !candidate.includes("--") && /^[a-z0-9-]+$/.test(candidate);
623
+ }
416
624
  //#endregion
417
625
  //#region src/init.ts
418
- function prompt(question, defaultVal) {
419
- const rl = readline.createInterface({
420
- input: process.stdin,
421
- output: process.stdout
422
- });
423
- return new Promise((resolve) => {
424
- rl.question(`${question} (${defaultVal}): `, (answer) => {
425
- rl.close();
426
- resolve(answer.trim() || defaultVal);
427
- });
428
- });
626
+ /** Bound on the re-prompt loop, so unusable input exits instead of looping. */
627
+ const MAX_APP_SLUG_ATTEMPTS = 5;
628
+ /**
629
+ * Ask which App this Function belongs to, defaulting to the directory name.
630
+ *
631
+ * Never falls back to `default-app`, and re-offers an illegal answer as a
632
+ * suggestion rather than rewriting it: the slug is part of the public URL a
633
+ * client reads, so the user sees the name they will get before agreeing to it.
634
+ */
635
+ async function promptForAppSlug(session, dirName) {
636
+ let suggestion = sanitizeSlug(dirName);
637
+ for (let attempt = 0; attempt < MAX_APP_SLUG_ATTEMPTS; attempt++) {
638
+ const answer = await session.ask(" App slug?", suggestion);
639
+ if (isLegalSlug(answer)) return answer;
640
+ const sanitized = sanitizeSlug(answer);
641
+ if (sanitized) {
642
+ console.log(`[wawesome] '${answer}' can't be used as an App slug. Suggested: '${sanitized}'.`);
643
+ suggestion = sanitized;
644
+ } else console.log("[wawesome] An App slug needs lowercase letters or numbers, separated by single hyphens.");
645
+ }
646
+ console.error(`[wawesome] Error: no usable App slug after ${MAX_APP_SLUG_ATTEMPTS} attempts. Nothing was written.`);
647
+ console.error("[wawesome] Run 'wawesome init' again once you know what to call the App.");
648
+ process.exit(1);
429
649
  }
430
650
  /**
431
651
  * Scaffold a new wawesome function project in the current directory.
@@ -435,8 +655,16 @@ async function init(options) {
435
655
  const cwd = process.cwd();
436
656
  const dirName = path.basename(cwd);
437
657
  console.log("[wawesome] Initializing a new function project...\n");
438
- const functionName = await prompt(" Function name?", dirName);
439
- const appSlug = await prompt(" App slug?", "default-app");
658
+ const session = openPromptSession();
659
+ let functionName;
660
+ let appSlug;
661
+ try {
662
+ functionName = await session.ask(" Function name?", dirName);
663
+ console.log(" (an App groups the Functions of one project — its slug is part of the public URL)");
664
+ appSlug = await promptForAppSlug(session, dirName);
665
+ } finally {
666
+ session.close();
667
+ }
440
668
  const entry = "src/index.ts";
441
669
  if (isVerbose) console.log(`[wawesome:verbose] function=${functionName}, app=${appSlug}, entry=${entry}`);
442
670
  const configContent = {
@@ -1323,6 +1551,105 @@ async function followFunctionLog(gatewayUrl, tenantJwt, funcNameInput, appOverri
1323
1551
  }
1324
1552
  }
1325
1553
  //#endregion
1554
+ //#region src/workspace.ts
1555
+ function requireCredentials() {
1556
+ const creds = readCredentials();
1557
+ if (!creds) {
1558
+ console.error("[wawesome] Error: Not logged in. Run 'wawesome login' first.");
1559
+ process.exit(1);
1560
+ }
1561
+ return creds;
1562
+ }
1563
+ /**
1564
+ * Show the workspace's name, address, and whether that address is still free to
1565
+ * change — the rename is offered here rather than discovered by attempting it.
1566
+ */
1567
+ async function showWorkspace(options) {
1568
+ const creds = requireCredentials();
1569
+ let tenant;
1570
+ try {
1571
+ tenant = await fetchTenantDetails(creds);
1572
+ } catch (err) {
1573
+ console.error(`[wawesome] Error: ${err instanceof Error ? err.message : err}`);
1574
+ process.exit(1);
1575
+ }
1576
+ console.log("\n[wawesome] Workspace:");
1577
+ console.log(` Name: ${tenant.name}`);
1578
+ console.log(` Address: ${tenant.tenant_slug}`);
1579
+ console.log(` Tenant: ${tenant.id}`);
1580
+ if (options.verbose) console.log(` Invoke: ${creds.gateway_url}/v1/s/${tenant.tenant_slug}/apps/<app>/functions/<function>/invoke`);
1581
+ if (tenant.slug_locked) console.log("\n 🔒 The address is fixed — a Function version has been promoted and live URLs carry it.");
1582
+ else {
1583
+ console.log("\n The address can still be changed: \x1B[36mwawesome workspace rename <name>\x1B[0m");
1584
+ console.log(" It locks for good the first time you deploy.");
1585
+ }
1586
+ console.log("");
1587
+ }
1588
+ /**
1589
+ * Change the workspace's public address.
1590
+ *
1591
+ * The lock is read from the gateway before asking, so a user who cannot rename
1592
+ * is told why instead of being walked into a rejection. The rejection is still
1593
+ * handled: a promotion landing between the two calls is the one case where the
1594
+ * gateway knows something this command could not.
1595
+ */
1596
+ async function renameWorkspace(slug, options) {
1597
+ const creds = requireCredentials();
1598
+ if (!slug || !slug.trim()) {
1599
+ console.error("[wawesome] Error: No name given. Usage: wawesome workspace rename <name>");
1600
+ process.exit(1);
1601
+ return;
1602
+ }
1603
+ try {
1604
+ const tenant = await fetchTenantDetails(creds);
1605
+ if (tenant.slug_locked) {
1606
+ console.error("\n[wawesome] \x1B[31mThe workspace address can no longer be changed.\x1B[0m");
1607
+ console.error(`[wawesome] '${tenant.tenant_slug}' is fixed: a Function version has been promoted, and live URLs already carry it.`);
1608
+ process.exit(1);
1609
+ return;
1610
+ }
1611
+ if (options.verbose) console.log(`[wawesome:verbose] Renaming '${tenant.tenant_slug}' → '${slug}'`);
1612
+ const result = await renameTenantSlug(creds, slug.trim());
1613
+ console.log("\n======================================================");
1614
+ console.log("✅ \x1B[32mWORKSPACE RENAMED\x1B[0m");
1615
+ console.log("======================================================");
1616
+ console.log(`\n Address: ${result.tenant_slug}`);
1617
+ console.log(` Previous: ${result.previous_slug} (still resolves)`);
1618
+ console.log("======================================================\n");
1619
+ } catch (err) {
1620
+ if (err instanceof GatewayError) {
1621
+ console.error(`\n[wawesome] \x1b[31m${err.message}\x1b[0m`);
1622
+ switch (err.reason) {
1623
+ case "taken":
1624
+ console.error("[wawesome] Pick a different name.");
1625
+ break;
1626
+ case "reserved":
1627
+ console.error("[wawesome] Pick a different name.");
1628
+ break;
1629
+ case "malformed":
1630
+ console.error("[wawesome] Use lowercase letters, numbers and single hyphens.");
1631
+ break;
1632
+ case "locked":
1633
+ console.error("[wawesome] A deploy landed while this was running, which fixed the address for good.");
1634
+ break;
1635
+ }
1636
+ console.error("");
1637
+ process.exit(1);
1638
+ return;
1639
+ }
1640
+ console.error(`[wawesome] Error: ${err instanceof Error ? err.message : err}`);
1641
+ process.exit(1);
1642
+ }
1643
+ }
1644
+ /** Dispatch for `wawesome workspace [action]`. */
1645
+ async function workspaceCommand(action, target, options) {
1646
+ if (!action || action === "show" || action === "info") return showWorkspace(options);
1647
+ if (action === "rename") return renameWorkspace(target, options);
1648
+ console.error(`[wawesome] Error: Unknown workspace action '${action}'.`);
1649
+ console.error("[wawesome] Usage: wawesome workspace [show|rename <name>]");
1650
+ process.exit(1);
1651
+ }
1652
+ //#endregion
1326
1653
  //#region src/index.ts
1327
1654
  const cli = cac("wawesome");
1328
1655
  cli.command("build [entry]", "Bundle a serverless function to an optimized JS file").option("-o, --out <path>", "Output JS bundle path", { default: "dist/index.js" }).option("-v, --verbose", "Enable verbose debug output").action((entry, options) => buildJs(entry, options));
@@ -1338,9 +1665,10 @@ cli.command("env [action] [key] [value]", "Manage environment variables (set, li
1338
1665
  cli.command("env set <key> <value>", "Set or overwrite an environment variable on the current app").option("-s, --secret", "Flag variable as secret (write-only)").option("-v, --verbose", "Enable verbose debug output").action((key, value, options) => setEnvVar(key, value, options));
1339
1666
  cli.command("env list", "List environment variables for the current app").alias("env ls").option("-v, --verbose", "Enable verbose debug output").action((options) => listEnvVars(options));
1340
1667
  cli.command("env rm <key>", "Delete an environment variable from the current app").alias("env remove").alias("env delete").alias("env unset").option("-v, --verbose", "Enable verbose debug output").action((key, options) => removeEnvVar(key, options));
1341
- cli.command("login", "Authenticate with the wawesome.io platform").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("--gateway <url>", "Alias for --api <url>").option("--provider <name>", "OAuth provider (default: github)").option("-v, --verbose", "Enable verbose debug output").action((options) => login(options));
1668
+ cli.command("login", "Authenticate with the wawesome.io platform").option("--api <url>", "API URL (default: https://api.wawesome.io)").option("--gateway <url>", "Alias for --api <url>").option("--provider <name>", "OAuth provider (default: github)").option("--workspace <name>", "Name for the workspace, when signing up without a terminal to prompt").option("-v, --verbose", "Enable verbose debug output").action((options) => login(options));
1342
1669
  cli.command("logout", "Clear stored authentication credentials").action(() => logout());
1343
1670
  cli.command("whoami", "Show current login session info").action(() => whoami());
1671
+ cli.command("workspace [action] [name]", "Show the workspace, or rename its public address").usage("workspace <action> [name]\n\nActions:\n show Show the workspace name, address, and whether it can still change\n rename <name> Change the public address, while nothing live depends on it").example("wawesome workspace").example("wawesome workspace rename northwind").option("-v, --verbose", "Enable verbose debug output").action((action, name, options) => workspaceCommand(action, name, options));
1344
1672
  cli.command("init", "Scaffold a new function project in the current directory").option("-v, --verbose", "Enable verbose debug output").action((options) => init(options));
1345
1673
  cli.command("logs [function-name-or-invocation-id]", "View invocation history, fetch log output, or follow live").usage(`logs [target] [options]
1346
1674
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wawesome",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "CLI tool for building and deploying serverless functions on wawesome.io platform",
5
5
  "type": "module",
6
6
  "bin": {