memorysync-cli 1.0.2__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.
@@ -0,0 +1,109 @@
1
+ """init: store a credential and pick defaults.
2
+
3
+ The key is verified against the API before it is written. Storing an unverified key
4
+ means the next command fails with something that looks like a network problem, and
5
+ the person has no reason to suspect the thing they just typed.
6
+
7
+ It is never written to the config file in plain text. It goes to the OS keychain
8
+ where one is reachable, and otherwise to an owner-only encrypted file, and `init`
9
+ says which of the two happened rather than implying a keychain that was never
10
+ there.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import getpass
16
+ import os
17
+ import sys
18
+
19
+ from .. import config as config_module
20
+ from .. import credentials
21
+ from ..errors import auth_error, usage_error
22
+ from ..http import ApiClient
23
+ from ..output import style
24
+
25
+
26
+ def init(ctx: dict) -> dict:
27
+ flags, settings = ctx["flags"], ctx["settings"]
28
+ profile_name = settings["profile_name"]
29
+
30
+ existing = config_module.profile(profile_name)
31
+ if existing and not flags.get("force"):
32
+ stored = credentials.read_key(profile_name)
33
+ if stored:
34
+ raise usage_error(
35
+ f'Profile "{profile_name}" already has a key.',
36
+ "Pass --force to replace it, or --profile <name> to add another.",
37
+ )
38
+
39
+ api_key = flags.get("api_key") or os.environ.get("MEMORYSYNC_API_KEY")
40
+ if not api_key:
41
+ if not sys.stdin.isatty():
42
+ raise usage_error(
43
+ "No API key given and no terminal to prompt from.",
44
+ "Pass --api-key, or set MEMORYSYNC_API_KEY.",
45
+ )
46
+ # getpass, so the key is not echoed into the terminal or the scrollback.
47
+ api_key = getpass.getpass("MemorySync API key: ").strip()
48
+
49
+ if not api_key:
50
+ raise usage_error("No API key given.")
51
+
52
+ # Verified before storing. A key that cannot read its own plan is not usable,
53
+ # and finding that out now is far cheaper than on the next command.
54
+ probe = ApiClient(
55
+ base_url=settings["base_url"],
56
+ api_key=api_key,
57
+ timeout=settings["timeout"],
58
+ )
59
+ try:
60
+ plan = probe.current_plan() or {}
61
+ except Exception as error: # noqa: BLE001 - re-raised as an auth failure below
62
+ raise auth_error(
63
+ f"That key was refused: {error}",
64
+ "Check it in the dashboard, or pass --base-url if you are not on production.",
65
+ ) from None
66
+
67
+ tier = credentials.write_key(api_key, profile_name)
68
+
69
+ data = config_module.load()
70
+ entry = data.setdefault("profiles", {}).setdefault(profile_name, {})
71
+ if flags.get("user"):
72
+ entry["user"] = flags["user"]
73
+ if flags.get("project"):
74
+ entry["project"] = flags["project"]
75
+ if settings["base_url"] != config_module.DEFAULT_BASE_URL:
76
+ entry["base_url"] = settings["base_url"]
77
+ path = config_module.save(data)
78
+
79
+ payload = {
80
+ "profile": profile_name,
81
+ "stored_in": tier,
82
+ "config": str(path),
83
+ "user": entry.get("user"),
84
+ "project": entry.get("project"),
85
+ "plan": plan.get("plan") or plan.get("name"),
86
+ }
87
+
88
+ def render() -> str:
89
+ where = (
90
+ "the OS keychain"
91
+ if tier == "keychain"
92
+ else "an owner-only encrypted file"
93
+ )
94
+ lines = [
95
+ f"{style.green('Ready.')} Profile {style.bold(profile_name)} is set up.",
96
+ f"{style.dim('key ')} stored in {where}",
97
+ f"{style.dim('config ')} {path}",
98
+ ]
99
+ if entry.get("user"):
100
+ lines.append(f"{style.dim('user ')} {entry['user']}")
101
+ else:
102
+ lines.append(
103
+ style.dim("no default user; pass --user on each call or re-run with --user")
104
+ )
105
+ if payload["plan"]:
106
+ lines.append(f"{style.dim('plan ')} {payload['plan']}")
107
+ return "\n".join(lines)
108
+
109
+ return {"data": payload, "text": render}