§
    øžyj³§  ã                  ó¦  — d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZ ddlmZ ddlmZ ddlmZmZmZmZmZmZ 	 ddlZn# e$ r dZY nw xY wddlmZ dd	lmZ d’d
„Z ej        e ¦  «        Z! G d„ de"¦  «        Z#dZ$dZ%dZ&dZ'dZ(dZ)dZ* ej+        d¦  «        Z,d“d„Z-d”d„Z.d•d„Z/d–d„Z0d—d!„Z1d˜d"„Z2d™d$„Z3dšd&„Z4d–d'„Z5ddd(œd›d-„Z6ddddd.œdœd2„Z7d�d3„Z8e G d4„ d5¦  «        ¦   «         Z9 ed6¬7¦  «         G d8„ d9¦  «        ¦   «         Z:džd:„Z;džd;„Z<dŸd=„Z=d d?„Z>d¡dB„Z?d¢dD„Z@e$e%dEœd£dH„ZAe$ddddIœd¤dP„ZBddQœd¥dV„ZCd¦dX„ZDd§dY„ZEd¨d[„ZFd©d\„ZGdªd^„ZHd«da„ZIe$d6ddbœd¬df„ZJd©dg„ZKd­di„ZLd®dj„ZMd¯dl„ZNe(dmdnœd°dp„ZOd±dq„ZPd²ds„ZQd³dt„ZRd´du„ZSddddvdwœdµd|„ZTdddd}œd¶d„ZUd·d�„ZVdšd‚„ZWd¸dƒ„ZXd–d„„ZYd¹d†„ZZdºd‡„Z[dˆd‰œd»d‹„Z\d6dŒœd¼dŽ„Z]e^fd½d�„Z_d¾d‘„Z`dS )¿uþ  
Photon Dashboard API client + device-code login flow.

This module is pure Python â€” it intentionally does not depend on
``spectrum-ts``.  Every management-plane operation (login, find/create
project, rotate the project secret, register a user, list the assigned
iMessage line) talks to Photon's **Dashboard API** on a single host,
exactly like the official Photon CLI (``photon-hq/cli``):

    Dashboard API   https://app.photon.codes/api/...
                    OAuth 2.0 device flow, Bearer access token

A Photon project has a single identifier: the dashboard ``id`` *is* the
Spectrum Cloud project id. They used to diverge (a separate
``spectrumProjectId`` field), but the dashboard unified them â€” every
project is created with matching ids and the pre-existing diverged rows
were backfilled so ``project.id == spectrumProjectId`` everywhere
(dashboard ENG-1582). Spectrum is always enabled and provisioned at
create-time, so there is no enable/toggle step anymore.

The ``spectrum-ts`` SDK (run by the Node sidecar) authenticates to Spectrum
Cloud with ``(id, projectSecret)`` â€” the same ``id`` used in Dashboard API
paths â€” which we persist as ``PHOTON_PROJECT_ID`` for the runtime.

Credential storage mirrors every other Hermes channel:

    * runtime SDK creds  -> ``~/.hermes/.env``  (``PHOTON_PROJECT_ID`` =
      project id, ``PHOTON_PROJECT_SECRET``) via ``save_env_value``
    * management metadata -> ``~/.hermes/auth.json`` under
      ``credential_pool.photon`` (device token),
      ``credential_pool.photon_project`` (dashboard id, spectrum id, name), and
      ``credential_pool.photon_user`` (operator number + assigned text line)

Reference: https://github.com/photon-hq/cli and
https://photon.codes/docs/api-reference/device-login/request-device-+-user-code
é    )ÚannotationsN)Ú	b64encode)Ú	dataclass)ÚPath)ÚAnyÚCallableÚDictÚListÚOptionalÚTuple)ÚUnscopedSecretError)Ú
get_secretc                óz   — 	 t          | |¦  «        }n$# t          $ r t          j        | ¦  «        }Y nw xY w|�|n|S )a¹  Scope-aware credential read with the default-profile startup fallback.

    Secondary profiles construct their adapters under a profile secret
    scope -- the scope is authoritative and a scoped miss returns ``default``
    (no cross-profile borrow from ``os.environ``, which may hold another
    profile's value). The DEFAULT profile's adapter constructs and sends
    *unscoped* under multiplexing, where a bare ``get_secret`` would raise
    ``UnscopedSecretError`` and crash this path; there ``os.environ`` is that
    profile's own value, so fall back to it. Same pattern as the Slack
    ``SLACK_APP_TOKEN`` read (#59739) and
    ``gateway/platforms/whatsapp_common.py::_get_wsecret``.
    )Ú_scoped_get_secretÚ_UnscopedSecretErrorÚosÚgetenv)ÚnameÚdefaultÚvals      úC/home/ragecks/.hermes/hermes-agent/plugins/platforms/photon/auth.pyÚ_get_scoped_secretr   <   sR   € ðÝ   wÑ/Ô/ˆˆøÝð ð ð ÝŒi˜‰oŒoˆˆˆðøøøà�/ˆ3ˆ3 wÐ.s   ‚ “4³4c                  ó   — e Zd ZdZdS )ÚPhotonDashboardAuthErrorzERaised when Photon rejects a device-flow token for the dashboard API.N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__© ó    r   r   r   S   s   € € € € € ØOÐOÐOÐOr    r   z
