pinecall-runtime 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pinecall/__init__.py +0 -0
- pinecall/channels/__init__.py +0 -0
- pinecall/channels/_chooser.py +45 -0
- pinecall/channels/offers.py +219 -0
- pinecall/channels/rooms.py +176 -0
- pinecall/channels/routes.py +275 -0
- pinecall/channels/telephony/__init__.py +0 -0
- pinecall/channels/telephony/_twilio.py +349 -0
- pinecall/channels/telephony/carrier.py +229 -0
- pinecall/channels/telephony/carrier_catalog.py +92 -0
- pinecall/channels/telephony/carriers.csv +41 -0
- pinecall/channels/telephony/dialing.py +419 -0
- pinecall/channels/telephony/firewall.py +58 -0
- pinecall/channels/telephony/hand_over.py +78 -0
- pinecall/channels/telephony/number_path.py +176 -0
- pinecall/channels/telephony/numbers.py +530 -0
- pinecall/channels/telephony/origination.py +77 -0
- pinecall/channels/telephony/sip.py +448 -0
- pinecall/channels/whatsapp.py +413 -0
- pinecall/cli/__init__.py +0 -0
- pinecall/cli/_archive.py +85 -0
- pinecall/cli/_drift.py +38 -0
- pinecall/cli/_facts.py +67 -0
- pinecall/cli/_load.py +433 -0
- pinecall/cli/_operator.py +626 -0
- pinecall/cli/_sessions.py +164 -0
- pinecall/cli/_traceback.py +95 -0
- pinecall/cli/_usage.py +34 -0
- pinecall/cli/main.py +521 -0
- pinecall/domain/__init__.py +0 -0
- pinecall/domain/agent.py +446 -0
- pinecall/domain/call.py +152 -0
- pinecall/domain/country_codes.py +32 -0
- pinecall/domain/errors.py +151 -0
- pinecall/domain/names.py +115 -0
- pinecall/domain/org.py +120 -0
- pinecall/domain/person.py +208 -0
- pinecall/domain/scope.py +21 -0
- pinecall/evals/__init__.py +0 -0
- pinecall/evals/_evidence.py +158 -0
- pinecall/evals/calibration.py +112 -0
- pinecall/evals/callers.py +160 -0
- pinecall/evals/case.py +327 -0
- pinecall/evals/checks.py +437 -0
- pinecall/evals/compliance.py +197 -0
- pinecall/evals/dataset.py +451 -0
- pinecall/evals/goldens.py +331 -0
- pinecall/evals/judges.py +525 -0
- pinecall/evals/runs.py +252 -0
- pinecall/evals/spoken.py +356 -0
- pinecall/fleet/__init__.py +0 -0
- pinecall/fleet/client.py +569 -0
- pinecall/fleet/demand.py +51 -0
- pinecall/fleet/heartbeat.py +194 -0
- pinecall/fleet/measures.py +157 -0
- pinecall/fleet/roster.py +232 -0
- pinecall/fleet/worlds.py +44 -0
- pinecall/gateway/__init__.py +0 -0
- pinecall/gateway/_call_setup.py +53 -0
- pinecall/gateway/_deps.py +556 -0
- pinecall/gateway/_gateway.py +75 -0
- pinecall/gateway/_served.py +396 -0
- pinecall/gateway/_sockets.py +444 -0
- pinecall/gateway/_streams.py +83 -0
- pinecall/gateway/_text_calls.py +182 -0
- pinecall/gateway/api/__init__.py +0 -0
- pinecall/gateway/api/accounts.py +324 -0
- pinecall/gateway/api/agents.py +150 -0
- pinecall/gateway/api/apps.py +478 -0
- pinecall/gateway/api/box.py +658 -0
- pinecall/gateway/api/calibration.py +58 -0
- pinecall/gateway/api/callbacks.py +105 -0
- pinecall/gateway/api/calls.py +655 -0
- pinecall/gateway/api/chat.py +223 -0
- pinecall/gateway/api/dataset.py +163 -0
- pinecall/gateway/api/desk.py +113 -0
- pinecall/gateway/api/entries.py +77 -0
- pinecall/gateway/api/evals.py +647 -0
- pinecall/gateway/api/fleet.py +73 -0
- pinecall/gateway/api/hosting.py +241 -0
- pinecall/gateway/api/judges.py +97 -0
- pinecall/gateway/api/keys.py +110 -0
- pinecall/gateway/api/line.py +142 -0
- pinecall/gateway/api/members.py +197 -0
- pinecall/gateway/api/metrics.py +226 -0
- pinecall/gateway/api/numbers.py +476 -0
- pinecall/gateway/api/ops.py +428 -0
- pinecall/gateway/api/org.py +283 -0
- pinecall/gateway/api/personas.py +157 -0
- pinecall/gateway/api/pipeline.py +206 -0
- pinecall/gateway/api/prompts.py +50 -0
- pinecall/gateway/api/providers.py +240 -0
- pinecall/gateway/api/recordings.py +89 -0
- pinecall/gateway/api/relay.py +169 -0
- pinecall/gateway/api/retrieval.py +403 -0
- pinecall/gateway/api/runner.py +109 -0
- pinecall/gateway/api/sessions.py +102 -0
- pinecall/gateway/api/settings.py +463 -0
- pinecall/gateway/api/signup.py +164 -0
- pinecall/gateway/api/sso_login.py +168 -0
- pinecall/gateway/api/threads.py +187 -0
- pinecall/gateway/api/usage.py +303 -0
- pinecall/gateway/api/visitors.py +222 -0
- pinecall/gateway/api/whatsapp.py +48 -0
- pinecall/gateway/api/widget.py +49 -0
- pinecall/gateway/app.py +504 -0
- pinecall/gateway/calls/__init__.py +0 -0
- pinecall/gateway/calls/binding.py +63 -0
- pinecall/gateway/calls/commands.py +18 -0
- pinecall/gateway/calls/inbox.py +100 -0
- pinecall/gateway/calls/known.py +23 -0
- pinecall/gateway/calls/owners.py +87 -0
- pinecall/gateway/calls/pump.py +33 -0
- pinecall/gateway/calls/serving.py +118 -0
- pinecall/gateway/calls/threads.py +367 -0
- pinecall/gateway/dispatching/__init__.py +0 -0
- pinecall/gateway/dispatching/arrivals.py +58 -0
- pinecall/gateway/dispatching/sweep.py +43 -0
- pinecall/gateway/ending/__init__.py +0 -0
- pinecall/gateway/ending/reaper.py +160 -0
- pinecall/gateway/ending/seal.py +312 -0
- pinecall/gateway/ending/stranded.py +117 -0
- pinecall/log/__init__.py +0 -0
- pinecall/log/_histogram.py +74 -0
- pinecall/log/_relay.py +259 -0
- pinecall/log/_writer.py +454 -0
- pinecall/log/days.py +154 -0
- pinecall/log/drift.py +550 -0
- pinecall/log/facts.py +358 -0
- pinecall/log/inbox.py +169 -0
- pinecall/log/lists.py +155 -0
- pinecall/log/logs.py +581 -0
- pinecall/log/openings.py +71 -0
- pinecall/log/private.py +135 -0
- pinecall/log/queries.py +229 -0
- pinecall/log/readers.py +207 -0
- pinecall/log/reduce.py +659 -0
- pinecall/log/refold.py +217 -0
- pinecall/log/store.py +370 -0
- pinecall/log/usage.py +242 -0
- pinecall/postgres/__init__.py +0 -0
- pinecall/postgres/migrate.py +202 -0
- pinecall/postgres/migrations/0001_schema.sql +591 -0
- pinecall/postgres/migrations/0002_worlds.sql +15 -0
- pinecall/postgres/migrations/0003_lent.sql +4 -0
- pinecall/postgres/migrations/0004_carriers.sql +23 -0
- pinecall/postgres/migrations/0005_retrieved_only.sql +11 -0
- pinecall/postgres/migrations/0006_persona_agents.sql +9 -0
- pinecall/postgres/migrations/0007_money_in_dollars.sql +27 -0
- pinecall/postgres/migrations/0008_lexicon_per_agent.sql +27 -0
- pinecall/postgres/migrations/0009_personas_per_agent.sql +31 -0
- pinecall/postgres/migrations/0010_agent_judges.sql +12 -0
- pinecall/postgres/migrations/0011_erasure.sql +32 -0
- pinecall/postgres/migrations/0012_org_policy.sql +10 -0
- pinecall/postgres/migrations/0013_calling_rules.sql +12 -0
- pinecall/postgres/migrations/0014_consents.sql +24 -0
- pinecall/postgres/migrations/0015_disclosure.sql +7 -0
- pinecall/postgres/migrations/0016_call_records.sql +21 -0
- pinecall/postgres/migrations/0017_reads.sql +17 -0
- pinecall/postgres/migrations/0018_hosting.sql +41 -0
- pinecall/postgres/migrations/0019_hosted_state.sql +10 -0
- pinecall/postgres/migrations/0020_written_once.sql +7 -0
- pinecall/postgres/migrations/0021_hosted_running.sql +21 -0
- pinecall/postgres/migrations/0022_sealed.sql +39 -0
- pinecall/postgres/migrations/0023_usage_totals.sql +158 -0
- pinecall/postgres/migrations/0024_log_key_by_day.sql +8 -0
- pinecall/postgres/migrations/0025_log_before_the_days.sql +15 -0
- pinecall/postgres/migrations/0026_log_before_the_days_validated.sql +4 -0
- pinecall/postgres/migrations/0027_log_by_day.sql +96 -0
- pinecall/postgres/migrations/0030_whatsapp_seen.sql +17 -0
- pinecall/postgres/migrations/0040_reads_widened.sql +15 -0
- pinecall/postgres/migrations/0060_drift.sql +62 -0
- pinecall/postgres/migrations/0061_canaries.sql +24 -0
- pinecall/postgres/migrations/0062_eval_cases.sql +23 -0
- pinecall/postgres/migrations/0063_prompts.sql +16 -0
- pinecall/postgres/migrations/0075_call_private.sql +14 -0
- pinecall/postgres/migrations/0076_recording_keys.sql +13 -0
- pinecall/postgres/migrations/0077_tokens_world.sql +7 -0
- pinecall/postgres/migrations/0078_facts_cost_by_stage.sql +12 -0
- pinecall/postgres/migrations/0079_judge_labels.sql +20 -0
- pinecall/postgres/migrations/0080_call_openings.sql +20 -0
- pinecall/postgres/migrations/0081_seal_lease.sql +7 -0
- pinecall/postgres/migrations/0082_caller_codes.sql +14 -0
- pinecall/postgres/migrations/0083_one_use_words.sql +15 -0
- pinecall/postgres/migrations/0084_knocks_and_run_leases.sql +23 -0
- pinecall/postgres/migrations/0085_seal_lease_holder.sql +8 -0
- pinecall/postgres/migrations/0086_route_origin.sql +17 -0
- pinecall/postgres/migrations/0087_carrier_networks.sql +20 -0
- pinecall/postgres/migrations/0088_route_last_call.sql +3 -0
- pinecall/postgres/migrations/0089_offers.sql +21 -0
- pinecall/postgres/migrations/0090_invitation_purpose.sql +5 -0
- pinecall/postgres/migrations/0091_head_opened_by.sql +6 -0
- pinecall/postgres/migrations/0092_key_parent.sql +6 -0
- pinecall/postgres/migrations/0093_code_tries.sql +10 -0
- pinecall/postgres/migrations/0094_route_approval.sql +12 -0
- pinecall/postgres/migrations/0095_sso_domain_proofs.sql +17 -0
- pinecall/postgres/migrations/0096_row_level_security.sql +38 -0
- pinecall/postgres/migrations/0097_case_status.sql +19 -0
- pinecall/postgres/pool.py +190 -0
- pinecall/process/__init__.py +0 -0
- pinecall/process/_objects.py +97 -0
- pinecall/process/box_settings.py +23 -0
- pinecall/process/connections.py +124 -0
- pinecall/process/metrics.py +127 -0
- pinecall/process/recordings.py +323 -0
- pinecall/process/resolver.py +41 -0
- pinecall/process/sealed_audio.py +173 -0
- pinecall/process/settings.py +534 -0
- pinecall/process/shared.py +158 -0
- pinecall/process/signal.py +416 -0
- pinecall/providers/__init__.py +0 -0
- pinecall/providers/build.py +570 -0
- pinecall/providers/catalog.py +266 -0
- pinecall/providers/credentials.py +230 -0
- pinecall/providers/declared.py +69 -0
- pinecall/providers/prices.py +240 -0
- pinecall/providers/tried.py +104 -0
- pinecall/providers/voices.py +174 -0
- pinecall/public/console/assets/index-BIfKPSgn.js +164 -0
- pinecall/public/console/assets/index-ClOTU-xX.css +1 -0
- pinecall/public/console/favicon.png +0 -0
- pinecall/public/console/index.html +20 -0
- pinecall/public/console/logo-mark.png +0 -0
- pinecall/public/console/pinecall-logo-dark.png +0 -0
- pinecall/public/console/pinecall-logo.png +0 -0
- pinecall/public/console/pinecall-mark.png +0 -0
- pinecall/public/console/sw.js +37 -0
- pinecall/public/widget/pinecall-widget.js +853 -0
- pinecall/py.typed +0 -0
- pinecall/retrieval/__init__.py +0 -0
- pinecall/retrieval/_search.py +186 -0
- pinecall/retrieval/_sections.py +70 -0
- pinecall/retrieval/embed.py +340 -0
- pinecall/retrieval/extraction.py +403 -0
- pinecall/retrieval/knowledge.py +659 -0
- pinecall/retrieval/lookups.py +192 -0
- pinecall/retrieval/memory.py +603 -0
- pinecall/runner/__init__.py +0 -0
- pinecall/runner/_kube.py +459 -0
- pinecall/runner/_plan.py +157 -0
- pinecall/runner/main.py +306 -0
- pinecall/session/__init__.py +0 -0
- pinecall/session/_agent.py +141 -0
- pinecall/session/_hearing.py +101 -0
- pinecall/session/_livekit.py +195 -0
- pinecall/session/_prompt.py +147 -0
- pinecall/session/_recording.py +237 -0
- pinecall/session/call.py +367 -0
- pinecall/session/clock.py +40 -0
- pinecall/session/hold.py +239 -0
- pinecall/session/room.py +626 -0
- pinecall/session/session.py +690 -0
- pinecall/session/text.py +144 -0
- pinecall/session/tools.py +483 -0
- pinecall/session/voice.py +118 -0
- pinecall/session/widget.py +201 -0
- pinecall/tenancy/__init__.py +0 -0
- pinecall/tenancy/admission.py +204 -0
- pinecall/tenancy/agents.py +164 -0
- pinecall/tenancy/canary.py +126 -0
- pinecall/tenancy/carrier_networks.py +156 -0
- pinecall/tenancy/carriers.py +265 -0
- pinecall/tenancy/codes.py +196 -0
- pinecall/tenancy/consents.py +167 -0
- pinecall/tenancy/dial_policy.py +405 -0
- pinecall/tenancy/disclosure.py +41 -0
- pinecall/tenancy/erasure.py +204 -0
- pinecall/tenancy/export.py +167 -0
- pinecall/tenancy/hosted_running.py +166 -0
- pinecall/tenancy/hosting.py +475 -0
- pinecall/tenancy/judges.py +126 -0
- pinecall/tenancy/keys.py +467 -0
- pinecall/tenancy/knocks.py +40 -0
- pinecall/tenancy/letters.py +378 -0
- pinecall/tenancy/mail.py +458 -0
- pinecall/tenancy/org_secrets.py +123 -0
- pinecall/tenancy/orgs.py +78 -0
- pinecall/tenancy/people.py +543 -0
- pinecall/tenancy/personas.py +145 -0
- pinecall/tenancy/policy.py +65 -0
- pinecall/tenancy/prompts.py +48 -0
- pinecall/tenancy/reads.py +73 -0
- pinecall/tenancy/recording_keys.py +41 -0
- pinecall/tenancy/remembered.py +148 -0
- pinecall/tenancy/retention.py +86 -0
- pinecall/tenancy/scopes.py +406 -0
- pinecall/tenancy/signin.py +622 -0
- pinecall/tenancy/spend.py +69 -0
- pinecall/tenancy/sso.py +431 -0
- pinecall/tenancy/sso_domains.py +132 -0
- pinecall/tenancy/throttle.py +88 -0
- pinecall/tenancy/tokens.py +400 -0
- pinecall/tenancy/traceback.py +70 -0
- pinecall/tenancy/usage.py +136 -0
- pinecall/tenancy/vault.py +204 -0
- pinecall/tenancy/words.py +144 -0
- pinecall/wire/__init__.py +0 -0
- pinecall/wire/commands.py +350 -0
- pinecall/wire/events.py +655 -0
- pinecall/wire/frames.py +65 -0
- pinecall/wire/metrics.py +287 -0
- pinecall/wire/parts.py +320 -0
- pinecall/wire/rest/__init__.py +0 -0
- pinecall/wire/rest/accounts.py +512 -0
- pinecall/wire/rest/agents.py +172 -0
- pinecall/wire/rest/calls.py +397 -0
- pinecall/wire/rest/evals.py +453 -0
- pinecall/wire/rest/fleet.py +69 -0
- pinecall/wire/rest/hosting.py +174 -0
- pinecall/wire/rest/numbers.py +280 -0
- pinecall/wire/rest/ops.py +342 -0
- pinecall/wire/rest/providers.py +83 -0
- pinecall/wire/rest/retrieval.py +301 -0
- pinecall/wire/rest/settings.py +184 -0
- pinecall/wire/rest/usage.py +245 -0
- pinecall/wire/room.py +61 -0
- pinecall/wire/scores.py +49 -0
- pinecall/wire/state.py +243 -0
- pinecall/worker/__init__.py +0 -0
- pinecall/worker/_job.py +655 -0
- pinecall/worker/_recorder.py +82 -0
- pinecall/worker/_traces.py +48 -0
- pinecall/worker/main.py +307 -0
- pinecall_runtime-0.1.6.dist-info/METADATA +212 -0
- pinecall_runtime-0.1.6.dist-info/RECORD +328 -0
- pinecall_runtime-0.1.6.dist-info/WHEEL +4 -0
- pinecall_runtime-0.1.6.dist-info/entry_points.txt +2 -0
- pinecall_runtime-0.1.6.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"""The account doors' bodies: sign-in, keys, people, sign-up, SSO, mail, who a key is, policy."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Annotated, Literal, Self
|
|
5
|
+
|
|
6
|
+
from pydantic import AwareDatetime, Field, model_validator
|
|
7
|
+
|
|
8
|
+
from pinecall.domain.names import Env
|
|
9
|
+
from pinecall.domain.person import Key, Member, MemberStatus, Role
|
|
10
|
+
from pinecall.wire.frames import WireModel
|
|
11
|
+
|
|
12
|
+
# ── people ──
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MemberRow(WireModel):
|
|
16
|
+
"""A member as every members door answers one: never a password nor a link."""
|
|
17
|
+
|
|
18
|
+
id: str
|
|
19
|
+
email: str
|
|
20
|
+
name: str
|
|
21
|
+
role: Role
|
|
22
|
+
agents: list[str]
|
|
23
|
+
status: MemberStatus
|
|
24
|
+
scopes: list[str]
|
|
25
|
+
operator: bool
|
|
26
|
+
# The production switch, or being an admin.
|
|
27
|
+
production: bool
|
|
28
|
+
verified: bool
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def of(cls, member: Member) -> Self:
|
|
32
|
+
"""The member as a row."""
|
|
33
|
+
return cls(
|
|
34
|
+
id=member.id,
|
|
35
|
+
email=member.email,
|
|
36
|
+
name=member.name,
|
|
37
|
+
role=member.role,
|
|
38
|
+
agents=sorted(member.agents),
|
|
39
|
+
status=member.status,
|
|
40
|
+
scopes=sorted(member.scopes),
|
|
41
|
+
operator=member.operator,
|
|
42
|
+
production=member.opens_production,
|
|
43
|
+
verified=member.verified,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MemberList(WireModel):
|
|
48
|
+
"""GET /v1/members, the answer: every member of the org, oldest first."""
|
|
49
|
+
|
|
50
|
+
members: list[MemberRow]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class InviteMemberRequest(WireModel):
|
|
54
|
+
"""POST /v1/members: who, the role, the agents they see (none is every one), production."""
|
|
55
|
+
|
|
56
|
+
email: str
|
|
57
|
+
name: str
|
|
58
|
+
role: str
|
|
59
|
+
agents: list[str] = Field(default_factory=list[str])
|
|
60
|
+
production: bool = False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class InvitationResponse(WireModel):
|
|
64
|
+
"""An invitation or a reset: the member, the link's token once (null when only mailed)."""
|
|
65
|
+
|
|
66
|
+
member: MemberRow
|
|
67
|
+
token: str | None
|
|
68
|
+
expires_at: datetime | None
|
|
69
|
+
mailed: bool
|
|
70
|
+
# The card the token opens, on the box's public name: what a terminal prints, a letter carries.
|
|
71
|
+
link: str | None = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ChangeMemberRequest(WireModel):
|
|
75
|
+
"""PATCH /v1/members/{id}: what is named changes, what is left out stays."""
|
|
76
|
+
|
|
77
|
+
role: str | None = None
|
|
78
|
+
agents: list[str] | None = None
|
|
79
|
+
status: str | None = None
|
|
80
|
+
production: bool | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AcceptInvitationRequest(WireModel):
|
|
84
|
+
"""POST /v1/invitations/{token}: the password chosen, and what the first key is called."""
|
|
85
|
+
|
|
86
|
+
password: str
|
|
87
|
+
device: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── keys ──
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class KeyIssuedResponse(WireModel):
|
|
94
|
+
"""Every door that mints a key: the key this once, and what it was written under."""
|
|
95
|
+
|
|
96
|
+
key: str
|
|
97
|
+
key_id: str
|
|
98
|
+
org: str
|
|
99
|
+
label: str | None
|
|
100
|
+
env: Env
|
|
101
|
+
scopes: list[str]
|
|
102
|
+
subject: str | None
|
|
103
|
+
name: str | None
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def of(cls, key: Key, secret: str) -> Self:
|
|
107
|
+
"""The key just minted, and its secret this once."""
|
|
108
|
+
return cls(
|
|
109
|
+
key=secret,
|
|
110
|
+
key_id=key.key_id,
|
|
111
|
+
org=key.org,
|
|
112
|
+
label=key.label,
|
|
113
|
+
env=key.env,
|
|
114
|
+
scopes=sorted(key.scopes),
|
|
115
|
+
subject=key.subject,
|
|
116
|
+
name=key.name,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class FirstKeyResponse(WireModel):
|
|
121
|
+
"""POST /v1/invitations/{token}: the member's first key, and the member now active."""
|
|
122
|
+
|
|
123
|
+
key: str
|
|
124
|
+
key_id: str
|
|
125
|
+
org: str
|
|
126
|
+
label: str | None
|
|
127
|
+
env: Env
|
|
128
|
+
scopes: list[str]
|
|
129
|
+
subject: str | None
|
|
130
|
+
name: str | None
|
|
131
|
+
member: MemberRow
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class OrgMadeResponse(WireModel):
|
|
135
|
+
"""POST /v1/signup/verify: the admin's key, the org's slug, and the code for the console."""
|
|
136
|
+
|
|
137
|
+
key: str
|
|
138
|
+
key_id: str
|
|
139
|
+
org: str
|
|
140
|
+
label: str | None
|
|
141
|
+
env: Env
|
|
142
|
+
scopes: list[str]
|
|
143
|
+
subject: str | None
|
|
144
|
+
name: str | None
|
|
145
|
+
slug: str
|
|
146
|
+
member: MemberRow
|
|
147
|
+
code: str
|
|
148
|
+
code_expires_at: float
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class CreateKeyRequest(WireModel):
|
|
152
|
+
"""POST /v1/keys: a server's token, named, for one world; fewer scopes, and an end, if asked."""
|
|
153
|
+
|
|
154
|
+
label: str
|
|
155
|
+
env: str
|
|
156
|
+
# None is every scope a server's token opens; a list is some of them.
|
|
157
|
+
scopes: list[str] | None = None
|
|
158
|
+
# None never expires.
|
|
159
|
+
expires_at: AwareDatetime | None = None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class KeyRow(WireModel):
|
|
163
|
+
"""GET /v1/keys, one row: a key by its fingerprint, never the key."""
|
|
164
|
+
|
|
165
|
+
fingerprint: str
|
|
166
|
+
label: str | None
|
|
167
|
+
kind: Literal["person", "server"]
|
|
168
|
+
# Only a server's token is bound to one world.
|
|
169
|
+
env: Env | None
|
|
170
|
+
name: str | None
|
|
171
|
+
created_by: str | None
|
|
172
|
+
created_at: datetime
|
|
173
|
+
last_used_at: datetime | None
|
|
174
|
+
revoked_at: datetime | None
|
|
175
|
+
scopes: list[str]
|
|
176
|
+
expires_at: datetime | None = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class RevokeKeyResponse(WireModel):
|
|
180
|
+
"""POST /v1/keys/{fingerprint}/revoke, the answer."""
|
|
181
|
+
|
|
182
|
+
fingerprint: str
|
|
183
|
+
revoked: bool
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ── who a key is ──
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class WhoamiResponse(WireModel):
|
|
190
|
+
"""GET /v1/whoami: whose key this is and what it opens; never the key nor its hash."""
|
|
191
|
+
|
|
192
|
+
org: str
|
|
193
|
+
# None only when the org's row is gone.
|
|
194
|
+
slug: str | None
|
|
195
|
+
key_id: str
|
|
196
|
+
label: str | None
|
|
197
|
+
# The world this request acts in.
|
|
198
|
+
env: Env
|
|
199
|
+
scopes: list[str]
|
|
200
|
+
subject: str | None
|
|
201
|
+
name: str | None
|
|
202
|
+
# The one name a person carries into every org; None for a server's token.
|
|
203
|
+
email: str | None
|
|
204
|
+
operator: bool
|
|
205
|
+
# A person of the box inside an org they are no member of.
|
|
206
|
+
visiting: bool
|
|
207
|
+
production: bool
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class BoxIdentityResponse(WireModel):
|
|
211
|
+
"""GET /v1/ops/whoami: the box an operator's key opens, and who holds the key."""
|
|
212
|
+
|
|
213
|
+
operator: bool
|
|
214
|
+
version: str
|
|
215
|
+
domain: str | None
|
|
216
|
+
# None for the box's own key.
|
|
217
|
+
name: str | None
|
|
218
|
+
org: str | None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class BrandRow(WireModel):
|
|
222
|
+
"""What the box's pages and letters are called and painted with."""
|
|
223
|
+
|
|
224
|
+
name: str
|
|
225
|
+
logo_url: str | None
|
|
226
|
+
accent: str
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class GatewayInfoResponse(WireModel):
|
|
230
|
+
"""GET /.well-known/pinecall: what a sign-in page reads before anybody holds a key."""
|
|
231
|
+
|
|
232
|
+
version: str
|
|
233
|
+
signup: bool
|
|
234
|
+
# 0 is no rule.
|
|
235
|
+
min_password: int
|
|
236
|
+
# Whether the box itself can post a letter; an org's own mailbox is not counted.
|
|
237
|
+
mail: bool
|
|
238
|
+
brand: BrandRow
|
|
239
|
+
google: bool
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ── signing in ──
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class SignInRequest(WireModel):
|
|
246
|
+
"""POST /v1/login: an address and a password, the org when there are several, or a code."""
|
|
247
|
+
|
|
248
|
+
org: str | None = None
|
|
249
|
+
email: str | None = None
|
|
250
|
+
password: str | None = None
|
|
251
|
+
code: str | None = None
|
|
252
|
+
# What the minted key is called in the org's list, so it is revoked on its own.
|
|
253
|
+
device: str | None = None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class SignInOrgsRequest(WireModel):
|
|
257
|
+
"""POST /v1/login/orgs: the address and the password, to list what they open."""
|
|
258
|
+
|
|
259
|
+
email: str
|
|
260
|
+
password: str
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class SignInOrgRow(WireModel):
|
|
264
|
+
"""One org a password opens, and the person's role there."""
|
|
265
|
+
|
|
266
|
+
org: str
|
|
267
|
+
slug: str
|
|
268
|
+
name: str
|
|
269
|
+
role: Role
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class SignInOrgsResponse(WireModel):
|
|
273
|
+
"""POST /v1/login/orgs, the answer: the orgs, oldest first."""
|
|
274
|
+
|
|
275
|
+
orgs: list[SignInOrgRow]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class OrgOfPersonRow(WireModel):
|
|
279
|
+
"""GET /v1/login/orgs, one org: the role there, and whether this key is the one opening it."""
|
|
280
|
+
|
|
281
|
+
org: str
|
|
282
|
+
slug: str | None
|
|
283
|
+
name: str | None
|
|
284
|
+
# A role, or "operator" for an org of the box a person runs and is no member of.
|
|
285
|
+
role: str
|
|
286
|
+
status: MemberStatus
|
|
287
|
+
here: bool
|
|
288
|
+
member: bool
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class OrgsOfPersonResponse(WireModel):
|
|
292
|
+
"""GET /v1/login/orgs, the answer: every org the key's person may open, oldest first."""
|
|
293
|
+
|
|
294
|
+
orgs: list[OrgOfPersonRow]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class SwitchOrgRequest(WireModel):
|
|
298
|
+
"""POST /v1/login/org: the org to be signed into, by id or slug."""
|
|
299
|
+
|
|
300
|
+
org: str
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class MintCodeResponse(WireModel):
|
|
304
|
+
"""A one-use word and when it dies, in epoch seconds: a login code, a terminal's pairing."""
|
|
305
|
+
|
|
306
|
+
code: str
|
|
307
|
+
expires_at: float
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class ForgotPasswordRequest(WireModel):
|
|
311
|
+
"""POST /v1/login/reset: the address whose password was forgotten."""
|
|
312
|
+
|
|
313
|
+
email: str
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class EmptyResponse(WireModel):
|
|
317
|
+
"""An answer whose status says everything: `{}`."""
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class OpenPairingRequest(WireModel):
|
|
321
|
+
"""POST /v1/login/pairings: what the terminal calls itself."""
|
|
322
|
+
|
|
323
|
+
device: str | None = None
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class PairingStatusResponse(WireModel):
|
|
327
|
+
"""GET /v1/login/pairings/{code}: which terminal a browser is about to sign in."""
|
|
328
|
+
|
|
329
|
+
device: str | None
|
|
330
|
+
expires_at: float
|
|
331
|
+
answered: bool
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class PairingApprovedResponse(WireModel):
|
|
335
|
+
"""POST /v1/login/pairings/{code}: the terminal signed in, and into which org."""
|
|
336
|
+
|
|
337
|
+
device: str | None
|
|
338
|
+
org: str
|
|
339
|
+
# What the page says it signed in to: the org's name, as a person knows it.
|
|
340
|
+
org_name: str
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class KeyCollectedResponse(WireModel):
|
|
344
|
+
"""GET /v1/login/pairings/{code}/key: the terminal's key, once."""
|
|
345
|
+
|
|
346
|
+
key: str
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# ── sign-up ──
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class SignupRequest(WireModel):
|
|
353
|
+
"""POST /v1/signup: the org's slug and name, the person, and their password."""
|
|
354
|
+
|
|
355
|
+
org: str
|
|
356
|
+
name: str | None = None
|
|
357
|
+
email: str
|
|
358
|
+
person: str
|
|
359
|
+
password: str
|
|
360
|
+
device: str | None = None
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class CodeMailedResponse(WireModel):
|
|
364
|
+
"""POST /v1/signup, the answer: where the code went and when it dies; never the code."""
|
|
365
|
+
|
|
366
|
+
email: str
|
|
367
|
+
code_expires_at: float
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class VerifySignupRequest(WireModel):
|
|
371
|
+
"""POST /v1/signup/verify: the address and the six digits mailed to it."""
|
|
372
|
+
|
|
373
|
+
email: str
|
|
374
|
+
code: str
|
|
375
|
+
device: str | None = None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class ResendCodeRequest(WireModel):
|
|
379
|
+
"""POST /v1/signup/resend: the address a sign-up is waiting on."""
|
|
380
|
+
|
|
381
|
+
email: str
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# ── the org's identity provider ──
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class OrgSsoRequest(WireModel):
|
|
388
|
+
"""PUT /v1/org/sso: the provider whole, its client secret included every time."""
|
|
389
|
+
|
|
390
|
+
issuer: str
|
|
391
|
+
client_id: str
|
|
392
|
+
client_secret: str
|
|
393
|
+
domains: list[str]
|
|
394
|
+
# Who the provider seats unasked; None seats nobody it was not told to.
|
|
395
|
+
role: str | None = None
|
|
396
|
+
required: bool = False
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class SsoDomainProof(WireModel):
|
|
400
|
+
"""One domain of the org's SSO: the TXT record that proves it the org's, and whether seen."""
|
|
401
|
+
|
|
402
|
+
domain: str
|
|
403
|
+
txt: str
|
|
404
|
+
verified: bool
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class OrgSsoResponse(WireModel):
|
|
408
|
+
"""GET /v1/org/sso: the org's provider, never its secret, and the URI to register there."""
|
|
409
|
+
|
|
410
|
+
configured: bool
|
|
411
|
+
issuer: str | None
|
|
412
|
+
client_id: str | None
|
|
413
|
+
domains: list[str]
|
|
414
|
+
role: Role | None
|
|
415
|
+
required: bool
|
|
416
|
+
redirect_uri: str
|
|
417
|
+
# Each domain's proof: discovery offers the provider for a verified domain alone.
|
|
418
|
+
proofs: list[SsoDomainProof] = Field(default_factory=list[SsoDomainProof])
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class DiscoverSsoRequest(WireModel):
|
|
422
|
+
"""POST /v1/login/sso/discover: the address whose domain is asked about."""
|
|
423
|
+
|
|
424
|
+
email: str
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class SsoOrgRow(WireModel):
|
|
428
|
+
"""An org that signs in an address's domain with its provider."""
|
|
429
|
+
|
|
430
|
+
org: str
|
|
431
|
+
slug: str
|
|
432
|
+
name: str
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class DiscoverSsoResponse(WireModel):
|
|
436
|
+
"""POST /v1/login/sso/discover, the answer: the orgs, oldest first."""
|
|
437
|
+
|
|
438
|
+
orgs: list[SsoOrgRow]
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# ── the org's mailbox ──
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class OrgMailRequest(WireModel):
|
|
445
|
+
"""PUT /v1/org/mail: the org's SMTP account, its password included every time."""
|
|
446
|
+
|
|
447
|
+
host: str
|
|
448
|
+
port: int
|
|
449
|
+
security: str = "starttls"
|
|
450
|
+
# Empty for a relay that asks for nothing.
|
|
451
|
+
username: str = ""
|
|
452
|
+
password: str = ""
|
|
453
|
+
sender: str = Field(alias="from")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class OrgMailResponse(WireModel):
|
|
457
|
+
"""GET /v1/org/mail: the org's mailbox, never its password, and how its last letter went."""
|
|
458
|
+
|
|
459
|
+
configured: bool
|
|
460
|
+
host: str | None
|
|
461
|
+
port: int | None
|
|
462
|
+
security: str | None
|
|
463
|
+
username: str | None
|
|
464
|
+
sender: str | None = Field(alias="from")
|
|
465
|
+
verified_at: datetime | None
|
|
466
|
+
last_error: str | None
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class SendTestLetterRequest(WireModel):
|
|
470
|
+
"""POST /v1/org/mail/test: who the test letter goes to."""
|
|
471
|
+
|
|
472
|
+
to: str
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class SendTestLetterResponse(WireModel):
|
|
476
|
+
"""POST /v1/org/mail/test, the answer: whether it went, and what the server said if not."""
|
|
477
|
+
|
|
478
|
+
sent: bool
|
|
479
|
+
error: str | None
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class CallingHours(WireModel):
|
|
483
|
+
"""The hours of the called number's own day a call may ring: from, until (exclusive)."""
|
|
484
|
+
|
|
485
|
+
from_: Annotated[int, Field(ge=0, le=23)] = Field(alias="from")
|
|
486
|
+
until: Annotated[int, Field(ge=1, le=24)]
|
|
487
|
+
|
|
488
|
+
@model_validator(mode="after")
|
|
489
|
+
def _a_window(self) -> Self:
|
|
490
|
+
if self.from_ >= self.until:
|
|
491
|
+
raise ValueError(f"calling hours from {self.from_} until {self.until} leave no hour")
|
|
492
|
+
return self
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class OrgPolicy(WireModel):
|
|
496
|
+
"""GET and PUT /v1/org/policy: the org's compliance settings, replaced whole."""
|
|
497
|
+
|
|
498
|
+
retention_days: Annotated[int, Field(gt=0)] | None = None
|
|
499
|
+
calling_hours: CallingHours | None = None
|
|
500
|
+
per_number_day: Annotated[int, Field(gt=0)] | None = None
|
|
501
|
+
consent_everywhere: bool = False
|
|
502
|
+
# An outbound call's opening sentence: null is the platform's, "" is none.
|
|
503
|
+
disclosure: Annotated[str, Field(max_length=500)] | None = None
|
|
504
|
+
recording_notice: bool = True
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class OrgPolicyRow(WireModel):
|
|
508
|
+
"""GET /v1/org/policy: the settings, who set them last, and when."""
|
|
509
|
+
|
|
510
|
+
policy: OrgPolicy
|
|
511
|
+
set_by: str | None
|
|
512
|
+
set_at: float | None
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""The bodies of the agent doors: agents held, sockets, the line, judging, pipeline, widget."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pinecall.domain.names import Channel, Env
|
|
6
|
+
from pinecall.wire.frames import WireModel
|
|
7
|
+
from pinecall.wire.parts import GreetingConfig, WidgetTheme
|
|
8
|
+
from pinecall.wire.rest.numbers import LegTrunk
|
|
9
|
+
from pinecall.wire.rest.providers import ProviderRow
|
|
10
|
+
|
|
11
|
+
# ── the agents an org holds, and whose terminal a ring lands in ──
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ScopeHolder(WireModel):
|
|
15
|
+
"""A scope by its member id and email; both null for the org's own."""
|
|
16
|
+
|
|
17
|
+
holder: str | None
|
|
18
|
+
name: str | None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AgentRow(WireModel):
|
|
22
|
+
"""An agent some socket of the org holds, the doors it answers, and whose scope."""
|
|
23
|
+
|
|
24
|
+
slug: str
|
|
25
|
+
channels: list[Channel]
|
|
26
|
+
holder: ScopeHolder | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentList(WireModel):
|
|
30
|
+
"""The org's held agents in the request's world."""
|
|
31
|
+
|
|
32
|
+
agents: list[AgentRow]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AppRow(WireModel):
|
|
36
|
+
"""One connected app socket and what it holds."""
|
|
37
|
+
|
|
38
|
+
app: str
|
|
39
|
+
agents: list[str]
|
|
40
|
+
env: Env
|
|
41
|
+
host: str | None
|
|
42
|
+
address: str | None
|
|
43
|
+
sdk: str | None
|
|
44
|
+
holder: ScopeHolder | None
|
|
45
|
+
connected_at: float
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AppList(WireModel):
|
|
49
|
+
"""The app sockets of the org this key may see."""
|
|
50
|
+
|
|
51
|
+
apps: list[AppRow]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class StopAppResponse(WireModel):
|
|
55
|
+
"""A stop answered."""
|
|
56
|
+
|
|
57
|
+
app: str
|
|
58
|
+
stopped: bool
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RingTarget(WireModel):
|
|
62
|
+
"""Whose terminal a ring at the agent lands in, and who else could take it."""
|
|
63
|
+
|
|
64
|
+
agent: str
|
|
65
|
+
env: Env
|
|
66
|
+
held: bool
|
|
67
|
+
holding: ScopeHolder | None = None
|
|
68
|
+
yours: bool
|
|
69
|
+
waiting: list[ScopeHolder]
|
|
70
|
+
calling: list[str]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DeveloperPhones(WireModel):
|
|
74
|
+
"""The phone a developer calls from."""
|
|
75
|
+
|
|
76
|
+
number: str
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestNumber(WireModel):
|
|
80
|
+
"""A production number and the agent it reaches."""
|
|
81
|
+
|
|
82
|
+
number: str
|
|
83
|
+
agent: str
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestNumbers(WireModel):
|
|
87
|
+
"""The person's own phones, and the production numbers they can dial to test."""
|
|
88
|
+
|
|
89
|
+
calling: list[str]
|
|
90
|
+
numbers: list[TestNumber]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class RingHandoff(WireModel):
|
|
94
|
+
"""Where a production ring goes: a developer's scope and its fleet, or nowhere (null)."""
|
|
95
|
+
|
|
96
|
+
holder: str | None = None
|
|
97
|
+
fleet: str | None = None
|
|
98
|
+
# Where the sandbox has a LiveKit of its own: the ring is dialled to its SIP through this.
|
|
99
|
+
trunk: LegTrunk | None = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class JudgingSettings(WireModel):
|
|
103
|
+
"""Whether an org's calls are judged at hang-up, and the ceiling per call."""
|
|
104
|
+
|
|
105
|
+
on: bool
|
|
106
|
+
ceiling_usd: float | None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class JudgingRequest(WireModel):
|
|
110
|
+
"""JudgingSettings on or off."""
|
|
111
|
+
|
|
112
|
+
on: bool
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class HoldAudio(WireModel):
|
|
116
|
+
"""What a caller hears while a tool runs: the box's melody, silence, or the org's own clip."""
|
|
117
|
+
|
|
118
|
+
played: Literal["default", "off", "custom"]
|
|
119
|
+
sha256: str | None = None
|
|
120
|
+
seconds: float | None = None
|
|
121
|
+
name: str | None = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class PlayedRequest(WireModel):
|
|
125
|
+
"""PUT /v1/agents/{slug}/pipeline/hold-audio/played: the box's melody back, or silence."""
|
|
126
|
+
|
|
127
|
+
played: Literal["default", "off"]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class PipelineStage(WireModel):
|
|
131
|
+
"""One stage as the next call would run it: the vendor, its model, the voice or language."""
|
|
132
|
+
|
|
133
|
+
vendor: str
|
|
134
|
+
model: str | None = None
|
|
135
|
+
voice_id: str | None = None
|
|
136
|
+
language: str | None = None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Measured(WireModel):
|
|
140
|
+
"""One latency over the agent's recent calls: livekit's name, the median, the turns."""
|
|
141
|
+
|
|
142
|
+
name: str
|
|
143
|
+
seconds: float
|
|
144
|
+
turns: int
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class PipelineReport(WireModel):
|
|
148
|
+
"""GET /v1/agents/{slug}/pipeline: what it hears, decides and speaks with, and how fast."""
|
|
149
|
+
|
|
150
|
+
agent: str
|
|
151
|
+
hears: PipelineStage
|
|
152
|
+
decides: PipelineStage
|
|
153
|
+
speaks: PipelineStage
|
|
154
|
+
greeting: GreetingConfig | None
|
|
155
|
+
voices: list[str]
|
|
156
|
+
providers: list[ProviderRow]
|
|
157
|
+
defaults: dict[str, str]
|
|
158
|
+
models: dict[str, list[str]]
|
|
159
|
+
calls: int
|
|
160
|
+
medians: list[Measured]
|
|
161
|
+
unavailable_reasons: dict[str, str]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class WidgetSettings(WireModel):
|
|
165
|
+
"""GET and PUT /v1/agents/{slug}/widget: how the widget presents the agent, per world."""
|
|
166
|
+
|
|
167
|
+
title: str | None = None
|
|
168
|
+
tagline: str | None = None
|
|
169
|
+
greeting: str | None = None
|
|
170
|
+
accent: str | None = None
|
|
171
|
+
autostart: bool = False
|
|
172
|
+
theme: WidgetTheme | None = None
|