From 19fe7159f119edb065621bccb976386e97ca7dbc Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Wed, 31 Dec 2025 11:08:36 +0000 Subject: [PATCH 1/5] remove connections stats command --- docs/Product-Requirements.md | 1 - docs/TODO.md | 3 - src/commands/connections/index.ts | 1 - src/commands/connections/stats.ts | 428 ------------------- test/unit/commands/connections/stats.test.ts | 145 ------- 5 files changed, 578 deletions(-) delete mode 100644 src/commands/connections/stats.ts delete mode 100644 test/unit/commands/connections/stats.test.ts diff --git a/docs/Product-Requirements.md b/docs/Product-Requirements.md index 98130793..f0953c45 100644 --- a/docs/Product-Requirements.md +++ b/docs/Product-Requirements.md @@ -92,7 +92,6 @@ The CLI uses topics (space-separated) to group related commands logically. **Connections (`ably connections`)** *(Interact with Pub/Sub connections)* -- `$ ably connections stats`: Shows connection stats (similar to `ably apps stats` but connection-focused). Supports `--live`. - `$ ably connections test`: Performs a simple connection test. Supports transport options. - `$ ably connections logs [TOPIC]`: Alias for `ably logs connection-lifecycle subscribe`. (Currently only supports `connection-lifecycle`). diff --git a/docs/TODO.md b/docs/TODO.md index 086d9c5b..f95c1579 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -107,9 +107,6 @@ - [ ] **Channel Rules (Legacy):** - [ ] `channel-rule create/list/update/delete`: Mock Control API calls, flag handling, output formats. (Verify necessity). - [ ] **Connections:** - - [ ] `connections stats`: Mock REST API call, flag handling, output formats. - - [ ] Test different stat aggregation periods - - [ ] Test connection types filtering - [ ] `connections test`: Mock SDK connection attempts, flag handling, output formats. - [ ] Test different transport options (WebSockets, HTTP) - [ ] Test environment selection diff --git a/src/commands/connections/index.ts b/src/commands/connections/index.ts index 00357851..e1f6f418 100644 --- a/src/commands/connections/index.ts +++ b/src/commands/connections/index.ts @@ -7,7 +7,6 @@ export default class Connections extends BaseTopicCommand { static override description = "Interact with Ably Pub/Sub connections"; static override examples = [ - "<%= config.bin %> <%= command.id %> stats", "<%= config.bin %> <%= command.id %> logs connections-lifecycle", "<%= config.bin %> <%= command.id %> test", ]; diff --git a/src/commands/connections/stats.ts b/src/commands/connections/stats.ts deleted file mode 100644 index 02f2fa8b..00000000 --- a/src/commands/connections/stats.ts +++ /dev/null @@ -1,428 +0,0 @@ -import { Flags } from "@oclif/core"; -import * as Ably from "ably"; -import chalk from "chalk"; - -import { AblyBaseCommand } from "../../base-command.js"; -import { BaseFlags } from "../../types/cli.js"; -import { StatsDisplay } from "../../services/stats-display.js"; -import { StatsDisplayData } from "../../services/stats-display.js"; - -export default class ConnectionsStats extends AblyBaseCommand { - static override description = "View connection statistics for an Ably app"; - - static override examples = [ - "$ ably connections stats", - "$ ably connections stats --unit hour", - "$ ably connections stats --start 1618005600000 --end 1618091999999", - "$ ably connections stats --limit 10", - "$ ably connections stats --json", - "$ ably connections stats --pretty-json", - "$ ably connections stats --live", - ]; - - static override flags = { - ...AblyBaseCommand.globalFlags, - debug: Flags.boolean({ - default: false, - description: "Show debug information for live stats polling", - }), - end: Flags.integer({ - description: "End time in milliseconds since epoch", - }), - interval: Flags.integer({ - default: 6, - description: "Polling interval in seconds (only used with --live)", - }), - limit: Flags.integer({ - default: 10, - description: "Maximum number of stats records to return", - }), - - live: Flags.boolean({ - default: false, - description: "Subscribe to live stats updates (uses minute interval)", - }), - start: Flags.integer({ - description: "Start time in milliseconds since epoch", - }), - unit: Flags.string({ - default: "minute", - description: "Time unit for stats", - options: ["minute", "hour", "day", "month"], - }), - }; - - private client: Ably.Rest | null = null; - private isPolling = false; - private pollInterval: NodeJS.Timeout | undefined = undefined; // Use NodeJS.Timeout - private statsDisplay: StatsDisplay | null = null; // Track when we're already fetching stats - - // Override finally to ensure resources are cleaned up - async finally(err: Error | undefined): Promise { - if (this.pollInterval) { - clearInterval(this.pollInterval); - this.pollInterval = undefined; - } - - // No need to close REST client explicitly - return super.finally(err); - } - - async run(): Promise { - const { flags } = await this.parse(ConnectionsStats); - - // For live stats, enforce minute interval - if (flags.live && flags.unit !== "minute") { - this.logCliEvent( - flags, - "stats", - "liveIntervalOverride", - "Live stats only support minute intervals. Using minute interval.", - ); - this.warn( - "Live stats only support minute intervals. Using minute interval.", - ); - flags.unit = "minute"; - } - - // Get API key from flags or config - const apiKey = flags["api-key"] || (await this.configManager.getApiKey()); - if (!apiKey) { - this.error( - 'No API key found. Please set an API key using "ably keys add" or set the ABLY_API_KEY environment variable.', - ); - return; - } - - // Create stats display - this.statsDisplay = new StatsDisplay({ - intervalSeconds: flags.interval, - isConnectionStats: true, - json: this.shouldOutputJson(flags), - live: flags.live, - startTime: flags.live ? new Date() : undefined, - unit: flags.unit as "day" | "hour" | "minute" | "month", - }); - - await (flags.live ? this.runLiveStats(flags) : this.runOneTimeStats(flags)); - } - - async runLiveStats(flags: Record): Promise { - try { - const client = await this.createAblyRestClient(flags as BaseFlags); - if (!client) { - return; - } - - this.logCliEvent( - flags, - "stats", - "liveSubscribeStarting", - "Subscribing to live connection stats...", - ); - if (!this.shouldOutputJson(flags)) { - this.log("Subscribing to live connection stats..."); - } - - // Setup graceful shutdown - const cleanup = () => { - this.logCliEvent( - flags, - "stats", - "liveCleanupInitiated", - "Cleanup initiated for live stats", - ); - if (this.pollInterval) { - clearInterval(this.pollInterval); - this.pollInterval = undefined; - } - - if (!this.shouldOutputJson(flags)) { - this.log("\nUnsubscribed from live stats"); - } - }; - - process.on("SIGINT", cleanup); - process.on("SIGTERM", cleanup); - - // Show stats immediately before starting polling - await this.fetchAndDisplayStats(flags, client); - - // Poll for stats at the specified interval - this.pollInterval = setInterval( - () => { - if (!this.isPolling) { - this.pollStats(flags, client!); - } else if (flags.debug) { - this.logCliEvent( - flags, - "stats", - "pollSkipped", - "Skipping poll - previous request still in progress", - ); - // Only show this message if debug flag is enabled - console.log( - chalk.yellow( - "Skipping poll - previous request still in progress", - ), - ); - } - }, - ((flags.interval as number) || 6) * 1000, - ); - - this.logCliEvent( - flags, - "stats", - "liveListening", - "Now listening for live stats updates", - ); - // Keep the process running - await new Promise(() => { - // This promise is intentionally never resolved - // The process will exit via the SIGINT/SIGTERM handlers - }); - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - this.logCliEvent( - flags, - "stats", - "liveSetupError", - `Error setting up live stats: ${errorMsg}`, - { error: errorMsg }, - ); - this.error(`Error setting up live stats: ${errorMsg}`); - if (this.pollInterval) { - clearInterval(this.pollInterval); - } - } - } - - async runOneTimeStats(flags: Record): Promise { - // Calculate time range based on the unit - const now = new Date(); - let startTime = new Date(); - - switch (flags.unit) { - case "minute": { - startTime = new Date(now.getTime() - 60 * 60 * 1000); // 1 hour ago for minutes - - break; - } - - case "hour": { - startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 24 hours ago for hours - - break; - } - - case "day": { - startTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days ago for days - - break; - } - - default: { - startTime = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago for months - } - } - - // Prepare query parameters - const params = { - direction: "backwards" as "backwards" | "forwards", - end: (flags.end as number | undefined) ?? now.getTime(), - limit: flags.limit as number, - start: (flags.start as number | undefined) ?? startTime.getTime(), - unit: flags.unit as "day" | "hour" | "minute" | "month", - }; - this.logCliEvent( - flags, - "stats", - "oneTimeFetchRequest", - "Fetching one-time stats with parameters", - { params }, - ); - - try { - const client = await this.createAblyRestClient(flags as BaseFlags); - if (!client) { - return; - } - - // Get stats - const statsPage = await client.stats(params); - const stats = statsPage.items; - this.logCliEvent( - flags, - "stats", - "oneTimeFetchResponse", - `Received ${stats.length} stats records`, - { count: stats.length, stats }, - ); - - if (stats.length === 0) { - this.logCliEvent( - flags, - "stats", - "noStatsAvailable", - "No connection stats available for the requested period", - ); - if (!this.shouldOutputJson(flags)) { - this.log("No connection stats available."); - } - - return; - } - - // Display stats using the StatsDisplay class - this.statsDisplay!.display(stats[0] as StatsDisplayData); // Display only the latest/first record for simplicity - // If you need to display all records for one-time stats, you'll need to adjust StatsDisplay or loop here. - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - this.logCliEvent( - flags, - "stats", - "oneTimeFetchError", - `Failed to fetch one-time stats: ${errorMsg}`, - { error: errorMsg }, - ); - this.error(`Failed to fetch stats: ${errorMsg}`); - } - } - - private async fetchAndDisplayStats( - flags: Record, - client: Ably.Rest, - ): Promise { - try { - // Calculate time range based on the unit - const now = new Date(); - let startTime = new Date(); - - switch (flags.unit) { - case "minute": { - startTime = new Date(now.getTime() - 60 * 60 * 1000); // 1 hour ago for minutes - - break; - } - - case "hour": { - startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 24 hours ago for hours - - break; - } - - case "day": { - startTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days ago for days - - break; - } - - default: { - startTime = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago for months - } - } - - // Prepare query parameters - const params = { - direction: "backwards" as "backwards" | "forwards", - end: now.getTime(), - limit: flags.live ? 1 : (flags.limit as number), - start: startTime.getTime(), - unit: flags.unit as "day" | "hour" | "minute" | "month", - }; - this.logCliEvent( - flags, - "stats", - "fetchRequest", - "Fetching stats with parameters", - { params }, - ); - - // Get stats - const statsPage = await client.stats(params); - const stats = statsPage.items; - this.logCliEvent( - flags, - "stats", - "fetchResponse", - `Received ${stats.length} stats records`, - { count: stats.length, stats }, - ); - - if (stats.length === 0) { - this.logCliEvent( - flags, - "stats", - "noStatsAvailable", - "No connection stats available for the requested period", - ); - if (!flags.live && !this.shouldOutputJson(flags)) { - this.log("No connection stats available."); - } - - return; - } - - // Display stats using the StatsDisplay class - this.statsDisplay!.display(stats[0] as StatsDisplayData); - - // Display each stat interval - for (const stat of stats) { - this.statsDisplay!.display(stat as StatsDisplayData); - } - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - this.logCliEvent( - flags, - "stats", - "fetchError", - `Failed to fetch stats: ${errorMsg}`, - { error: errorMsg }, - ); - this.error(`Failed to fetch stats: ${errorMsg}`); - } - } - - private async pollStats( - flags: Record, - client: Ably.Rest, - ): Promise { - try { - this.isPolling = true; - this.logCliEvent( - flags, - "stats", - "pollStarting", - "Polling for new stats...", - ); - if (flags.debug) { - console.log( - chalk.dim(`[${new Date().toISOString()}] Polling for new stats...`), - ); - } - - await this.fetchAndDisplayStats(flags, client); - this.logCliEvent( - flags, - "stats", - "pollSuccess", - "Successfully polled and displayed stats", - ); - } catch (error) { - const errorMsg = error instanceof Error ? error.message : String(error); - this.logCliEvent( - flags, - "stats", - "pollError", - `Error during stats polling: ${errorMsg}`, - { error: errorMsg }, - ); - if (flags.debug) { - console.error(chalk.red(`Error during stats polling: ${errorMsg}`)); - } - } finally { - this.isPolling = false; - } - } -} diff --git a/test/unit/commands/connections/stats.test.ts b/test/unit/commands/connections/stats.test.ts deleted file mode 100644 index 84030a4d..00000000 --- a/test/unit/commands/connections/stats.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { describe, it, expect, beforeEach } from "vitest"; -import { runCommand } from "@oclif/test"; -import { getMockAblyRest } from "../../../helpers/mock-ably-rest.js"; - -describe("ConnectionsStats", function () { - beforeEach(function () { - const mock = getMockAblyRest(); - - // Set up default mock response for stats - mock.stats.mockResolvedValue({ - items: [ - { - intervalId: Date.now().toString(), - entries: { - "connections.all.peak": 10, - "connections.all.min": 5, - "connections.all.mean": 7.5, - "connections.all.opened": 15, - "connections.all.refused": 2, - "connections.all.count": 8, - "channels.peak": 25, - "channels.min": 10, - "channels.mean": 18, - "channels.opened": 30, - "channels.refused": 1, - "channels.count": 20, - "messages.inbound.all.messages.count": 100, - "messages.outbound.all.messages.count": 90, - "messages.all.all.count": 190, - "messages.all.all.data": 5000, - "apiRequests.all.succeeded": 50, - "apiRequests.all.failed": 3, - "apiRequests.all.refused": 1, - "apiRequests.tokenRequests.succeeded": 10, - "apiRequests.tokenRequests.failed": 0, - "apiRequests.tokenRequests.refused": 0, - }, - }, - ], - }); - }); - - it("should retrieve and display connection stats successfully", async function () { - const mock = getMockAblyRest(); - - const { stdout } = await runCommand(["connections:stats"], import.meta.url); - - expect(mock.stats).toHaveBeenCalledOnce(); - - // Verify the stats method was called with correct parameters - const callArgs = mock.stats.mock.calls[0][0]; - expect(callArgs).toHaveProperty("unit", "minute"); - expect(callArgs).toHaveProperty("limit", 10); - expect(callArgs).toHaveProperty("direction", "backwards"); - - // Check that stats were displayed - expect(stdout).toContain("Connections:"); - expect(stdout).toContain("Channels:"); - expect(stdout).toContain("Messages:"); - }); - - it("should handle different time units", async function () { - const mock = getMockAblyRest(); - - await runCommand( - ["connections:stats", "--unit", "hour", "--limit", "24"], - import.meta.url, - ); - - expect(mock.stats).toHaveBeenCalledOnce(); - - const callArgs = mock.stats.mock.calls[0][0]; - expect(callArgs).toHaveProperty("unit", "hour"); - expect(callArgs).toHaveProperty("limit", 24); - }); - - it("should handle custom time range with start and end", async function () { - const mock = getMockAblyRest(); - const startTime = 1618005600000; - const endTime = 1618091999999; - - await runCommand( - [ - "connections:stats", - "--start", - startTime.toString(), - "--end", - endTime.toString(), - ], - import.meta.url, - ); - - expect(mock.stats).toHaveBeenCalledOnce(); - - const callArgs = mock.stats.mock.calls[0][0]; - expect(callArgs).toHaveProperty("start", startTime); - expect(callArgs).toHaveProperty("end", endTime); - }); - - it("should handle empty stats response", async function () { - const mock = getMockAblyRest(); - mock.stats.mockResolvedValue({ items: [] }); - - const { stdout } = await runCommand(["connections:stats"], import.meta.url); - - expect(mock.stats).toHaveBeenCalledOnce(); - expect(stdout).toContain("No connection stats available"); - }); - - it("should handle API errors", async function () { - const mock = getMockAblyRest(); - mock.stats.mockRejectedValue(new Error("API request failed")); - - const { error } = await runCommand(["connections:stats"], import.meta.url); - - expect(error).toBeDefined(); - expect(error?.message).toContain("Failed to fetch stats"); - }); - - it("should output JSON when requested", async function () { - const mock = getMockAblyRest(); - - const { stdout } = await runCommand( - ["connections:stats", "--json"], - import.meta.url, - ); - - expect(mock.stats).toHaveBeenCalledOnce(); - - // Check for JSON output - should contain entries - const jsonOutput = stdout.split("\n").find((line) => { - try { - const parsed = JSON.parse(line); - return parsed.entries && typeof parsed.entries === "object"; - } catch { - return false; - } - }); - expect(jsonOutput).toBeDefined(); - }); - - // Note: Live mode tests are omitted because the command runs indefinitely - // and is difficult to test reliably with runCommand. The live mode functionality - // is tested manually or through integration tests. -}); From 29afc718977c3dc6565069b4f3aa44ac058c6ca9 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Wed, 7 Jan 2026 09:59:08 +0000 Subject: [PATCH 2/5] remove connection stats e2e tests --- test/e2e/connections/connections.test.ts | 127 ----------------------- 1 file changed, 127 deletions(-) diff --git a/test/e2e/connections/connections.test.ts b/test/e2e/connections/connections.test.ts index e1718e1f..cd4b06a4 100644 --- a/test/e2e/connections/connections.test.ts +++ b/test/e2e/connections/connections.test.ts @@ -39,133 +39,6 @@ describe("Connections E2E Tests", () => { await cleanupTrackedResources(); }); - describe("Connection Stats E2E", () => { - it( - "should retrieve real connection stats successfully", - { timeout: 60000 }, - async () => { - setupTestFailureHandler( - "should retrieve real connection stats successfully", - ); - - const result = await runCommand( - ["connections", "stats", "--limit", "5"], - { - timeoutMs: 30000, - env: { ABLY_CLI_TEST_MODE: "false" }, - }, - ); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("Connections:"); - expect(result.stdout).toContain("Channels:"); - expect(result.stdout).toContain("Messages:"); - }, - ); - - it( - "should output connection stats in JSON format", - { timeout: 60000 }, - async () => { - setupTestFailureHandler( - "should output connection stats in JSON format", - ); - - const result = await runCommand( - ["connections", "stats", "--json", "--limit", "3"], - { - timeoutMs: 30000, - env: { ABLY_CLI_TEST_MODE: "false" }, - }, - ); - - expect(result.exitCode).toBe(0); - - // Verify it's valid JSON - let jsonOutput; - try { - jsonOutput = JSON.parse(result.stdout); - } catch { - throw new Error(`Invalid JSON output: ${result.stdout}`); - } - - // Check for expected stats structure - expect(jsonOutput).toHaveProperty("intervalId"); - }, - ); - - it( - "should handle different time units correctly", - { timeout: 60000 }, - async () => { - setupTestFailureHandler("should handle different time units correctly"); - - const result = await runCommand( - ["connections", "stats", "--unit", "hour", "--limit", "2"], - { - timeoutMs: 30000, - env: { ABLY_CLI_TEST_MODE: "false" }, - }, - ); - - expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("Stats for"); - }, - ); - - it("should handle custom time ranges", { timeout: 60000 }, async () => { - setupTestFailureHandler("should handle custom time ranges"); - - const endTime = Date.now(); - const startTime = endTime - 60 * 60 * 1000; // 1 hour ago - - const result = await runCommand( - [ - "connections", - "stats", - "--start", - startTime.toString(), - "--end", - endTime.toString(), - "--limit", - "2", - ], - { - timeoutMs: 30000, - env: { ABLY_CLI_TEST_MODE: "false" }, - }, - ); - - expect(result.exitCode).toBe(0); - }); - - it("should handle empty stats gracefully", { timeout: 60000 }, async () => { - setupTestFailureHandler("should handle empty stats gracefully"); - - // Use a very recent time range that's unlikely to have stats - const endTime = Date.now(); - const startTime = endTime - 1000; // 1 second ago - - const result = await runCommand( - [ - "connections", - "stats", - "--start", - startTime.toString(), - "--end", - endTime.toString(), - ], - { - timeoutMs: 30000, - env: { ABLY_CLI_TEST_MODE: "false" }, - }, - ); - - // Should exit successfully even with no stats - expect(result.exitCode).toBe(0); - }); - }); - describe("Connection Test E2E", () => { it( "should test WebSocket connection successfully", From 56facef2dee265a863f8ebb1f9d47195d967d7a7 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Wed, 31 Dec 2025 11:08:45 +0000 Subject: [PATCH 3/5] regenerate readme --- README.md | 314 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 182 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index d97885b3..11cac968 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ See [MCP Server section](#mcp-server) for more details on how to use the MCP Ser * [`ably apps channel-rules update NAMEORID`](#ably-apps-channel-rules-update-nameorid) * [`ably apps create`](#ably-apps-create) * [`ably apps current`](#ably-apps-current) -* [`ably apps delete [ID]`](#ably-apps-delete-id) +* [`ably apps delete [APPID]`](#ably-apps-delete-appid) * [`ably apps list`](#ably-apps-list) * [`ably apps logs`](#ably-apps-logs) * [`ably apps logs history`](#ably-apps-logs-history) @@ -142,14 +142,15 @@ See [MCP Server section](#mcp-server) for more details on how to use the MCP Ser * [`ably channels publish CHANNEL MESSAGE`](#ably-channels-publish-channel-message) * [`ably channels subscribe CHANNELS`](#ably-channels-subscribe-channels) * [`ably config`](#ably-config) +* [`ably config path`](#ably-config-path) +* [`ably config show`](#ably-config-show) * [`ably connections`](#ably-connections) * [`ably connections logs [TOPIC]`](#ably-connections-logs-topic) -* [`ably connections stats`](#ably-connections-stats) * [`ably connections test`](#ably-connections-test) * [`ably help [COMMANDS]`](#ably-help-commands) * [`ably integrations`](#ably-integrations) * [`ably integrations create`](#ably-integrations-create) -* [`ably integrations delete RULEID`](#ably-integrations-delete-ruleid) +* [`ably integrations delete INTEGRATIONID`](#ably-integrations-delete-integrationid) * [`ably integrations get RULEID`](#ably-integrations-get-ruleid) * [`ably integrations list`](#ably-integrations-list) * [`ably integrations update RULEID`](#ably-integrations-update-ruleid) @@ -171,7 +172,7 @@ See [MCP Server section](#mcp-server) for more details on how to use the MCP Ser * [`ably mcp start-server`](#ably-mcp-start-server) * [`ably queues`](#ably-queues) * [`ably queues create`](#ably-queues-create) -* [`ably queues delete QUEUENAME`](#ably-queues-delete-queuename) +* [`ably queues delete QUEUEID`](#ably-queues-delete-queueid) * [`ably queues list`](#ably-queues-list) * [`ably rooms`](#ably-rooms) * [`ably rooms list`](#ably-rooms-list) @@ -182,7 +183,7 @@ See [MCP Server section](#mcp-server) for more details on how to use the MCP Ser * [`ably rooms messages reactions send ROOM MESSAGESERIAL REACTION`](#ably-rooms-messages-reactions-send-room-messageserial-reaction) * [`ably rooms messages reactions subscribe ROOM`](#ably-rooms-messages-reactions-subscribe-room) * [`ably rooms messages send ROOM TEXT`](#ably-rooms-messages-send-room-text) -* [`ably rooms messages subscribe ROOM`](#ably-rooms-messages-subscribe-room) +* [`ably rooms messages subscribe ROOMS`](#ably-rooms-messages-subscribe-rooms) * [`ably rooms occupancy`](#ably-rooms-occupancy) * [`ably rooms occupancy get ROOM`](#ably-rooms-occupancy-get-room) * [`ably rooms occupancy subscribe ROOM`](#ably-rooms-occupancy-subscribe-room) @@ -811,17 +812,17 @@ EXAMPLES _See code: [src/commands/apps/current.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/current.ts)_ -## `ably apps delete [ID]` +## `ably apps delete [APPID]` Delete an app ``` USAGE - $ ably apps delete [ID] [--access-token ] [--api-key ] [--client-id ] [--env ] + $ ably apps delete [APPID] [--access-token ] [--api-key ] [--client-id ] [--env ] [--endpoint ] [--host ] [--json | --pretty-json] [--token ] [-v] [-f] [--app ] ARGUMENTS - ID App ID to delete (uses current app if not specified) + APPID App ID to delete (uses current app if not specified) FLAGS -f, --force Skip confirmation prompt @@ -1227,7 +1228,7 @@ FLAGS --pretty-json Output in colorized JSON format --token= Authenticate using an Ably Token or JWT Token instead of an API key --token-only Output only the token string without any formatting or additional information - --ttl= [default: 3600] Time to live in seconds + --ttl= [default: 3600] Time to live in seconds (default: 3600, 1 hour) DESCRIPTION Creates an Ably Token with capabilities @@ -1279,7 +1280,7 @@ FLAGS --pretty-json Output in colorized JSON format --token= Authenticate using an Ably Token or JWT Token instead of an API key --token-only Output only the token string without any formatting or additional information - --ttl= [default: 3600] Time to live in seconds + --ttl= [default: 3600] Time to live in seconds (default: 3600, 1 hour) DESCRIPTION Creates an Ably JWT token with capabilities @@ -1772,7 +1773,7 @@ Run a subscriber benchmark test ``` USAGE $ ably bench subscriber CHANNEL [--access-token ] [--api-key ] [--client-id ] [--env ] - [--endpoint ] [--host ] [--json | --pretty-json] [--token ] [-v] + [--endpoint ] [--host ] [--json | --pretty-json] [--token ] [-v] [-d ] ARGUMENTS CHANNEL The channel name to subscribe to @@ -1845,13 +1846,14 @@ ARGUMENTS MESSAGE The message to publish (JSON format or plain text, not needed if using --spec) FLAGS - -e, --encoding= The encoding for the message - -n, --name= The event name (if not specified in the message JSON) + -e, --encoding= The encoding for the message (not used with --spec) + -n, --name= The event name (if not specified in the message JSON, not used with --spec) -v, --verbose Output verbose logs --access-token= Overrides any configured access token used for the Control API --api-key= Overrides any configured API key used for the product APIs - --channels= Comma-separated list of channel names to publish to - --channels-json= JSON array of channel names to publish to + --channels= Comma-separated list of channel names to publish to (mutually exclusive with + --channels-json and --spec) + --channels-json= JSON array of channel names to publish to (mutually exclusive with --channels and --spec) --client-id= Overrides any default client ID when using API authentication. Use "none" to explicitly set no client ID. Not applicable when using token authentication. --endpoint= Override the endpoint for all product API calls @@ -1860,7 +1862,7 @@ FLAGS --json Output in JSON format --pretty-json Output in colorized JSON format --spec= Complete batch spec JSON (either a single BatchSpec object or an array of BatchSpec - objects) + objects). When used, --channels, --channels-json, --name, and --encoding are ignored --token= Authenticate using an Ably Token or JWT Token instead of an API key DESCRIPTION @@ -1904,14 +1906,14 @@ FLAGS --cipher= Decryption key for encrypted messages (AES-128) --client-id= Overrides any default client ID when using API authentication. Use "none" to explicitly set no client ID. Not applicable when using token authentication. - --direction=