photon-clizopenid profile emailzhttps://app.photon.codeszhttps://spectrum.photon.codeszHermes Agenté   i  z^\+[1-9]\d{6,14}$Úreturnr   c                 óÂ   — 	 ddl m}  t           | ¦   «         ¦  «        dz  S # t          $ r2 t          t          j                             d¦  «        ¦  «        dz  cY S w xY w)zDResolve ``~/.hermes/auth.json`` honouring the active Hermes profile.r   ©Úget_hermes_homez	auth.jsonz	~/.hermes)Úhermes_constantsr%   r   Ú	Exceptionr   ÚpathÚ
expanduserr$   s    r   Ú_auth_json_pathr*   r   s}   € ðCØ4Ð4Ð4Ð4Ð4Ð4Ý�O�OÑ%Ô%Ñ&Ô&¨Ñ4Ð4øÝð Cð Cð CÝ•B”G×&Ò& {Ñ3Ô3Ñ4Ô4°{ÑBÐBÐBÐBðCøøøs   ‚" ¢9AÁAúDict[str, Any]c                 ó\  — t          ¦   «         } |                      ¦   «         si S 	 |                      dd¬¦  «        5 }t          j        |¦  «        pi cd d d ¦  «         S # 1 swxY w Y   d S # t
          t          j        f$ r(}t                               d| |¦  «         i cY d }~S d }~ww xY w)NÚrúutf-8©Úencodingzphoton: could not read %s: %s)	r*   ÚexistsÚopenÚjsonÚloadÚOSErrorÚJSONDecodeErrorÚloggerÚwarning)r(   ÚfhÚes      r   Ú
_load_authr;   {   sû   € ÝÑÔ€DØ�;Š;‰=Œ=ð Øˆ	ðØ�YŠY�s WˆYÑ-Ô-ð 	'°Ý”9˜R‘=”=Ð& Bð	'ð 	'ð 	'ð 	'ñ 	'ô 	'ð 	'ð 	'ð 	'ð 	'ð 	'ð 	'øøøð 	'ð 	'ð 	'ð 	'ð 	'ð 	'øå•TÔ)Ð*ð ð ð Ý�ŠÐ6¸¸aÑ@Ô@Ð@Øˆ	ˆ	ˆ	ˆ	ˆ	ˆ	øøøøðøøøs@   ¦A- ½A ÁA- Á A$Á$A- Á'A$Á(A- Á-B+ÂB&Â B+Â&B+ÚdataÚNonec                óþ  — t          ¦   «         }|j                             dd¬¦  «         |                     |j        › dt          j        ¦   «         › dt          j        ¦   «         j	        › �¦  «        }t          j
        t          |¦  «        t
          j        t
          j        z  t
          j        z  t          j        t          j        z  ¦  «        }	 t          j        |dd¬¦  «        }n[# t&          $ rN 	 t          j        |¦  «         n# t*          $ r Y nw xY w	 |                     ¦   «          n# t*          $ r Y nw xY w‚ w xY w	 |5  t/          j        | |dd¬	¦  «         |                     ¦   «          t          j        |                     ¦   «         ¦  «         d d d ¦  «         n# 1 swxY w Y   |                     |¦  «         d S # t&          $ r( 	 |                     ¦   «          n# t*          $ r Y nw xY w‚ w xY w)
NT)ÚparentsÚexist_okz.tmp.ú.Úwr.   r/   é   )ÚindentÚ	sort_keys)r*   ÚparentÚmkdirÚ	with_namer   r   ÚgetpidÚuuidÚuuid4Úhexr2   ÚstrÚO_WRONLYÚO_CREATÚO_EXCLÚstatÚS_IRUSRÚS_IWUSRÚfdopenÚBaseExceptionÚcloser5   Úunlinkr3   ÚdumpÚflushÚfsyncÚfilenoÚreplace)r<   r(   ÚtmpÚfdr9   s        r   Ú
_save_authr_   ‡   sJ  € ÝÑÔ€DØ„K×Ò˜d¨TÐÑ2Ô2Ð2ð �.Š.˜DœIÐLÐL­B¬I©K¬KÐLÐL½$¼*¹,¼,Ô:JÐLÐLÑ
MÔ
M€Cõ 
ŒÝˆC‰ŒÝ
Œ•b”jÑ ¥2¤9Ñ,ÝŒ•t”|Ñ#ñ
ô 
€Bð
ÝŒY�r˜3¨Ð1Ñ1Ô1ˆˆøÝð ð ð ð	ÝŒH�R‰LŒLˆLˆLøÝð 	ð 	ð 	ØˆDð	øøøð	Ø�JŠJ‰LŒLˆLˆLøÝð 	ð 	ð 	ØˆDð	øøøàðøøøðØð 	"ð 	"ÝŒI�d˜B q°DÐ9Ñ9Ô9Ð9Ø�HŠH‰JŒJˆJÝŒH�R—Y’Y‘[”[Ñ!Ô!Ð!ð	"ð 	"ð 	"ñ 	"ô 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"øøøð 	"ð 	"ð 	"ð 	"ð 	�Š�DÑÔÐÐÐøÝð ð ð ð	Ø�JŠJ‰LŒLˆLˆLøÝð 	ð 	ð 	ØˆDð	øøøàðøøøs©   ÃC+ Ã+
EÃ6DÄ
EÄ
DÄEÄDÄEÄD1Ä0EÄ1
D>Ä;EÄ=D>Ä>EÅG
 Å	AF(ÆG
 Æ(F,Æ,G
 Æ/F,Æ0G
 Ç

G<ÇG*Ç)G<Ç*
G7Ç4G<Ç6G7Ç7G<úOptional[str]c                 óÜ  — t          ¦   «         } |                      di ¦  «                             d¦  «        pg }t          |t          ¦  «        rI|rG|d                              d¦  «        p|d                              d¦  «        }|rt	          |¦  «        S |                      di ¦  «                             di ¦  «        }|                     d¦  «        rt	          |d         ¦  «        S dS )zFReturn the device-flow bearer token stored by ``login()`` or ``None``.Úcredential_poolÚphotonr   Úaccess_tokenÚtokenÚ	providersN)r;   ÚgetÚ
isinstanceÚlistrM   )ÚauthÚpoolre   Úlegacys       r   Úload_photon_tokenrm   ´   sØ   € å‰<Œ<€DØ�8Š8Ð% rÑ*Ô*×.Ò.¨xÑ8Ô8Ð>¸B€DÝ�$�ÑÔð  $ð Ø�Q”—’˜NÑ+Ô+ÐC¨t°A¬w¯{ª{¸7Ñ/CÔ/CˆØð 	Ý�u‘:”:Ðà�XŠX�k 2Ñ&Ô&×*Ò*¨8°RÑ8Ô8€FØ‡z‚z�.Ñ!Ô!ð +Ý�6˜.Ô)Ñ*Ô*Ð*Øˆ4r    re   rM   c                ó  — ddl m}  |¦   «         5  t          ¦   «         }| t          t	          j        ¦   «         ¦  «        dœg|                     di ¦  «        d<   t          |¦  «         ddd¦  «         dS # 1 swxY w Y   dS )zBPersist a dashboard bearer token under ``credential_pool.photon``.r   ©Ú_auth_store_lock)rd   Ú	issued_atrb   rc   N©Úhermes_cli.authrp   r;   ÚintÚtimeÚ
setdefaultr_   )re   rp   rj   s      r   Ústore_photon_tokenrw   Ã   sÔ   € à0Ð0Ð0Ð0Ð0Ð0à	Ð	Ñ	Ô	ð ð Ý‰|Œ|ˆà"µµT´Y±[´[Ñ1AÔ1AÐBÐBð<
ˆ�ŠÐ)¨2Ñ.Ô.¨xÑ8õ 	�4ÑÔÐðð ð ñ ô ð ð ð ð ð ð ð øøøð ð ð ð ð ð s   ‘AA7Á7A;Á>A;c                 ó0  — t          ¦   «         } |                      di ¦  «        }|                     dg ¦  «        }t          |t          ¦  «        r|rg |d<   t	          | ¦  «         |                      di ¦  «        }d|v ri |d<   t	          | ¦  «         dS dS )z‚Remove any stored Photon dashboard token from auth.json.

    Used to discard a stale/expired token before re-authentication.
    rb   rc   rf   N)r;   rg   rh   ri   r_   )rj   rk   rc   rf   s       r   Úclear_photon_tokenry   Ï   s¨   € õ
 ‰<Œ<€DØ�8Š8Ð% rÑ*Ô*€DØ�XŠX�h Ñ#Ô#€FÝ�&�$ÑÔð  Fð ØˆˆX‰Ý�4ÑÔÐà—’˜ bÑ)Ô)€IØ�9ÐÐØ ˆ	�(ÑÝ�4ÑÔÐÐÐð Ðr    Úboolc                óh   — | sdS 	 t          | ¦  «         dS # t          $ r Y dS t          $ r Y dS w xY w)u*  Return True if the token is accepted by the dashboard API.

    Delegates to :func:`validate_photon_token`, which checks both
    ``/api/auth/get-session`` and ``/api/projects/`` â€” the device flow can
    mint tokens that pass the session lookup but are rejected by the project
    APIs, and setup's management calls all hit the project APIs.  A
    definitive rejection (``PhotonDashboardAuthError``) is treated as stale;
    transient failures (network blips, 5xx) are treated as "probably valid"
    so they don't force an unnecessary re-login.
    FT)Úvalidate_photon_tokenr   r'   ©re   s    r   Úcheck_photon_token_validr~   á   sf   € ð ð ØˆuðÝ˜eÑ$Ô$Ð$ØˆtøÝ#ð ð ð ØˆuˆuÝð ð ð ð ˆtˆtðøøøs   † —
1¤	1°1ú#Tuple[Optional[str], Optional[str]]c                 óš  — t          j        d¦  «        } t          d¦  «        }| r|r| |fS t          ¦   «         }|                     di ¦  «                             d¦  «        pg }t          |t          ¦  «        rO|rM|d         }|                     d¦  «        p|                     d¦  «        }| p||p|                     d¦  «        fS | |fS )	at  Return the runtime SDK creds ``(spectrum_project_id, project_secret)``.

    Precedence: process env (``~/.hermes/.env`` is loaded into the gateway's
    environment at startup) wins, then ``auth.json`` for offline / status
    use.  This is the pair the Node sidecar feeds to ``spectrum-ts``; the id
    is the unified project id (dashboard id == spectrumProjectId).
    ÚPHOTON_PROJECT_IDÚPHOTON_PROJECT_SECRETrb   Úphoton_projectr   Úspectrum_project_idÚ
project_idÚproject_secret)r   r   r   r;   rg   rh   ri   )Úenv_idÚenv_secrj   ÚprojÚentryÚsids         r   Úload_project_credentialsrŒ   ù   sÞ   € õ ŒYÐ*Ñ+Ô+€FÝ Ð!8Ñ9Ô9€GØð �'ð Ø�wˆÐÝ‰<Œ<€DØ�8Š8Ð% rÑ*Ô*×.Ò.Ð/?Ñ@Ô@ÐFÀB€DÝ�$�ÑÔð G $ð GØ�Q”ˆà�iŠiÐ-Ñ.Ô.ÐI°%·)²)¸LÑ2IÔ2IˆØ�˜#˜wÐE¨%¯)ª)Ð4DÑ*EÔ*EÐFÐFØ�7ˆ?Ðr    c                 ód  — t          j        d¦  «        } | r| S t          ¦   «         }|                     di ¦  «                             d¦  «        pg }t	          |t
          ¦  «        rI|rG|d         }|                     d¦  «        p)|                     d¦  «        p|                     d¦  «        S dS )	a§  Return the project id used for management API calls.

    Post-unification the dashboard id and the Spectrum id are the same value,
    so we prefer the stored ``spectrum_project_id``: for pre-backfill installs
    the old ``dashboard_project_id`` is the diverged id that the unification
    rewrote (it now 404s), while the Spectrum id always matches the live row.
    Falls back to the legacy keys for older records.
    ÚPHOTON_DASHBOARD_PROJECT_IDrb   rƒ   r   r„   Údashboard_project_idr…   N)r   r   r;   rg   rh   ri   )r‡   rj   r‰   rŠ   s       r   Úload_dashboard_project_idr�     s¹   € õ ŒYÐ4Ñ5Ô5€FØð ØˆÝ‰<Œ<€DØ�8Š8Ð% rÑ*Ô*×.Ò.Ð/?Ñ@Ô@ÐFÀB€DÝ�$�ÑÔð 
 $ð 
Ø�Q”ˆà�IŠIÐ+Ñ,Ô,ð 'Ø�yŠyÐ/Ñ0Ô0ð'à�yŠy˜Ñ&Ô&ð	
ð
 ˆ4r    )r�   r   r„   r†   r�   r   c                óH  — ddl m}  |¦   «         5  t          ¦   «         }| |t          t	          j        ¦   «         ¦  «        dœ}|r||d<   |r||d<   |g|                     di ¦  «        d<   t          |¦  «         ddd¦  «         n# 1 swxY w Y   t          | |¦  «         dS )	aå  Persist project credentials to both .env (runtime) and auth.json (mgmt).

    The runtime SDK creds land in ``~/.hermes/.env`` via the same
    ``save_env_value`` helper every other channel uses, so the gateway picks
    them up from the environment with zero adapter changes.  A copy of the
    non-secret ids (plus the secret, for offline ``status``) is written to
    ``auth.json`` so management commands work even when ``.env`` hasn't been
    loaded into the current process.
    r   ro   )r„   r†   rq   r�   r   rb   rƒ   N)rs   rp   r;   rt   ru   rv   r_   Ú_persist_runtime_env)r„   r†   r�   r   rp   rj   Úrecords          r   Ústore_project_credentialsr”   '  s  € ð  1Ð0Ð0Ð0Ð0Ð0à	Ð	Ñ	Ô	ð ð Ý‰|Œ|ˆà#6Ø,Ý�TœY™[œ[Ñ)Ô)ð"
