Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/ably-codebase-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,14 @@ Launch these agents **in parallel**. Each agent gets a focused mandate and uses
- Subscribe/stream commands must have `durationFlag`
- Subscribe commands with replay must have `rewindFlag`
- History/stats commands must have `timeRangeFlags`
- Commands creating realtime connections or performing mutations (publish, update, delete, append) must have `clientIdFlag`
- Commands that perform writes (subscribe, publish, enter, set, acquire, update, delete, append) must have `clientIdFlag`; read-only queries (get, get-all, history, occupancy get) must NOT have `clientIdFlag`
- Control API commands must use `ControlBaseCommand.globalFlags`
**Method (LSP — for ambiguous cases):**
3. Use `LSP goToDefinition` on flag spread references to confirm they resolve to `src/flags.ts` (not a local redefinition)
**Reasoning guidance:**
- A command that creates a realtime client or performs a mutation (publish, update, delete, append) but doesn't have `clientIdFlag` is a deviation
- A write command (subscribe, publish, enter, set, acquire, update, delete, append) without `clientIdFlag` is a deviation; a read-only query (get, get-all, history, occupancy get) WITH `clientIdFlag` is also a deviation
- A non-subscribe command having `durationFlag` is suspicious but might be valid (e.g., presence enter)
- Control API commands should NOT have `productApiFlags`
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/ably-new-command/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ static flags = {
- `await this.requireAppId(flags)` — resolves and validates the app ID, returns `Promise<string>` (non-nullable). Calls `this.fail()` internally if no app found — no manual null check needed.
- `await this.runControlCommand(flags, api => api.method(appId))` — creates the Control API client, executes the call, and handles errors in one step. Returns `Promise<T>` (non-nullable). Useful for single API calls; for multi-step flows, use `this.createControlApi(flags)` directly.

**When to include `clientIdFlag`:** Add `...clientIdFlag` whenever the user might want to control which client identity performs the operation. This includes: presence enter/subscribe, spaces members, typing, cursors, publish, and any mutation where permissions may depend on the client (update, delete, annotate). The reason is that users may want to test auth scenarios — e.g., "can client B update client A's message?" — so they need the ability to set their client ID.
**When to include `clientIdFlag`:** Add `...clientIdFlag` to commands where client identity affects the operation: subscribe, publish, enter, set, acquire, update, delete, append, annotate. The reason is that users may want to test auth scenarios — e.g., "can client B update client A's message?" — so they need the ability to set their client ID. Do NOT add to read-only queries (get, get-all, history, occupancy get) — Ably capabilities are operation-based, not clientId-based, so client identity is irrelevant for pure reads.

For history commands, also use `timeRangeFlags`:
```typescript
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/ably-new-command/references/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ async run(): Promise<void> {

## Get Pattern

Get commands perform one-shot queries for current state. They use REST clients and don't need `clientIdFlag`, `durationFlag`, or `rewindFlag`.
Get commands perform one-shot read-only queries for current state. They don't need `clientIdFlag` (Ably capabilities are operation-based, not clientId-based — client identity is irrelevant for reads), `durationFlag`, or `rewindFlag`.

```typescript
static override flags = {
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/ably-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ For each changed command file, run the relevant checks. Spawn agents for paralle
**Flag architecture check (grep, with LSP for ambiguous cases):**
1. **Grep** for flag spreads (`productApiFlags`, `clientIdFlag`, `durationFlag`, `rewindFlag`, `timeRangeFlags`, `ControlBaseCommand.globalFlags`)
2. Verify correct flag sets per the skill rules
3. Check subscribe commands have `durationFlag`, `rewindFlag`, `clientIdFlag` as appropriate; mutation commands (publish, update, delete, append) should also have `clientIdFlag`
3. Check subscribe commands have `durationFlag`, `rewindFlag`, `clientIdFlag` as appropriate; write commands (publish, enter, set, acquire, update, delete, append) should also have `clientIdFlag`; read-only queries (get, get-all, history, occupancy get) must NOT have `clientIdFlag`
4. For ambiguous cases, use **LSP** `goToDefinition` to confirm flag imports resolve to `src/flags.ts`
**JSON output check (grep/read):**
Expand Down
140 changes: 53 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ $ npm install -g @ably/cli
$ ably COMMAND
running command...
$ ably (--version)
@ably/cli/0.17.0 darwin-arm64 node-v25.3.0
@ably/cli/0.17.0 darwin-arm64 node-v24.4.1
$ ably --help [COMMAND]
USAGE
$ ably COMMAND
Expand Down Expand Up @@ -204,23 +204,22 @@ $ ably-interactive
* [`ably spaces`](#ably-spaces)
* [`ably spaces create SPACE_NAME`](#ably-spaces-create-space_name)
* [`ably spaces cursors`](#ably-spaces-cursors)
* [`ably spaces cursors get-all SPACE_NAME`](#ably-spaces-cursors-get-all-space_name)
* [`ably spaces cursors get SPACE_NAME`](#ably-spaces-cursors-get-space_name)
* [`ably spaces cursors set SPACE_NAME`](#ably-spaces-cursors-set-space_name)
* [`ably spaces cursors subscribe SPACE_NAME`](#ably-spaces-cursors-subscribe-space_name)
* [`ably spaces get SPACE_NAME`](#ably-spaces-get-space_name)
* [`ably spaces list`](#ably-spaces-list)
* [`ably spaces locations`](#ably-spaces-locations)
* [`ably spaces locations get-all SPACE_NAME`](#ably-spaces-locations-get-all-space_name)
* [`ably spaces locations get SPACE_NAME`](#ably-spaces-locations-get-space_name)
* [`ably spaces locations set SPACE_NAME`](#ably-spaces-locations-set-space_name)
* [`ably spaces locations subscribe SPACE_NAME`](#ably-spaces-locations-subscribe-space_name)
* [`ably spaces locks`](#ably-spaces-locks)
* [`ably spaces locks acquire SPACE_NAME LOCKID`](#ably-spaces-locks-acquire-space_name-lockid)
* [`ably spaces locks get SPACE_NAME LOCKID`](#ably-spaces-locks-get-space_name-lockid)
* [`ably spaces locks get-all SPACE_NAME`](#ably-spaces-locks-get-all-space_name)
* [`ably spaces locks get SPACE_NAME [LOCKID]`](#ably-spaces-locks-get-space_name-lockid)
* [`ably spaces locks subscribe SPACE_NAME`](#ably-spaces-locks-subscribe-space_name)
* [`ably spaces members`](#ably-spaces-members)
* [`ably spaces members enter SPACE_NAME`](#ably-spaces-members-enter-space_name)
* [`ably spaces members get-all SPACE_NAME`](#ably-spaces-members-get-all-space_name)
* [`ably spaces members get SPACE_NAME`](#ably-spaces-members-get-space_name)
* [`ably spaces members subscribe SPACE_NAME`](#ably-spaces-members-subscribe-space_name)
* [`ably spaces occupancy`](#ably-spaces-occupancy)
* [`ably spaces occupancy get SPACE_NAME`](#ably-spaces-occupancy-get-space_name)
Expand Down Expand Up @@ -4128,17 +4127,15 @@ Get current occupancy metrics for a room

```
USAGE
$ ably rooms occupancy get ROOM [-v] [--json | --pretty-json] [--client-id <value>]
$ ably rooms occupancy get ROOM [-v] [--json | --pretty-json]
ARGUMENTS
ROOM Room to get occupancy for
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
-v, --verbose Output verbose logs
--json Output in JSON format
--pretty-json Output in colorized JSON format
DESCRIPTION
Get current occupancy metrics for a room
Expand Down Expand Up @@ -4573,41 +4570,39 @@ EXAMPLES
$ ably spaces cursors subscribe my-space
$ ably spaces cursors get-all my-space
$ ably spaces cursors get my-space
```

_See code: [src/commands/spaces/cursors/index.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/cursors/index.ts)_

## `ably spaces cursors get-all SPACE_NAME`
## `ably spaces cursors get SPACE_NAME`

Get all current cursors in a space

```
USAGE
$ ably spaces cursors get-all SPACE_NAME [-v] [--json | --pretty-json] [--client-id <value>]
$ ably spaces cursors get SPACE_NAME [-v] [--json | --pretty-json]
ARGUMENTS
SPACE_NAME Name of the space to get cursors from
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
-v, --verbose Output verbose logs
--json Output in JSON format
--pretty-json Output in colorized JSON format
DESCRIPTION
Get all current cursors in a space
EXAMPLES
$ ably spaces cursors get-all my-space
$ ably spaces cursors get my-space
$ ably spaces cursors get-all my-space --json
$ ably spaces cursors get my-space --json
$ ably spaces cursors get-all my-space --pretty-json
$ ably spaces cursors get my-space --pretty-json
```

_See code: [src/commands/spaces/cursors/get-all.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/cursors/get-all.ts)_
_See code: [src/commands/spaces/cursors/get.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/cursors/get.ts)_

## `ably spaces cursors set SPACE_NAME`

Expand Down Expand Up @@ -4769,41 +4764,39 @@ EXAMPLES
$ ably spaces locations subscribe my-space
$ ably spaces locations get-all my-space
$ ably spaces locations get my-space
```

_See code: [src/commands/spaces/locations/index.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locations/index.ts)_

## `ably spaces locations get-all SPACE_NAME`
## `ably spaces locations get SPACE_NAME`

Get all current locations in a space

```
USAGE
$ ably spaces locations get-all SPACE_NAME [-v] [--json | --pretty-json] [--client-id <value>]
$ ably spaces locations get SPACE_NAME [-v] [--json | --pretty-json]
ARGUMENTS
SPACE_NAME Name of the space to get locations from
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
-v, --verbose Output verbose logs
--json Output in JSON format
--pretty-json Output in colorized JSON format
DESCRIPTION
Get all current locations in a space
EXAMPLES
$ ably spaces locations get-all my-space
$ ably spaces locations get my-space
$ ably spaces locations get-all my-space --json
$ ably spaces locations get my-space --json
$ ably spaces locations get-all my-space --pretty-json
$ ably spaces locations get my-space --pretty-json
```

_See code: [src/commands/spaces/locations/get-all.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locations/get-all.ts)_
_See code: [src/commands/spaces/locations/get.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locations/get.ts)_

## `ably spaces locations set SPACE_NAME`

Expand Down Expand Up @@ -4891,7 +4884,7 @@ EXAMPLES
$ ably spaces locks get my-space my-lock-id
$ ably spaces locks get-all my-space
$ ably spaces locks get my-space
```

_See code: [src/commands/spaces/locks/index.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locks/index.ts)_
Expand Down Expand Up @@ -4931,68 +4924,41 @@ EXAMPLES

_See code: [src/commands/spaces/locks/acquire.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locks/acquire.ts)_

## `ably spaces locks get SPACE_NAME LOCKID`
## `ably spaces locks get SPACE_NAME [LOCKID]`

Get a lock in a space
Get a lock or all locks in a space

```
USAGE
$ ably spaces locks get SPACE_NAME LOCKID [-v] [--json | --pretty-json] [--client-id <value>]
$ ably spaces locks get SPACE_NAME [LOCKID] [-v] [--json | --pretty-json]
ARGUMENTS
SPACE_NAME Name of the space to get lock from
LOCKID Lock ID to get
SPACE_NAME Name of the space to get locks from
LOCKID Lock ID to get (omit to get all locks)
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
-v, --verbose Output verbose logs
--json Output in JSON format
--pretty-json Output in colorized JSON format
DESCRIPTION
Get a lock in a space
Get a lock or all locks in a space
EXAMPLES
$ ably spaces locks get my-space my-lock
$ ably spaces locks get my-space my-lock --json
$ ably spaces locks get my-space
$ ably spaces locks get my-space my-lock --pretty-json
```
$ ably spaces locks get my-space --json
_See code: [src/commands/spaces/locks/get.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locks/get.ts)_
$ ably spaces locks get my-space --pretty-json
## `ably spaces locks get-all SPACE_NAME`
$ ably spaces locks get my-space lock-id
Get all current locks in a space
$ ably spaces locks get my-space lock-id --json
$ ably spaces locks get my-space lock-id --pretty-json
```
USAGE
$ ably spaces locks get-all SPACE_NAME [-v] [--json | --pretty-json] [--client-id <value>]

ARGUMENTS
SPACE_NAME Name of the space to get locks from
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
Get all current locks in a space
EXAMPLES
$ ably spaces locks get-all my-space
$ ably spaces locks get-all my-space --json
$ ably spaces locks get-all my-space --pretty-json
```

_See code: [src/commands/spaces/locks/get-all.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locks/get-all.ts)_
_See code: [src/commands/spaces/locks/get.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/locks/get.ts)_

## `ably spaces locks subscribe SPACE_NAME`

Expand Down Expand Up @@ -5044,7 +5010,7 @@ EXAMPLES
$ ably spaces members subscribe my-space
$ ably spaces members get-all my-space
$ ably spaces members get my-space
```

_See code: [src/commands/spaces/members/index.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/members/index.ts)_
Expand Down Expand Up @@ -5085,13 +5051,13 @@ EXAMPLES

_See code: [src/commands/spaces/members/enter.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/members/enter.ts)_

## `ably spaces members get-all SPACE_NAME`
## `ably spaces members get SPACE_NAME`

Get all members in a space

```
USAGE
$ ably spaces members get-all SPACE_NAME [-v] [--json | --pretty-json]
$ ably spaces members get SPACE_NAME [-v] [--json | --pretty-json]
ARGUMENTS
SPACE_NAME Name of the space to get members from
Expand All @@ -5105,14 +5071,14 @@ DESCRIPTION
Get all members in a space
EXAMPLES
$ ably spaces members get-all my-space
$ ably spaces members get my-space
$ ably spaces members get-all my-space --json
$ ably spaces members get my-space --json
$ ably spaces members get-all my-space --pretty-json
$ ably spaces members get my-space --pretty-json
```

_See code: [src/commands/spaces/members/get-all.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/members/get-all.ts)_
_See code: [src/commands/spaces/members/get.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/spaces/members/get.ts)_

## `ably spaces members subscribe SPACE_NAME`

Expand Down
9 changes: 9 additions & 0 deletions src/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const SKIP_AUTH_INFO_COMMANDS = [
export abstract class AblyBaseCommand extends InteractiveBaseCommand {
protected _authInfoShown = false;
protected cleanupInProgress = false;
protected _suppressSdkErrorLogs = false;
private _cachedRestClient: Ably.Rest | null = null;
private _cachedRealtimeClient: Ably.Realtime | null = null;

Expand Down Expand Up @@ -934,6 +935,14 @@ export abstract class AblyBaseCommand extends InteractiveBaseCommand {

// Always add a log handler to control SDK output formatting and destination
options.logHandler = (message: string, level: number) => {
// Allow commands to suppress SDK error logs during cleanup. Set by
// SpacesBaseCommand.finally() to silence teardown noise (e.g. the
// Spaces SDK's cursors module queues a presence enter that fails with
// 80017 when the connection closes). Only suppresses error-level logs
// (level <= 1); only active after run() completes — see the comment
// in SpacesBaseCommand.finally() for the full safety rationale.
if (this._suppressSdkErrorLogs && level <= 1) return;

if (isJsonMode) {
// JSON Mode Handling
if (flags.verbose && level <= 2) {
Expand Down
3 changes: 1 addition & 2 deletions src/commands/rooms/occupancy/get.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Args } from "@oclif/core";
import { ChatClient, Room } from "@ably/chat";
import { ChatBaseCommand } from "../../../chat-base-command.js";
import { clientIdFlag, productApiFlags } from "../../../flags.js";
import { productApiFlags } from "../../../flags.js";
import { formatResource } from "../../../utils/output.js";

export default class RoomsOccupancyGet extends ChatBaseCommand {
Expand All @@ -23,7 +23,6 @@ export default class RoomsOccupancyGet extends ChatBaseCommand {

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

private chatClient: ChatClient | null = null;
Expand Down
2 changes: 1 addition & 1 deletion src/commands/spaces/cursors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ export default class SpacesCursors extends BaseTopicCommand {
static override examples = [
"<%= config.bin %> <%= command.id %> set my-space --x 100 --y 200",
"<%= config.bin %> <%= command.id %> subscribe my-space",
"<%= config.bin %> <%= command.id %> get-all my-space",
"<%= config.bin %> <%= command.id %> get my-space",
];
}
Loading
Loading