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.
- memorysync_cli/__init__.py +13 -0
- memorysync_cli/__main__.py +9 -0
- memorysync_cli/_version.py +1 -0
- memorysync_cli/args.py +220 -0
- memorysync_cli/commands/__init__.py +6 -0
- memorysync_cli/commands/admin.py +354 -0
- memorysync_cli/commands/init.py +109 -0
- memorysync_cli/commands/memory.py +629 -0
- memorysync_cli/commands/source.py +132 -0
- memorysync_cli/commands/tooling.py +238 -0
- memorysync_cli/completions.py +150 -0
- memorysync_cli/config.py +147 -0
- memorysync_cli/credentials.py +259 -0
- memorysync_cli/errors.py +110 -0
- memorysync_cli/http.py +257 -0
- memorysync_cli/main.py +325 -0
- memorysync_cli/output.py +311 -0
- memorysync_cli/registry.json +612 -0
- memorysync_cli/registry.py +101 -0
- memorysync_cli-1.0.2.dist-info/METADATA +158 -0
- memorysync_cli-1.0.2.dist-info/RECORD +24 -0
- memorysync_cli-1.0.2.dist-info/WHEEL +4 -0
- memorysync_cli-1.0.2.dist-info/entry_points.txt +3 -0
- memorysync_cli-1.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -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}
|