diff --git a/package-lock.json b/package-lock.json index 43c4b81..1a5e27d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "MIT", "dependencies": { "@doist/cli-core": "1.6.0", - "@doist/comms-sdk": "3.4.0", + "@doist/comms-sdk": "3.8.1", "@pnpm/tabtab": "0.5.4", "chalk": "5.6.2", "commander": "14.0.3", @@ -184,9 +184,9 @@ } }, "node_modules/@doist/comms-sdk": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@doist/comms-sdk/-/comms-sdk-3.4.0.tgz", - "integrity": "sha512-CbSUTp2n9JyYebMM4/ifr6XKPl3fjNWQmdnsf01ckz4Vuztnjtuc8pnQHBRTsK52hNVDa3F6TqxxZJaifrhSLw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@doist/comms-sdk/-/comms-sdk-3.8.1.tgz", + "integrity": "sha512-6lgdPNDd1u70pwCdCGcW5T2TBJHPkJ5fZubzwKjceCmL0j/OIhvmriOuLa+rFt2YuEmcGleIhJIMB2NBYFXj7Q==", "license": "MIT", "dependencies": { "@doist/sdk-kmp": "0.2.3", @@ -910,6 +910,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -1189,9 +1190,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1209,9 +1207,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1229,9 +1224,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1249,9 +1241,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1269,9 +1258,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1289,9 +1275,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1309,9 +1292,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1329,9 +1309,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1799,6 +1776,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1815,6 +1793,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1831,6 +1810,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1847,6 +1827,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1863,6 +1844,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1879,6 +1861,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1895,6 +1878,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1911,6 +1895,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1927,6 +1912,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1943,6 +1929,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1959,6 +1946,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1975,6 +1963,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1991,6 +1980,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2007,6 +1997,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2023,6 +2014,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2656,8 +2648,9 @@ "version": "25.8.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz", "integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", - "devOptional": true, + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -3497,6 +3490,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20" } @@ -4272,6 +4266,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5168,6 +5163,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5188,6 +5184,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5208,6 +5205,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5228,6 +5226,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5248,6 +5247,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5268,6 +5268,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5288,6 +5289,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5308,6 +5310,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5328,6 +5331,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5348,6 +5352,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5368,6 +5373,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -5604,6 +5610,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.3.tgz", "integrity": "sha512-7VT90JOkDeaRWpfjOReRGPEKn0ecdARBkDGL+tT1wZY0efPPqkUxLUSmzy/C7TIylQYJC9STISEsCHrqb/7VIA==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -5616,6 +5623,7 @@ "resolved": "https://registry.npmjs.org/marked-terminal-renderer/-/marked-terminal-renderer-2.2.0.tgz", "integrity": "sha512-cUDuyNl4CyI3atFoDtxKL+zDsJAnLo/+1ETfJd8UvFVYRAltCbcPPa1UVEuS//N/85ZoGZFfqWyeRqyjXR37Wg==", "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2", "boxen": "^8.0.1", @@ -7664,6 +7672,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7768,6 +7777,7 @@ "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.6.tgz", "integrity": "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -7818,6 +7828,7 @@ "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "license": "MIT", + "peer": true, "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", @@ -8608,6 +8619,7 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -8745,6 +8757,7 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -9571,6 +9584,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "devOptional": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9679,6 +9693,7 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9727,7 +9742,7 @@ "version": "7.24.6", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { @@ -9850,6 +9865,7 @@ "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -9941,6 +9957,7 @@ "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.11", "@vitest/mocker": "4.1.11", diff --git a/package.json b/package.json index f18b710..d9d8bc3 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ ], "dependencies": { "@doist/cli-core": "1.6.0", - "@doist/comms-sdk": "3.4.0", + "@doist/comms-sdk": "3.8.1", "@pnpm/tabtab": "0.5.4", "chalk": "5.6.2", "commander": "14.0.3", diff --git a/src/lib/api.test.ts b/src/lib/api.test.ts index 3efad8e..2051433 100644 --- a/src/lib/api.test.ts +++ b/src/lib/api.test.ts @@ -1,5 +1,6 @@ import { CommsRequestError } from '@doist/comms-sdk' import { beforeEach, describe, expect, it, vi } from 'vitest' +import packageJson from '../../package.json' with { type: 'json' } import { CliError } from './errors.js' @@ -133,6 +134,10 @@ describe('wrapResult — central 403 translation', () => { expect(sdkMocks.createClient).toHaveBeenCalledWith('test-token', { baseUrl: 'https://comms.staging.todoist.com', + headers: { + 'Doist-Platform': 'cli', + 'Doist-Version': packageJson.version, + }, }) }) @@ -141,6 +146,10 @@ describe('wrapResult — central 403 translation', () => { expect(sdkMocks.createClient).toHaveBeenCalledWith('test-token', { baseUrl: 'https://comms.staging.todoist.com', + headers: { + 'Doist-Platform': 'cli', + 'Doist-Version': packageJson.version, + }, }) }) diff --git a/src/lib/api.ts b/src/lib/api.ts index 534fda7..cdfd1f5 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -9,6 +9,7 @@ import { type Workspace, type WorkspaceUser, } from '@doist/comms-sdk' +import { DOIST_ATTRIBUTION_HEADERS } from './attribution.js' import { getApiTokenSnapshot } from './auth.js' import { getConfig, updateConfig } from './config.js' import { @@ -324,7 +325,10 @@ export function createWrappedCommsClient( options: { baseUrl?: string } = {}, ): CommsApi { const baseUrl = process.env.COMMS_BASE_URL ?? options.baseUrl - const rawApi = new CommsApi(token, baseUrl ? { baseUrl } : undefined) + const rawApi = new CommsApi(token, { + ...(baseUrl ? { baseUrl } : {}), + headers: DOIST_ATTRIBUTION_HEADERS, + }) return createSpinnerWrappedApi(rawApi) } diff --git a/src/lib/attribution.ts b/src/lib/attribution.ts new file mode 100644 index 0000000..82eb4c2 --- /dev/null +++ b/src/lib/attribution.ts @@ -0,0 +1,7 @@ +import packageJson from '../../package.json' with { type: 'json' } + +/** Attribution headers Comms uses to slice traffic by client and release. */ +export const DOIST_ATTRIBUTION_HEADERS = { + 'Doist-Platform': 'cli', + 'Doist-Version': packageJson.version, +} as const diff --git a/src/lib/search-api.test.ts b/src/lib/search-api.test.ts index 94d9d1a..ef5be4f 100644 --- a/src/lib/search-api.test.ts +++ b/src/lib/search-api.test.ts @@ -1,4 +1,6 @@ +import { DEFAULT_API_VERSION } from '@doist/comms-sdk' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import packageJson from '../../package.json' with { type: 'json' } const authMocks = vi.hoisted(() => ({ getApiTokenSnapshot: vi.fn(), @@ -44,7 +46,15 @@ describe('extendedSearch', () => { await extendedSearch({ workspaceId: 69, query: 'roadmap' }) expect(fetchMock.mock.calls[0][0]).toBe( - 'https://comms.staging.todoist.com/api/v1/search?workspace_id=69&query=roadmap', + `https://comms.staging.todoist.com/api/${DEFAULT_API_VERSION}/search?workspace_id=69&query=roadmap`, ) }) + + it('attributes the request to the CLI via Doist-Platform', async () => { + await extendedSearch({ workspaceId: 69, query: 'roadmap' }) + + const headers = fetchMock.mock.calls[0][1].headers as Record + expect(headers['Doist-Platform']).toBe('cli') + expect(headers['Doist-Version']).toBe(packageJson.version) + }) }) diff --git a/src/lib/search-api.ts b/src/lib/search-api.ts index 2dc1a54..8649ea0 100644 --- a/src/lib/search-api.ts +++ b/src/lib/search-api.ts @@ -1,4 +1,5 @@ -import type { SearchResult } from '@doist/comms-sdk' +import { DEFAULT_API_VERSION, type SearchResult } from '@doist/comms-sdk' +import { DOIST_ATTRIBUTION_HEADERS } from './attribution.js' import { getApiTokenSnapshot } from './auth.js' import { CliError } from './errors.js' @@ -6,13 +7,13 @@ function getBaseUrl(authResource?: string): string { const override = process.env.COMMS_BASE_URL if (override) { const trimmed = override.endsWith('/') ? override.slice(0, -1) : override - return `${trimmed}/api/v1` + return `${trimmed}/api/${DEFAULT_API_VERSION}` } if (authResource && authResource !== 'https://comms.todoist.com') { const trimmed = authResource.endsWith('/') ? authResource.slice(0, -1) : authResource - return `${trimmed}/api/v1` + return `${trimmed}/api/${DEFAULT_API_VERSION}` } - return 'https://api.comms.todoist.com/api/v1' + return `https://api.comms.todoist.com/api/${DEFAULT_API_VERSION}` } export type SearchType = 'threads' | 'messages' | 'all' @@ -105,6 +106,7 @@ export async function extendedSearch( headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', + ...DOIST_ATTRIBUTION_HEADERS, }, })