ravmem-cli 1.1.1__tar.gz → 1.3.0__tar.gz

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.
Files changed (38) hide show
  1. {ravmem_cli-1.1.1/ravmem_cli.egg-info → ravmem_cli-1.3.0}/PKG-INFO +5 -3
  2. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/README.md +4 -2
  3. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/pyproject.toml +1 -1
  4. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/license.py +181 -52
  5. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/main.py +10 -0
  6. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0/ravmem_cli.egg-info}/PKG-INFO +5 -3
  7. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/LICENSE +0 -0
  8. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/__init__.py +0 -0
  9. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/_common.py +0 -0
  10. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/_errors.py +0 -0
  11. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/analyze.py +0 -0
  12. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/audit.py +0 -0
  13. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/auth.py +0 -0
  14. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/browse.py +0 -0
  15. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/config_cmd.py +0 -0
  16. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/diff.py +0 -0
  17. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/doctor.py +0 -0
  18. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/git_local.py +0 -0
  19. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/impact.py +0 -0
  20. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/info.py +0 -0
  21. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/kg.py +0 -0
  22. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/remote.py +0 -0
  23. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/search.py +0 -0
  24. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/cli/serve.py +0 -0
  25. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/client/__init__.py +0 -0
  26. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/client/api.py +0 -0
  27. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/client/config.py +0 -0
  28. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/config.py +0 -0
  29. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/config_loader.py +0 -0
  30. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/kg_config.py +0 -0
  31. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem/kg_init.py +0 -0
  32. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem_cli.egg-info/SOURCES.txt +0 -0
  33. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem_cli.egg-info/dependency_links.txt +0 -0
  34. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem_cli.egg-info/entry_points.txt +0 -0
  35. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem_cli.egg-info/requires.txt +0 -0
  36. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/ravmem_cli.egg-info/top_level.txt +0 -0
  37. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/setup.cfg +0 -0
  38. {ravmem_cli-1.1.1 → ravmem_cli-1.3.0}/tests/test_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ravmem-cli
3
- Version: 1.1.1
3
+ Version: 1.3.0
4
4
  Summary: RavMem CLI — lightweight client for the RavMem code knowledge graph server
5
5
  Author-email: RavMem <harpreet.singh@ravmem.com>
6
6
  License-Expression: MIT
@@ -44,8 +44,10 @@ pip install ravmem-cli
44
44
  # Point the CLI at your RavMem server
45
45
  ravmem remote config --url http://localhost:7432 --api-key <ADMIN_TOKEN>
46
46
 
47
- # Activate the license (one command: seals the key to this machine, then
48
- # brings the server live — the key is never stored in a file)
47
+ # Activate the license (one command: seals the key to the server's machine,
48
+ # then brings the server live — the key is saved on the server's own data
49
+ # volume so it survives restarts; it's never stored on the machine you run
50
+ # this CLI command from)
49
51
  ravmem license activate RAVMEM-XXXX-XXXX-XXXX-XXXX
50
52
 
51
53
  # Register a repository
@@ -14,8 +14,10 @@ pip install ravmem-cli
14
14
  # Point the CLI at your RavMem server
15
15
  ravmem remote config --url http://localhost:7432 --api-key <ADMIN_TOKEN>
16
16
 
17
- # Activate the license (one command: seals the key to this machine, then
18
- # brings the server live — the key is never stored in a file)
17
+ # Activate the license (one command: seals the key to the server's machine,
18
+ # then brings the server live — the key is saved on the server's own data
19
+ # volume so it survives restarts; it's never stored on the machine you run
20
+ # this CLI command from)
19
21
  ravmem license activate RAVMEM-XXXX-XXXX-XXXX-XXXX
20
22
 
21
23
  # Register a repository
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ravmem-cli"
7
- version = "1.1.1"
7
+ version = "1.3.0"
8
8
  description = "RavMem CLI — lightweight client for the RavMem code knowledge graph server"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -39,6 +39,55 @@ def _get_client():
39
39
  return RavMemClient(base_url=cfg["server_url"], api_key=cfg.get("api_key") or None)
40
40
 
41
41
 
