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
7 changes: 4 additions & 3 deletions src/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
formatWarning,
} from "./utils/output.js";
import stripAnsi from "strip-ansi";
import { getCliVersion } from "./utils/version.js";
import { getAgentName, getCliVersion } from "./utils/version.js";
import Spaces from "@ably/spaces";
import { ChatClient } from "@ably/chat";
import {
Expand Down Expand Up @@ -1099,10 +1099,11 @@ export abstract class AblyBaseCommand extends InteractiveBaseCommand {
// Set logLevel to highest ONLY when using custom handler to capture everything needed by it
options.logLevel = 4;

// Add agent header to identify requests from the CLI
// Add agent header to identify requests from the CLI. Web CLI traffic is
// tagged separately so Ably can distinguish hosted vs local usage.
(
options as Ably.ClientOptions & { agents: Record<string, string> }
).agents = { "ably-cli": getCliVersion() };
).agents = { [getAgentName()]: getCliVersion() };

return options;
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AblyBaseCommand } from "../base-command.js";
import { coreGlobalFlags } from "../flags.js";
import { BaseFlags } from "../types/cli.js";
import openUrl from "../utils/open-url.js";
import { getCliVersion } from "../utils/version.js";
import { getAgentName, getCliVersion } from "../utils/version.js";

interface StatusResponse {
status?: boolean;
Expand Down Expand Up @@ -46,7 +46,7 @@ export default class StatusCommand extends AblyBaseCommand {
try {
const response = await fetch("https://ably.com/status/up.json", {
headers: {
"Ably-Agent": `ably-cli/${getCliVersion()}`,
"Ably-Agent": `${getAgentName()}/${getCliVersion()}`,
},
});
const data = (await response.json()) as StatusResponse;
Expand Down
4 changes: 2 additions & 2 deletions src/services/control-api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fetch, { type RequestInit } from "node-fetch";
import { CommandError } from "../errors/command-error.js";
import { getCliVersion } from "../utils/version.js";
import { getAgentName, getCliVersion } from "../utils/version.js";

export interface ControlApiOptions {
accessToken: string;
Expand Down Expand Up @@ -528,7 +528,7 @@ export class ControlApi {
Accept: "application/json",
Authorization: `Bearer ${this.accessToken}`,
...(!isFormData && { "Content-Type": "application/json" }),
"Ably-Agent": `ably-cli/${getCliVersion()}`,
"Ably-Agent": `${getAgentName()}/${getCliVersion()}`,
},
method,
};
Expand Down
10 changes: 10 additions & 0 deletions src/utils/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import chalk from "chalk";
// Import package.json directly - TypeScript will resolve this at compile time
import packageJson from "../../package.json" with { type: "json" };
import isWebCliMode from "./web-mode.js";

/**
* Get the CLI version from package.json
Expand All @@ -13,6 +14,15 @@ export function getCliVersion(): string {
return packageJson.version;
}

/**
* Agent name used in Ably-Agent headers and SDK agents option. Distinguishes
* traffic from the local CLI vs the hosted Web CLI so it can be attributed
* separately in Ably's analytics.
*/
export function getAgentName(): string {
return isWebCliMode() ? "ably-web-cli" : "ably-cli";
}

/**
* Get standardized version information object
*/
Expand Down
29 changes: 21 additions & 8 deletions test/unit/base-command/agent-header.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { describe, it, expect, afterEach, vi } from "vitest";
import { Config } from "@oclif/core";
import { AblyBaseCommand } from "../../../src/base-command.js";
import { getCliVersion } from "../../../src/utils/version.js";
Expand All @@ -18,24 +18,37 @@ class TestCommand extends AblyBaseCommand {
}

describe("Agent Header Unit Tests", function () {
beforeEach(function () {});
afterEach(function () {
delete process.env.ABLY_WEB_CLI_MODE;
});

describe("Ably SDK Agent Header", function () {
it("should include agent header in client options", function () {
it("should tag CLI traffic with the ably-cli agent by default", function () {
const mockConfig = { runHook: vi.fn() } as unknown as Config;
const command = new TestCommand([], mockConfig);

const flags = {
const clientOptions = command.testGetClientOptions({
"api-key": "test-key:secret",
};

const clientOptions = command.testGetClientOptions(flags);
});

expect(clientOptions.agents).toBeDefined();
expect(clientOptions.agents).toEqual({
"ably-cli": getCliVersion(),
});
});

it("should tag Web CLI traffic with the ably-web-cli agent", function () {
process.env.ABLY_WEB_CLI_MODE = "true";
const mockConfig = { runHook: vi.fn() } as unknown as Config;
const command = new TestCommand([], mockConfig);

const clientOptions = command.testGetClientOptions({
"api-key": "test-key:secret",
});

expect(clientOptions.agents).toEqual({
"ably-web-cli": getCliVersion(),
});
});
});

describe("Version Format", function () {
Expand Down
Loading