diff --git a/src/base-command.ts b/src/base-command.ts index bb8d9cf16..557de85ed 100644 --- a/src/base-command.ts +++ b/src/base-command.ts @@ -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 { @@ -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 } - ).agents = { "ably-cli": getCliVersion() }; + ).agents = { [getAgentName()]: getCliVersion() }; return options; } diff --git a/src/commands/status.ts b/src/commands/status.ts index dfc4b3b13..6c3cdcc69 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -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; @@ -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; diff --git a/src/services/control-api.ts b/src/services/control-api.ts index 5565dc1d8..fe4de0f7a 100644 --- a/src/services/control-api.ts +++ b/src/services/control-api.ts @@ -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; @@ -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, }; diff --git a/src/utils/version.ts b/src/utils/version.ts index a0aee04e9..a92018015 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -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 @@ -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 */ diff --git a/test/unit/base-command/agent-header.test.ts b/test/unit/base-command/agent-header.test.ts index 4c4006a53..d49da7358 100644 --- a/test/unit/base-command/agent-header.test.ts +++ b/test/unit/base-command/agent-header.test.ts @@ -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"; @@ -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 () {