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
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,15 @@ Recent Captures
<N automations need attention> # only when count > 0
Open Desktop App
Open Cloud Dashboard
Account: connected · <N> days left
Pause Sync / Sync (offline)
Login...
Settings...
Quit
```

The account row changes to a sign-in action, an expiry warning, or an
unavailable status. Selecting it opens the credential settings page.

During a local operation, the recording item changes to Starting, Stop
Recording, Stopping, or Compiling. These labels reflect events; the tray does
not perform the work.
Expand All @@ -137,12 +140,24 @@ The poller calls:
```text
GET <hosted_url>/api/needs-attention/count
Authorization: Bearer <ingest token>
→ { "count": 0, "credential": {
"expires_at": "2026-08-05T12:00:00Z",
"expires_in_days": 8,
"expiring_soon": true,
"legacy_non_expiring": false,
"warning_days": 14
} }
```

The token is resolved from `OPENADAPT_INGEST_TOKEN` or the OS keychain and is
not written to `tray.json`. The default poll interval is 60 seconds, clamped to
at least 30 seconds, with a slower offline retry.

The control plane decides when the credential enters its 14-day warning
window. The tray shows one actionable notification for each credential and
expiry, including after a tray restart. It stores only a non-secret identity
digest for notification deduplication. It never stores or logs the token.

This is a narrow status endpoint, not hosted execution. The tray does not upload
screenshots, workflow bundles, or capture artifacts through this poller.

Expand Down
11 changes: 10 additions & 1 deletion src/openadapt_tray/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
from openadapt_tray.app import TrayApplication, main
from openadapt_tray.config import TrayConfig
from openadapt_tray.hosted import CountResult, HostedPoller
from openadapt_tray.state import AppState, StateManager, SyncState, TrayState
from openadapt_tray.state import (
AppState,
CredentialState,
CredentialStatus,
StateManager,
SyncState,
TrayState,
)

__all__ = [
"AppState",
"CountResult",
"CredentialState",
"CredentialStatus",
"HostedPoller",
"StateManager",
"SyncState",
Expand Down
7 changes: 7 additions & 0 deletions src/openadapt_tray/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
LANE_BYOC,
LANE_CLOUD,
AppState,
CredentialStatus,
StateManager,
SyncState,
TrayState,
Expand Down Expand Up @@ -85,6 +86,8 @@ def __init__(self, config: TrayConfig | None = None):
on_count=self._on_hosted_count,
notifier=self.notifications,
on_break_clicked=self.open_needs_attention,
on_credential=self._on_hosted_credential,
on_credential_clicked=self.login,
set_offline=self._on_hosted_offline,
)

Expand Down Expand Up @@ -458,6 +461,10 @@ def _on_hosted_count(self, result: CountResult) -> None:
"""Apply a needs-attention count from the cloud poller."""
self.state.set_break_count(result.count)

def _on_hosted_credential(self, credential: CredentialStatus) -> None:
"""Apply the privacy-safe credential status from the cloud poller."""
self.state.set_credential_status(credential)

def _on_hosted_offline(self, offline: bool) -> None:
"""Reflect the cloud poller's online/offline status on the sync channel."""
if offline:
Expand Down
Loading