ð "
ˆð
  ð 	BØ-AˆFÐ)Ñ*Øð 	"Ø!ˆF�6‰NØDJÀ8ˆ�ŠÐ)¨2Ñ.Ô.Ð/?Ñ@Ý�4ÑÔÐðð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð õ Ð,¨nÑ=Ô=Ð=Ð=Ð=s   ‘A*BÂBÂB©Úphone_numberÚassigned_phone_numberÚuser_idr�   r–   r—   r˜   c                óN  — | s|sdS ddl m}  |¦   «         5  t          ¦   «         }dt          t	          j        ¦   «         ¦  «        i}| r| |d<   |r||d<   |r||d<   |r||d<   |g|                     d	i ¦  «        d
<   t          |¦  «         ddd¦  «         dS # 1 swxY w Y   dS )zEPersist non-secret Photon user numbers for offline ``status`` output.Nr   ro   rq   r–   r—   r˜   r�   rb   Úphoton_userrr   )r–   r—   r˜   r�   rp   rj   r“   s          r   Ústore_user_numbersr›   I  s4  € ð ð Ð 5ð ØˆØ0Ð0Ð0Ð0Ð0Ð0à	Ð	Ñ	Ô	ð ð Ý‰|Œ|ˆØ"-­sµ4´9±;´;Ñ/?Ô/?Ð!@ˆØð 	2Ø%1ˆF�>Ñ"Ø ð 	DØ.CˆFÐ*Ñ+Øð 	(Ø 'ˆF�9ÑØð 	BØ-AˆFÐ)Ñ*ØAGÀˆ�ŠÐ)¨2Ñ.Ô.¨}Ñ=Ý�4ÑÔÐðð ð ñ ô ð ð ð ð ð ð ð øøøð ð ð ð ð ð s   —A6BÂBÂ!Bc                ó  — 	 ddl m} n+# t          $ r t                               d¦  «         Y dS w xY w	  |d| ¦  «          |d|¦  «         dS # t
          $ r&}t                               d|¦  «         Y d}~dS d}~ww xY w)u'  Write the SDK creds to ``~/.hermes/.env`` (canonical runtime store).

    Isolated in its own helper so the secret value flows straight into
    ``save_env_value`` without ever being bound to a printable local in a
    caller â€” same CodeQL-clean-flow rationale as the rest of this module.
    r   )Úsave_env_valueu=   photon: hermes_cli.config unavailable â€” skipping .env writeNr�   r‚   z1photon: could not write project creds to .env: %s)Úhermes_cli.configr�   ÚImportErrorr7   r8   r'   )r„   r†   r�   r:   s       r   r’   r’   d  sÎ   € ðØ4Ð4Ð4Ð4Ð4Ð4Ð4øÝð ð ð Ý�ŠÐVÑWÔWÐWØˆˆðøøøðOØˆÐ*Ð,?Ñ@Ô@Ð@ØˆÐ.°Ñ?Ô?Ð?Ð?Ð?øÝð Oð Oð OÝ�ŠÐJÈAÑNÔNÐNÐNÐNÐNÐNÐNÐNøøøøðOøøøs#   ‚	 ‰$1°1µA Á
