plexus-python 0.11.2__py3-none-any.whl → 0.11.3__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.
- plexus/__init__.py +1 -1
- plexus/cli.py +109 -4
- {plexus_python-0.11.2.dist-info → plexus_python-0.11.3.dist-info}/METADATA +1 -1
- {plexus_python-0.11.2.dist-info → plexus_python-0.11.3.dist-info}/RECORD +7 -7
- {plexus_python-0.11.2.dist-info → plexus_python-0.11.3.dist-info}/WHEEL +0 -0
- {plexus_python-0.11.2.dist-info → plexus_python-0.11.3.dist-info}/entry_points.txt +0 -0
- {plexus_python-0.11.2.dist-info → plexus_python-0.11.3.dist-info}/licenses/LICENSE +0 -0
plexus/__init__.py
CHANGED
plexus/cli.py
CHANGED
|
@@ -31,6 +31,7 @@ import threading
|
|
|
31
31
|
import urllib.parse
|
|
32
32
|
import webbrowser
|
|
33
33
|
from pathlib import Path
|
|
34
|
+
from typing import Any
|
|
34
35
|
|
|
35
36
|
from . import config
|
|
36
37
|
|
|
@@ -428,8 +429,15 @@ def cmd_logout(_args: argparse.Namespace) -> int:
|
|
|
428
429
|
return 0
|
|
429
430
|
|
|
430
431
|
|
|
431
|
-
def cmd_whoami(
|
|
432
|
-
"""
|
|
432
|
+
def cmd_whoami(args: argparse.Namespace) -> int:
|
|
433
|
+
"""Report the local key, and whether the server still accepts it.
|
|
434
|
+
|
|
435
|
+
Printing the key alone was worse than printing nothing: a revoked or
|
|
436
|
+
expired credential produced confident-looking output and a zero exit, and
|
|
437
|
+
the 401s that followed from the SDK looked unrelated. `whoami` is what
|
|
438
|
+
someone runs precisely when they suspect their auth — it has to answer
|
|
439
|
+
that question rather than confirm a file exists.
|
|
440
|
+
"""
|
|
433
441
|
key = config.get_api_key()
|
|
434
442
|
endpoint = config.get_endpoint()
|
|
435
443
|
if not key:
|
|
@@ -438,7 +446,60 @@ def cmd_whoami(_args: argparse.Namespace) -> int:
|
|
|
438
446
|
masked = f"{key[:8]}…{key[-4:]}" if len(key) > 12 else key
|
|
439
447
|
print(f"key: {masked}")
|
|
440
448
|
print(f"endpoint: {endpoint}")
|
|
441
|
-
|
|
449
|
+
|
|
450
|
+
if args.no_verify:
|
|
451
|
+
return 0
|
|
452
|
+
|
|
453
|
+
status, body = _verify_key(endpoint, key)
|
|
454
|
+
if status == 200:
|
|
455
|
+
org = body.get("org_id") or "unknown"
|
|
456
|
+
scopes = ", ".join(body.get("scopes") or []) or "default"
|
|
457
|
+
print(f"org: {org}")
|
|
458
|
+
print(f"scopes: {scopes}")
|
|
459
|
+
print("status: valid")
|
|
460
|
+
return 0
|
|
461
|
+
if status == 401:
|
|
462
|
+
print("status: REJECTED — this key is invalid, revoked or expired.")
|
|
463
|
+
print(" Run `plexus init --force` to authorize this machine again.")
|
|
464
|
+
return 1
|
|
465
|
+
if status == 403:
|
|
466
|
+
print("status: DISABLED — the key is real but access is switched off.")
|
|
467
|
+
print(" Usually billing; check with your org admin.")
|
|
468
|
+
return 1
|
|
469
|
+
if status is None:
|
|
470
|
+
# Could not ask. Say so rather than implying either answer.
|
|
471
|
+
print(f"status: unknown — could not reach {endpoint} ({body})")
|
|
472
|
+
return 0
|
|
473
|
+
print(f"status: unexpected response ({status})")
|
|
474
|
+
return 1
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _verify_key(endpoint: str, key: str) -> tuple[int | None, Any]:
|
|
478
|
+
"""Ask the server whether a key is good. Returns (status, parsed_or_reason).
|
|
479
|
+
|
|
480
|
+
A network failure returns (None, reason): unreachable is not the same as
|
|
481
|
+
rejected, and reporting one as the other is how a flaky connection gets
|
|
482
|
+
mistaken for a credentials problem.
|
|
483
|
+
"""
|
|
484
|
+
import json as _json
|
|
485
|
+
import urllib.error
|
|
486
|
+
import urllib.request
|
|
487
|
+
|
|
488
|
+
req = urllib.request.Request(
|
|
489
|
+
f"{endpoint.rstrip('/')}/api/auth/verify-key",
|
|
490
|
+
headers={"x-api-key": key},
|
|
491
|
+
method="GET",
|
|
492
|
+
)
|
|
493
|
+
try:
|
|
494
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
495
|
+
return resp.status, _json.loads(resp.read().decode() or "{}")
|
|
496
|
+
except urllib.error.HTTPError as e:
|
|
497
|
+
try:
|
|
498
|
+
return e.code, _json.loads(e.read().decode() or "{}")
|
|
499
|
+
except Exception:
|
|
500
|
+
return e.code, {}
|
|
501
|
+
except Exception as e: # DNS, TLS, timeout, offline
|
|
502
|
+
return None, str(e)
|
|
442
503
|
|
|
443
504
|
|
|
444
505
|
def _bundled_skills_dir() -> Path | None:
|
|
@@ -507,11 +568,50 @@ def _default_skills_target(args: argparse.Namespace) -> Path:
|
|
|
507
568
|
return Path.home() / ".claude" / "skills"
|
|
508
569
|
|
|
509
570
|
|
|
571
|
+
class _VersionAwareParser(argparse.ArgumentParser):
|
|
572
|
+
"""Turns an unknown subcommand into a version diagnosis.
|
|
573
|
+
|
|
574
|
+
argparse says `invalid choice: 'skills'` and stops. When the real cause is
|
|
575
|
+
an old install — a pipx shim from months ago shadowing a fresh pip
|
|
576
|
+
install, say — that message sends people looking for a typo instead of at
|
|
577
|
+
their version. The command they were told to run genuinely does not exist
|
|
578
|
+
*here*, and the fix is an upgrade, so say which version is running and how
|
|
579
|
+
to move it.
|
|
580
|
+
"""
|
|
581
|
+
|
|
582
|
+
def error(self, message: str) -> None: # type: ignore[override]
|
|
583
|
+
if "invalid choice" in message:
|
|
584
|
+
from plexus import __version__
|
|
585
|
+
|
|
586
|
+
self.print_usage(sys.stderr)
|
|
587
|
+
print(f"\n{self.prog}: error: {message}", file=sys.stderr)
|
|
588
|
+
print(
|
|
589
|
+
f"\nYou are running plexus-python {__version__}. If you were "
|
|
590
|
+
"following a doc that\nnames this command, your install is "
|
|
591
|
+
"probably older than the doc:\n"
|
|
592
|
+
"\n pip install --upgrade plexus-python"
|
|
593
|
+
"\n pipx upgrade plexus-python # if you installed with pipx"
|
|
594
|
+
"\n\nThen check with: plexus --version",
|
|
595
|
+
file=sys.stderr,
|
|
596
|
+
)
|
|
597
|
+
self.exit(2)
|
|
598
|
+
super().error(message)
|
|
599
|
+
|
|
600
|
+
|
|
510
601
|
def build_parser() -> argparse.ArgumentParser:
|
|
511
|
-
|
|
602
|
+
from plexus import __version__
|
|
603
|
+
|
|
604
|
+
parser = _VersionAwareParser(
|
|
512
605
|
prog="plexus",
|
|
513
606
|
description="Plexus CLI — auth, send, query telemetry from your terminal.",
|
|
514
607
|
)
|
|
608
|
+
# The first thing anyone types when a CLI misbehaves, and previously an
|
|
609
|
+
# error: `plexus --version` demanded a subcommand and told you nothing.
|
|
610
|
+
parser.add_argument(
|
|
611
|
+
"--version",
|
|
612
|
+
action="version",
|
|
613
|
+
version=f"plexus-python {__version__}",
|
|
614
|
+
)
|
|
515
615
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
516
616
|
|
|
517
617
|
init = sub.add_parser(
|
|
@@ -537,6 +637,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
537
637
|
logout.set_defaults(func=cmd_logout)
|
|
538
638
|
|
|
539
639
|
whoami = sub.add_parser("whoami", help="Show the local credential summary.")
|
|
640
|
+
whoami.add_argument(
|
|
641
|
+
"--no-verify",
|
|
642
|
+
action="store_true",
|
|
643
|
+
help="Skip the server check and only print what is stored locally.",
|
|
644
|
+
)
|
|
540
645
|
whoami.set_defaults(func=cmd_whoami)
|
|
541
646
|
|
|
542
647
|
skills = sub.add_parser(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
plexus/__init__.py,sha256=
|
|
1
|
+
plexus/__init__.py,sha256=CQs5A5m9byvbgba4x0bu0jjqCeav-DGVTxdlygcwNdg,808
|
|
2
2
|
plexus/_log.py,sha256=3fjXrHFZghQ_17umMcvDUjjTH6aTQB3J4SpVDBiH03w,335
|
|
3
3
|
plexus/batching.py,sha256=mPMa3m9xK-DCwRmuyL_aqaTltimIOtwoDzz4PfKWyh8,10452
|
|
4
4
|
plexus/buffer.py,sha256=UNv_jEcrDwbkjJ6uhCehb7uBI2EuFEwO40waDpZn_5I,9579
|
|
5
|
-
plexus/cli.py,sha256=
|
|
5
|
+
plexus/cli.py,sha256=YFkptze8LRc6mBmDgbsTeU8J6i8MMsdTWvtm3jCINiI,21773
|
|
6
6
|
plexus/client.py,sha256=H69DR30pj3X8PiURZsViGTDHsIsQ2Kn4muHNECsioKw,53865
|
|
7
7
|
plexus/config.py,sha256=RuDh5UdVGdVQld5kQlXZO6CVXO4tS0HBalyaoAlXNvc,4416
|
|
8
8
|
plexus/ws.py,sha256=2kUCTPkS-tG1XpL9RAzTiytk4Dbck5ypqkbDRSKaTF8,19811
|
|
@@ -12,8 +12,8 @@ plexus/_skills/README.md,sha256=9TNo9mssmBiBPkrYBiA2JJHWOIAEw6DvCWCYAvfzmjM,3243
|
|
|
12
12
|
plexus/_skills/plexus/SKILL.md,sha256=Q1ZYe9YH-gOhMcGYuMG5H9DkHMfQOr3OazCtlxtP9f8,13617
|
|
13
13
|
plexus/_skills/plexus-dashboard/SKILL.md,sha256=8xjdvI3orM31XHTLv1fSAWstCGMwt1HnEbo0SlpTxV4,9508
|
|
14
14
|
plexus/_skills/plexus-firmware/SKILL.md,sha256=IkUqNbg5sM5VaZEb189P_aOUkm9_PlSqd4JLH0Nv830,12081
|
|
15
|
-
plexus_python-0.11.
|
|
16
|
-
plexus_python-0.11.
|
|
17
|
-
plexus_python-0.11.
|
|
18
|
-
plexus_python-0.11.
|
|
19
|
-
plexus_python-0.11.
|
|
15
|
+
plexus_python-0.11.3.dist-info/METADATA,sha256=9HNLVMyUVehl40sr477LJ1W5_XryDW7RELpbyDysq7k,13517
|
|
16
|
+
plexus_python-0.11.3.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
17
|
+
plexus_python-0.11.3.dist-info/entry_points.txt,sha256=YlkOtTn_7Q_IGuJaKdvpU-90dCeBSPx2p_UTGMAz5Zs,43
|
|
18
|
+
plexus_python-0.11.3.dist-info/licenses/LICENSE,sha256=nm3qP1F-JAGcfLpRVtIX24L20LMnRpxmZ2oKZzFpLVo,10755
|
|
19
|
+
plexus_python-0.11.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|