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
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,41 @@ npm run verify
The verification gate checks formatting, lint, strict TypeScript, tests,
production build, npm package contents, and the production dependency audit on
Node.js 20 and 22 in hosted CI.

### Guided session creation and configuration

Create real or simulated sessions with `polymorfa quicklink create --project <id> --input request.json`.
The request accepts `configuration` for connection, phone, history and test simulation.
Page copy, appearance, legal links and callbacks belong in saved widget settings.
Free-tier real accounts must pair inside the authenticated Console.

Session configuration uses Platform authority and requires a server API key.
Updates reject unknown fields, invalid nested values, and fields listed in both `set` and `reset` before sending a request. Include at least one of `set` or `reset`; the API enforces permissions, consent, and storage eligibility.
Inspect configuration with `polymorfa session configuration get <session>`.
Update it with `polymorfa session configuration update <session> --input update.json`:

```json
{
"revision": 2,
"configuration": {
"set": {"historySync": {"mode": "deliver"}},
"reset": ["historySync.requestFull"]
}
}
```

Use the revision from the preceding read. Reset removes an override so future defaults
continue to apply. A saved update can remain pending until the runtime acknowledges it.
The CLI does not offer direct session creation; QuickLinks preserve onboarding authority.

Upload simulated history separately from configuration, then use the returned
fixture ID in `configuration.testing.configuration.historyFixtureId`:

```sh
polymorfa testing history-fixture create --project PROJECT_ID --input history.json
```

The file contains `messages`, with up to 100 entries specifying `id`,
`senderPhone`, `text`, `timestamp` (Unix seconds), and `fromMe`. Senders must be
existing simulated numbers in the selected project. This command does not enable
hosted message storage.
6 changes: 3 additions & 3 deletions package-lock.json

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

14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dependencies": {
"@napi-rs/keyring": "1.3.0",
"@oclif/core": "^4.13.5",
"@polymorfa/sdk": "github:polymorfa/sdks#da65b22a8c4bf79aa378cf1848d2b7cc765ba326"
"@polymorfa/sdk": "github:polymorfa/sdks#08452992a32a392de5fc3d0fa3954ba6addd7f32"
},
"devDependencies": {
"@eslint/js": "^9.39.5",
Expand Down Expand Up @@ -195,6 +195,18 @@
},
"widget": {
"description": "Inspect and update saved Connect widget settings."
},
"quicklink": {
"description": "Create persistent onboarding invitations."
},
"session:configuration": {
"description": "Inspect and update inherited session configuration."
},
"testing": {
"description": "Manage simulated session fixtures."
},
"testing:history-fixture": {
"description": "Upload project-scoped test history."
}
}
},
Expand Down
25 changes: 25 additions & 0 deletions src/commands/quicklink/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {Flags} from '@oclif/core'
import {readJsonInput} from '../../api/request.js'
import {BaseCommand} from '../../base-command.js'
import {createQuickLink} from '../../services/quicklinks.js'

export default class QuickLinkCreate extends BaseCommand {
static override description =
'Create a persistent real or simulated onboarding QuickLink.'
static override flags = {
input: Flags.string({
description:
'JSON request file or - for stdin. Use configuration for connection, phone, history and simulation.',
required: true,
}),
}
async run(): Promise<void> {
const {flags} = await this.parse(QuickLinkCreate)
const result = await createQuickLink(
await this.messagingClient(flags, {serverApiKeyOnly: 'QuickLink'}),
await readJsonInput(flags.input),
flags.project,
)
this.writeValue(result, result.data.url, flags)
}
}
22 changes: 22 additions & 0 deletions src/commands/session/configuration/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Args} from '@oclif/core'
import {BaseCommand} from '../../../base-command.js'