A?ÁA:Á:A?c                  óL   — e Zd ZU ded<   ded<   ded<   ded<   ded<   ded	<   d
S )Ú
DeviceCoderM   Údevice_codeÚ	user_codeÚverification_urir`   Úverification_uri_completert   Ú
expires_inÚintervalN)r   r   r   Ú__annotations__r   r    r   r¡   r¡   z  sO   € € € € € € àÐÐÑØ€N€N�NØÐÐÑØ,Ð,Ð,Ñ,Ø€O€O�OØ€M€M�M€M€Mr    r¡   T)Úfrozenc                  ó(   — e Zd ZU dZded<   ded<   dS )Ú_DeviceTokenCandidatez<A token-like value extracted from the device-token response.rM   Úsourcere   N)r   r   r   r   r¨   r   r    r   r«   r«   „  s(   € € € € € € àFÐFØ€K€K�KØ€J€J�J€J€Jr    r«   c                 ó^   — t          j        d¦  «        pt                               d¦  «        S )NÚPHOTON_DASHBOARD_HOSTú/)r   r   ÚDEFAULT_DASHBOARD_HOSTÚrstripr   r    r   Ú_dashboard_hostr²   ‹  s(   € ÝŒIÐ-Ñ.Ô.ÐHÕ2H×PÒPÐQTÑUÔUÐUr    c                 ó^   — t          j        d¦  «        pt                               d¦  «        S )NÚPHOTON_SPECTRUM_HOSTr¯   )r   r   ÚDEFAULT_SPECTRUM_HOSTr±   r   r    r   Ú_spectrum_hostr¶   �  s'   € ÝŒIÐ,Ñ-Ô-ÐFÕ1F×NÒNÈsÑSÔSÐSr    úDict[str, str]c                ó   — dd| › �iS )NÚAuthorizationúBearer r   r}   s    r   Ú_bearerr»   “  s   € ØÐ. uÐ.Ð.Ð/Ð/r    r…   c                ó„   — t          | › d|› �                     d¦  «        ¦  «                             d¦  «        }dd|› �iS )Nú:r.   Úasciir¹   zBasic )r   ÚencodeÚdecode)r…   r†   re   s      r   Ú_basicrÁ   —  sM   € Ý˜Ð6Ð6 nÐ6Ð6×=Ò=¸gÑFÔFÑGÔG×NÒNÈwÑWÔW€EØÐ- eÐ-Ð-Ð.Ð.r    Úrespr   c                óT  — 	 |                       ¦   «         }n# t          $ r d }Y nw xY wt          |t          ¦  «        rKdD ]*}|                     |¦  «        }|rt          |¦  «        c S Œ+t          j        |d¬¦  «        d d…         S t          | dd¦  «        pd}|r
|d d…         ndS )N)ÚerrorÚmessageÚdetailT)rE   iô  ÚtextÚ zno response body)r3   r'   rh   Údictrg   rM   ÚdumpsÚgetattr)rÂ   r<   Úkeyr   rÇ   s        r   Ú_response_error_detailrÍ   œ  sÒ   € ðØ�yŠy‰{Œ{ˆˆøÝð ð ð Øˆˆˆðøøøå�$�ÑÔð 6Ø1ð 	 ð 	 ˆCØ—(’(˜3‘-”-ˆCØð  Ý˜3‘x”x���ð åŒz˜$¨$Ð/Ñ/Ô/°°°Ô5Ð5Ý�4˜ Ñ$Ô$Ð*¨€DØÐ5ˆ4���Œ:ˆ:Ð#5Ð5s   ‚ —&¥&Úactionc           
     ó~   — t          | dd¦  «        }|dk     rd S t          d|› d|› dt          | ¦  «        › �¦  «        ‚)NÚstatus_codeéÈ   é�  zPhoton z failed: HTTP z: )rË   ÚRuntimeErrorrÍ   )rÂ   rÎ   Ústatuss      r   Ú_raise_for_statusrÕ   «  sW   € Ý�T˜=¨#Ñ.Ô.€FØ�‚|€|ØˆÝ
ØP�&ÐPÐP¨ÐPÐPÕ2HÈÑ2NÔ2NÐPÐPñô ð r    )Ú	client_idÚscoperÖ   r×   c                óô  — t           €t          d¦  «        ‚t          ¦   «         › d�}d| i}|r||d<   t          j        ||d¬¦  «        }|                     ¦   «          |                     ¦   «         }t          |d         |d	         |d
         |                     d¦  «        t          |                     d¦  «        pt          ¦  «        t          |                     d¦  «        pt          ¦  «        ¬¦  «        S )zBPOST ``/api/auth/device/code`` and return the device + user codes.Nú)httpx is required for Photon device loginz/api/auth/device/coderÖ   r×   ç      >@©r3   Útimeoutr¢   r£   r¤   r¥   r¦   r§   )r¢   r£   r¤   r¥   r¦   r§   )ÚhttpxrÓ   r²   ÚpostÚraise_for_statusr3   r¡   rg   rt   ÚDEFAULT_POLL_TIMEOUTÚDEFAULT_POLL_INTERVAL)rÖ   r×   ÚurlÚbodyrÂ   r<   s         r   Úrequest_device_coderä   ´  sö   € õ €}ÝÐFÑGÔGÐGÝÑÔÐ
5Ð
5Ð
5€CØ'¨Ð3€DØð ØˆˆW‰ÝŒ:�c ¨dÐ3Ñ3Ô3€DØ×ÒÑÔÐØ�9Š9‰;Œ;€DÝØ˜Ô'Ø�{Ô#ØÐ0Ô1Ø"&§(¢(Ð+FÑ"GÔ"GÝ�t—x’x Ñ-Ô-ÐEÕ1EÑFÔFÝ�T—X’X˜jÑ)Ô)ÐBÕ-BÑCÔCðñ ô ð r    )rÖ   rÜ   r§   Ú
on_pendingÚcoderÜ   úOptional[int]r§   rå   úOptional[Callable[[], None]]c               óŽ  — t           €t          d¦  «        ‚t          ¦   «         › d�}t          j        ¦   «         |p| j        pt
          z   }|�|n| j        pt          }t          j        ¦   «         |k     �rBt          j        |¦  «         	 t          j	        |d| j
        |dœd¬¦  «        }n7# t           j        $ r%}	t                               d|	¦  «         Y d}	~	Œ}d}	~	ww xY w|j        d	k    r•i }
	 |                     ¦   «         pi }t!          |t"          ¦  «        r|ni }
n$# t$          t&          t          j        f$ r i }
Y nw xY wt+          |
t-          |d
i ¦  «        ¬¦  «        }|st          d¦  «        ‚|d         j        S |j        dk    r|dz  }|rt1          |¦  «         �ŒH|j        dk    rÁi }
	 |                     ¦   «         pi }
n# t          j        $ r Y nw xY w|
                     d¦  «        p|
                     d¦  «        pd}|dk    r|rt1          |¦  «         �ŒÇ|dk    r|dz  }|rt1          |¦  «         �Œå|dv rt          d|› �¦  «        ‚t          d|p|j        › �¦  «        ‚t                               d|j        |j        dd	…         ¦  «         t          j        ¦   «         |k     �°Bt7          d¦  «        ‚)aó  Poll ``/api/auth/device/token`` until the user approves.

    Mirrors the official CLI's polling loop: sleep first, then poll;
    ``authorization_pending`` keeps the interval, ``slow_down`` adds 5s,
    HTTP 429 adds 10s, and ``access_denied`` / ``expired_token`` abort.

    The bearer token comes from the response body's top-level
    ``access_token`` (better-auth device-grant shape), with
    ``session.access_token`` and the ``set-auth-token`` header kept as
    fallbacks for API drift.
    NrÙ   z/api/auth/device/tokenz,urn:ietf:params:oauth:grant-type:device_code)Ú
grant_typer¢   rÖ   rÚ   rÛ   z$photon: device-token poll failed: %srÑ   Úheaders©rë   z“Photon returned 200 but no token candidate in the device-token response (expected access_token, data.access_token, accessToken, or set-auth-token).r   i­  é
   rÒ   rÄ   rÅ   rÈ   Úauthorization_pendingÚ	slow_downr!   )Úexpired_tokenÚaccess_deniedzPhoton login failed: zPhoton device token error: z-photon: device-token unexpected status %s: %szPhoton device login timed out)rÝ   rÓ   r²   ru   r¦   rà   r§   rá   ÚsleeprÞ   r¢   ÚRequestErrorr7   r8   rÐ   r3   rh   rÉ   Ú	TypeErrorÚ
ValueErrorr6   Ú!_device_response_token_candidatesrË   re   Ú_saferg   rÇ   ÚTimeoutError)ræ   rÖ   rÜ   r§   rå   râ   Údeadlinerò   rÂ   r:   rã   ÚdecodedÚ
candidatesÚerrs                 r   Úpoll_for_tokenrý   Ë  sS  € õ& €}ÝÐFÑGÔGÐGÝÑÔÐ
6Ð
6Ð
6€CÝŒy‰{Œ{˜gÐP¨¬ÐPÕ<PÑQ€HØ Ð,ˆHˆH°4´=Ð3YÕDY€EÝ
Œ)‰+Œ+˜Ò
 Ñ
 ÝŒ
�5ÑÔÐð	Ý”:Øà"PØ#'Ô#3Ø!*ðð ð
 ðñ ô ˆDˆDøõ Ô!ð 	ð 	ð 	Ý�NŠNÐAÀ1ÑEÔEÐEØˆHˆHˆHˆHøøøøð	øøøð Ô˜sÒ"Ð"Ø#%ˆDðØŸ)š)™+œ+Ð+¨�Ý",¨WµdÑ";Ô";ÐC�w�wÀ��øÝ�z­4Ô+?Ð@ð ð ð Ø���ðøøøå:Ø�g d¨I°rÑ:Ô:ðñ ô ˆJð ð Ý"ðJñô ð ð
 ˜a”=Ô&Ð&ØÔ˜sÒ"Ð"à�R‰KˆEØð "Ý�jÑ!Ô!Ð!ÙØÔ˜sÒ"Ð"ØˆDðØ—y’y‘{”{Ð( b��øÝÔ'ð ð ð Ø�ðøøøà—(’(˜7Ñ#Ô#Ð@ t§x¢x°	Ñ':Ô':Ð@¸bˆCØÐ-Ò-Ð-Øð &Ý˜*Ñ%Ô%Ð%ÙØ�kÒ!Ð!Ø˜‘
�Øð &Ý˜*Ñ%Ô%Ð%ÙØÐ8Ð8Ð8Ý"Ð#@¸3Ð#@Ð#@ÑAÔAÐAÝÐO¸SÐ=MÀDÄIÐOÐOÑPÔPÐPÝ�ŠØ;ØÔ˜dœi¨¨¨œoñ	
ô 	
ð 	
õs Œ)‰+Œ+˜Ò
 Ñ
 õz Ð6Ñ
7Ô
7Ð7s<   Â B, Â,C Â;CÃC Ã1/D! Ä!EÅEÆ4G ÇGÇGrì   rã   rë   úOptional[Any]ri   c               óJ  ‡‡— g Št          ¦   «         Šdˆˆfd„} |d|                      d¦  «        ¦  «          |d	|                      d	¦  «        ¦  «         |                      d
¦  «        }t          |t          ¦  «        r |d|                     d¦  «        ¦  «         |                      d¦  «        }t          |t          ¦  «        r> |d|                     d¦  «        ¦  «          |d|                     d	¦  «        ¦  «          |dt	          |d¦  «        ¦  «         ‰S )a}  Extract de-duplicated token candidates from a device-token response.

    Photon's device-token endpoint has returned tokens under several keys
    across versions (``access_token``, ``accessToken``, ``data.*``) and the
    documented ``set-auth-token`` response header.  We collect every shape so
    the caller can validate each against the dashboard API before trusting it.
    r¬   rM   Úvaluer   r"   r=   c                ó¨   •— t          |¦  «        }|r|‰v rd S ‰                     |¦  «         ‰                     t          | |¬¦  «        ¦  «         d S )N©r¬   re   )Ú_clean_bearer_tokenÚaddÚappendr«   )r¬   r   re   rû   Úseens      €€r   r  z._device_response_token_candidates.<locals>.add2  s\   ø€ Ý# EÑ*Ô*ˆØð 	˜ ˜˜ØˆFØ�Š�‰ŒˆØ×ÒÕ/°vÀUÐKÑKÔKÑLÔLÐLÐLÐLr    rd   ÚaccessTokenÚsessionzsession.access_tokenr<   zdata.access_tokenzdata.accessTokenzset-auth-token)r¬   rM   r   r   r"   r=   )Úsetrg   rh   rÉ   Ú_header_value)rã   rë   r  r  r<   rû   r  s        @@r   rö   rö   #  sD  øø€ ð €JÝ‘”€DðMð Mð Mð Mð Mð Mð Mð €Cˆ˜Ÿš Ñ0Ô0Ñ1Ô1Ð1Ø€Cˆ�t—x’x Ñ.Ô.Ñ/Ô/Ð/Ø�hŠh�yÑ!Ô!€GÝ�'�4Ñ Ô ð AØˆÐ" G§K¢K°Ñ$?Ô$?Ñ@Ô@Ð@Ø�8Š8�FÑÔ€DÝ�$�ÑÔð 9ØˆÐ §¢¨.Ñ!9Ô!9Ñ:Ô:Ð:ØˆÐ §¢¨Ñ 7Ô 7Ñ8Ô8Ð8Ø€CÐ�-¨Ð1AÑBÔBÑCÔCÐCØÐr    r   c                óæ   — t          | t          ¦  «        sd S |                      ¦   «         }|                     ¦   «                              d¦  «        r|dd …                              ¦   «         }|pd S )Nzbearer é   )rh   rM   ÚstripÚlowerÚ
startswith)r   re   s     r   r  r  F  se   € Ý�e�SÑ!Ô!ð ØˆtØ�KŠK‰MŒM€EØ‡{‚{�}„}×Ò 	Ñ*Ô*ð "Ø�a�b�b”	—’Ñ!Ô!ˆØˆ=�DÐr    c                ó”  — | sd S 	 |                       |¦  «        }|rt          |¦  «        S n# t          $ r Y nw xY w	 t          | ¦  «                             ¦   «         D ]O\  }}t          |¦  «                             ¦   «         |                     ¦   «         k    r|rt          |¦  «        c S ŒPn# t          t          f$ r Y d S w xY wd S ©N)rg   rM   ÚAttributeErrorrÉ   Úitemsr  rô   rõ   )rë   r   r   rÌ   s       r   r
  r
  O  sö   € Øð ØˆtðØ—’˜DÑ!Ô!ˆØð 	Ý�u‘:”:Ðð	øåð ð ð ØˆðøøøðÝ˜w™-œ-×-Ò-Ñ/Ô/ð 	"ð 	"‰JˆC�Ý�3‰xŒx�~Š~ÑÔ 4§:¢:¡<¤<Ò/Ð/°EÐ/Ý˜5‘z”zÐ!Ð!Ð!øð	"øõ •zÐ"ð ð ð Øˆtˆtðøøøàˆ4s$   †%- ­
:¹:¾A/B0 Â.B0 Â0CÃCr(   c                óŠ   — t           €t          d¦  «        ‚t          ¦   «         › | › �}t          j        |dd|› �id¬¦  «        S )NrÙ   r¹   rº   rÚ   ©rë   rÜ   )rÝ   rÓ   r²   rg   )r(   re   râ   s      r   Ú_dashboard_getr  a  s\   € Ý€}ÝÐFÑGÔGÐGÝÑÔÐ
& Ð
&Ð
&€CÝŒ9ØØ Ð"3¨EÐ"3Ð"3Ð4Øðñ ô ð r    c                óÂ  — t          d| ¦  «        }|j        dv rt          d¦  «        ‚|                     ¦   «          |                     ¦   «         }t          |t          ¦  «        r|                     d¦  «        nd}t          |t          ¦  «        r|st          d¦  «        ‚t          d| ¦  «        }|j        dv rt          d¦  «        ‚|                     ¦   «          |S )	ac  Verify a device-flow token is usable for dashboard project APIs.

    The device flow can return a token that authenticates the Better Auth
    session lookup but is rejected by the project APIs.  Validate against
    ``/api/auth/get-session`` and ``/api/projects/`` so we fail loudly at
    login instead of saving a token that 404s/401s downstream.
    ú/api/auth/get-session)i‘  i“  zKPhoton issued a device token, but the dashboard session lookup rejected it.ÚuserNzTPhoton issued a device token, but the dashboard session lookup did not recognize it.ú/api/projects/zXPhoton device token was accepted for the session lookup but rejected by the project API.)r  rÐ   r   rß   r3   rh   rÉ   rg   )re   rÂ   r<   r  Úprojects_resps        r   r|   r|   l  sÿ   € õ Ð1°5Ñ9Ô9€DØÔ˜:Ð%Ð%Ý&ðñ
ô 
ð 	
ð 	×ÒÑÔÐØ�9Š9‰;Œ;€DÝ)¨$µÑ5Ô5Ð?ˆ4�8Š8�FÑÔÐ¸4€DÝ�d�DÑ!Ô!ð 
¨ð 
Ý&ð$ñ
ô 
ð 	
õ #Ð#3°UÑ;Ô;€MØÔ  JÐ.Ð.Ý&ð+ñ
ô 
ð 	
ð ×"Ò"Ñ$Ô$Ð$Ø€Kr    rû   c                óf  — | st          d¦  «        ‚d}d}| D ]N}	 t          |j        ¦  «         |j        c S # t          $ r}|}|}Y d}~Œ3d}~wt          $ r}|}Y d}~ŒGd}~ww xY w|�7d                     d„ | D ¦   «         ¦  «        pd}t          |› d|› d�¦  «        |‚|�|‚t          d¦  «        ‚)	zBReturn the first candidate token that passes dashboard validation.zHPhoton returned 200 but no token candidate in the device-token response.Nz, c              3  ó$   K  — | ]}|j         V — Œd S r  )r¬   )Ú.0Úcs     r   ú	<genexpr>z-_validated_dashboard_token.<locals>.<genexpr>¡  s$   è è € Ð9Ð9¨˜AœHÐ9Ð9Ð9Ð9Ð9Ð9r    Únonez@ Device login returned no project-valid dashboard token (tried: z).z.Photon did not return a usable dashboard token)rÓ   r|   re   r   r'   Újoin)rû   Údashboard_errorÚ
last_errorÚ	candidateÚexcÚsourcess         r   Ú_validated_dashboard_tokenr(  Œ  s9  € àð 
Ýðñ
ô 
ð 	
ð ;?€OØ*.€JØð 
ð 
ˆ	ð		Ý! )¤/Ñ2Ô2Ð2Ø”?Ð"Ð"Ð"øÝ'ð 	ð 	ð 	Ø!ˆOØˆJØˆHˆHˆHˆHøøøøÝð 	ð 	ð 	ØˆJØˆHˆHˆHˆHøøøøð	øøøð Ð"Ø—)’)Ð9Ð9¨jÐ9Ñ9Ô9Ñ9Ô9ÐC¸VˆÝ&Øð 3ð 3Ø'.ð3ð 3ð 3ñ
ô 
ð ð	ð ÐØÐÝ
ÐGÑ
HÔ
HÐHs!   ›8¸
A$ÁAÁA$ÁAÁA$ÚfnúCallable[[], None]c                ó>   — 	  | ¦   «          d S # t           $ r Y d S w xY wr  )r'   )r)  s    r   r÷   r÷   «  s8   € ðØ
ˆ‰ŒˆˆˆøÝð ð ð Øˆˆðøøøs   ‚
 Ž
›)rÖ   Úopen_browserÚon_user_coder,  r-  ú(Optional[Callable[['DeviceCode'], None]]c                óP  ‡‡— t          | ¬¦  «        Š‰rt          ˆˆfd„¦  «         |r;	 ddl}‰j        p‰j        }|                     |d¬¦  «         n# t          $ r Y nw xY wt          ‰| ¬¦  «        }t          d|¬¦  «        g}t          |¦  «        }t          |¦  «         |S )	zÊRun the full device-code login flow and persist the token.

    Returns the bearer token.  ``on_user_code`` receives the
    :class:`DeviceCode` so callers can print it + optionally open a browser.
    )rÖ   c                 ó   •—  ‰‰ ¦  «        S r  r   )ræ   r-  s   €€r   ú<lambda>z#login_device_flow.<locals>.<lambda>¿  s   ø€ �l�l 4Ñ(Ô(€ r    r   NrC   )ÚnewÚpollr  )rä   r÷   Ú
webbrowserr¥   r¤   r2   r'   rý   r«   r(  rw   )	rÖ   r,  r-  r4  ÚtargetÚfirst_tokenrû   re   ræ   s	     `     @r   Úlogin_device_flowr7  ²  sä   øø€ õ ¨Ð3Ñ3Ô3€DØð *ÝÐ(Ð(Ð(Ð(Ð(Ñ)Ô)Ð)Øð ð	ØÐÐÐØÔ3ÐL°tÔ7LˆFØ�OŠO˜F¨ˆOÑ*Ô*Ð*Ð*øÝð 	ð 	ð 	ØˆDð	øøøõ ! °Ð;Ñ;Ô;€KÝ'¨v¸[ÐIÑIÔIÐJ€JÝ& zÑ2Ô2€EÝ�uÑÔÐØ€Ls   «)A Á
A"Á!A"c                óì   — t           €t          d¦  «        ‚t          ¦   «         › d�}t          j        |t	          | ¦  «        d¬¦  «        }|                     ¦   «          |                     ¦   «         pi S )uE   GET ``/api/auth/get-session`` â€” confirm the token + fetch the user.Núhttpx is required for Photonr  rÚ   r  )rÝ   rÓ   r²   rg   r»   rß   r3   ©re   râ   rÂ   s      r   Úget_sessionr;  Ò  sk   € å€}ÝÐ9Ñ:Ô:Ð:ÝÑÔÐ
5Ð
5Ð
5€CÝŒ9�S¥'¨%¡.¤.¸$Ð?Ñ?Ô?€DØ×ÒÑÔÐØ�9Š9‰;Œ;Ð˜"Ðr    úList[Dict[str, Any]]c                óX  — t          | t          ¦  «        r| S t          | t          ¦  «        r}dD ]z}|                      |¦  «        }t          |t          ¦  «        r|c S t          |t          ¦  «        r5dD ]2}|                     |¦  «        }t          |t          ¦  «        r|c c S Œ3Œ{g S )N)r<   ÚprojectsÚusersÚlinesr  )r>  r?  r@  r  )rh   ri   rÉ   rg   )r<   rÌ   ÚinnerÚ
nested_keyÚnesteds        r   Ú_unwrap_listrD  ß  sÀ   € Ý�$�ÑÔð ØˆÝ�$�ÑÔð 	&ØBð 	&ð 	&ˆCØ—H’H˜S‘M”MˆEÝ˜%¥Ñ&Ô&ð Ø���Ý˜%¥Ñ&Ô&ð &Ø"Ið &ð &�JØ"ŸYšY zÑ2Ô2�FÝ! &­$Ñ/Ô/ð &Ø%˜˜˜˜˜ð&øà€Ir    c                ó  — t           €t          d¦  «        ‚t          ¦   «         › d�}t          j        |t	          | ¦  «        d¬¦  «        }|                     ¦   «          t          |                     ¦   «         ¦  «        S )u7   GET ``/api/projects`` â€” return the caller's projects.Nr9  ú/api/projectsrÚ   r  ©rÝ   rÓ   r²   rg   r»   rß   rD  r3   r:  s      r   Úlist_projectsrH  ï  sn   € å€}ÝÐ9Ñ:Ô:Ð:ÝÑÔÐ
-Ð
-Ð
-€CÝŒ9�S¥'¨%¡.¤.¸$Ð?Ñ?Ô?€DØ×ÒÑÔÐÝ˜Ÿ	š	™œÑ$Ô$Ð$r    úOptional[Dict[str, Any]]c                ó   — |pd                      ¦   «                              ¦   «         }t          | ¦  «        D ]E}|                     d¦  «        pd                      ¦   «                              ¦   «         |k    r|c S ŒFdS )z?Return the first project whose name matches (case-insensitive).rÈ   r   N)r  r  rH  rg   )re   r   r5  r‰   s       r   Úfind_project_by_namerK  ù  s�   € àˆj�b×ÒÑ!Ô!×'Ò'Ñ)Ô)€FÝ˜eÑ$Ô$ð ð ˆØ�HŠH�VÑÔÐ" ×)Ò)Ñ+Ô+×1Ò1Ñ3Ô3°vÒ=Ð=ØˆKˆKˆKð >àˆ4r    zUnited States)r   ÚlocationrL  c               óÂ  — t           €t          d¦  «        ‚t          ¦   «         › d�}||dddœ}t          j        ||t	          | ¦  «        d¬¦  «        }|                     ¦   «          |                     ¦   «         pi }t          |t          ¦  «        st          d¦  «        ‚| 	                    d	¦  «        rt          d
|d	         › �¦  «        ‚| 	                    d¦  «        du r't          d
| 	                    d¦  «        p|› �¦  «        ‚| 	                    d¦  «        }t          |t          ¦  «        r|n|}| 	                    d¦  «        st          d¦  «        ‚|S )zÑPOST ``/api/projects`` and return ``{success, id}``.

    Spectrum is always provisioned at create-time, so the request body no
    longer carries a ``spectrum`` flag (the field was dropped from the API).
    Nz-httpx is required for Photon project creationrF  F)r   rL  ÚtemplateÚobservabilityrÚ   ©r3   rë   rÜ   z5Photon create-project returned an unexpected responserÄ   zPhoton create-project failed: ÚsucceedrÅ   r<   Úidz1Photon create-project did not return a project id)
