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
16 changes: 0 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1573,8 +1573,20 @@ 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 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
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
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
Expand Down Expand Up @@ -2304,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.

Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
33 changes: 33 additions & 0 deletions src/commands/session/capabilities.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
const {args, flags} = await this.parse(SessionCapabilitiesCommand)
const result = await getSessionCapabilities(
await this.platformClient(flags),
args.session,
)
this.writeValue(result, renderSessionCapabilities(result.data), flags)
}
}
33 changes: 33 additions & 0 deletions src/services/platform-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
type SessionBatchRequest,
type SessionBatchStopResult,
type PlatformSession,
type SessionCapabilities,
type UpdateQuickLinkSettingsInput,
type OrganizationQuickLinkSettings,
type ProjectQuickLinkSettings,
Expand Down Expand Up @@ -188,6 +189,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<PlatformResult<SessionCapabilities>> {
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[],
Expand Down
54 changes: 54 additions & 0 deletions test/commands/platform-management.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,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}}})

Expand Down