42
+ def _post_with_key_fallback(client, con: Console, path: str, body: Optional[dict] = None) -> dict:
43
+ """POST to `path`; if the server has no license key on file, prompt for it
44
+ once and retry with it included in the body (the server persists it there,
45
+ so this shouldn't come up again on subsequent calls)."""
46
+ body = dict(body or {})
47
+ try:
48
+ return client.post(path, json=body)
49
+ except RavMemAPIError as exc:
50
+ if exc.status == 400 and "RAVMEM_LICENSE_KEY is not set" in (exc.detail or "") and not body.get("license_key"):
51
+ con.print("[yellow]Server has no license key on file — enter it to proceed.[/yellow]")
52
+ key = typer.prompt("License key", hide_input=True).strip()
53
+ if not key:
54
+ raise
55
+ body["license_key"] = key
56
+ return client.post(path, json=body)
57
+ raise
58
+
59
+
60
+ def _license_hint(detail: str) -> Optional[str]:
61
+ """Map a license-server refusal to the concrete next step for the operator."""
62
+ d = detail or ""
63
+ if "LICENSE_REVOKED" in d:
64
+ return ("This license was revoked. Rotate the key in the console (that un-revokes it and "
65
+ "issues a new key), then run: ravmem license activate <new-key>")
66
+ if "LICENSE_KEY_NOT_FOUND" in d or "License key not found" in d or "No account found" in d:
67
+ return ("The license server doesn't recognise this key. If it was rotated in the console, "
68
+ "run: ravmem license activate <new-key>")
69
+ if "ACTIVATION_LIMIT_REACHED" in d:
70
+ return ("Every seat on this license is in use — possibly by this same machine under a "
71
+ "previous key. Deactivate the old machine in the console, then retry.")
72
+ if "SUBSCRIPTION_SUSPENDED" in d:
73
+ return "The subscription is suspended. Resolve billing in the console, then run: ravmem license renew"
74
+ if "SUBSCRIPTION_EXPIRED" in d:
75
+ return "The subscription has expired. Renew it in the console, then run: ravmem license renew"
76
+ if "No license record found" in d:
77
+ return "Run: ravmem license activate <key>"
78
+ return None
79
+
80
+
81
+ def _fail(con: Console, prefix: str, exc: Exception, note: Optional[str] = None) -> None:
82
+ con.print(f"[red]{prefix}: {exc}[/red]")
83
+ if note:
84
+ con.print(f"[dim]{note}[/dim]")
85
+ hint = _license_hint(getattr(exc, "detail", "") or str(exc))
86
+ if hint:
87
+ con.print(f"[yellow]-> {hint}[/yellow]")
88
+ raise typer.Exit(1)
89
+
90
+
42
91
  # ---------------------------------------------------------------------------
43
92
  # ravmem license init
44
93
  # ---------------------------------------------------------------------------
@@ -106,9 +155,11 @@ def activate_cmd(
106
155
  ) -> None:
107
156
  """One-step activation: seal the license to this machine and bring the server live.
108
157
 
109
- The key is sent once, sealed to this machine (machine-bound), then discarded —
110
- it is never stored in .env. Works whether the server is in activation-only mode
111
- (first run) or already running.
158
+ The key is sent once and sealed to this machine (machine-bound). It's also
159
+ saved to the server's own data volume (not to its .env) so the license
160
+ survives restarts and container recreates without needing the key re-entered.
161
+ It is never stored on the machine you run this CLI command from. Works
162
+ whether the server is in activation-only mode (first run) or already running.
112
163
  """
113
164
  con = _console()
114
165
  if not key:
@@ -126,19 +177,25 @@ def activate_cmd(
126
177
  # that is fine — we just continue to the entitlement step.
127
178
  con.print("Sealing license to this machine…")
128
179
  sealed_now = False
129
- try:
130
- client.post("/activate", json={"license_key": key})
131
- sealed_now = True
132
- con.print("[green]K3 sealed. Waiting for the server to load modules…[/green]")
133
- except RavMemAPIError as exc:
134
- if exc.status == 404:
135
- con.print("[dim]Server already activated — proceeding to entitlement init.[/dim]")
136
- else:
137
- con.print(f"[red]Activation failed: {exc}[/red]")
180
+ for attempt in range(3):
181
+ try:
182
+ client.post("/activate", json={"license_key": key})
183
+ sealed_now = True
184
+ con.print("[green]K3 sealed. Waiting for the server to load modules…[/green]")
185
+ break
186
+ except RavMemAPIError as exc:
187
+ if exc.status == 404:
188
+ con.print("[dim]Server already activated — proceeding to entitlement init.[/dim]")
189
+ break
190
+ _fail(con, "Activation failed", exc)
191
+ except Exception as exc:
192
+ # Transient: server still booting, or a network blip to it.
193
+ if attempt < 2:
194
+ time.sleep(3)
195
+ continue
196
+ con.print(f"[red]Could not reach the server: {exc}[/red]")
197
+ con.print("[yellow]-> Check it's running ('docker logs ravmem'), then retry.[/yellow]")
138
198
  raise typer.Exit(1)
139
- except Exception as exc:
140
- con.print(f"[red]Could not reach the server: {exc}[/red]")
141
- raise typer.Exit(1)
142
199
 
143
200
  # Phase 2 — wait for the re-exec'd full server to finish loading modules.
144
201
  if sealed_now:
@@ -161,17 +218,57 @@ def activate_cmd(
161
218
 
162
219
  # Phase 3 — store the signed entitlement JWT (idempotent: 409 = already done).
163
220
  con.print("Activating entitlements…")
164
- init_payload: dict = {"key_hash": key_hash}
221
+ init_payload: dict = {"key_hash": key_hash, "license_key": key}
165
222
  if server_url:
166
223
  init_payload["server_url"] = server_url
167
224
  try:
168
225
  result = client.post("/api/license/init", json=init_payload)
169
226
  except RavMemAPIError as exc:
170
- if exc.status == 409:
227
+ if exc.status != 409:
228
+ _fail(con, "Entitlement init failed", exc)
229
+
230
+ # Already initialized. That alone doesn't mean everything is fine: the
231
+ # stored token may be broken (recreate), the license may be
232
+ # deactivated/revoked, or the key typed here may differ from the one
233
+ # this server holds (rotated key, or a typo). Only report "ready" when
234
+ # all three check out; otherwise re-fetch with the key given here —
235
+ # the license server validates it either way, and on refusal nothing
236
+ # stored locally is changed.
237
+ con.print("[dim]License already initialized — verifying…[/dim]")
238
+ try:
239
+ status_data = client.get("/api/license/status")
240
+ except RavMemAPIError as exc2:
241
+ _fail(con, "Could not verify license status", exc2)
242
+
243
+ integrity = status_data.get("token_integrity")
244
+ status = status_data.get("status")
245
+ stored_prefix = status_data.get("key_hash_prefix")
246
+ key_differs = bool(stored_prefix) and not key_hash.startswith(stored_prefix)
247
+
248
+ if integrity == "ok" and status in ("ACTIVE", "GRACE_PERIOD") and not key_differs:
171
249
  con.print("[bold green]License already initialized — ravmem is ready.[/bold green]")
172
250
  return
173
- con.print(f"[red]Entitlement init failed: {exc}[/red]")
174
- raise typer.Exit(1)
251
+
252
+ if key_differs:
253
+ con.print("[yellow]This key differs from the one this server is activated with — "
254
+ "switching to it…[/yellow]")
255
+ elif status not in ("ACTIVE", "GRACE_PERIOD"):
256
+ con.print(f"[yellow]License status is {status} — re-activating…[/yellow]")
257
+ else:
258
+ con.print(f"[yellow]Stored token integrity is '{integrity}' — repairing…[/yellow]")
259
+ try:
260
+ repair_result = client.post("/api/license/repair", json={"license_key": key})
261
+ except RavMemAPIError as exc3:
262
+ _fail(con, "Activation failed", exc3,
263
+ note="Your existing license on this server was left unchanged." if key_differs else None)
264
+
265
+ if repair_result.get("status") == "rebind_pending":
266
+ con.print(f"[yellow]{repair_result.get('message')}[/yellow]")
267
+ raise typer.Exit(1)
268
+ con.print("\n[bold green]License activated. ravmem is ready.[/bold green]")
269
+ con.print(f" Status: {repair_result.get('status')}")
270
+ con.print(f" Expires: {repair_result.get('expiry_ts')}")
271
+ return
175
272
 
176
273
  con.print("\n[bold green]License activated. ravmem is ready.[/bold green]")
177
274
  con.print(f" Status: {result.get('status')}")
@@ -335,6 +432,8 @@ def status_cmd(
335
432
  expiry_ts = data.get("expiry_ts")
336
433
  table.add_row("Expires", _fmt_ts(expiry_ts) + _days_until(expiry_ts))
337
434
  table.add_row("Last heartbeat", _fmt_ts(data.get("last_heartbeat")))
435
+ if data.get("heartbeat_interval"):
436
+ table.add_row("Heartbeat every", f"{data['heartbeat_interval']}s (set in the license console)")
338
437
  table.add_row("Last server time", _fmt_ts(data.get("last_server_time")))
339
438
 
340
439
  grace_deadline = data.get("grace_deadline")
@@ -358,6 +457,8 @@ def status_cmd(
358
457
  )
359
458
  elif status == "GRACE_PERIOD":
360
459
  con.print("\n[yellow]⚠ License in grace period. Renew before the grace window closes.[/yellow]")
460
+ else:
461
+ _status_hint(con, status)
361
462
 
362
463
  if status not in ("ACTIVE", "GRACE_PERIOD", "DEACTIVATED"):
363
464
  raise typer.Exit(1)
@@ -367,16 +468,31 @@ def status_cmd(
367
468
  # ravmem license renew
368
469
  # ---------------------------------------------------------------------------
369
470
 
471
+ _KEY_OPTION_HELP = (
472
+ "License key to use. Only needed if the key was rotated in the console "
473
+ "(this server is then switched onto it), or if the server has no key on file."
474
+ )
475
+
476
+
477
+ def _run_recovery(con: Console, path: str, key: Optional[str], failed: str) -> dict:
478
+ client = _get_client()
479
+ body = {"license_key": key.strip()} if key and key.strip() else {}
480
+ try:
481
+ return _post_with_key_fallback(client, con, path, body)
482
+ except RavMemAPIError as exc:
483
+ _fail(con, failed, exc)
484
+
485
+
370
486
  @app.command("renew")
371
- def renew_cmd() -> None:
487
+ def renew_cmd(
488
+ key: Optional[str] = typer.Option(None, "--key", help=_KEY_OPTION_HELP),
489
+ ) -> None:
372
490
  """Re-activate an expired license against the live license server."""
373
491
  con = _console()
374
- client = _get_client()
375
492
  con.print("Renewing license…")
376
- try:
377
- result = client.post("/api/license/renew", json={})
378
- except RavMemAPIError as exc:
379
- con.print(f"[red]Renewal failed: {exc}[/red]")
493
+ result = _run_recovery(con, "/api/license/renew", key, "Renewal failed")
494
+ if result.get("status") == "rebind_pending":
495
+ con.print(f"[yellow]{result.get('message')}[/yellow]")
380
496
  raise typer.Exit(1)
381
497
  con.print(f"[green]Renewed. Status: {result.get('status')} | Expiry: {result.get('expiry_ts')}[/green]")
382
498
 
@@ -386,20 +502,19 @@ def renew_cmd() -> None:
386
502
  # ---------------------------------------------------------------------------
387
503
 
388
504
  @app.command("repair")
389
- def repair_cmd() -> None:
390
- """Re-fetch and re-encrypt the JWT when the stored token is corrupted."""
505
+ def repair_cmd(
506
+ key: Optional[str] = typer.Option(None, "--key", help=_KEY_OPTION_HELP),
507
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt"),
508
+ ) -> None:
509
+ """Re-fetch a valid license token for this server (broken token, recreated
510
+ container, deactivated machine, or a key rotated in the console)."""
391
511
  con = _console()
392
- con.print(
393
- "[yellow]This will re-contact the license server using the stored key_hash "
394
- "and machine fingerprint. RAVMEM_LICENSE_KEY must be set on the server.[/yellow]"
395
- )
396
- typer.confirm("Continue?", abort=True)
397
-
398
- client = _get_client()
399
- try:
400
- result = client.post("/api/license/repair", json={})
401
- except RavMemAPIError as exc:
402
- con.print(f"[red]Repair failed: {exc}[/red]")
512
+ if not yes:
513
+ con.print("[yellow]This re-contacts the license server and replaces this server's stored token.[/yellow]")
514
+ typer.confirm("Continue?", abort=True)
515
+ result = _run_recovery(con, "/api/license/repair", key, "Repair failed")
516
+ if result.get("status") == "rebind_pending":
517
+ con.print(f"[yellow]{result.get('message')}[/yellow]")
403
518
  raise typer.Exit(1)
404
519
  con.print(f"[green]Repair complete. Status: {result.get('status')}[/green]")
405
520
 
@@ -442,29 +557,43 @@ def check_cmd() -> None:
442
557
  table.add_row("Revision", str(data.get("revision", "—")))
443
558
  table.add_row("Expires", _fmt_ts(data.get("expiry_ts")))
444
559
  table.add_row("Last heartbeat", _fmt_ts(data.get("last_heartbeat")))
560
+ if data.get("heartbeat_interval"):
561
+ table.add_row("Heartbeat every", f"{data['heartbeat_interval']}s (set in the license console)")
445
562
  grace_deadline = data.get("grace_deadline")
446
563
  if grace_deadline:
447
564
  table.add_row("[yellow]Grace deadline[/yellow]", f"[yellow]{_fmt_ts(grace_deadline)}[/yellow]")
448
565
  con.print(table)
566
+ _status_hint(con, status)
449
567
 
450
568
  if status not in ("ACTIVE", "GRACE_PERIOD", "DEACTIVATED", "SUSPENDED"):
451
569
  raise typer.Exit(1)
452
570
 
453
571
 
572
+ def _status_hint(con: Console, status: str) -> None:
573
+ hints = {
574
+ "REVOKED": _license_hint("LICENSE_REVOKED"),
575
+ "REQUIRES_REACTIVATION": "Run: ravmem license repair (or: ravmem license activate <key>)",
576
+ "TAMPERED": "Run: ravmem license repair",
577
+ "DEACTIVATED": "This machine was deactivated. To keep it, run: ravmem license activate <key>",
578
+ "SUSPENDED": _license_hint("SUBSCRIPTION_SUSPENDED"),
579
+ "UNINITIALIZED": "Run: ravmem license activate <key>",
580
+ }
581
+ if hints.get(status):
582
+ con.print(f"[yellow]-> {hints[status]}[/yellow]")
583
+
584
+
454
585
  @app.command("rebind")
455
- def rebind_cmd() -> None:
456
- """Request a hardware fingerprint rebind after machine migration."""
586
+ def rebind_cmd(
587
+ key: Optional[str] = typer.Option(None, "--key", help=_KEY_OPTION_HELP),
588
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt"),
589
+ ) -> None:
590
+ """Move this license's seat onto this machine (after migration/recreate)."""
457
591
  con = _console()
458
- con.print(
459
- "[yellow]This submits a rebind request to the license server. "
460
- "Vendor approval is required before a new token is issued.[/yellow]"
461
- )
462
- typer.confirm("Continue?", abort=True)
463
-
464
- client = _get_client()
465
- try:
466
- result = client.post("/api/license/rebind", json={})
467
- except RavMemAPIError as exc:
468
- con.print(f"[red]Rebind request failed: {exc}[/red]")
592
+ if not yes:
593
+ con.print("[yellow]This moves the license seat onto this machine's current identity.[/yellow]")
594
+ typer.confirm("Continue?", abort=True)
595
+ result = _run_recovery(con, "/api/license/rebind", key, "Rebind failed")
596
+ if result.get("status") == "rebind_pending":
597
+ con.print(f"[yellow]{result.get('message')}[/yellow]")
469
598
  raise typer.Exit(1)
470
- con.print(f"[green]{result.get('message', 'Rebind request submitted.')}[/green]")
599
+ con.print(f"[green]{result.get('message', 'Rebound.')}[/green]")
@@ -1,10 +1,20 @@
1
1
  """CLI entry point — registers all subcommands."""
2
2
 
3
3
  import logging
4
+ import sys
4
5
  from typing import Optional
5
6
 
6
7
  import typer
7
8
 
9
+ # Legacy Windows consoles (cp1252) can't encode the ⚠/…/— glyphs used in
10
+ # output; without this, e.g. `ravmem license status` crashed with a
11
+ # UnicodeEncodeError *after* printing, exiting non-zero on a healthy license.
12
+ for _stream in (sys.stdout, sys.stderr):
13
+ try:
14
+ _stream.reconfigure(errors="replace")
15
+ except Exception:
16
+ pass
17
+
8
18
 
9
19
  def setup_logging(verbose: bool = False) -> None:
10
20
  """Configure root logger based on verbosity or RAVMEM_LOG_LEVEL env var.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ravmem-cli
3
- Version: 1.1.1
3
+ Version: 1.3.0
4
4
  Summary: RavMem CLI — lightweight client for the RavMem code knowledge graph server
5
5
  Author-email: RavMem <harpreet.singh@ravmem.com>
6
6
  License-Expression: MIT
@@ -44,8 +44,10 @@ pip install ravmem-cli
44
44
  # Point the CLI at your RavMem server
45
45
  ravmem remote config --url http://localhost:7432 --api-key <ADMIN_TOKEN>
46
46
 
47
- # Activate the license (one command: seals the key to this machine, then
48
- # brings the server live — the key is never stored in a file)
47
+ # Activate the license (one command: seals the key to the server's machine,
48
+ # then brings the server live — the key is saved on the server's own data
49
+ # volume so it survives restarts; it's never stored on the machine you run
50
+ # this CLI command from)
49
51
  ravmem license activate RAVMEM-XXXX-XXXX-XXXX-XXXX
50
52
 
51
53
  # Register a repository
File without changes
File without changes
File without changes
File without changes
File without changes