rÝ   rÓ   r²   rÞ   r»   rß   r3   rh   rÉ   rg   )	re   r   rL  râ   rã   rÂ   r<   Úproject_candidateÚprojects	            r   Úcreate_projectrU    sy  € õ €}ÝÐJÑKÔKÐKÝÑÔÐ
-Ð
-Ð
-€CàØØØð	ð €Dõ Œ:�c ­g°e©n¬nÀdÐKÑKÔK€DØ×ÒÑÔÐØ�9Š9‰;Œ;Ð˜"€DÝ�d�DÑ!Ô!ð TÝÐRÑSÔSÐSØ‡x‚x�ÑÔð MÝÐK¸DÀ¼MÐKÐKÑLÔLÐLØ‡x‚x�	ÑÔ˜eÐ#Ð#ÝØJ¨T¯XªX°iÑ-@Ô-@Ð-HÀDÐJÐJñ
ô 
ð 	
ð Ÿš Ñ(Ô(ÐÝ3=Ð>OÕQUÑ3VÔ3VÐ`Ð/Ð/Ð\`€GØ�;Š;�tÑÔð PÝÐNÑOÔOÐOØ€Nr    c                ó¸  — t           €t          d¦  «        ‚t          ¦   «         › d|› d�}t          j        |i t	          | ¦  «        d¬¦  «        }|                     ¦   «          |                     ¦   «         pi }|                     d¦  «        rt          d|d         › �¦  «        ‚|                     d	¦  «        }|st          d
¦  «        ‚t          |¦  «        S )uæ   POST ``/api/projects/{id}/regenerate-secret`` â†’ the new project secret.

    This is the only way to read a project secret (the dashboard shows it
    exactly once), so callers should persist the returned value immediately.
    Nr9  r  z/regenerate-secretrÚ   rP  rÄ   z!Photon regenerate-secret failed: ÚprojectSecretz2Photon regenerate-secret returned no projectSecret)	rÝ   rÓ   r²   rÞ   r»   rß   r3   rg   rM   )re   r…   râ   rÂ   r<   Úsecrets         r   Úregenerate_project_secretrY  (  sÛ   € õ €}ÝÐ9Ñ:Ô:Ð:ÝÑÔÐ
LÐ
L¨jÐ
LÐ
LÐ
L€CÝŒ:�c ­G°E©N¬NÀDÐIÑIÔI€DØ×ÒÑÔÐØ�9Š9‰;Œ;Ð˜"€DØ‡x‚x�ÑÔð PÝÐN¸tÀG¼}ÐNÐNÑOÔOÐOØ�XŠX�oÑ&Ô&€FØð QÝÐOÑPÔPÐPÝˆv‰;Œ;Ðr    Úphonec                ó2   — t          j        dd| pd¦  «        S )z?Reduce a phone string to ``+`` and digits for dedup comparison.z[^\d+]rÈ   )ÚreÚsub)rZ  s    r   Ú_normalize_phoner^  ?  s   € åŒ6�)˜R  ¨"Ñ-Ô-Ð-r    c                ó  — t           €t          d¦  «        ‚t          ¦   «         › d| › d�}t          j        |t	          | |¦  «        d¬¦  «        }t          |d¦  «         t          |                     ¦   «         ¦  «        S )uD   GET Spectrum Cloud ``/projects/{id}/users/`` â†’ ``SpectrumUser[]``.Nr9  ú
/projects/ú/users/rÚ   r  z
list-users)rÝ   rÓ   r¶   rg   rÁ   rÕ   rD  r3   )r…   r†   râ   rÂ   s       r   Ú
list_usersrb  D  sx   € å€}ÝÐ9Ñ:Ô:Ð:ÝÑÔÐ
<Ð
<¨Ð
<Ð
<Ð
<€CÝŒ9�S¥&¨°^Ñ"DÔ"DÈdÐSÑSÔS€DÝ�d˜LÑ)Ô)Ð)Ý˜Ÿ	š	™œÑ$Ô$Ð$r    c                ó¢   — t          |¦  «        }t          | |¦  «        D ].}t          |                     d¦  «        pd¦  «        |k    r|c S Œ/dS )zFReturn an existing Spectrum user with the given phone number, or None.ÚphoneNumberrÈ   N)r^  rb  rg   )r…   r†   r–   r5  r  s        r   Úfind_user_by_phonere  N  sb   € õ ˜lÑ+Ô+€FÝ˜: ~Ñ6Ô6ð ð ˆÝ˜DŸHšH ]Ñ3Ô3Ð9°rÑ:Ô:¸fÒDÐDØˆKˆKˆKð Eàˆ4r    F)Ú
first_nameÚ	last_nameÚemailÚsend_inviterf  rg  rh  ri  c               ó°  — t           €t          d¦  «        ‚t                               |¦  «        st	          d|›�¦  «        ‚t          ¦   «         › d| › d�}d|dœ}|rt                               d¦  «         |r||d	<   |r||d
<   |r||d<   t          j        ||t          | |¦  «        d¬¦  «        }	t          |	d¦  «         |	                     ¦   «         pi }
|
                     d¦  «        rt          d|
d         › �¦  «        ‚|
                     d¦  «        p|
                     d¦  «        p|
}t          |t          ¦  «        r|S t          d¦  «        ‚)zBPOST Spectrum Cloud ``/projects/{id}/users/`` and return the user.Nz*httpx is required for Photon user creationz4phone_number must be E.164 (e.g. +15551234567); got r`  ra  Úshared)Útyperd  z?photon: send_invite is ignored by Spectrum shared-user creationÚ	firstNameÚlastNamerh  rÚ   rP  zcreate-userrÄ   zPhoton create-user failed: r  r<   z2Photon create-user returned an unexpected response)rÝ   rÓ   ÚE164_REÚmatchrõ   r¶   r7   ÚdebugrÞ   rÁ   rÕ   r3   rg   rh   rÉ   )r…   r†   r–   rf  rg  rh  ri  râ   rã   rÂ   r<   r  s               r   Úcreate_userrr  Y  s’  € õ €}ÝÐGÑHÔHÐHÝ�=Š=˜Ñ&Ô&ð 
ÝØSÀ<ÐSÐSñ
ô 
ð 	
õ ÑÔÐ
<Ð
<¨Ð
<Ð
<Ð
<€CØ$,¸\ÐJÐJ€DØð XÝ�ŠÐVÑWÔWÐWØð 'Ø&ˆˆ[ÑØð %Ø$ˆˆZÑØð ØˆˆW‰ÝŒ:ØØÝ�z >Ñ2Ô2Øð	ñ ô €Dõ �d˜MÑ*Ô*Ð*Ø�9Š9‰;Œ;Ð˜"€DØ‡x‚x�ÑÔð JÝÐH¸¸g¼ÐHÐHÑIÔIÐIØ�8Š8�FÑÔÐ7˜tŸxšx¨Ñ/Ô/Ð7°4€DÝ�$�ÑÔð ØˆÝ
ÐKÑ
LÔ
LÐLr    )rf  rg  rh  úTuple[Dict[str, Any], bool]c               ób   — t          | ||¦  «        }|�|dfS t          | |||||¬¦  «        }|dfS )uý   Idempotently register a Spectrum user.

    Returns ``(user, created)`` â€” ``created`` is False when a user with the
    same phone number already exists (the official CLI does no dedup, so we
    add it here to make ``setup`` safely re-runnable).
    NF)r–   rf  rg  rh  T)re  rr  )r…   r†   r–   rf  rg  rh  Úexistingr  s           r   Úregister_user_if_absentrv  „  sW   € õ " *¨n¸lÑKÔK€HØÐØ˜ˆÐÝØØØ!ØØØðñ ô €Dð �ˆ:Ðr    r  c                óZ   — | sdS |                       d¦  «        }|rt          |¦  «        ndS )uÇ  Return the iMessage number a Spectrum user is assigned to text on.

    This is the user's ``assignedPhoneNumber`` (the dashboard's "TEXTS ON"
    column) â€” i.e. the number to text to reach the agent, as opposed to the
    user's own ``phoneNumber``. On shared-number plans there is no dedicated
    entry in ``/lines``, so this per-user field is the source of truth.
    Returns ``None`` when unset (e.g. a freshly created, not-yet-assigned user).
    NÚassignedPhoneNumber)rg   rM   )r  r   s     r   Úuser_assigned_linery  ¡  s8   € ð ð ØˆtØ
