icmd-python 3.3.4__py3-none-any.whl → 3.3.5__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.
- icmd/apps/agent.py +12 -1
- icmd/cli/__init__.py +16 -6
- icmd/cli/account.py +4 -1
- icmd/client.py +21 -14
- icmd/core/credentials.py +41 -28
- icmd/core/session.py +33 -19
- icmd/version.py +1 -1
- {icmd_python-3.3.4.dist-info → icmd_python-3.3.5.dist-info}/METADATA +1 -1
- {icmd_python-3.3.4.dist-info → icmd_python-3.3.5.dist-info}/RECORD +13 -13
- {icmd_python-3.3.4.dist-info → icmd_python-3.3.5.dist-info}/WHEEL +0 -0
- {icmd_python-3.3.4.dist-info → icmd_python-3.3.5.dist-info}/entry_points.txt +0 -0
- {icmd_python-3.3.4.dist-info → icmd_python-3.3.5.dist-info}/licenses/LICENSE +0 -0
- {icmd_python-3.3.4.dist-info → icmd_python-3.3.5.dist-info}/top_level.txt +0 -0
icmd/apps/agent.py
CHANGED
|
@@ -212,5 +212,16 @@ class AgentApp:
|
|
|
212
212
|
return self._client.request_json("get", "agent/providers")
|
|
213
213
|
|
|
214
214
|
def usage(self) -> dict | list[dict]:
|
|
215
|
-
"""Get the group's
|
|
215
|
+
"""Get the group's Assist credit balance (``GET agent/assist/usage``).
|
|
216
|
+
|
|
217
|
+
Group admins only.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
dict
|
|
222
|
+
``granted``, ``used``, ``balance`` and ``month_used`` in credits,
|
|
223
|
+
and ``status`` (``"ok"`` / ``"low"`` / ``"out"``). ``balance``
|
|
224
|
+
can be negative. An unmetered group returns ``None`` for all five
|
|
225
|
+
keys, ``status`` included.
|
|
226
|
+
"""
|
|
216
227
|
return self._client.request_json("get", "agent/assist/usage")
|
icmd/cli/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ Entry point: ``icmd`` (installed via ``[project.scripts]``).
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import contextlib
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
10
11
|
import click
|
|
@@ -23,7 +24,7 @@ from icmd.version import __version__
|
|
|
23
24
|
from .account import agent_usage, register_account_commands
|
|
24
25
|
from .auth import CLIAuthenticator
|
|
25
26
|
from .auth_status import auth_cmd
|
|
26
|
-
from .client import _build_account_client
|
|
27
|
+
from .client import _build_account_client, build_client
|
|
27
28
|
from .commands import (
|
|
28
29
|
_domain_option,
|
|
29
30
|
_format,
|
|
@@ -139,12 +140,21 @@ def login() -> None:
|
|
|
139
140
|
@cli.command()
|
|
140
141
|
@_domain_option
|
|
141
142
|
def logout() -> None:
|
|
142
|
-
"""
|
|
143
|
+
"""Sign out of an ICMD® instance and clear its cached credentials."""
|
|
143
144
|
ctx = click.get_current_context()
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
domain = _require_domain(ctx)
|
|
146
|
+
normalized = normalize_domain(domain)
|
|
147
|
+
store = CredentialManager()
|
|
148
|
+
# Revoke a saved login on the server first, or its refresh token stays
|
|
149
|
+
# usable until it expires. Gated on a stored refresh token so that with
|
|
150
|
+
# nothing saved no client is built (no request, no prompt). Best effort:
|
|
151
|
+
# the local clear runs whatever the server does, Ctrl-C included.
|
|
152
|
+
try:
|
|
153
|
+
if store.get_refresh_token(normalized):
|
|
154
|
+
with contextlib.suppress(Exception):
|
|
155
|
+
build_client(domain).logout(clear_saved_credentials=True)
|
|
156
|
+
finally:
|
|
157
|
+
store.clear_domain_credentials(normalized)
|
|
148
158
|
write_output({"status": "ok", "domain": normalized}, fmt=_format(ctx))
|
|
149
159
|
|
|
150
160
|
|
icmd/cli/account.py
CHANGED
|
@@ -371,7 +371,10 @@ def invite_cancel(uuid: str, yes: bool) -> None:
|
|
|
371
371
|
@click.command("usage")
|
|
372
372
|
@_global_options
|
|
373
373
|
def agent_usage() -> None:
|
|
374
|
-
"""Get the group's
|
|
374
|
+
"""Get the group's ICMD® Assist credit balance.
|
|
375
|
+
|
|
376
|
+
Prints ``granted`` / ``used`` / ``balance`` / ``month_used`` (credits) and
|
|
377
|
+
``status``; ``null`` values mean the group is unmetered.
|
|
375
378
|
|
|
376
379
|
Unlike the account commands, this is group-scoped: it honours ``--group``
|
|
377
380
|
and injects ``owner_group`` (the backend gates on group-admin membership).
|
icmd/client.py
CHANGED
|
@@ -148,11 +148,9 @@ class ICMD:
|
|
|
148
148
|
def _save_credentials(self) -> None:
|
|
149
149
|
"""Persist the credentials produced by an interactive login.
|
|
150
150
|
|
|
151
|
-
Only ever called after an interactive flow
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
backend's absolute session cap forward on every refresh, so the refresh
|
|
155
|
-
path saves the access token alone (:meth:`_cache_access_token`).
|
|
151
|
+
Only ever called after an interactive flow: it replaces the whole
|
|
152
|
+
record unconditionally, whereas a refresh must write only while the
|
|
153
|
+
record still holds the token it presented (:meth:`_cache_access_token`).
|
|
156
154
|
"""
|
|
157
155
|
issued = self._session.refresh_token
|
|
158
156
|
self._credentials.save_domain_credentials(
|
|
@@ -167,8 +165,9 @@ class ICMD:
|
|
|
167
165
|
def _cache_access_token(self, refresh_token: str | None = None) -> None:
|
|
168
166
|
"""Cache the session's access token for the next process to reuse.
|
|
169
167
|
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
A refresh token the server rotated in exchange for *refresh_token* is
|
|
169
|
+
written with it, in the same compare-and-swap, so the next process
|
|
170
|
+
presents the current token rather than a superseded one.
|
|
172
171
|
|
|
173
172
|
Parameters
|
|
174
173
|
----------
|
|
@@ -185,11 +184,13 @@ class ICMD:
|
|
|
185
184
|
"""
|
|
186
185
|
if not self._session.has_valid_token():
|
|
187
186
|
return
|
|
187
|
+
current = self._session.refresh_token
|
|
188
188
|
self._credentials.save_access_token(
|
|
189
189
|
self._session.domain,
|
|
190
190
|
self._session.access_token,
|
|
191
|
-
|
|
191
|
+
expires=self._session.access_token_expires_at,
|
|
192
192
|
expected_refresh_token=refresh_token,
|
|
193
|
+
rotated_refresh_token=current if current and current != refresh_token else None,
|
|
193
194
|
)
|
|
194
195
|
|
|
195
196
|
def _adopt_cached_access_token(self) -> bool:
|
|
@@ -347,8 +348,11 @@ class ICMD:
|
|
|
347
348
|
"""Clear cached credentials for a domain.
|
|
348
349
|
|
|
349
350
|
Removes the whole record — refresh token and cached access token
|
|
350
|
-
alike — unconditionally
|
|
351
|
-
|
|
351
|
+
alike — unconditionally, not as a reaction to a token the server
|
|
352
|
+
rejected. Local only: the server session stays live; use
|
|
353
|
+
:meth:`logout` to end it. Processes already running on that login
|
|
354
|
+
may then sign it out, since each rotates its refresh token on its
|
|
355
|
+
own once the record is gone.
|
|
352
356
|
|
|
353
357
|
Can be called without an ``ICMD`` instance, which is useful when
|
|
354
358
|
instantiation itself fails (e.g. after a server-side auth-method
|
|
@@ -506,10 +510,13 @@ class ICMD:
|
|
|
506
510
|
Parameters
|
|
507
511
|
----------
|
|
508
512
|
clear_saved_credentials : bool
|
|
509
|
-
Also clear the stored refresh token
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
+
Also clear the stored refresh token. Every refresh token of a
|
|
514
|
+
login belongs to the session just ended, so once the server has
|
|
515
|
+
ended it the stored one is dead either way; clearing it only spares
|
|
516
|
+
the next client one rejected refresh before it authenticates
|
|
517
|
+
interactively. The cached access
|
|
518
|
+
token is dropped either way — it belongs to the session just ended,
|
|
519
|
+
and leaving it behind would let another process keep using it.
|
|
513
520
|
|
|
514
521
|
Notes
|
|
515
522
|
-----
|
icmd/core/credentials.py
CHANGED
|
@@ -41,7 +41,7 @@ Per-domain record::
|
|
|
41
41
|
|
|
42
42
|
{
|
|
43
43
|
"auth_method": "SAML", # required for auth-method detection
|
|
44
|
-
"refresh_token": "…", # long-lived;
|
|
44
|
+
"refresh_token": "…", # long-lived; replaced when a refresh rotates it
|
|
45
45
|
"access_token": "…", # optional cache, short-lived
|
|
46
46
|
"access_token_expires_at": "…", # absolute UTC ISO-8601 timestamp
|
|
47
47
|
"saved_at": "…"
|
|
@@ -717,18 +717,15 @@ class CredentialManager:
|
|
|
717
717
|
domain: str,
|
|
718
718
|
access_token: str,
|
|
719
719
|
*,
|
|
720
|
-
|
|
721
|
-
expires_at: datetime | None = None,
|
|
720
|
+
expires: float | datetime | None = None,
|
|
722
721
|
expected_refresh_token: str | None = None,
|
|
722
|
+
rotated_refresh_token: str | None = None,
|
|
723
723
|
) -> bool:
|
|
724
|
-
"""Cache an access token for *domain
|
|
724
|
+
"""Cache an access token for *domain*, with the refresh token rotated beside it.
|
|
725
725
|
|
|
726
726
|
This is the only writer of the access-token fields, and it merges into
|
|
727
|
-
the existing record rather than replacing it
|
|
728
|
-
|
|
729
|
-
server mints a *new* refresh token on every exchange and persisting the
|
|
730
|
-
rotation would roll the backend's absolute session cap forward
|
|
731
|
-
indefinitely.
|
|
727
|
+
the existing record rather than replacing it, so the auth method and
|
|
728
|
+
the rest of the record survive.
|
|
732
729
|
|
|
733
730
|
Parameters
|
|
734
731
|
----------
|
|
@@ -736,11 +733,10 @@ class CredentialManager:
|
|
|
736
733
|
Normalised ICMD domain the token belongs to.
|
|
737
734
|
access_token : str
|
|
738
735
|
The access token to cache.
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
naive value is interpreted as UTC.
|
|
736
|
+
expires : float | datetime | None
|
|
737
|
+
A number is the lifetime in seconds as reported by the server,
|
|
738
|
+
measured from now; a datetime is the absolute expiry, and a naive
|
|
739
|
+
one is interpreted as UTC.
|
|
744
740
|
expected_refresh_token : str | None
|
|
745
741
|
When given, cache only while the stored refresh token is still
|
|
746
742
|
exactly this value — the compare-and-swap counterpart to
|
|
@@ -751,6 +747,12 @@ class CredentialManager:
|
|
|
751
747
|
would resurrect a signed-out record (an access-token-only leftover
|
|
752
748
|
that the next process happily adopts) or file one account's token
|
|
753
749
|
under another's. ``None`` merges unconditionally.
|
|
750
|
+
rotated_refresh_token : str | None
|
|
751
|
+
The refresh token the server returned with *access_token*, when it
|
|
752
|
+
differs from the one presented. Written in the same rewrite, after
|
|
753
|
+
the *expected_refresh_token* check, so both tokens land or neither
|
|
754
|
+
does. A failed write is logged at WARNING: the stored token is then
|
|
755
|
+
one the server has already superseded.
|
|
754
756
|
|
|
755
757
|
Returns
|
|
756
758
|
-------
|
|
@@ -765,24 +767,26 @@ class CredentialManager:
|
|
|
765
767
|
Raises
|
|
766
768
|
------
|
|
767
769
|
ValueError
|
|
768
|
-
If
|
|
770
|
+
If *expires* is not given.
|
|
769
771
|
"""
|
|
770
|
-
if
|
|
771
|
-
raise ValueError("save_access_token requires
|
|
772
|
+
if expires is None:
|
|
773
|
+
raise ValueError("save_access_token requires expires")
|
|
772
774
|
|
|
773
775
|
if not access_token or not isinstance(access_token, str):
|
|
774
776
|
logger.debug("Refusing to cache an empty access token for %s", domain)
|
|
775
777
|
return False
|
|
776
778
|
|
|
777
|
-
resolved = self._resolve_expiry(
|
|
779
|
+
resolved = self._resolve_expiry(expires)
|
|
778
780
|
if resolved is None:
|
|
779
781
|
logger.debug("Access token for %s has no usable expiry; not caching", domain)
|
|
780
782
|
return False
|
|
781
783
|
|
|
782
784
|
stamp = resolved.isoformat()
|
|
783
785
|
canonical = canonical_domain(domain)
|
|
786
|
+
attempted = False
|
|
784
787
|
|
|
785
788
|
def mutate(records: dict[str, Any]) -> bool:
|
|
789
|
+
nonlocal attempted
|
|
786
790
|
# Folded before the compare-and-swap, so the token compared against
|
|
787
791
|
# is the one this domain's single surviving record holds. Refusing
|
|
788
792
|
# below writes nothing and discards the fold with it; the next
|
|
@@ -804,23 +808,32 @@ class CredentialManager:
|
|
|
804
808
|
records[canonical] = record
|
|
805
809
|
record[_ACCESS_TOKEN_KEY] = access_token
|
|
806
810
|
record[_ACCESS_TOKEN_EXPIRES_AT_KEY] = stamp
|
|
811
|
+
if rotated_refresh_token:
|
|
812
|
+
record[_REFRESH_TOKEN_KEY] = rotated_refresh_token
|
|
813
|
+
attempted = True
|
|
807
814
|
return True
|
|
808
815
|
|
|
809
|
-
|
|
816
|
+
saved = self._mutate_store(mutate)
|
|
817
|
+
if attempted and not saved and rotated_refresh_token:
|
|
818
|
+
logger.warning(
|
|
819
|
+
"Could not save the rotated refresh token for %s; this process's next "
|
|
820
|
+
"refresh and any later process will present a superseded one, which "
|
|
821
|
+
"signs the login out.",
|
|
822
|
+
domain,
|
|
823
|
+
)
|
|
824
|
+
return saved
|
|
810
825
|
|
|
811
826
|
@staticmethod
|
|
812
|
-
def _resolve_expiry(
|
|
813
|
-
"""Resolve
|
|
814
|
-
if
|
|
815
|
-
if
|
|
816
|
-
return None
|
|
817
|
-
return expires_at.replace(tzinfo=UTC) if expires_at.tzinfo is None else expires_at
|
|
827
|
+
def _resolve_expiry(expires: float | datetime) -> datetime | None:
|
|
828
|
+
"""Resolve either expiry form into one absolute UTC timestamp."""
|
|
829
|
+
if isinstance(expires, datetime):
|
|
830
|
+
return expires.replace(tzinfo=UTC) if expires.tzinfo is None else expires
|
|
818
831
|
|
|
819
|
-
if not isinstance(
|
|
832
|
+
if not isinstance(expires, int | float) or isinstance(expires, bool):
|
|
820
833
|
return None
|
|
821
|
-
if
|
|
834
|
+
if expires <= 0:
|
|
822
835
|
return None
|
|
823
|
-
return datetime.now(UTC) + timedelta(seconds=float(
|
|
836
|
+
return datetime.now(UTC) + timedelta(seconds=float(expires))
|
|
824
837
|
|
|
825
838
|
def _mutate_store(self, mutate: Callable[[dict[str, Any]], bool]) -> bool:
|
|
826
839
|
"""Read, mutate, and atomically rewrite the store under the credential lock.
|
icmd/core/session.py
CHANGED
|
@@ -9,6 +9,7 @@ this module only manages token state.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import logging
|
|
12
13
|
import os
|
|
13
14
|
import re
|
|
14
15
|
import warnings
|
|
@@ -60,6 +61,8 @@ if TYPE_CHECKING:
|
|
|
60
61
|
from .constants import HTTPMethod
|
|
61
62
|
from .contracts import ContractClient
|
|
62
63
|
|
|
64
|
+
logger = logging.getLogger(__name__)
|
|
65
|
+
|
|
63
66
|
|
|
64
67
|
class ResourceClient(Protocol):
|
|
65
68
|
"""Structural type for objects that can make JSON API requests."""
|
|
@@ -718,7 +721,6 @@ class Session:
|
|
|
718
721
|
EXCLUDED_ENDPOINTS: ClassVar[set[str]] = {
|
|
719
722
|
LOGIN,
|
|
720
723
|
AUTH_REFRESH,
|
|
721
|
-
AUTH_LOGOUT,
|
|
722
724
|
MFA_VERIFY,
|
|
723
725
|
MFA_EMAIL_GENERATE,
|
|
724
726
|
SAML_LOGIN,
|
|
@@ -1162,15 +1164,11 @@ class Session:
|
|
|
1162
1164
|
|
|
1163
1165
|
# The server already refused this exact access token on a 401 that a
|
|
1164
1166
|
# refresh did not fix, so exchanging again cannot help — and would not
|
|
1165
|
-
# be free
|
|
1166
|
-
#
|
|
1167
|
-
#
|
|
1168
|
-
#
|
|
1169
|
-
#
|
|
1170
|
-
# exhausts the bucket after 30 — the same refresh lockout the
|
|
1171
|
-
# empty-token guard above prevents, reached by another door. No reset
|
|
1172
|
-
# is needed: the guard compares against the token currently held, so
|
|
1173
|
-
# any path that installs a new one clears it.
|
|
1167
|
+
# be free. Without this, a 401 that is not about expiry (an
|
|
1168
|
+
# authorization failure, a proxy stripping ``Authorization``) costs one
|
|
1169
|
+
# refresh POST per *request*, each counted by the server's refresh
|
|
1170
|
+
# throttle. No reset is needed: the guard compares against the token
|
|
1171
|
+
# currently held, so any path that installs a new one clears it.
|
|
1174
1172
|
if self._access_token and self._access_token == self._refresh_rejected_token:
|
|
1175
1173
|
return response
|
|
1176
1174
|
|
|
@@ -1325,18 +1323,34 @@ class Session:
|
|
|
1325
1323
|
# -----------------------------------------------------------------
|
|
1326
1324
|
|
|
1327
1325
|
def logout(self) -> None:
|
|
1328
|
-
"""
|
|
1326
|
+
"""Revoke the server session and clear in-memory tokens.
|
|
1329
1327
|
|
|
1330
|
-
|
|
1328
|
+
The server revokes the session named by the bearer, so the POST takes
|
|
1329
|
+
the same renew-then-send path as any authenticated request, including
|
|
1330
|
+
the single refresh-and-retry on a ``401``. Best effort: it never
|
|
1331
|
+
raises, and the outcome is logged at DEBUG — callers decide how to
|
|
1332
|
+
communicate success.
|
|
1331
1333
|
"""
|
|
1332
1334
|
try:
|
|
1333
1335
|
if self._access_token:
|
|
1334
|
-
self.
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1336
|
+
self._renew_expired_token(AUTH_LOGOUT)
|
|
1337
|
+
if (
|
|
1338
|
+
self._refresh_coordinator is not None
|
|
1339
|
+
and self._proactive_renewal
|
|
1340
|
+
and self.is_token_expired()
|
|
1341
|
+
):
|
|
1342
|
+
# Renewal had its chance and failed; the server would only
|
|
1343
|
+
# answer this token with a 401.
|
|
1344
|
+
logger.debug("Logout skipped: the access token expired and was not renewed")
|
|
1345
|
+
else:
|
|
1346
|
+
url = f"{self.api_root}/{AUTH_LOGOUT}/"
|
|
1347
|
+
response = self._raw_request("POST", url, timeout=5)
|
|
1348
|
+
if response.status_code == HTTPStatus.UNAUTHORIZED:
|
|
1349
|
+
response = self._retry_on_unauthorized(
|
|
1350
|
+
response, AUTH_LOGOUT, "POST", url, timeout=5
|
|
1351
|
+
)
|
|
1352
|
+
logger.debug("Logout answered HTTP %s", response.status_code)
|
|
1353
|
+
except Exception as exc:
|
|
1354
|
+
logger.debug("Logout request failed: %s", exc)
|
|
1341
1355
|
|
|
1342
1356
|
self.clear_tokens()
|
icmd/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "3.3.
|
|
1
|
+
__version__ = "3.3.5"
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
icmd/__init__.py,sha256=BoE6tl0PaBgXqXmL_QjebFos5eOvS91Dj6tjJwolezk,2739
|
|
2
|
-
icmd/client.py,sha256=
|
|
2
|
+
icmd/client.py,sha256=8PXqAskxzkuUwz2wtHYFp9kDAgQlyOxDNbEyVSKayeY,22643
|
|
3
3
|
icmd/compose.py,sha256=V_B4EYuX01oJexnGGvhIwDUKgfx_Fyd0lPVvSRUoH8c,6931
|
|
4
4
|
icmd/links.py,sha256=I8v8_xhI6lbUwrMjIUcYYcqKUi2PIPm6zQ7mXYP9A5E,6827
|
|
5
5
|
icmd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
icmd/types.py,sha256=LizuU96hF7VRjl4OlyF0oPcao1cKe795YXEsLW9fKI4,79496
|
|
7
|
-
icmd/version.py,sha256=
|
|
7
|
+
icmd/version.py,sha256=QO4UWv5ZUgBpuBbLNKWKecIDPPTa5HfOdEYEe2q2Afo,22
|
|
8
8
|
icmd/_vendor/__init__.py,sha256=VFA4EQd2l76sxgkHPUpgzZavFF6kFdpq9v71AZsPIdQ,579
|
|
9
9
|
icmd/_vendor/assembler.py,sha256=U8VPlqN1605RJQ5qMVoNrL3uJ7NpUe8BVmD8q06IN5w,5282
|
|
10
10
|
icmd/apps/__init__.py,sha256=ak2ch1UU-qaiyOT1eCdRwqSI0Avhzyw5DLkw-U2ivQQ,659
|
|
11
11
|
icmd/apps/account.py,sha256=BWDO0Zysv0ygLVhbeIk82BjsvuDVb_vBdu6jqBPw3tc,14557
|
|
12
|
-
icmd/apps/agent.py,sha256=
|
|
12
|
+
icmd/apps/agent.py,sha256=MmWuH1sYqo-hOhNNuTwh930H_BFXs7oMP0QgyzaC0Iw,8387
|
|
13
13
|
icmd/apps/base.py,sha256=y4uvIiomCLia89UL2rV4oufLBUQaODZ8qwl-EwDtjhM,18979
|
|
14
14
|
icmd/apps/constants.py,sha256=5xoIwFNGOaAp2cHAlVDlbzJ7ObQHYXJgVGMBfNLCHzI,757
|
|
15
15
|
icmd/apps/documents.py,sha256=8_wFc7NLUFQMYZA5hqsciXniHECZEWS-egX_ni8F4LE,3720
|
|
@@ -20,8 +20,8 @@ icmd/apps/studio.py,sha256=XN6gVCqkTzp7qH9I-UOhW7UJ2RfkGv60H-G7ulkR850,2419
|
|
|
20
20
|
icmd/apps/suite.py,sha256=04MIHUima_RGfnHyBPjyxcghcIMHhgDLWPcex6tIWls,2761
|
|
21
21
|
icmd/apps/types.py,sha256=jOzykkXWj9e_lk805_ABCC-sWBwIpJGN3HEb2AyQ9vw,1290
|
|
22
22
|
icmd/apps/workspace.py,sha256=BXCOLQo5BMu-okW2Ink8L_FPhU8r7OWy2jDsvilU3Oo,3055
|
|
23
|
-
icmd/cli/__init__.py,sha256=
|
|
24
|
-
icmd/cli/account.py,sha256=
|
|
23
|
+
icmd/cli/__init__.py,sha256=mBpAlW1BCKglPzdDffTc4uLdr_2Ri7qnVC2I-Q9FjEA,7395
|
|
24
|
+
icmd/cli/account.py,sha256=equTAGWR3NCCMYEPTw4IZKHEtBEdGD8F4Pp8cfiSNoU,13859
|
|
25
25
|
icmd/cli/auth.py,sha256=UFpEf-lLlkbphivgBfd9NxAEqHcCsPmEl-DXet-NStg,3317
|
|
26
26
|
icmd/cli/auth_status.py,sha256=6wkI3Q0c-t8DBOfuPaN_xXEUcL9HLZm1jX4lQ76X3Ag,13505
|
|
27
27
|
icmd/cli/client.py,sha256=3iToPSABFYWWUPn-uDVFAv9GoBWQXP04l2Id_aYgd64,2186
|
|
@@ -36,15 +36,15 @@ icmd/core/advisory.py,sha256=ErXByq5UgpSGkjIiAjnmVCBLdY76REiU-Ilf_HoJ06Y,20783
|
|
|
36
36
|
icmd/core/auth.py,sha256=bG6eLbSkmyDWLyprx7NJMDwLTGsOIrsRdBZ3f6vz1vk,29480
|
|
37
37
|
icmd/core/constants.py,sha256=P3a01sDt5WmGYqkp5hu4UqWbMgfp50o6rBV3q9jU4yA,2339
|
|
38
38
|
icmd/core/contracts.py,sha256=0Dw7tq_IxQY5YC4CKtaubJ0Npa4Ae66-sm-0UuqH_no,9901
|
|
39
|
-
icmd/core/credentials.py,sha256=
|
|
39
|
+
icmd/core/credentials.py,sha256=39vyoFH1_MrzD9TpTh4FVPhWbrY5X35zGCS22KeTi_8,46835
|
|
40
40
|
icmd/core/display.py,sha256=RkgJhr2Bfw3-ZjGvE100ddD6oyAAUUAJfBAy6DvX5ik,13060
|
|
41
41
|
icmd/core/exceptions.py,sha256=9iFCw93yZ0Eh1x_6-acPU8pVtaUy7EhSz-h1JkYS4M4,6985
|
|
42
42
|
icmd/core/groups.py,sha256=OFbQce42DLDK0rDAdMbh8H7imNPAkBJMl7o7tbKQ4Iw,7211
|
|
43
43
|
icmd/core/model_classes.py,sha256=lOwTwZzmQ247fddFMjPVo43Z_VZfdPhhM0NI8LfRqlc,40047
|
|
44
|
-
icmd/core/session.py,sha256=
|
|
45
|
-
icmd_python-3.3.
|
|
46
|
-
icmd_python-3.3.
|
|
47
|
-
icmd_python-3.3.
|
|
48
|
-
icmd_python-3.3.
|
|
49
|
-
icmd_python-3.3.
|
|
50
|
-
icmd_python-3.3.
|
|
44
|
+
icmd/core/session.py,sha256=_hdA3Yo_dH1A_qRH1xpyEB8sBkUNQKcUywc54WsPZos,60894
|
|
45
|
+
icmd_python-3.3.5.dist-info/licenses/LICENSE,sha256=644lGvCmDFa5hQl6xdhaYk3o-1oWqPXAIiiQlvqeZdE,1028
|
|
46
|
+
icmd_python-3.3.5.dist-info/METADATA,sha256=YXJOLdFBGc9bfWGYDvIWMAKdFNyPH_WSvSWU32oaBxY,4919
|
|
47
|
+
icmd_python-3.3.5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
48
|
+
icmd_python-3.3.5.dist-info/entry_points.txt,sha256=CFX537PlyyPQL3Q_3TKUFeiiDSkOAboqaLEy9AIEwVU,39
|
|
49
|
+
icmd_python-3.3.5.dist-info/top_level.txt,sha256=olzM6Z_QEwg7RrFVOnkgO0I2Ps2m7vhSqYQZYeiawcw,5
|
|
50
|
+
icmd_python-3.3.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|