Skip to content

[DX-964] Add new spaces commands#177

Open
sacOO7 wants to merge 1 commit intomainfrom
feature/add-new-spaces-commands
Open

[DX-964] Add new spaces commands#177
sacOO7 wants to merge 1 commit intomainfrom
feature/add-new-spaces-commands

Conversation

@sacOO7
Copy link
Contributor

@sacOO7 sacOO7 commented Mar 19, 2026

Added following missing commands

  1. spaces create
  2. spaces get
  3. spaces subscribe
  4. spaces members get-all

Summary by CodeRabbit

  • New Features

    • Added ably spaces create SPACE_NAME command to create new spaces
    • Added ably spaces get SPACE_NAME command to view space state and members
    • Added ably spaces members get-all SPACE_NAME command to retrieve all members in a space
    • Added ably spaces subscribe SPACE_NAME command to monitor real-time space updates
  • Documentation

    • Extended README with new space management command documentation and examples

@vercel
Copy link

vercel bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli-web-cli Ready Ready Preview, Comment Mar 19, 2026 2:25pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Walkthrough

This PR introduces four new space management commands (create, get, subscribe, and members get-all) to the Ably CLI, complete with command implementations, utility updates, comprehensive test coverage, and documentation. The changes extend the spaces feature with membership and state retrieval capabilities.

Changes

Cohort / File(s) Summary
Documentation & Examples
README.md, src/commands/spaces/index.ts
Extended README with new command documentation for create, get, members get-all, and subscribe; added example usage entries in spaces index command.
Space Management Commands
src/commands/spaces/create.ts, src/commands/spaces/get.ts, src/commands/spaces/subscribe.ts
Implemented three new oclif commands: create initializes a space without entering; get retrieves presence state and member list via REST; subscribe listens to space update events with ongoing member tracking.
Space Members Command
src/commands/spaces/members/get-all.ts
Added members get-all command to fetch all current members from a specified space with formatted output support.
Output Formatting
src/utils/spaces-output.ts
Updated member block formatting to separate last event name and timestamp into distinct labeled lines.
Testing Utilities
test/helpers/mock-ably-spaces.ts
Extended MockSpace interface with subscribe, unsubscribe, and getState methods plus internal _emitter for space-level event simulation.
Command Test Suites
test/unit/commands/spaces/create.test.ts, test/unit/commands/spaces/get.test.ts, test/unit/commands/spaces/members/get-all.test.ts, test/unit/commands/spaces/subscribe.test.ts
Added comprehensive Vitest suites for each new command covering success paths, JSON output validation, error handling, and edge cases (empty members, API failures).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • jamiehenson
  • denissellu

Poem

🐰 Four new spaces to explore and command,
From creation through subscription so grand,
Members gathered, their presence displayed,
With tests for each move that our spaces have made! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change—adding four new spaces commands (create, get, subscribe, members get-all)—which aligns with the changeset's primary focus.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/add-new-spaces-commands
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/commands/spaces/get.ts (2)

128-158: Consider extracting member formatting to avoid duplication with formatMemberBlock.

The inline formatting here closely mirrors formatMemberBlock from src/utils/spaces-output.ts (context snippet 1). While the input types differ (MemberInfo vs SpaceMember), the output structure is identical.

You could either:

  1. Create a shared interface that both types satisfy for formatting purposes, or
  2. Accept this duplication given the REST-based approach in this command