�(Š(Ð(Ñ
)Ô
)€CØÐ$�3ˆs‰8Œ8ˆ8 Ð$r    c                 ó  — t          ¦   «         } |                      di ¦  «                             d¦  «        pg }t          |t          ¦  «        r«|r©|d         pi }t          |t          ¦  «        rŠ|                     d¦  «        p|                     d¦  «        }|                     d¦  «        p|                     d¦  «        }|s|r2|rt          |¦  «        nt          ¦   «         |rt          |¦  «        ndfS t          ¦   «         dfS )	zEReturn ``(operator_phone_number, assigned_phone_number)`` for status.rb   rš   r   r–   rd  r—   rx  N)r;   rg   rh   ri   rÉ   rM   Ú_configured_operator_phone)rj   Úuser_entriesrŠ   rZ  Úassigneds        r   Úload_user_numbersr~  °  s  € å‰<Œ<€DØ—8’8Ð-¨rÑ2Ô2×6Ò6°}ÑEÔEÐKÈ€LÝ�,¥Ñ%Ô%ð ¨,ð Ø˜Q”Ð% 2ˆÝ�e�TÑ"Ô"ð 
	Ø—I’I˜nÑ-Ô-ÐI°·²¸=Ñ1IÔ1IˆEà—	’	Ð1Ñ2Ô2ð 4Ø—9’9Ð2Ñ3Ô3ð ð ð ˜ð à"'ÐI•C˜‘J”J�JÕ-GÑ-IÔ-IØ%-Ð7•C˜‘M”M�M°4ðð õ &Ñ'Ô'¨Ð-Ð-r    c                ó  — t          ¦   «         \  }}d}|rt          | ||¦  «        }n+t          | |¦  «        }t          |¦  «        dk    r|d         }d}|}|rq|                     d¦  «        }t          t          |                     d¦  «        pd¦  «        ¦  «        }t                               |¦  «        r|}t          |¦  «        }t          ¦   «         }	|s„t          ¦   «         }
