From 499797e206a1ec194b3e0bd0fb91bb175015fcfc Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Wed, 31 Dec 2025 11:08:36 +0000 Subject: [PATCH 1/9] remove connections stats command --- src/commands/connections/index.ts | 1 - src/commands/connections/stats.ts | 428 ------------------- test/unit/commands/connections/stats.test.ts | 145 ------- 3 files changed, 574 deletions(-) delete mode 100644 src/commands/connections/stats.ts delete mode 100644 test/unit/commands/connections/stats.test.ts 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 7259365519e658c529d07c6e379bb06737d8c9f2 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Wed, 7 Jan 2026 09:59:08 +0000 Subject: [PATCH 2/9] 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 f79716964556019b1feb90425c8b8bdf02a44ae5 Mon Sep 17 00:00:00 2001 From: Vlad Velici Date: Wed, 31 Dec 2025 11:08:45 +0000 Subject: [PATCH 3/9] regenerate readme --- README.md | 738 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 525 insertions(+), 213 deletions(-) diff --git a/README.md b/README.md index a7b71a2f..11cac968 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,34 @@ -# Ably CLI +# Ably CLI and MCP server [![npm version](https://badge.fury.io/js/@ably%2Fcli.svg)](https://badge.fury.io/js/@ably%2Fcli) -[Ably](https://ably.com) CLI for [Ably Pub/Sub](https://ably.com/pubsub), [Ably Spaces](https://ably.com/spaces), [Ably Chat](https://ably.com/chat) and the [Ably Control API](https://ably.com/docs/account/control-api). +[Ably](https://ably.com) CLI and MCP server for [Ably Pub/Sub](https://ably.com/pubsub), [Ably Spaces](https://ably.com/spaces), [Ably Chat](https://ably.com/chat) and the [Ably Control API](https://ably.com/docs/account/control-api). + +> [!NOTE] +> This project is in beta and this CLI and MCP server project is being actively developed. +> Please [raise an issue](https://github.com/ably/ably-cli/issues) if you have feedback, feature requests or want to report a bug. We welcome [pull requests too](https://github.com/ably/ably-cli/pulls). ![Ably CLI screenshot](assets/cli-screenshot.png) -* [Ably CLI](#ably-cli) +* [Ably CLI and MCP server](#ably-cli-and-mcp-server) * [CLI Usage](#cli-usage) +* [MCP Usage](#mcp-usage) * [Commands](#commands) +* [MCP Server](#mcp-server) * [Contributing](#contributing) +* [or](#or) # CLI Usage -> [!NOTE] -> The Ably CLI is currently in Public Preview status. Please [raise an issue](https://github.com/ably/ably-cli/issues) if you have feedback, feature requests or want to report a bug. - ```sh-session $ npm install -g @ably/cli $ ably COMMAND running command... $ ably (--version) -@ably/cli/0.16.0 darwin-arm64 node-v22.14.0 +@ably/cli/0.15.0 darwin-arm64 node-v22.14.0 $ ably --help [COMMAND] USAGE $ ably COMMAND @@ -67,6 +71,20 @@ $ ably-interactive - Double Ctrl+C (within 500ms) force quits the shell - **No "ably" prefix needed**: Commands can be typed directly (e.g., just `channels list` instead of `ably channels list`) +# MCP Usage + +> [!WARNING] +> The MCP server is currently experimental. Please [raise an issue](https://github.com/ably/ably-cli/issues) if you have feedback or suggestions for features. + +1. Install the CLI following the [CLI usage](#cli-usage) steps. +2. Follow the instructions for your tool to set up an MCP server, such as [Claude desktop](https://modelcontextprotocol.io/quickstart/user), and configure: + 1. `command` as ably mcp start-server + +See [MCP Server section](#mcp-server) for more details on how to use the MCP Server. + +> [!NOTE] +> If you are having trouble getting the MCP server running, use [MCP inspector](https://github.com/modelcontextprotocol/inspector) + # Commands @@ -87,6 +105,9 @@ $ ably-interactive * [`ably apps current`](#ably-apps-current) * [`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) +* [`ably apps logs subscribe`](#ably-apps-logs-subscribe) * [`ably apps set-apns-p12 ID`](#ably-apps-set-apns-p12-id) * [`ably apps stats [ID]`](#ably-apps-stats-id) * [`ably apps switch [APPID]`](#ably-apps-switch-appid) @@ -111,6 +132,7 @@ $ ably-interactive * [`ably channels batch-publish [MESSAGE]`](#ably-channels-batch-publish-message) * [`ably channels history CHANNEL`](#ably-channels-history-channel) * [`ably channels list`](#ably-channels-list) +* [`ably channels logs [TOPIC]`](#ably-channels-logs-topic) * [`ably channels occupancy`](#ably-channels-occupancy) * [`ably channels occupancy get CHANNEL`](#ably-channels-occupancy-get-channel) * [`ably channels occupancy subscribe CHANNEL`](#ably-channels-occupancy-subscribe-channel) @@ -123,7 +145,7 @@ $ ably-interactive * [`ably config path`](#ably-config-path) * [`ably config show`](#ably-config-show) * [`ably connections`](#ably-connections) -* [`ably connections stats`](#ably-connections-stats) +* [`ably connections logs [TOPIC]`](#ably-connections-logs-topic) * [`ably connections test`](#ably-connections-test) * [`ably help [COMMANDS]`](#ably-help-commands) * [`ably integrations`](#ably-integrations) @@ -134,16 +156,20 @@ $ ably-interactive * [`ably integrations update RULEID`](#ably-integrations-update-ruleid) * [`ably login [TOKEN]`](#ably-login-token) * [`ably logs`](#ably-logs) +* [`ably logs app`](#ably-logs-app) +* [`ably logs app history`](#ably-logs-app-history) +* [`ably logs app subscribe`](#ably-logs-app-subscribe) * [`ably logs channel-lifecycle`](#ably-logs-channel-lifecycle) * [`ably logs channel-lifecycle subscribe`](#ably-logs-channel-lifecycle-subscribe) * [`ably logs connection-lifecycle`](#ably-logs-connection-lifecycle) * [`ably logs connection-lifecycle history`](#ably-logs-connection-lifecycle-history) * [`ably logs connection-lifecycle subscribe`](#ably-logs-connection-lifecycle-subscribe) -* [`ably logs history`](#ably-logs-history) +* [`ably logs connection subscribe`](#ably-logs-connection-subscribe) * [`ably logs push`](#ably-logs-push) * [`ably logs push history`](#ably-logs-push-history) * [`ably logs push subscribe`](#ably-logs-push-subscribe) -* [`ably logs subscribe`](#ably-logs-subscribe) +* [`ably mcp`](#ably-mcp) +* [`ably mcp start-server`](#ably-mcp-start-server) * [`ably queues`](#ably-queues) * [`ably queues create`](#ably-queues-create) * [`ably queues delete QUEUEID`](#ably-queues-delete-queueid) @@ -226,7 +252,7 @@ COMMANDS ably accounts switch Switch to a different Ably account ``` -_See code: [src/commands/accounts/index.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/accounts/index.ts)_ +_See code: [src/commands/accounts/index.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/accounts/index.ts)_ ## `ably accounts current` @@ -261,7 +287,7 @@ EXAMPLES $ ably accounts current --pretty-json ``` -_See code: [src/commands/accounts/current.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/accounts/current.ts)_ +_See code: [src/commands/accounts/current.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/accounts/current.ts)_ ## `ably accounts list` @@ -296,7 +322,7 @@ EXAMPLES $ ably accounts list --pretty-json ``` -_See code: [src/commands/accounts/list.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/accounts/list.ts)_ +_See code: [src/commands/accounts/list.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/accounts/list.ts)_ ## `ably accounts login [TOKEN]` @@ -338,7 +364,7 @@ EXAMPLES $ ably accounts login --pretty-json ``` -_See code: [src/commands/accounts/login.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/accounts/login.ts)_ +_See code: [src/commands/accounts/login.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/accounts/login.ts)_ ## `ably accounts logout [ALIAS]` @@ -379,7 +405,7 @@ EXAMPLES $ ably accounts logout --pretty-json ``` -_See code: [src/commands/accounts/logout.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/accounts/logout.ts)_ +_See code: [src/commands/accounts/logout.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/accounts/logout.ts)_ ## `ably accounts stats` @@ -433,7 +459,7 @@ EXAMPLES $ ably accounts stats --live --interval 15 ``` -_See code: [src/commands/accounts/stats/index.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/accounts/stats/index.ts)_ +_See code: [src/commands/accounts/stats/index.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/accounts/stats/index.ts)_ ## `ably accounts switch [ALIAS]` @@ -473,7 +499,7 @@ EXAMPLES $ ably accounts switch --pretty-json ``` -_See code: [src/commands/accounts/switch.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/accounts/switch.ts)_ +_See code: [src/commands/accounts/switch.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/accounts/switch.ts)_ ## `ably apps` @@ -509,13 +535,14 @@ COMMANDS ably apps current Show the currently selected app ably apps delete Delete an app ably apps list List all apps in the current account + ably apps logs Stream or retrieve app logs ably apps set-apns-p12 Upload Apple Push Notification Service P12 certificate for an app ably apps stats Get app stats with optional live updates ably apps switch Switch to a different Ably app ably apps update Update an app ``` -_See code: [src/commands/apps/index.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/index.ts)_ +_See code: [src/commands/apps/index.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/index.ts)_ ## `ably apps channel-rules` @@ -538,7 +565,7 @@ EXAMPLES $ ably apps channel-rules delete chat ``` -_See code: [src/commands/apps/channel-rules/index.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/channel-rules/index.ts)_ +_See code: [src/commands/apps/channel-rules/index.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/channel-rules/index.ts)_ ## `ably apps channel-rules create` @@ -590,7 +617,7 @@ EXAMPLES $ ably apps channel-rules create --name "notifications" --persisted --push-enabled --app "My App" ``` -_See code: [src/commands/apps/channel-rules/create.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/channel-rules/create.ts)_ +_See code: [src/commands/apps/channel-rules/create.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/channel-rules/create.ts)_ ## `ably apps channel-rules delete NAMEORID` @@ -634,7 +661,7 @@ EXAMPLES $ ably apps channel-rules delete chat --pretty-json ``` -_See code: [src/commands/apps/channel-rules/delete.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/channel-rules/delete.ts)_ +_See code: [src/commands/apps/channel-rules/delete.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/channel-rules/delete.ts)_ ## `ably apps channel-rules list` @@ -657,7 +684,7 @@ EXAMPLES $ ably apps:channel-rules:list --pretty-json ``` -_See code: [src/commands/apps/channel-rules/list.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/channel-rules/list.ts)_ +_See code: [src/commands/apps/channel-rules/list.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/channel-rules/list.ts)_ ## `ably apps channel-rules update NAMEORID` @@ -711,7 +738,7 @@ EXAMPLES $ ably apps channel-rules update notifications --persisted --push-enabled --app "My App" ``` -_See code: [src/commands/apps/channel-rules/update.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/channel-rules/update.ts)_ +_See code: [src/commands/apps/channel-rules/update.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/channel-rules/update.ts)_ ## `ably apps create` @@ -748,7 +775,7 @@ EXAMPLES $ ably apps create --name "My New App" --access-token "YOUR_ACCESS_TOKEN" ``` -_See code: [src/commands/apps/create.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/create.ts)_ +_See code: [src/commands/apps/create.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/create.ts)_ ## `ably apps current` @@ -783,7 +810,7 @@ EXAMPLES $ ably apps current --pretty-json ``` -_See code: [src/commands/apps/current.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/current.ts)_ +_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 [APPID]` @@ -831,7 +858,7 @@ EXAMPLES $ ably apps delete app-id --pretty-json ``` -_See code: [src/commands/apps/delete.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/delete.ts)_ +_See code: [src/commands/apps/delete.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/delete.ts)_ ## `ably apps list` @@ -866,7 +893,105 @@ EXAMPLES $ ably apps list --pretty-json ``` -_See code: [src/commands/apps/list.ts](https://github.com/ably/ably-cli/blob/v0.16.0/src/commands/apps/list.ts)_ +_See code: [src/commands/apps/list.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/list.ts)_ + +## `ably apps logs` + +Stream or retrieve app logs + +``` +USAGE + $ ably apps logs + +DESCRIPTION + Stream or retrieve app logs + +EXAMPLES + $ ably apps logs subscribe + + $ ably apps logs subscribe --rewind 10 + + $ ably apps logs history +``` + +_See code: [src/commands/apps/logs/index.ts](https://github.com/ably/ably-cli/blob/v0.15.0/src/commands/apps/logs/index.ts)_ + +## `ably apps logs history` + +Alias for `ably logs app history` + +``` +USAGE + $ ably apps logs history [--access-token ] [--api-key ] [--client-id ] [--env ] + [--endpoint ] [--host ] [--pretty-json | --json] [--token ] [-v] [--direction + backwards|forwards] [--limit ] + +FLAGS + -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 + --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=