From 202ae2d55b129cf4fac197df0b3e1e50a3b97429 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Thu, 24 Sep 2026 16:53:47 +0200 Subject: [PATCH 1/3] feat(cli): session capabilities command (beta) --- README.md | 8 ++++ src/commands/session/capabilities.ts | 33 ++++++++++++++ src/services/platform-management.ts | 33 ++++++++++++++ test/commands/platform-management.test.ts | 54 +++++++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 src/commands/session/capabilities.ts diff --git a/README.md b/README.md index 69c4d7c..3ab1c62 100644 --- a/README.md +++ b/README.md @@ -1274,8 +1274,16 @@ polymorfa widget view --project project_123 polymorfa widget update --project project_123 --input widget-patch.json polymorfa session stop session_1 session_2 --project project_123 polymorfa session delete session_1 session_2 --project project_123 --yes +polymorfa session capabilities support-line ``` +`session capabilities` shows the WhatsApp features and limits WhatsApp has +enabled for one number, as of the number's last configuration sync. Each line +is the capability key, its value (`enabled`, `off`, a limit, or `unknown`), and +whether WhatsApp sent the setting (`server`) or its default applies +(`client_default`). Before the number's first sync every value is `unknown`. +It is a beta: the team must be enrolled, and it needs `sessions:read`. + Key and project-token lists contain metadata only; neither command returns raw bearer material. The API's reported API-key scope mask is retained in JSON but must not be treated as proof of live authorization. `key revoke` requires diff --git a/src/commands/session/capabilities.ts b/src/commands/session/capabilities.ts new file mode 100644 index 0000000..5711fc4 --- /dev/null +++ b/src/commands/session/capabilities.ts @@ -0,0 +1,33 @@ +import {Args} from '@oclif/core' + +import {BaseCommand} from '../../base-command.js' +import { + getSessionCapabilities, + renderSessionCapabilities, +} from '../../services/platform-management.js' + +export default class SessionCapabilitiesCommand extends BaseCommand { + static override args = { + session: Args.string({ + description: 'Session ID or name.', + required: true, + }), + } + + static override description = + "Show the WhatsApp features and limits WhatsApp has enabled for a number, as of the number's last configuration sync. Beta: the team must be enrolled." + + static override examples = [ + '<%= config.bin %> session capabilities support-line', + '<%= config.bin %> session capabilities support-line --json', + ] + + async run(): Promise { + const {args, flags} = await this.parse(SessionCapabilitiesCommand) + const result = await getSessionCapabilities( + await this.platformClient(flags), + args.session, + ) + this.writeValue(result, renderSessionCapabilities(result.data), flags) + } +} diff --git a/src/services/platform-management.ts b/src/services/platform-management.ts index 1b7409f..c46d981 100644 --- a/src/services/platform-management.ts +++ b/src/services/platform-management.ts @@ -15,6 +15,7 @@ import { type SessionBatchRemoveResult, type SessionBatchRequest, type SessionBatchStopResult, + type SessionCapabilities, type UpdateQuickLinkSettingsInput, type OrganizationQuickLinkSettings, type ProjectQuickLinkSettings, @@ -113,6 +114,38 @@ export async function stopSessions( return result(response.data.data, response.metadata) } +/** WhatsApp features and limits WhatsApp enabled for a number (beta). */ +export async function getSessionCapabilities( + client: ClientInstance<'organization'>, + session: string, +): Promise> { + const response = await client.sessions.getCapabilities(session) + return result(response.data.data, response.metadata) +} + +export function renderSessionCapabilities( + capabilities: SessionCapabilities, +): string { + const header = [ + `Session\t${capabilities.session}`, + `Status\t${capabilities.status}`, + `Synced\t${capabilities.syncedAt ?? 'never'}`, + `Account\t${capabilities.accountType ?? 'unknown'}`, + ] + const rows = capabilities.capabilities.map((capability) => { + const value = + capability.value === null + ? 'unknown' + : typeof capability.value === 'boolean' + ? capability.value + ? 'enabled' + : 'off' + : `${capability.value}${capability.unit === null ? '' : ` ${capability.unit}`}` + return `${capability.key}\t${value}\t${capability.source ?? '-'}` + }) + return [...header, '', ...rows].join('\n') +} + export async function deleteSessions( client: ClientInstance<'organization'>, sessionIds: readonly string[], diff --git a/test/commands/platform-management.test.ts b/test/commands/platform-management.test.ts index 3c6e151..e2bac8c 100644 --- a/test/commands/platform-management.test.ts +++ b/test/commands/platform-management.test.ts @@ -341,6 +341,60 @@ describe('Platform management commands', () => { expect(server.requests).toHaveLength(0) }) + test("reads a number's WhatsApp capabilities", async () => { + server = await startHttpServer({ + body: { + data: { + session: 'support/line', + projectId: 'project_1', + status: 'synced', + syncedAt: '2026-09-24T10:00:00.000Z', + checkedAt: '2026-09-24T11:00:00.000Z', + accountType: 'personal', + capabilities: [ + { + key: 'channels', + kind: 'feature', + unit: null, + value: true, + source: 'server', + }, + { + key: 'messageEdit.windowSeconds', + kind: 'limit', + unit: 'seconds', + value: 1200, + source: 'client_default', + }, + { + key: 'metaAi', + kind: 'feature', + unit: null, + value: null, + source: null, + }, + ], + }, + }, + }) + + const result = await platformEnvironment(() => + runCommand('session capabilities support/line'), + ) + + expect(result.error, result.stderr).toBeUndefined() + expect(server.requests[0]).toMatchObject({ + method: 'GET', + url: '/platform/sessions/support%2Fline/capabilities', + }) + expect(result.stdout).toContain('Status\tsynced') + expect(result.stdout).toContain('channels\tenabled\tserver') + expect(result.stdout).toContain( + 'messageEdit.windowSeconds\t1200 seconds\tclient_default', + ) + expect(result.stdout).toContain('metaAi\tunknown\t-') + }) + test('requests asynchronous stops for a bounded session batch', async () => { server = await startHttpServer({body: {data: {stopping: 1}}}) From eeac390e33665c28c541b903819ec025c404c6dd Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sun, 27 Sep 2026 07:51:57 +0200 Subject: [PATCH 2/3] docs(cli): explain the account_type capability source --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ab1c62..d9935d8 100644 --- a/README.md +++ b/README.md @@ -1280,8 +1280,10 @@ polymorfa session capabilities support-line `session capabilities` shows the WhatsApp features and limits WhatsApp has enabled for one number, as of the number's last configuration sync. Each line is the capability key, its value (`enabled`, `off`, a limit, or `unknown`), and -whether WhatsApp sent the setting (`server`) or its default applies -(`client_default`). Before the number's first sync every value is `unknown`. +its source: `server` (WhatsApp sent the setting), `client_default` (WhatsApp +sent none, so its default applies) or `account_type` (the capability does not +apply to this kind of account, for example a business-only feature on a +personal number). Before the number's first sync every value is `unknown`. It is a beta: the team must be enrolled, and it needs `sessions:read`. Key and project-token lists contain metadata only; neither command returns raw From 35a82aaa01fbf2928ef13795073f7c7cb6af68e9 Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Sat, 3 Oct 2026 08:26:56 +0300 Subject: [PATCH 3/3] fix(cli): verify capabilities against published SDK --- .github/workflows/ci.yml | 16 ---------------- README.md | 22 +++++++++++----------- package-lock.json | 9 +++++---- package.json | 2 +- 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6dc69c..144b7c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,21 +27,5 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: npm - # Hybrid Link tier transitions need the companion SDK PR. Verify this PR - # against that immutable source revision until its package is published. - - run: npm ci --ignore-scripts - - uses: actions/checkout@v6 - with: - persist-credentials: false - repository: polymorfa/sdks - ref: 6271f4734476c195b4a84d1863561222a8e515d1 - path: .sdk-source - run: npm ci - working-directory: .sdk-source - - run: npm run build -w @polymorfa/sdk - working-directory: .sdk-source - - run: npm pack -w @polymorfa/sdk --pack-destination .. --ignore-scripts - working-directory: .sdk-source - - run: npm install --no-save --package-lock=false --ignore-scripts ./polymorfa-sdk-*.tgz - - run: npm run build - run: npm run verify diff --git a/README.md b/README.md index 0ee8426..d83f6f0 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ prerelease in `package.json`. The version output includes both runtime components: ```text -polymorfa/0.1.0-dev.0 sdk/0.1.0-dev.20261001215108 +polymorfa/0.1.0-dev.0 sdk/0.1.0-dev.20261003052138 ``` The SDK component reports the version built into the installed SDK. Dev @@ -1576,8 +1576,10 @@ polymorfa session delete session_1 session_2 --project project_123 --yes polymorfa session capabilities support-line ``` -`session capabilities` shows the WhatsApp features and limits WhatsApp has -enabled for one number, as of the number's last configuration sync. Each line +`session capabilities` shows the WhatsApp configuration gates and limits for +one number, as of its last configuration sync. An enabled gate does not prove +that the feature is usable in a particular client or environment, or that +WhatsApp will accept an operation. Each line is the capability key, its value (`enabled`, `off`, a limit, or `unknown`), and its source: `server` (WhatsApp sent the setting), `client_default` (WhatsApp sent none, so its default applies) or `account_type` (the capability does not @@ -2314,17 +2316,15 @@ Business Platform > Disconnect Account. An ineligible Number or pair fails with `hybrid_transition_ineligible` (exit code 4) and the API's reason in the message. Merging requires Hybrid Link access, which the API enforces. -These commands are tested against the companion SDK source, which pins API -contract `fcf7b21015dd185e987b5aa3580e8af5f4c7f1a4`. Update the registry SDK -dependency after that SDK is published and before releasing this CLI. +These commands use published SDK `0.1.0-dev.20261003052138`, which pins API +contract `ee885ec4bf62af36b9b85df4310c505f38a224db`. ### Hybrid Link controls -These additions are under development and are not available in the published -CLI. They are tested against the companion Hybrid Link SDK source using API -contract `d5245f76d5d6bc7b9af439571bd1f157185b1b87`. The registry SDK dependency -must be updated after that SDK artifact is published before releasing this CLI. -The companion SDK defaults native requests to API revision `2026-09-22`. An +These additions require a CLI release containing the Hybrid Link commands. +They use published SDK `0.1.0-dev.20261003052138`, which pins API contract +`ee885ec4bf62af36b9b85df4310c505f38a224db`. The SDK defaults native requests +to API revision `2026-09-22`. An explicit `--api-version` or saved-profile version remains explicit; retired versions fail without an automatic upgrade or resend. diff --git a/package-lock.json b/package-lock.json index 0ace622..a1a2b55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@modelcontextprotocol/server": "2.0.0", "@napi-rs/keyring": "1.3.0", "@oclif/core": "^4.13.5", - "@polymorfa/sdk": "0.1.0-dev.20261002023214", + "@polymorfa/sdk": "0.1.0-dev.20261003052138", "jq-wasm": "3.0.0-jq-1.8.2", "zod": "4.2.0" }, @@ -997,9 +997,10 @@ } }, "node_modules/@polymorfa/sdk": { - "version": "0.1.0-dev.20261002023214", - "resolved": "https://registry.npmjs.org/@polymorfa/sdk/-/sdk-0.1.0-dev.20261002023214.tgz", - "integrity": "sha512-ElS5mQaYHAoF53o77MZqD8ETXbR6qNNNgYNcEXfOBwQFw2reYqoIRtlS0QOKcTwTVIdaQbZtN7nBLyNymhtU0A==", + "version": "0.1.0-dev.20261003052138", + "resolved": "https://registry.npmjs.org/@polymorfa/sdk/-/sdk-0.1.0-dev.20261003052138.tgz", + "integrity": "sha512-NJLO6WcbDgCzmEiFiuQhSDdrF9cCuuVtKVH1CgmyH27yFQ/mFW62TK/p2lOHRVH5p45eyzX3r/7iCqSF2cOECA==", + "license": "MIT", "engines": { "node": ">=20" } diff --git a/package.json b/package.json index e36bc80..6d095e4 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@modelcontextprotocol/server": "2.0.0", "@napi-rs/keyring": "1.3.0", "@oclif/core": "^4.13.5", - "@polymorfa/sdk": "0.1.0-dev.20261002023214", + "@polymorfa/sdk": "0.1.0-dev.20261003052138", "jq-wasm": "3.0.0-jq-1.8.2", "zod": "4.2.0" },