|
rt|	rr	 t          |
|	d¬¦  «        }|r*|                     d¦  «        rt          |d         ¦  «        }n2# t          $ r%}t                               d	|¦  «         Y d}~nd}~ww xY wt!          |||rt          |¦  «        nd|	¬
¦  «         ||fS )zFRefresh cached user numbers from Photon without provisioning anything.Né   r   rR  rd  rÈ   F©Úcreate_if_missingz6photon: could not refresh iMessage line for status: %sr•   )r~  re  rb  Úlenrg   r^  rM   ro  rp  ry  r�   rm   Úget_imessage_liner'   r7   rq  r›   )r…   r†   rZ  Úcached_assignedr  r?  r˜   r}  Údashboard_phoneÚdashboard_idÚdashboard_tokenÚliner:   s                r   Úrefresh_user_numbersrŠ  Ä  sÜ  € õ /Ñ0Ô0Ñ€Eˆ?Ø%)€DØð Ý! *¨n¸eÑDÔDˆˆå˜: ~Ñ6Ô6ˆÝˆu‰:Œ:˜Š?ˆ?Ø˜”8ˆDà€GØ-€HØð ,Ø—(’(˜4‘.”.ˆÝ*­3¨t¯xªx¸Ñ/FÔ/FÐ/LÈ"Ñ+MÔ+MÑNÔNˆÝ�=Š=˜Ñ)Ô)ð 	$Ø#ˆEÝ% dÑ+Ô+ˆå,Ñ.Ô.€LØð 8Ý+Ñ-Ô-ˆØð 	8˜|ð 	8ð8Ý(Ø#Ø Ø&+ðñ ô �ð ð 8˜DŸHšH ]Ñ3Ô3ð 8Ý" 4¨Ô#6Ñ7Ô7�Høøõ ð ð ð Ý—’ØLÈañô ð ð ð ð ð ð øøøøðøøøõ ØØ&Ø 'Ð1•�G‘”�¨TØ)ð	ñ ô ð ð �(ˆ?Ðs   Ã-D, Ä,
EÄ6EÅEc                 óŠ  — t          d¦  «        } | r+t          | ¦  «        }t                               |¦  «        r|S t          d¦  «        }|sdS g }t	          j        d|¦  «        D ]@}t          |¦  «        }t                               |¦  «        r|                     |¦  «         ŒAt          |¦  «        dk    r|d         S dS )zDInfer the operator's E.164 number from existing Photon env settings.ÚPHOTON_HOME_CHANNELÚPHOTON_ALLOWED_USERSNz[,\s]+r€  r   )Ú_get_config_env_valuer^  ro  rp  r\  Úsplitr  rƒ  )ÚhomeÚ
normalizedÚallowedrû   Úparts        r   r{  r{  õ  sÐ   € å Ð!6Ñ7Ô7€DØð Ý% dÑ+Ô+ˆ
Ý�=Š=˜Ñ$Ô$ð 	ØÐå#Ð$:Ñ;Ô;€GØð ØˆtØ€JÝ”˜ GÑ,Ô,ð *ð *ˆÝ% dÑ+Ô+ˆ
Ý�=Š=˜Ñ$Ô$ð 	*Ø×Ò˜jÑ)Ô)Ð)øÝ
ˆ:�„˜!ÒÐØ˜!Œ}ÐØˆ4r    rÌ   c                óp   — 	 ddl m} n$# t          $ r t          j        | ¦  «        cY S w xY w || ¦  «        S )Nr   )Úget_env_value)rž   r•  r'   r   r   )rÌ   r•  s     r   rŽ  rŽ  
  s[   € ðØ3Ð3Ð3Ð3Ð3Ð3Ð3øÝð ð ð ÝŒy˜‰~Œ~ÐÐÐðøøøàˆ=˜ÑÔÐs   ‚	 ‰*©*c                ó  — t           €t          d¦  «        ‚t          ¦   «         › d|› d�}t          j        |t	          | ¦  «        d¬¦  «        }|                     ¦   «          t          |                     ¦   «         ¦  «        S )uO   GET ``/api/projects/{id}/lines`` â†’ ``[{id, platform, phoneNumber, status}]``.Nr9  r  ú/linesrÚ   r  rG  )re   r…   râ   rÂ   s       r   Ú
list_linesr˜    sv   € å€}ÝÐ9Ñ:Ô:Ð:ÝÑÔÐ
@Ð
@¨jÐ
@Ð
@Ð
@€CÝŒ9�S¥'¨%¡.¤.¸$Ð?Ñ?Ô?€DØ×ÒÑÔÐÝ˜Ÿ	š	™œÑ$Ô$Ð$r    Úimessage©Úplatformr›  c               ó€  — t           €t          d¦  «        ‚t          ¦   «         › d|› d�}t          j        |d|it	          | ¦  «        d¬¦  «        }|                     ¦   «          |                     ¦   «         pi }|                     d¦  «        rt          d	|d         › �¦  «        ‚|                     d
¦  «        p|S )z:POST ``/api/projects/{id}/lines`` to provision a new line.Nr9  r  r—  r›  rÚ   rP  rÄ   zPhoton add-line failed: r‰  )rÝ   rÓ   r²   rÞ   r»   rß   r3   rg   )re   r…   r›  râ   rÂ   r<   s         r   Úadd_liner�    sË   € õ €}ÝÐ9Ñ:Ô:Ð:ÝÑÔÐ
@Ð
@¨jÐ
@Ð
@Ð
@€CÝŒ:Ø�:˜xÐ(µ'¸%±.´.È$ðñ ô €Dð 	×ÒÑÔÐØ�9Š9‰;Œ;Ð˜"€DØ‡x‚x�ÑÔð GÝÐE°d¸7´mÐEÐEÑFÔFÐFØ�8Š8�FÑÔÐ#˜tÐ#r    r�  r‚  c               ó  — t          | |¦  «        D ]3}|                     d¦  «        pd                     ¦   «         dk    r|c S Œ4|rF	 t          | |d¬¦  «        S # t          $ r&}t
                               d|¦  «         Y d}~dS d}~ww xY wdS )zÏReturn the project's iMessage line (the number to text the agent).

    If none exists and ``create_if_missing`` is set, provision one.  Returns
    ``None`` if there is no line and provisioning failed.
    r›  rÈ   r™  rš  z2photon: could not auto-provision iMessage line: %sN)r˜  rg   r  r�  r'   r7   r8   )re   r…   r‚  r‰  r:   s        r   r„  r„  0  s¾   € õ ˜5 *Ñ-Ô-ð ð ˆØ�HŠH�ZÑ Ô Ð& B×-Ò-Ñ/Ô/°:Ò=Ð=ØˆKˆKˆKð >àð ð	Ý˜E :¸
ÐCÑCÔCÐCøÝð 	ð 	ð 	Ý�NŠNÐOÐQRÑSÔSÐSØ�4�4�4�4�4øøøøð	øøøð ˆ4s   ÁA Á
B
Á$BÂB
Úemitc           	     óh  — i }t          ¦   «         rdnd|d<   t          ¦   «         \  }}|r|nd|d<   |rdnd|d<   t          ¦   «         \  }}|r|nd|d<   |r|nd|d	<   d
dd|d         z   d|d         z   d|d         z   d|d         z   d|d	         z   g} | d                     |¦  «        ¦  «         dS )uR  Pretty-print the credential status table via the *emit* callback.

    Every secret-bearing read is reduced to a display literal inside this
    function (``"âœ“ stored"`` / ``"âœ— missing"`` / a non-secret id); the
    callback only ever receives the assembled banner string, so no tainted
    value escapes into the caller's scope.
    õ
   âœ“ storedõ'   âœ— missing (run `hermes photon setup`)Údevice_tokenõ   âœ— missingr…   Úproject_keyõ3   âœ— missing (run `hermes photon setup --phone ...`)r–   r—   zPhoton iMessage statusuB   â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€z  device token        : z  project id          : z  project secret      : z  my number           : z  assigned number     : Ú