export default class SessionConfigurationGet extends BaseCommand {
static override description =
'Read effective session configuration, inheritance sources and revisions.'
static override args = {
session: Args.string({description: 'Session ID.', required: true}),
}
async run(): Promise<void> {
const {args, flags} = await this.parse(SessionConfigurationGet)
const response = await (
await this.platformClient(flags)
).sessions.retrieve(args.session)
const data = response.data.data
this.writeValue(
{data, metadata: response.metadata},
JSON.stringify(data, null, 2),
flags,
)
}
}
33 changes: 33 additions & 0 deletions src/commands/session/configuration/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Args, Flags} from '@oclif/core'
import {readJsonInput} from '../../../api/request.js'
import {BaseCommand} from '../../../base-command.js'
import {parseSessionConfigurationUpdate} from '../../../services/session-configuration.js'

export default class SessionConfigurationUpdate extends BaseCommand {
static override description =
'Set configuration overrides or reset them to inherited defaults using a revision.'
static override args = {
session: Args.string({description: 'Session ID.', required: true}),
}
static override flags = {
input: Flags.string({
description:
'JSON file or stdin (-), containing revision and configuration.set/reset.',
required: true,
}),
}
async run(): Promise<void> {
const {args, flags} = await this.parse(SessionConfigurationUpdate)
const input = parseSessionConfigurationUpdate(
await readJsonInput(flags.input),
)
const response = await (
await this.platformClient(flags)
).sessions.update(args.session, input)
this.writeValue(
{data: response.data.data, metadata: response.metadata},
'Configuration saved. Check application status for runtime acknowledgement.',
flags,
)
}
}
49 changes: 49 additions & 0 deletions src/commands/testing/history-fixture/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {Flags} from '@oclif/core'
import {
PolymorfaConfigurationError,
type TestingHistoryMessage,
} from '@polymorfa/sdk'
import {readJsonInput} from '../../../api/request.js'
import {BaseCommand} from '../../../base-command.js'

export default class TestingHistoryFixtureCreate extends BaseCommand {
static override description =
'Upload a project-scoped simulated history fixture for a test QuickLink.'
static override flags = {
input: Flags.string({
description: 'JSON file or - for stdin, containing a messages array.',
required: true,
}),
}
async run(): Promise<void> {
const {flags} = await this.parse(TestingHistoryFixtureCreate)
if (!flags.project)
throw new PolymorfaConfigurationError(
'Select a project with --project.',
'project',
)
const input = await readJsonInput(flags.input)
if (
!input ||
typeof input !== 'object' ||
Array.isArray(input) ||
!('messages' in input) ||
!Array.isArray(input.messages)
)
throw new PolymorfaConfigurationError(
'Fixture input must contain a messages array.',
'input',
)
const response = await (
await this.messagingClient(flags, {serverApiKeyOnly: 'Testing history'})
).testing.createHistoryFixture(
flags.project,
input as {messages: TestingHistoryMessage[]},
)
this.writeValue(
{data: response.data, metadata: response.metadata},
response.data.fixtureId,
flags,
)
}
}
35 changes: 35 additions & 0 deletions src/services/quicklinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {PolymorfaConfigurationError, type MessagingClient} from '@polymorfa/sdk'

export async function createQuickLink(
client: MessagingClient,
input: unknown,
projectId?: string,
) {
if (!input || typeof input !== 'object' || Array.isArray(input))
throw new PolymorfaConfigurationError(
'QuickLink input must be an object.',
'input',
)
const body = input as Record<string, unknown>
const allowed = new Set([
'projectId',
'customerId',
'externalId',
'configuration',
])
if (Object.keys(body).some((key) => !allowed.has(key)))
throw new PolymorfaConfigurationError(
'QuickLinks accept configuration; presentation and callbacks belong in saved settings.',
'input',
)
if (projectId && body.projectId !== undefined && body.projectId !== projectId)
throw new PolymorfaConfigurationError(
'Input projectId conflicts with --project.',
'project',
)
const response = await client.quickLinks.create({
...body,
...(projectId ? {projectId} : {}),
})
return {data: response.data.data, metadata: response.metadata}
}
Loading