This is a minor refactor opportunity that can be deferred.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/spaces/get.ts` around lines 128 - 158, The member-printing loop
duplicates the output of formatMemberBlock; refactor by creating a small adapter
that converts the REST MemberInfo shape used in the get command to the shape
expected by formatMemberBlock (or introduce a shared interface both MemberInfo
and SpaceMember implement), then replace the inline logging inside the loop with
a single call to formatMemberBlock(memberAdapter) to centralize formatting and
remove duplication; reference the loop in the get command, the types MemberInfo
and SpaceMember, and the existing formatMemberBlock utility when making the
change.

17-17: Extract SPACE_CHANNEL_TAG to a shared constant file.

This constant is defined in three files: src/commands/spaces/get.ts, src/commands/spaces/occupancy/get.ts, and src/commands/spaces/occupancy/subscribe.ts. Extract it to src/utils/spaces-constants.ts to prevent divergence if the suffix changes.

♻️ Suggested refactor

Create a shared constant file:

// src/utils/spaces-constants.ts
export const SPACE_CHANNEL_TAG = "::$space";

Then import it in all three files:

-const SPACE_CHANNEL_TAG = "::$space";
+import { SPACE_CHANNEL_TAG } from "../../utils/spaces-constants.js";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/spaces/get.ts` at line 17, Extract the repeated constant
SPACE_CHANNEL_TAG into a single shared module (e.g., export const
SPACE_CHANNEL_TAG = "::$space" from a new spaces-constants module) and replace
the local declarations in each file with an import from that module; update the
three places where SPACE_CHANNEL_TAG is currently defined to import the shared
constant so the suffix is maintained in one location.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/spaces/members/get-all.ts`:
- Around line 35-38: Remove clientIdFlag from the static flags definition on the
get-all command: update the static override flags object (the "static override
flags" declaration in the get-all command class) to only spread productApiFlags
and remove ...clientIdFlag; also search the same class for any references to
clientIdFlag and delete or adapt them so the command remains a read-only query
using only productApiFlags.

In `@src/commands/spaces/subscribe.ts`:
- Around line 42-43: The listener stored in the property "listener" is
subscribed via this.space!.subscribe("update", this.listener) but never removed;
override or extend the class's finally() method (the one in SpacesBaseCommand or
the command class that sets this.listener) to, if this.space and this.listener
are non-null, call this.space.unsubscribe("update", this.listener), then set
this.listener = null to allow GC; ensure the unsubscribe call is guarded (check
this.space and this.listener) and placed before calling super.finally() if
applicable.

---

Nitpick comments:
In `@src/commands/spaces/get.ts`:
- Around line 128-158: The member-printing loop duplicates the output of
formatMemberBlock; refactor by creating a small adapter that converts the REST
MemberInfo shape used in the get command to the shape expected by
formatMemberBlock (or introduce a shared interface both MemberInfo and
SpaceMember implement), then replace the inline logging inside the loop with a
single call to formatMemberBlock(memberAdapter) to centralize formatting and
remove duplication; reference the loop in the get command, the types MemberInfo
and SpaceMember, and the existing formatMemberBlock utility when making the
change.
- Line 17: Extract the repeated constant SPACE_CHANNEL_TAG into a single shared
module (e.g., export const SPACE_CHANNEL_TAG = "::$space" from a new
spaces-constants module) and replace the local declarations in each file with an
import from that module; update the three places where SPACE_CHANNEL_TAG is
currently defined to import the shared constant so the suffix is maintained in
one location.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fabbcfdc-be62-4728-9cfb-dd91c60baabe

📥 Commits

Reviewing files that changed from the base of the PR and between 5692f63 and 28a33b5.

📒 Files selected for processing (12)
  • README.md
  • src/commands/spaces/create.ts
  • src/commands/spaces/get.ts
  • src/commands/spaces/index.ts
  • src/commands/spaces/members/get-all.ts
  • src/commands/spaces/subscribe.ts
  • src/utils/spaces-output.ts
  • test/helpers/mock-ably-spaces.ts
  • test/unit/commands/spaces/create.test.ts
  • test/unit/commands/spaces/get.test.ts
  • test/unit/commands/spaces/members/get-all.test.ts
  • test/unit/commands/spaces/subscribe.test.ts
👮 Files not reviewed due to content moderation or server errors (8)
  • test/helpers/mock-ably-spaces.ts
  • src/commands/spaces/index.ts
  • src/utils/spaces-output.ts
  • README.md
  • test/unit/commands/spaces/subscribe.test.ts
  • test/unit/commands/spaces/create.test.ts
  • src/commands/spaces/create.ts
  • test/unit/commands/spaces/members/get-all.test.ts

Comment on lines +35 to +38
static override flags = {
...productApiFlags,
...clientIdFlag,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if other read-only spaces commands (like occupancy get) include clientIdFlag
rg -A5 'static override flags' src/commands/spaces/occupancy/get.ts src/commands/spaces/get.ts

Repository: ably/ably-cli

Length of output: 744


Remove clientIdFlag from the flags definition.

Read-only query commands like spaces get and spaces occupancy get use only productApiFlags. Since get-all is a read-only query that doesn't perform an operation requiring client identity control, clientIdFlag should be removed to keep the flag surface minimal and consistent with the existing pattern.

static override flags = {
  ...productApiFlags,
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/spaces/members/get-all.ts` around lines 35 - 38, Remove
clientIdFlag from the static flags definition on the get-all command: update the
static override flags object (the "static override flags" declaration in the
get-all command class) to only spread productApiFlags and remove
...clientIdFlag; also search the same class for any references to clientIdFlag
and delete or adapt them so the command remains a read-only query using only
productApiFlags.

Comment on lines +42 to +43
private listener: ((spaceState: { members: SpaceMember[] }) => void) | null =
null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if space.unsubscribe is called anywhere in SpacesBaseCommand or related cleanup
rg -n 'space\.unsubscribe' src/

Repository: ably/ably-cli

Length of output: 39


🏁 Script executed:

cat -n src/commands/spaces/subscribe.ts

Repository: ably/ably-cli

Length of output: 3840


🏁 Script executed:

cat -n src/spaces-base-command.ts

Repository: ably/ably-cli

Length of output: 16879


🏁 Script executed:

rg -n "finally" src/commands/spaces/subscribe.ts

Repository: ably/ably-cli

Length of output: 39


Listener is stored but never unsubscribed—resource leak in cleanup.