N)rm   rŒ   r~  r"  )rŸ  Úlabelsr‹   ÚsecrZ  r}  Úrowss          r   Úprint_credential_summaryr«  G  s  € ð  €Få)Ñ+Ô+ð 	7ˆˆØ6ð ˆ>Ñõ (Ñ)Ô)�H€Cˆð #&Ð8˜3˜3¨=€Fˆ<ÑØ,/ÐB˜L˜L°]€Fˆ=ÑÝ'Ñ)Ô)�O€Eˆ8àÐQˆˆÐQð ˆ>Ñð ÐKˆˆÐ"Kð Ð"Ñ#ð
 	!ØLØ" V¨NÔ%;Ñ;Ø" V¨LÔ%9Ñ9Ø" V¨MÔ%:Ñ:Ø" V¨NÔ%;Ñ;Ø" VÐ,CÔ%DÑDð€Dð 	€Dˆ�Š�4‰ŒÑÔÐÐÐr    c                 óŠ   — d	d„} d	d„}d	d„}d	d„}d	d„} | ¦   «          |¦   «          |¦   «          |¦   «          |¦   «         dœS )
zEReturn a fully pre-formatted credential status dict (no raw secrets).r"   rM   c                 ó&   — t          ¦   «         rdndS )Nr¡  r¢  )rm   r   r    r   Ú_present_tokenz*credential_summary.<locals>._present_tokeno  s   € å-Ñ/Ô/ð ;ˆLˆLØ:ð	
r    c                 ó,   — t          ¦   «         \  } }| pdS )Nr¤  ©rŒ   )r‹   Ú_secs     r   Ú_present_project_idz/credential_summary.<locals>._present_project_idu  s   € Ý,Ñ.Ô.‰	ˆˆTØÐ#�mÐ#r    c                 ó0   — t          ¦   «         \  } }|rdndS )Nr¡  r¤  r°  )Ú_sidr©  s     r   Ú_present_secretz+credential_summary.<locals>._present_secrety  s    € Ý,Ñ.Ô.‰	ˆˆcØ"Ð5ˆ|ˆ|¨Ð5r    c                 ó,   — t          ¦   «         \  } }| pdS )Nr¦  ©r~  )rZ  Ú	_assigneds     r   Ú_present_phonez*credential_summary.<locals>._present_phone}  s   € Ý,Ñ.Ô.ÑˆˆyØÐMÐMÐMr    c                 ó,   — t          ¦   «         \  } }|pdS )Nr¢  r·  )Ú_phoner}  s     r   Ú_present_assigned_phonez3credential_summary.<locals>._present_assigned_phone�  s   € Ý,Ñ.Ô.Ñˆ�ØÐDÐDÐDr    )r£  r…   r¥  r–   r—   ©r"   rM   r   )r®  r²  rµ  r¹  r¼  s        r   Úcredential_summaryr¾  m  s¸   € ð
ð 
ð 
ð 
ð$ð $ð $ð $ð6ð 6ð 6ð 6ðNð Nð Nð NðEð Eð Eð Eð
 '˜Ñ(Ô(Ø)Ð)Ñ+Ô+Ø&�Ñ(Ô(Ø&˜Ñ(Ô(Ø!8Ð!8Ñ!:Ô!:ðð ð r    r  )r"   r   )r"   r+   )r<   r+   r"   r=   )r"   r`   )re   rM   r"   r=   )r"   r=   )re   rM   r"   rz   )r"   r   )
r„   rM   r†   rM   r�   r`   r   r`   r"   r=   )
r–   r`   r—   r`   r˜   r`   r�   r`   r"   r=   )r„   rM   r†   rM   r"   r=   r½  )re   rM   r"   r·   )r…   rM   r†   rM   r"   r·   )rÂ   r   r"   rM   )rÂ   r   rÎ   rM   r"   r=   )rÖ   rM   r×   r`   r"   r¡   )ræ   r¡   rÖ   rM   rÜ   rç   r§   rç   rå   rè   r"   rM   )rã   r+   rë   rþ   r"   ri   )r   r   r"   r`   )rë   rþ   r   rM   r"   r`   )r(   rM   re   rM   r"   r   )re   rM   r"   r+   )rû   ri   r"   rM   )r)  r*  r"   r=   )rÖ   rM   r,  rz   r-  r.  r"   rM   )r<   r   r"   r<  )re   rM   r"   r<  )re   rM   r   rM   r"   rI  )re   rM   r   rM   rL  rM   r"   r+   )re   rM   r…   rM   r"   rM   )rZ  rM   r"   rM   )r…   rM   r†   rM   r"   r<  )r…   rM   r†   rM   r–   rM   r"   rI  )r…   rM   r†   rM   r–   rM   rf  r`   rg  r`   rh  r`   ri  rz   r"   r+   )r…   rM   r†   rM   r–   rM   rf  r`   rg  r`   rh  r`   r"   rs  )r  rI  r"   r`   )r…   rM   r†   rM   r"   r   )rÌ   rM   r"   r`   )re   rM   r…   rM   r"   r<  )re   rM   r…   rM   r›  rM   r"   r+   )re   rM   r…   rM   r‚  rz   r"   rI  )rŸ  r   r"   r=   )r"   r·   )ar   Ú
__future__r   r3   Úloggingr   r\  rQ   ru   rJ   Úbase64r   Údataclassesr   Úpathlibr   Útypingr   r   r	   r
   r   r   rÝ   rŸ   Úagent.secret_scoper   r   r   r   r   Ú	getLoggerr   r7   rÓ   r   ÚDEFAULT_CLIENT_IDÚDEFAULT_SCOPEr°   rµ   ÚDEFAULT_PROJECT_NAMErá   rà   Úcompilero  r*   r;   r_   rm   rw   ry   r~   rŒ   r�   r”   r›   r’   r¡   r«   r²   r¶   r»   rÁ   rÍ   rÕ   rä   rý   rö   r  r
  r  r|   r(  r÷   r7  r;  rD  rH  rK  rU  rY  r^  rb  re  rr  rv  ry  r~  rŠ  r{  rŽ  r˜  r�  r„  Úprintr«  r¾  r   r    r   ú<module>rÌ     s  ðð#ð #ðH #Ð "Ð "Ð "Ð "Ð "à €€€Ø €€€Ø 	€	€	€	Ø 	€	€	€	Ø €€€Ø €€€Ø €€€Ø Ð Ð Ð Ð Ð Ø !Ð !Ð !Ð !Ð !Ð !Ø Ð Ð Ð Ð Ð Ø =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =Ð =ðØ€L€L€L€LøØð ð ð Ø€E€E€Eðøøøð KÐ JÐ JÐ JÐ JÐ JØ ?Ð ?Ð ?Ð ?Ð ?Ð ?ð/ð /ð /ð /ð( 
ˆÔ	˜8Ñ	$Ô	$€ðPð Pð Pð Pð P˜|ñ Pô Pð Pð !Ð Ø&€à3Ð Ø7Ð ð &Ð ð Ð ØÐ à
ˆ"Œ*Ð)Ñ
*Ô
*€ðCð Cð Cð Cð	ð 	ð 	ð 	ð*ð *ð *ð *ðZð ð ð ð	ð 	ð 	ð 	ðð ð ð ð$ð ð ð ð0ð ð ð ð,ð ð ð ð8 +/Øð>ð >ð >ð >ð >ð >ðH #'Ø+/Ø!Ø*.ðð ð ð ð ð ð6Oð Oð Oð Oð, ðð ð ð ð ñ ô ñ „ðð €�$ÐÑÔðð ð ð ð ñ ô ñ ÔððVð Vð Vð VðTð Tð Tð Tð0ð 0ð 0ð 0ð/ð /ð /ð /ð
6ð 6ð 6ð 6ðð ð ð ð *À-ðð ð ð ð ð ð4 'Ø!Ø"Ø/3ðU8ð U8ð U8ð U8ð U8ð U8ðv "ð ð  ð  ð  ð  ð  ðFð ð ð ðð ð ð ð$ð ð ð ðð ð ð ð@Ið Ið Ið Ið>ð ð ð ð 'ØØ=Að	ð ð ð ð ð ð@ð ð ð ðð ð ð ð %ð %ð %ð %ðð ð ð ð %Ø#ð	#ð #ð #ð #ð #ð #ðLð ð ð ð..ð .ð .ð .ð
%ð %ð %ð %ðð ð ð ð  !%Ø#ØØð(Mð (Mð (Mð (Mð (Mð (Mð` !%Ø#Øðð ð ð ð ð ð:%ð %ð %ð %ð.ð .ð .ð .ð(.ð .ð .ð .ðbð ð ð ð*ð ð ð ð%ð %ð %ð %ð 5?ð$ð $ð $ð $ð $ð $ð$ ?Cðð ð ð ð ð ð. */ð #ð #ð #ð #ð #ðLð ð ð ð ð s   ÁA ÁAÁA