The this.listener reference is registered at line 96 via this.space!.subscribe("update", this.listener), but SpacesBaseCommand.finally() does not call this.space.unsubscribe("update", ...). The listener will persist after the command exits, preventing garbage collection and causing memory leaks during long-running CLI sessions.

Override finally() to clean up the space "update" listener:

🛠️ Proposed fix
+  async finally(error: Error | undefined): Promise<void> {
+    if (this.space && this.listener) {
+      try {
+        this.space.unsubscribe("update", this.listener);
+      } catch (e) {
+        this.debug(`Failed to unsubscribe from space update: ${e}`);
+      }
+    }
+    await super.finally(error);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private listener: ((spaceState: { members: SpaceMember[] }) => void) | null =
null;
private listener: ((spaceState: { members: SpaceMember[] }) => void) | null =
null;
async finally(error: Error | undefined): Promise<void> {
if (this.space && this.listener) {
try {
this.space.unsubscribe("update", this.listener);
} catch (e) {
this.debug(`Failed to unsubscribe from space update: ${e}`);
}
}
await super.finally(error);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/spaces/subscribe.ts` around lines 42 - 43, The listener stored
in the property "listener" is subscribed via this.space!.subscribe("update",
this.listener) but never removed; override or extend the class's finally()
method (the one in SpacesBaseCommand or the command class that sets
this.listener) to, if this.space and this.listener are non-null, call
this.space.unsubscribe("update", this.listener), then set this.listener = null
to allow GC; ensure the unsubscribe call is guarded (check this.space and
this.listener) and placed before calling super.finally() if applicable.

@sacOO7 sacOO7 requested review from Copilot March 19, 2026 14:58
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds missing Ably Spaces CLI commands to create/inspect/subscribe to spaces and to retrieve all space members, along with supporting mocks, output tweaks, tests, and README docs.

Changes:

  • Introduces new commands: spaces create, spaces get, spaces subscribe, and spaces members get-all.
  • Extends the Spaces test mocks and adds unit tests covering JSON and non-JSON output plus error cases.
  • Updates human-readable member output to separate “Last Event” from its timestamp, and documents the new commands in the README.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/unit/commands/spaces/subscribe.test.ts Adds unit coverage for spaces:subscribe output, JSON mode, and error handling.
test/unit/commands/spaces/members/get-all.test.ts Adds unit coverage for spaces:members:get-all including NDJSON envelopes and non-JSON display.
test/unit/commands/spaces/get.test.ts Adds unit coverage for REST-backed spaces:get behavior and parsing.
test/unit/commands/spaces/create.test.ts Adds unit coverage for spaces:create including JSON envelope and failure path.
test/helpers/mock-ably-spaces.ts Extends Spaces SDK mocks with space-level subscribe/unsubscribe/state support.
src/utils/spaces-output.ts Adjusts member block formatting to output event type and timestamp on separate lines.
src/commands/spaces/subscribe.ts Implements spaces subscribe command to stream space update events.
src/commands/spaces/members/get-all.ts Implements spaces members get-all command to retrieve all members without entering.
src/commands/spaces/index.ts Updates topic examples to include new spaces subcommands.
src/commands/spaces/get.ts Implements spaces get via REST presence query against the ::$space channel.
src/commands/spaces/create.ts Implements spaces create command behavior and JSON/human output.
README.md Documents the new spaces commands and adds them to the command index.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +56 to +61
if (!this.shouldOutputJson(flags)) {
this.log(
formatSuccess(`Subscribed to space: ${formatResource(spaceName)}.`),
);
this.log(formatListening("Listening for space updates."));
}
Comment on lines +19 to +50
static override description = "Create a new space";

static override examples = [
"$ ably spaces create my-space",
"$ ably spaces create my-space --json",
"$ ably spaces create my-space --client-id my-client",
];

static override flags = {
...productApiFlags,
...clientIdFlag,
};

async run(): Promise<void> {
const { args, flags } = await this.parse(SpacesCreate);
const spaceName = args.space_name;

try {
if (!this.shouldOutputJson(flags)) {
this.log(formatProgress(`Creating space ${formatResource(spaceName)}`));
}

await this.initializeSpace(flags, spaceName, {
enterSpace: false,
setupConnectionLogging: false,
});

if (this.shouldOutputJson(flags)) {
this.logJsonResult({ space: { name: spaceName } }, flags);
} else {
this.log(formatSuccess(`Space ${formatResource(spaceName)} created.`));
}
Comment on lines +4426 to +4446
## `ably spaces create SPACE_NAME`

Create a new space

```
USAGE
$ ably spaces create SPACE_NAME [-v] [--json | --pretty-json] [--client-id <value>]

ARGUMENTS
SPACE_NAME Name of the space to create

FLAGS
-v, --verbose Output verbose logs
--client-id=<value> Overrides any default client ID when using API authentication. Use "none" to explicitly set
no client ID. Not applicable when using token authentication.
--json Output in JSON format
--pretty-json Output in colorized JSON format

DESCRIPTION
Create a new space

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants