AQIT 0.1.0
Loading...
Searching...
No Matches
cli.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2# This file is part of the Aquin Engine. Unauthorized copying, modification,
3# distribution, or use of this file, via any medium, is strictly prohibited.
4# Proprietary and confidential. See LICENSE for terms.
5
6from __future__ import annotations
7
8import json
9import os
10import sys
11from pathlib import Path
12from typing import Any
13
14_CONFIG_PATH = Path.home() / ".aquin" / "config.json"
15_BASE_URL = "https://api.aquin.app"
16
17
18def _load_config() -> dict:
19 if _CONFIG_PATH.exists():
20 with open(_CONFIG_PATH) as f:
21 return json.load(f)
22 return {}
23
24
25def _save_config(data: dict) -> None:
26 _CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
27 with open(_CONFIG_PATH, "w") as f:
28 json.dump(data, f, indent=2)
30
31def _get_api_key(explicit: str | None = None) -> str:
32 """Optional cloud token from env / legacy config — not required for local use."""
33 from aquin.auth_config import resolve_api_key
34 return resolve_api_key(explicit=explicit)
36
37def cmd_status(args: list[str]) -> None:
38 """aquin status — loaded model and local engine state"""
39 plain = False
40 as_json = False
41 i = 0
42 while i < len(args):
43 if args[i] == "--plain":
44 plain = True
45 i += 1
46 elif args[i] == "--json":
47 as_json = True
48 i += 1
49 elif args[i] in ("--api-key", "-k", "--live"):
50 print(f"Removed flag: {args[i]} (no account auth in this framework).", file=sys.stderr)
51 sys.exit(1)
52 elif args[i] in ("--help", "-h", "help"):
53 print("Usage: aquin status [--plain] [--json]")
54 print("")
55 print(" Show loaded model and local engine state.")
56 print(" --plain ASCII bullets (no color)")
57 print(" --json Machine-readable rows")
58 return
59 else:
60 print(f"Unknown flag: {args[i]}", file=sys.stderr)
61 sys.exit(1)
62
63 from aquin.status_display import collect_status_snapshot, render_status, status_payload
64
65 snap = collect_status_snapshot(logged_in=False)
66
67 if as_json:
68 print(json.dumps(status_payload(snap), ensure_ascii=False))
69 return
70
71 render_status(snap, plain=plain)
72
73
74def cmd_version(args: list[str]) -> None:
75 """aquin version — show the installed CLI version"""
76 if args and args[0] in ("--help", "-h", "help"):
77 print("Usage: aquin version [--json]")
78 print("")
79 print(" Show the installed CLI build version.")
80 print(" --json Machine-readable")
81 return
82
83 from aquin.version_display import render_version, version_payload
84
85 if "--json" in args:
86 print(json.dumps(version_payload(), ensure_ascii=False))
87 return
88
89 render_version()
90
91
92def cmd_license(args: list[str]) -> None:
93 """Show the AQIT Apache 2.0 license"""
94 if args and args[0] in ("--help", "-h", "help"):
95 print("Usage: aqit license [--json] [--plain]")
96 print("")
97 print(" Display the AQIT Apache License 2.0.")
98 print(" --json Machine-readable text + acceptance status")
99 print(" --plain Full ASCII frame (legacy layout)")
100 return
101
102 from aquin.auth_config import load_config
103 from aquin.license_acceptance import is_license_accepted
104 from aquin.license_text import LICENSE_URL, read_license_text
105
106 as_json = "--json" in args
107 plain = "--plain" in args
108 text = read_license_text()
109 accepted = is_license_accepted()
110 record = (load_config().get("license_accepted") or {}) if accepted else {}
111 accepted_at = str(record.get("accepted_at") or "") if isinstance(record, dict) else ""
112
113 if as_json:
114 try:
115 sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
116 except Exception:
117 pass
118 print(json.dumps({
119 "accepted": accepted,
120 "acceptedAt": accepted_at or None,
121 "url": LICENSE_URL,
122 "text": text,
123 }, ensure_ascii=False))
124 return
125
126 if plain:
127 from aquin.license_display import render_license_document
128
129 render_license_document(context="view")
130 return
131
132 # Quiet default (npm CLI / scripts)
133 try:
134 sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
135 except Exception:
136 pass
137 print(f"license {LICENSE_URL}")
138 if accepted:
139 if accepted_at:
140 print(f"accepted {accepted_at}")
141 else:
142 print("accepted")
143 else:
144 print("not accepted")
145 print()
146 print("full text: aquin license --plain")
147
148
149def cmd_update(args: list[str]) -> None:
150 """aquin update — upgrade aquin to the latest version from PyPI"""
151 import subprocess
152 from importlib.metadata import PackageNotFoundError, version
154 if args and args[0] in ("--help", "-h", "help"):
155 print("Usage: aquin update [--json]")
156 print("")
157 print(" Upgrade the Aquin engine package from PyPI.")
158 print(" --json Machine-readable result (npm CLI)")
159 return
160
161 as_json = "--json" in args
162
163 try:
164 current = version("aquin")
165 except PackageNotFoundError:
166 current = "unknown"
167
168 if not as_json:
169 print(f"Current version: {current}")
170 print("Checking for updates...")
171
172 result = subprocess.run(
173 [sys.executable, "-m", "pip", "install", "--upgrade", "aquin"],
174 capture_output=True,
175 text=True,
176 )
177
178 if result.returncode != 0:
179 err = (result.stderr or result.stdout or "Update failed.")[-1000:]
180 if as_json:
181 print(json.dumps({
182 "ok": False,
183 "current": current,
184 "error": err.strip() or "Update failed.",
185 }, ensure_ascii=False))
186 else:
187 print("Update failed:")
188 print(err)
189 sys.exit(1)
190
191 try:
192 import importlib
193 import importlib.metadata as _meta
194
195 importlib.invalidate_caches()
196 new = _meta.version("aquin")
197 except Exception:
198 new = "unknown"
199
200 updated = new != current
201 if as_json:
202 print(json.dumps({
203 "ok": True,
204 "current": current,
205 "new": new,
206 "updated": updated,
207 }, ensure_ascii=False))
208 return
209
210 if updated:
211 print(f"Updated to {new}")
212 else:
213 print("Already up to date.")
214
215
216def _detect_desktop_platform() -> tuple[str, str]:
217 """Return (os_label, arch) for desktop install messaging."""
218 import platform
219
220 system = platform.system()
221 machine = (platform.machine() or "").lower()
222 if machine in ("x86_64", "amd64"):
223 arch = "x64"
224 elif machine in ("aarch64", "arm64"):
225 arch = "arm64"
226 elif machine in ("i386", "i686", "x86"):
227 arch = "x86"
228 else:
229 arch = machine or "unknown"
230
231 if system == "Darwin":
232 return "macOS", arch
233 if system == "Windows":
234 return "Windows", arch
235 if system == "Linux":
236 return "Linux", arch
237 return system or "Unknown OS", arch
238
239
240def cmd_desktop(args: list[str]) -> None:
241 """aquin desktop install — install the Aquin desktop app (stub until releases ship)."""
242 if not args or args[0] in ("--help", "-h", "help"):
243 print("Usage: aquin desktop install")
244 print("")
245 print(" Detect this machine's OS, confirm, then install Aquin desktop.")
246 print(" Download packaging is not published yet; yes only acknowledges for now.")
247 return
248
249 if args[0] != "install":
250 print(f"Unknown desktop subcommand: {args[0]}")
251 print("Usage: aquin desktop install")
252 sys.exit(1)
253
254 rest = args[1:]
255 if rest and rest[0] in ("--help", "-h", "help"):
256 print("Usage: aquin desktop install")
257 print("")
258 print(" Detect this machine's OS, confirm, then install Aquin desktop.")
259 print(" Download packaging is not published yet; yes only acknowledges for now.")
260 return
261
262 os_label, arch = _detect_desktop_platform()
263 print(f"Detected: {os_label} ({arch})")
264 try:
265 answer = input("Install Aquin desktop on this machine? [y/N] ").strip().lower()
266 except EOFError:
267 print("Cancelled.")
268 return
269
270 if answer not in ("y", "yes"):
271 print("Cancelled.")
272 return
273
274 print("Yes.")
275 print(f"Desktop install for {os_label} ({arch}) will download and install here when builds ship.")
276
277
278def cmd_list(args: list[str]) -> None:
279 """aquin list simulation"""
280 if not args:
281 print("Usage: aquin list simulation")
282 sys.exit(1)
283 if args[0] in ("model", "models"):
284 print("Removed. Pass a HuggingFace model id to aquin load model <id>.", file=sys.stderr)
285 sys.exit(1)
286 if args[0] in ("sae", "saes"):
287 print("Removed. Use: aquin load sae <model-l{n}> | --user <name> | --path <file>", file=sys.stderr)
288 sys.exit(1)
289 if args[0] in ("watch", "watches"):
290 print("Removed. Training watch is no longer part of the CLI.", file=sys.stderr)
291 print("Use the SDK: aquin.init() / run.log() → ./aquin_run/", file=sys.stderr)
292 sys.exit(1)
293 if args[0] == "simulation":
294 _cmd_simulation_catalog("list simulation", "list_simulation_runs", args[1:])
295 return
296 if args[0] == "simulations":
297 print("Use: aquin list simulation", file=sys.stderr)
298 sys.exit(1)
299 print("Usage: aquin list simulation")
300 sys.exit(1)
301
302
303def cmd_info(args: list[str]) -> None:
304 """aquin info sae <model-l{layer}>"""
305 if not args or args[0] != "sae" or "--help" in args or "-h" in args:
306 print("Usage: aquin info sae <model-l{n}> [--json] [--plain]")
307 print("Example: aquin info sae pythia-70m-l3")
308 print("")
309 print(" --json Machine-readable fields")
310 print(" --plain Rich table layout")
311 if args and args[0] == "sae" and ("--help" in args or "-h" in args):
312 return
313 sys.exit(1)
314
315 as_json = "--json" in args
316 plain = "--plain" in args
317 rest = [a for a in args[1:] if a not in ("--json", "--plain")]
318 if not rest:
319 print("Usage: aquin info sae <model-l{n}> [--json] [--plain]")
320 sys.exit(1)
321
322 from aquin.info_sae_display import (
323 info_sae_payload,
324 render_info_sae,
325 render_info_sae_quiet,
326 )
327 from aquin.saes import get_sae
328
329 r = get_sae(rest[0])
330 if as_json:
331 try:
332 sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
333 except Exception:
334 pass
335 print(json.dumps(info_sae_payload(r), ensure_ascii=False))
336 return
337 if plain:
338 render_info_sae(r)
339 return
340 render_info_sae_quiet(r)
341
342
343def _default_sae_load_output(sae_id: str) -> Path | None:
344 """Default ~/.aquin/sae/... path for aquin load sae <model-l{n}>."""
345 import re
346
347 from aquin.compute.model_loader import resolve_model_id, resolve_sae_path
348
349 m = re.match(r"^(.+)-l(\d+)$", sae_id)
350 if not m:
351 return None
352 model_slug, layer_s = m.group(1), int(m.group(2))
353 try:
354 model_id = resolve_model_id(model_slug)
355 except ValueError:
356 return None
357 return resolve_sae_path(model_id, layer_s)
358
359
360def _layer_from_filename_cli(path: Path) -> int | None:
361 stem = path.stem
362 if stem.startswith("sae_layer"):
363 try:
364 return int(stem.replace("sae_layer", ""))
365 except ValueError:
366 return None
367 return None
368
369
370def _print_load_sae_help() -> None:
371 print("Usage:")
372 print(" aquin load sae <model-l{n}> pull catalog SAE")
373 print(" aquin load sae --user <name> [--layer N] activate user-trained SAE")
374 print(" aquin load sae --path <file.pt> [--layer N] [--model <id>] # self-hosted weights")
375 print("")
376 print("Examples:")
377 print(" aquin load sae gpt2-small-l8")
378 print(" aquin sae train --layer 8 --name my-run")
379 print(" aquin load sae --user my-run --layer 8")
380 print(" aquin info sae <model-l{n}>")
381
382
383def _warm_daemon_sae(model_id: str, layer: int) -> None:
384 """Best-effort: cache the SAE in the persistent daemon so tools reuse it."""
385 try:
386 from .engine import model_daemon
388 if model_daemon.is_running():
389 model_daemon.warm_sae(model_id, int(layer))
390 except Exception:
391 pass
392
393
394def _cmd_load_sae(args: list[str]) -> None:
395 """aquin load sae <model-l{n}> | --user <name> | --path <file>"""
396 if not args or args[0] in ("-h", "--help", "help"):
398 return
399
400 user_name: str | None = None
401 path_arg: str | None = None
402 layer: int | None = None
403 model_id: str | None = None
404 explicit_key: str | None = None
405 output = "./sae.pt"
406 explicit_output = False
407 force = False
408 positional: list[str] = []
409
410 i = 0
411 while i < len(args):
412 a = args[i]
413 if a in ("--force", "-f"):
414 force = True
415 i += 1
416 elif a == "--user" and i + 1 < len(args):
417 user_name = args[i + 1]
418 i += 2
419 elif a == "--path" and i + 1 < len(args):
420 path_arg = args[i + 1]
421 i += 2
422 elif a == "--layer" and i + 1 < len(args):
423 layer = int(args[i + 1])
424 i += 2
425 elif a == "--model" and i + 1 < len(args):
426 model_id = args[i + 1]
427 i += 2
428 elif a == "--key" and i + 1 < len(args):
429 explicit_key = args[i + 1]
430 i += 2
431 elif a.startswith("--"):
432 print(f"Unknown flag: {a}", file=sys.stderr)
433 sys.exit(1)
434 else:
435 positional.append(a)
436 i += 1
437
438 if user_name:
439 locked = model_id or _require_locked_model_id()
440 from aquin.compute.user_sae import activate_user_sae, resolve_user_sae_path
441
442 try:
443 sae_path = resolve_user_sae_path(locked, user_name, layer)
444 except (FileNotFoundError, ValueError) as exc:
445 print(f"Error: {exc}", file=sys.stderr)
446 sys.exit(1)
447 resolved_layer = layer if layer is not None else _layer_from_filename_cli(sae_path)
448 if resolved_layer is None:
449 print("Error: could not infer SAE layer. Pass --layer <n>.", file=sys.stderr)
450 sys.exit(1)
451 binding = activate_user_sae(
452 model_id=locked,
453 layer=resolved_layer,
454 path=sae_path,
455 name=user_name,
456 )
457 print(f"Active user SAE: {user_name} layer {resolved_layer}")
458 print(f" model: {binding['model_id']}")
459 print(f" path: {binding['path']}")
460 _warm_daemon_sae(binding["model_id"], resolved_layer)
461 return
462
463 if path_arg:
464 from aquin.compute.user_sae import activate_user_sae, resolve_path_sae
465
466 try:
467 sae_path, resolved_model, resolved_layer = resolve_path_sae(
468 path_arg,
469 model_id=model_id,
470 layer=layer,
471 )
472 except (FileNotFoundError, ValueError) as exc:
473 print(f"Error: {exc}", file=sys.stderr)
474 sys.exit(1)
475 binding = activate_user_sae(
476 model_id=resolved_model,
477 layer=resolved_layer,
478 path=sae_path,
479 name=sae_path.parent.name,
480 )
481 print(f"Active user SAE from {sae_path}")
482 print(f" model: {binding['model_id']} layer: {resolved_layer}")
483 _warm_daemon_sae(binding["model_id"], resolved_layer)
484 return
485
486 if not positional:
488 sys.exit(1)
489
490 import re
491
492 from .saes import pull_sae
493
494 sae_id = positional[0]
495 if not explicit_output:
496 default_out = _default_sae_load_output(sae_id)
497 if default_out is not None:
498 output = str(default_out)
499
500 m = re.match(r"^(.+)-l(\d+)$", sae_id)
501 if m:
502 model_slug, layer_s = m.group(1), int(m.group(2))
503 resolved_catalog: str | None = None
504 try:
505 from aquin.compute.model_loader import (
506 resolve_model_id,
507 resolve_sae_path,
508 )
509
510 resolved_catalog = resolve_model_id(model_slug)
511 except ValueError:
512 resolved_catalog = None
513
514 if resolved_catalog:
515 from aquin.compute.model_loader import resolve_sae_path
516 from aquin.compute.torch_io import is_valid_sae_checkpoint_path
517 from aquin.compute.user_sae import clear_active_binding
518
519 cfg_layer = layer_s
520 sae_path = resolve_sae_path(resolved_catalog, cfg_layer)
521 if sae_path.is_file() and not force:
522 if is_valid_sae_checkpoint_path(sae_path):
523 clear_active_binding()
524 print(f"SAE already on disk: {sae_path}")
525 _warm_daemon_sae(resolved_catalog, cfg_layer)
526 return
527 print(f"Removing invalid catalog SAE cache: {sae_path}", file=sys.stderr)
528 sae_path.unlink(missing_ok=True)
529 norm_stale = sae_path.parent / f"norm_layer{cfg_layer}.pt"
530 if norm_stale.is_file():
531 norm_stale.unlink(missing_ok=True)
532 elif sae_path.is_file() and force:
533 print(f"Re-downloading catalog SAE (--force): {sae_path}", file=sys.stderr)
534 sae_path.unlink(missing_ok=True)
535 norm_stale = sae_path.parent / f"norm_layer{cfg_layer}.pt"
536 if norm_stale.is_file():
537 norm_stale.unlink(missing_ok=True)
538 api_key = explicit_key or _get_api_key()
539 print(f"Pulling SAE {resolved_catalog}-l{cfg_layer} from Aquin catalog...")
540 pull_sae(f"{resolved_catalog}-l{cfg_layer}", sae_path, api_key)
541 clear_active_binding()
542 _warm_daemon_sae(resolved_catalog, cfg_layer)
543 return
544
545 api_key = explicit_key or _get_api_key()
546 print(f"Loading SAE {sae_id}...")
547 pull_sae(sae_id, output, api_key)
548
549
550
551def _require_loaded_model_id() -> str:
552 """Return the loaded model id, or exit."""
553 from .compute.model_loader import get_active_model_id, resolve_model_id
554
555 active = (get_active_model_id() or "").strip()
556 if not active:
557 print("Error: no model loaded.")
558 print(" Run: aquin load model <model-id>")
559 sys.exit(1)
560 try:
561 return resolve_model_id(active)
562 except ValueError:
563 return active
564
565
566def _require_locked_model_id() -> str:
567 """Alias — session lock removed; uses loaded model."""
569
571def _assert_no_model_override(raw_args: list[str]) -> None:
572 i = 0
573 while i < len(raw_args):
574 if raw_args[i] == "--model" and i + 1 < len(raw_args):
575 print("Error: --model is not supported here.")
576 print(" Run: aquin load model <id>")
577 sys.exit(1)
578 i += 1
579
580
581def _build_tool_ctx(model_id: str | None = None) -> dict:
582 """Local tool context for GPU dispatch + command tracking."""
583 from .engine.main import _load_state
584 from .compute.model_loader import get_active_model_id, resolve_model_id
586 state = _load_state()
587 try:
588 from aquin.auth_config import resolve_api_key
589 api_key = state.get("api_key") or resolve_api_key(allow_missing=True)
590 except Exception:
591 api_key = state.get("api_key") or ""
592 base_url = (state.get("base_url") or os.environ.get("AQUIN_BASE_URL", _BASE_URL)).rstrip("/")
593 active = model_id or get_active_model_id() or ""
594 if active:
595 try:
596 active = resolve_model_id(active)
597 except ValueError:
598 pass
599 from aquin.engine.session_memory_store import load_local_memory
600 local_mem = load_local_memory("local")
601 return {
602 "session_id": "",
603 "api_key": api_key,
604 "base_url": base_url,
605 "state": _inject_session_mode({
606 "activeModelId": active,
607 "memory": local_mem,
608 }),
609 }
610
611
612def cmd_trace(args: list[str]) -> None:
613 """aquin trace --prompt <text> --layer <n> [--check] [--umap]"""
614 from aquin.cli_flags import reject_legacy_output_flags
615
616 if not args or args[0] in ("-h", "--help", "help") or "-h" in args or "--help" in args:
617 print("Full attribution for one prompt: generate, SAE features, causal trace, logit lens.")
618 print("")
619 print("Usage: aquin trace --prompt <text> --layer <n> [--check] [--umap]")
620 print("")
621 print(" --prompt Input prompt (model completes from here).")
622 print(" --layer SAE layer with a downloaded checkpoint.")
623 print(" --check Save trace-check.json and trace-check.png in cwd.")
624 print(" --umap Load SAE UMAP projection after the run (web explorer).")
625 print("")
626 print("Docs: https://aquin.app/docs/inspection-sae/llm")
627 return
628
629 reject_legacy_output_flags(args)
630 print("[trace] starting...", flush=True)
631 _guard_llm_only_cmd("trace")
633 tool_args = _parse_tool_flags(args)
634 prompt = tool_args.get("prompt")
635 layer_raw = tool_args.get("layer")
636 layer: int | None = int(layer_raw) if layer_raw is not None else None
637 do_check = bool(tool_args.get("check")) or "--check" in args
638 want_umap = _pop_umap_flag(tool_args, args)
639
640 if not prompt:
641 print("Usage: aquin trace --prompt <text> --layer <n> [--check] [--umap]")
642 sys.exit(1)
643 if layer is None:
644 from aquin.compute.model_loader import format_sae_layer_choice_message, resolve_model_id
645
647 try:
648 mid = resolve_model_id(mid)
649 except ValueError as e:
650 print(f"Error: {e}"); sys.exit(1)
651 print(
652 format_sae_layer_choice_message(
653 mid,
654 command="trace",
655 example_suffix=f'--prompt "{str(prompt)[:60]}"',
656 ),
657 file=sys.stderr,
658 )
659 sys.exit(1)
660
661 from .compute.model_loader import resolve_model_id, ComputeNotAvailableError, require_sae_layer
662 from .compute.loader_shim import apply as _shim_apply
663 from .engine.sync_dispatch import run_dispatch
664
666 try:
667 mid = resolve_model_id(mid)
668 except ValueError as e:
669 print(f"Error: {e}"); sys.exit(1)
670
671 try:
672 layer = require_sae_layer(
673 mid,
674 layer,
675 command="trace",
676 example_suffix=f'--prompt "{str(prompt)[:60]}"',
677 )
678 except ValueError as e:
679 print(str(e), file=sys.stderr)
680 sys.exit(1)
681
682 _shim_apply()
683 ctx = _build_tool_ctx()
684 ctx["state"]["activeModelId"] = mid
685
686 try:
687 print(f"[trace] running full inspection (sae layer {layer})...")
688 result = run_dispatch(
689 "run_full_inspection",
690 {"prompt": prompt, "layer": layer},
691 ctx,
692 command="trace",
693 ensure_model=mid,
694 )
695 if isinstance(result, dict) and result.get("error"):
696 print(result["error"]); sys.exit(1)
697
698 content = result.get("content", {}) if isinstance(result, dict) else {}
699 card_data = (result.get("card") or {}).get("data", {}) if isinstance(result, dict) else {}
700 response = content.get("response") or card_data.get("response", "")
701 top_features = content.get("top_features") or card_data.get("topFeatures", [])
702 prompt_tokens = card_data.get("promptTokens", [])
703 response_tokens = card_data.get("responseTokens", [])
704 attribution = card_data.get("attribution", [])
705 sae_layer = content.get("sae_layer") or card_data.get("saeLayer", "?")
706 if sae_layer != layer:
707 print(
708 f"Error: trace used SAE layer {sae_layer} but --layer {layer} was requested.",
709 file=sys.stderr,
710 )
711 sys.exit(1)
712 logit_lens = card_data.get("logitLensResults", [])
713
714 print(f"[trace] response: {response!r}\n")
715 if prompt_tokens:
716 print(f"prompt tokens : {prompt_tokens}")
717 print(f"response tokens: {response_tokens}")
718 print(f"sae layer : {sae_layer}")
719 print(f"\ntop response features ({len(top_features)}):")
720 for f in top_features[:10]:
721 ref = f.get("feature_ref") or f["feature_idx"]
722 print(f" [{ref}] act={f['activation']:.3f} token={f.get('token', '')!r}")
723
724 if attribution:
725 print(f"\nattribution ({len(attribution)} response tokens with driven features):")
726 for a in attribution[:5]:
727 driven = ", ".join(
728 f"{d.get('feature_ref') or d['feature_idx']}({d['activation']:.2f})"
729 for d in a.get("driven_by_features", [])[:3]
730 )
731 print(f" {a.get('response_token', ''):>12} <- {driven}")
732
733 if logit_lens:
734 print(f"\nlogit lens ({len(logit_lens)} layers), last 3 layers:")
735 for row in logit_lens[-3:]:
736 tops = " ".join(f"{t['token']!r}({t['prob']:.3f})" for t in row.get("top_tokens", [])[:3])
737 print(f" layer {row['layer']:>2}: {tops}")
738
739 if do_check:
740 from .trace_check import write_trace_check
741 try:
742 json_path, png_path = write_trace_check(
743 result, tool_name="run_full_inspection", cwd=os.getcwd(),
744 )
745 print(f"Saved {json_path}")
746 print(f"Saved {png_path}")
747 except Exception as exc:
748 from .user_errors import die
749 die(message="Could not save check output files.", exc=exc, label="trace --check")
750
751 if want_umap:
753 ctx,
754 result=result,
755 tool_args={"layer": layer},
756 ensure_model=mid,
757 layer=layer,
758 )
759
760 except ComputeNotAvailableError as e:
761 from .user_errors import die
762 die(str(e) or "Compute is not available on this machine.")
763 except Exception as e:
764 from .user_errors import die
765 die(exc=e)
766
767
768def cmd_feature_logits(args: list[str]) -> None:
769 """aquin feature logit --feature <idx> [--layer <n>] [--topk <n>] [--check] [--umap]"""
770 if not args or args[0] in ("-h", "--help", "help") or "-h" in args or "--help" in args:
771 print("Usage: aquin feature logit --feature <idx> [--layer <n>] [--topk <n>] [--check] [--umap]")
772 return
773
774 _guard_llm_only_cmd("feature logit")
776 feature_idx = None
777 layer = None
778 top_k = 10
779 do_check = False
780 want_umap = False
781 i = 0
782 while i < len(args):
783 if args[i] == "--feature" and i + 1 < len(args):
784 feature_idx = int(args[i + 1]); i += 2
785 elif args[i] == "--layer" and i + 1 < len(args):
786 layer = int(args[i + 1]); i += 2
787 elif args[i] in ("--topk", "--top-k") and i + 1 < len(args):
788 top_k = int(args[i + 1]); i += 2
789 elif args[i] == "--check":
790 do_check = True; i += 1
791 elif args[i] == "--umap":
792 want_umap = True; i += 1
793 else:
794 i += 1
795
796 if feature_idx is None:
797 print("Usage: aquin feature logit --feature <idx> [--layer <n>] [--topk <n>] [--check] [--umap]")
798 sys.exit(1)
799
800 from .compute.model_loader import resolve_model_id, ComputeNotAvailableError
801 from .engine.sync_dispatch import run_dispatch
802
804 ctx = _build_tool_ctx()
805 try:
806 mid = resolve_model_id(mid)
807 except ValueError as e:
808 print(f"Error: {e}"); sys.exit(1)
809 ctx["state"]["activeModelId"] = mid
810
811 tool_args: dict[str, Any] = {"feature_idx": feature_idx, "top_k": top_k}
812 if layer is not None:
813 tool_args["layer"] = layer
814
815 try:
816 result = run_dispatch("get_feature_logits", tool_args, ctx, command="feature logit", ensure_model=mid)
817 if isinstance(result, dict) and result.get("error"):
818 print(f"Error: {result['error']}"); sys.exit(1)
819 data = result.get("content", result) if isinstance(result, dict) else result
820 ref = data.get("feature_ref") or feature_idx
821 print(f"feature {ref} (model={mid} layer={layer or 'default'})")
822 print(f"\ntop {top_k} boosted tokens:")
823 for t in data.get("top", []):
824 print(f" {t['token']!r:<20} logit={t['logit']:+.4f}")
825 print(f"\ntop {top_k} suppressed tokens:")
826 for t in data.get("bottom", []):
827 print(f" {t['token']!r:<20} logit={t['logit']:+.4f}")
828
829 if do_check:
830 from .feature_logits_check import write_feature_logits_check
831 try:
832 json_path, png_path = write_feature_logits_check(
833 result, tool_name="get_feature_logits", cwd=os.getcwd(),
834 )
835 print(f"Saved {json_path}")
836 print(f"Saved {png_path}")
837 except Exception as exc:
838 from .user_errors import die
839 die(message="Could not save check output files.", exc=exc, label="feature logit --check")
840
841 if want_umap:
843 ctx,
844 result=result,
845 tool_args=tool_args,
846 ensure_model=mid,
847 layer=layer,
848 feature_idxs=[feature_idx],
849 )
850 except ComputeNotAvailableError as e:
851 from .user_errors import die
852 die(str(e) or "Compute is not available on this machine.")
853 except Exception as e:
854 from .user_errors import die
855 die(exc=e)
856
857
858def cmd_feature_neighbors(args: list[str]) -> None:
859 """aquin feature neighbor --feature <idx> [--layer <n>] [--topk <n>] [--check] [--umap]"""
860 if not args or args[0] in ("-h", "--help", "help") or "-h" in args or "--help" in args:
861 print("Usage: aquin feature neighbor --feature <idx> [--layer <n>] [--topk <n>] [--check] [--umap]")
862 return
863
864 _guard_llm_only_cmd("feature neighbor")
866 feature_idx = None
867 layer = None
868 top_k = 8
869 do_check = False
870 want_umap = False
871 i = 0
872 while i < len(args):
873 if args[i] == "--feature" and i + 1 < len(args):
874 feature_idx = int(args[i + 1]); i += 2
875 elif args[i] == "--layer" and i + 1 < len(args):
876 layer = int(args[i + 1]); i += 2
877 elif args[i] in ("--topk", "--top-k") and i + 1 < len(args):
878 top_k = int(args[i + 1]); i += 2
879 elif args[i] == "--check":
880 do_check = True; i += 1
881 elif args[i] == "--umap":
882 want_umap = True; i += 1
883 else:
884 i += 1
885
886 if feature_idx is None:
887 print("Usage: aquin feature neighbor --feature <idx> [--layer <n>] [--topk <n>] [--check] [--umap]")
888 sys.exit(1)
889
890 from .compute.model_loader import resolve_model_id, ComputeNotAvailableError
891 from .engine.sync_dispatch import run_dispatch
892
894 ctx = _build_tool_ctx()
895 try:
896 mid = resolve_model_id(mid)
897 except ValueError as e:
898 print(f"Error: {e}"); sys.exit(1)
899 ctx["state"]["activeModelId"] = mid
900
901 tool_args: dict[str, Any] = {"feature_idx": feature_idx, "top_k": top_k}
902 if layer is not None:
903 tool_args["layer"] = layer
904
905 try:
906 result = run_dispatch("get_feature_neighbors", tool_args, ctx, command="feature neighbor", ensure_model=mid)
907 if isinstance(result, dict) and result.get("error"):
908 print(f"Error: {result['error']}"); sys.exit(1)
909 data = result.get("content", result) if isinstance(result, dict) else result
910 ref = data.get("feature_ref") or feature_idx
911 print(f"feature {ref} (model={mid} layer={layer or 'default'})")
912 print(f"\n{top_k} nearest neighbors:")
913 for n in data.get("neighbors", []):
914 nref = n.get("feature_ref") or n["feature_idx"]
915 print(f" [{nref}] similarity={n['similarity']:.4f}")
916
917 if do_check:
918 from .feature_neighbors_check import write_feature_neighbors_check
919 try:
920 json_path, png_path = write_feature_neighbors_check(
921 result, tool_name="get_feature_neighbors", cwd=os.getcwd(),
922 )
923 print(f"Saved {json_path}")
924 print(f"Saved {png_path}")
925 except Exception as exc:
926 from .user_errors import die
927 die(message="Could not save check output files.", exc=exc, label="feature neighbor --check")
928
929 if want_umap:
931 ctx,
932 result=result,
933 tool_args=tool_args,
934 ensure_model=mid,
935 layer=layer,
936 feature_idxs=[feature_idx],
937 )
938 except ComputeNotAvailableError as e:
939 from .user_errors import die
940 die(str(e) or "Compute is not available on this machine.")
941 except Exception as e:
942 from .user_errors import die
943 die(exc=e)
944
945
946def _push_log(line: str) -> None:
947 """Legacy no-op — web session sync removed."""
948 _ = line
949
951def load_model_local(model_id: str, *, as_json: bool = False) -> str:
952 """Load a model into VRAM. Returns the resolved model id."""
953 from .compute.model_loader import ComputeNotAvailableError
954 from .compute.model_runtime import load_weights, resolve_kind, vram_line
955 from .engine.main import _load_state, _save_state
956 from .load_model_display import LoadProgress, load_model_payload
957 from .session_mode import mode_for_model_id, mode_label
958
959 try:
960 mode = mode_for_model_id(model_id)
961 except ValueError as e:
962 if as_json:
963 print(json.dumps(load_model_payload(
964 model=model_id, mode="?", daemon=False, ok=False, error=str(e),
965 ), ensure_ascii=False))
966 else:
967 print(f"Error: {e}")
968 sys.exit(1)
969
970 label = mode_label(mode)
971 progress = None if as_json else LoadProgress(f"Loading {model_id} · {label}")
972 _push_log(f"Loading {model_id} ({label})...")
973
974 try:
975 resolved, _kind = resolve_kind(model_id)
976 loaded_via_daemon = False
977 vram: str | None = None
978 elapsed: float | None = None
979
980 try:
981 from .engine import model_daemon
982
983 if model_daemon.ensure_running():
984 if progress:
985 progress.phase("engine ready")
986 res = model_daemon.switch_model(resolved, on_tick=progress)
987 if res and res.get("ok"):
988 loaded_via_daemon = True
989 vram = res.get("vram")
990 elapsed = progress.elapsed() if progress else None
991 elif progress and res and res.get("error"):
992 progress.phase(f"engine failed · {res['error']}")
993 except Exception:
994 loaded_via_daemon = False
995
996 if not loaded_via_daemon:
997 if progress:
998 progress.phase("loading weights in-process")
999
1000 def _on_progress(msg: str, _elapsed: float) -> None:
1001 if progress:
1002 progress.tick(msg)
1003
1004 resolved = load_weights(model_id, progress=_on_progress if progress else None)
1005 elapsed = progress.elapsed() if progress else None
1006 vram = vram_line()
1007
1008 model_id = resolved
1009 if as_json:
1010 print(json.dumps(load_model_payload(
1011 model=model_id,
1012 mode=mode,
1013 daemon=loaded_via_daemon,
1014 ok=True,
1015 elapsed_s=elapsed,
1016 vram=vram,
1017 ), ensure_ascii=False))
1018 else:
1019 tail = f"Model ready · {model_id}"
1020 if vram:
1021 tail = f"{tail} · {vram}"
1022 if progress:
1023 progress.done(tail)
1024 if loaded_via_daemon:
1025 progress.done("resident in background engine", indent=1)
1026 else:
1027 print(tail, flush=True)
1028 _push_log(f"Model ready: {model_id} ({label} tools active)")
1029 state = _load_state()
1030 _save_state({**state, "session_mode": mode, "active_model_id": model_id})
1031 return model_id
1032 except ComputeNotAvailableError as e:
1033 err = str(e)
1034 if as_json:
1035 print(json.dumps(load_model_payload(
1036 model=model_id, mode=mode, daemon=False, ok=False, error=err,
1037 ), ensure_ascii=False))
1038 else:
1039 print(f"Error: {err}")
1040 _push_log(f"Error: {err}")
1041 sys.exit(1)
1042 except ValueError as e:
1043 err = str(e)
1044 if as_json:
1045 print(json.dumps(load_model_payload(
1046 model=model_id, mode=mode, daemon=False, ok=False, error=err,
1047 ), ensure_ascii=False))
1048 else:
1049 print(f"Error: {err}")
1050 _push_log(f"Error: {err}")
1051 sys.exit(1)
1052 except Exception as e:
1053 from .user_errors import friendly_message
1054
1055 err = friendly_message(e)
1056 if as_json:
1057 print(json.dumps(load_model_payload(
1058 model=model_id, mode=mode, daemon=False, ok=False, error=err,
1059 ), ensure_ascii=False))
1060 else:
1061 print(f"Error: {err}", file=sys.stderr)
1062 _push_log(f"Error: {err}")
1063 sys.exit(1)
1064
1065
1066def cmd_unload(args: list[str]) -> None:
1067 """aquin unload [--stop] [--json] — free the resident model's VRAM."""
1068 if args and args[0] in ("--help", "-h", "help"):
1069 print("Usage: aquin unload [--stop] [--json]")
1070 print("")
1071 print(" Free the loaded model's VRAM (engine stays up by default).")
1072 print(" --stop Also stop the background engine")
1073 print(" --json Machine-readable result")
1074 return
1075
1076 as_json = "--json" in args
1077 stop_daemon = "--stop" in args or "--kill" in args
1078 from .compute.model_runtime import unload_weights
1079 from .load_model_display import LoadProgress, unload_payload
1080
1081 progress = None if as_json else LoadProgress("Unloading model")
1082 freed = False
1083 daemon_running = False
1084 message = "Cleared active model."
1085 try:
1086 from .engine import model_daemon
1087
1088 daemon_running = model_daemon.is_running()
1089 if daemon_running:
1090 if stop_daemon:
1091 if progress:
1092 progress.phase("stopping engine")
1093 freed = model_daemon.stop()
1094 message = "Engine stopped · VRAM released"
1095 else:
1096 if progress:
1097 progress.phase("releasing daemon VRAM")
1098 freed = model_daemon.unload()
1099 message = "Model unloaded · engine still running"
1100 else:
1101 message = "No background engine · cleared active model"
1102 except Exception as exc:
1103 if as_json:
1104 print(json.dumps(unload_payload(
1105 ok=False,
1106 daemon_running=False,
1107 stopped=stop_daemon,
1108 message=str(exc),
1109 ), ensure_ascii=False))
1110 sys.exit(1)
1111 print(f"Could not reach background engine: {exc}", file=sys.stderr)
1112 message = f"Engine unreachable · {exc}"
1113
1114 try:
1115 if progress:
1116 progress.phase("releasing local VRAM")
1117 unload_weights()
1118 except Exception:
1119 pass
1120
1121 if as_json:
1122 print(json.dumps(unload_payload(
1123 ok=True,
1124 daemon_running=daemon_running and not stop_daemon,
1125 stopped=stop_daemon,
1126 message=message,
1127 ), ensure_ascii=False))
1128 return
1129
1130 if progress:
1131 progress.done(message)
1132 else:
1133 print(message, flush=True)
1134
1135
1136def cmd_load(args: list[str]) -> None:
1137 """aquin load model <model-id> | sae <model-l{n}>"""
1138 if args and args[0] == "sae":
1139 _cmd_load_sae(args[1:])
1140 return
1141
1142 rest = list(args)
1143 if rest and rest[0] == "model":
1144 rest = rest[1:]
1145
1146 if "--help" in rest or "-h" in rest or (rest and rest[0] == "help") or not rest:
1147 print("Usage: aquin load model <model-id> [--json]")
1148 print(" aquin load <model-id> # legacy shorthand")
1149 print(" aquin load sae <model-l{n}> | --user <name> | --path <file>")
1150 print("")
1151 print(" --json Machine-readable result")
1152 print("")
1153 print(" Env: AQUIN_DEVICE=cuda|mps|cpu|auto (default: cuda → mps → cpu)")
1154 print(" AQUIN_ALLOW_CPU=1 slow CPU smoke tests")
1155 print("")
1156 print(" Pass a HuggingFace model id (e.g. meta-llama/Llama-3.2-1B-Instruct)")
1157 if not rest or "--help" in rest or "-h" in rest or rest[0] == "help":
1158 return
1159 sys.exit(1)
1160
1161 if rest[0] == "pull":
1162 print("Use: aquin load sae", file=sys.stderr)
1163 sys.exit(1)
1164
1165 as_json = "--json" in rest
1166 rest = [a for a in rest if a != "--json"]
1167
1168 model_id = None
1169 i = 0
1170 while i < len(rest):
1171 if rest[i] == "--model" and i + 1 < len(rest):
1172 model_id = rest[i + 1]
1173 i += 2
1174 elif not rest[i].startswith("-") and model_id is None:
1175 model_id = rest[i]
1176 i += 1
1177 else:
1178 i += 1
1179
1180 if not model_id:
1181 print("Usage: aquin load model <model-id> [--json]")
1182 print(" Example: aquin load model meta-llama/Llama-3.2-1B-Instruct")
1183 sys.exit(1)
1184
1185 load_model_local(model_id, as_json=as_json)
1186
1187
1188def cmd_chat(args: list[str]) -> None:
1189 """aquin chat — Ink (via npm), Textual detect, or Rich fallback."""
1190 if "--ink-bridge" in args:
1191 from aquin.tui.ink_bridge import run_ink_chat_bridge
1193 run_ink_chat_bridge()
1194 return
1195
1196 if args and args[0] in ("--help", "-h", "help"):
1197 print("Usage: aquin chat [--plain]")
1198 print("")
1199 print(" Multi-turn agent chat (requires login + loaded model).")
1200 print(" --plain Force Rich REPL (skip Ink / Textual)")
1201 return
1202
1203 from aquin.tui.detect import detect_tui_capability
1204 from aquin.tui.fallback import choose_chat_ui, run_chat_fallback
1205
1206 cap = detect_tui_capability(argv=["aquin", "chat", *args])
1207 if choose_chat_ui(cap) == "tui":
1208 # Step 3: launch Textual here. Until then, Rich keeps chat working.
1209 run_chat_fallback(args, capability=cap)
1210 return
1211 run_chat_fallback(args, capability=cap)
1212
1213
1214def cmd_prompt(args: list[str]) -> None:
1215 """aquin prompt <text> — quick try-out generation against the loaded model."""
1216 if not args or args[0] in ("--help", "-h", "help"):
1217 print("Usage: aquin prompt <text>")
1218 print(" aquin prompting <text>")
1219 print("")
1220 print(" Generate a response from the loaded LLM (try the model).")
1221 print("")
1222 print(" --model <id> Model to use (default: last aquin load)")
1223 print(" --max-tokens <n> Max new tokens (default: 200)")
1224 print(" --temperature <t> Sampling temperature (default: 0.7)")
1225 print(" --json Machine-readable result")
1226 print("")
1227 print(" Examples:")
1228 print(' aquin prompt "Why is the sky blue?"')
1229 print(" aquin prompting hello")
1230 print(" aquin prompt --model gpt2-small Tell me a joke")
1231 return
1232
1233 as_json = "--json" in args
1234 model_flag: str | None = None
1235 max_tokens = 200
1236 temperature = 0.7
1237 text_parts: list[str] = []
1238 i = 0
1239 while i < len(args):
1240 a = args[i]
1241 if a == "--json":
1242 i += 1
1243 continue
1244 if a == "--model" and i + 1 < len(args):
1245 model_flag = args[i + 1]
1246 i += 2
1247 continue
1248 if a in ("--max-tokens", "--max_new_tokens") and i + 1 < len(args):
1249 max_tokens = int(args[i + 1])
1250 i += 2
1251 continue
1252 if a == "--temperature" and i + 1 < len(args):
1253 temperature = float(args[i + 1])
1254 i += 2
1255 continue
1256 if a.startswith("-"):
1257 print(f"Unknown flag: {a}", file=sys.stderr)
1258 sys.exit(1)
1259 text_parts.append(a)
1260 i += 1
1261
1262 prompt_text = " ".join(text_parts).strip()
1263 if not prompt_text:
1264 print("Usage: aquin prompt <text>")
1265 sys.exit(1)
1266
1267 from .compute.model_loader import get_active_model_id
1268 from .prompt_display import print_prompt_result, prompt_payload
1269
1270 model_id = (model_flag or get_active_model_id() or "").strip()
1271 if not model_id:
1272 err = "No model loaded. Run: aquin load model <id>"
1273 if as_json:
1274 print(json.dumps(prompt_payload(
1275 model="", prompt=prompt_text, response="", ok=False, error=err,
1276 ), ensure_ascii=False))
1277 else:
1278 print(f"Error: {err}", file=sys.stderr)
1279 sys.exit(1)
1280
1281 used_daemon = False
1282 response = ""
1283 try:
1284 from .engine import model_daemon
1285
1286 if model_daemon.is_running():
1287 res = model_daemon.prompt(
1288 prompt_text,
1289 model_id=model_id,
1290 max_new_tokens=max_tokens,
1291 temperature=temperature,
1292 )
1293 if res and res.get("ok"):
1294 response = str(res.get("response") or "")
1295 model_id = str(res.get("model_id") or model_id)
1296 used_daemon = True
1297 elif res and res.get("error"):
1298 err = str(res["error"])
1299 if as_json:
1300 print(json.dumps(prompt_payload(
1301 model=model_id, prompt=prompt_text, response="", ok=False, error=err,
1302 ), ensure_ascii=False))
1303 else:
1304 print(f"Error: {err}", file=sys.stderr)
1305 sys.exit(1)
1306 except Exception:
1307 used_daemon = False
1308
1309 if not used_daemon:
1310 try:
1311 from .compute.causal_trace import run_chat
1312
1313 if not as_json:
1314 from .load_model_display import print_phase
1315 print_phase(f"Generating · {model_id}")
1316 response = run_chat(
1317 prompt_text,
1318 model_id=model_id,
1319 max_new_tokens=max_tokens,
1320 temperature=temperature,
1321 )
1322 except Exception as exc:
1323 from .user_errors import friendly_message
1324
1325 err = friendly_message(exc)
1326 if as_json:
1327 print(json.dumps(prompt_payload(
1328 model=model_id, prompt=prompt_text, response="", ok=False, error=err,
1329 ), ensure_ascii=False))
1330 else:
1331 print(f"Error: {err}", file=sys.stderr)
1332 sys.exit(1)
1333
1334 if as_json:
1335 print(json.dumps(prompt_payload(
1336 model=model_id,
1337 prompt=prompt_text,
1338 response=response,
1339 ok=True,
1340 daemon=used_daemon,
1341 ), ensure_ascii=False))
1342 return
1343
1344 print_prompt_result(model=model_id, prompt=prompt_text, response=response)
1345
1346
1347# ── Raw CLI tool dispatch (Step 57) ──────────────────────────────────────────
1348# Verb maps live in session_mode.py.
1349
1350from .session_mode import (
1351 LLM_CLI_VERBS,
1352 SHARED_CLI_VERBS,
1353 cli_verbs_for_mode,
1354 resolve_legacy_verb,
1355 verb_known,
1356)
1357
1358_CLI_TOOL_MAP: dict[str, str] = cli_verbs_for_mode(None)
1359
1360
1361def _connected_state() -> dict:
1362 from .engine.main import _load_state
1363 return _load_state()
1364
1366def _has_loaded_model() -> bool:
1367 from aquin.compute.model_loader import get_active_model_id
1368 return bool(get_active_model_id())
1369
1371def _session_mode() -> str | None:
1372 """Return session mode from the loaded model."""
1373 from .session_mode import mode_for_active_model
1374 if not _has_loaded_model():
1375 return None
1376 return mode_for_active_model()
1377
1378
1379def _active_cli_tool_map() -> dict[str, str]:
1380 from .session_mode import SHARED_TOOLS, tools_for_mode
1381
1382 mode = _session_mode()
1383 m = cli_verbs_for_mode(mode)
1384 if mode is not None:
1385 allowed = tools_for_mode(mode)
1386 return {verb: tool for verb, tool in m.items() if tool in allowed}
1387 if _has_loaded_model():
1388 return {verb: tool for verb, tool in m.items() if tool in SHARED_TOOLS}
1389 return m
1390
1391
1392def _inject_session_mode(state: dict) -> dict:
1393 from .session_mode import normalize_mode
1394
1395 mode = _session_mode()
1396 if mode is not None:
1397 state = {**state, "session_mode": mode}
1398 return state
1399
1400
1401def _guard_llm_only_cmd(cmd: str) -> None:
1402 mode = _session_mode()
1403 if mode is None:
1404 print(f"[{cmd}] requires a loaded model.")
1405 print(" aquin load model pythia-70m")
1406 sys.exit(1)
1407
1408
1409def _model_switch_hint(mode: str | None) -> None:
1410 print(" aquin load model pythia-70m")
1411
1412
1413def _guard_tool_verb(verb: str) -> None:
1414 from .session_mode import mode_label
1415
1416 verb = resolve_legacy_verb(verb)
1418 if verb in active:
1419 return
1420 mode = _session_mode()
1421 if mode is None and _has_loaded_model() is False:
1422 print(f"[{verb}] no model loaded — run:")
1423 print(" aquin load model llama-3.2-1b")
1424 sys.exit(1)
1425 if mode is not None and verb_known(verb):
1426 print(f"[{verb}] not available for the loaded {mode_label(mode)} model.")
1427 _model_switch_hint(mode)
1428 sys.exit(1)
1429 print(f"Unknown command: {verb}")
1430 print("Run aquin help to see available commands.")
1431 sys.exit(1)
1432
1433
1434def _coerce_flag_value(val: str) -> Any:
1435 """Parse CLI flag values: JSON arrays/objects, bools, numbers, or raw strings."""
1436 import json as _json
1437
1438 if not isinstance(val, str):
1439 return val
1440 stripped = val.strip()
1441 if stripped.startswith(("[", "{")):
1442 try:
1443 return _json.loads(stripped)
1444 except _json.JSONDecodeError:
1445 pass
1446 if stripped.lower() == "true":
1447 return True
1448 if stripped.lower() == "false":
1449 return False
1450 try:
1451 return int(stripped)
1452 except ValueError:
1453 pass
1454 try:
1455 return float(stripped)
1456 except ValueError:
1457 pass
1458 return val
1459
1460
1461def _normalize_topk_flags(tool_args: dict[str, Any]) -> None:
1462 if tool_args.get("topk") is not None and tool_args.get("top_k") is None:
1463 tool_args["top_k"] = tool_args.pop("topk")
1464 if tool_args.get("topk_retrieval") is not None and tool_args.get("top_k_retrieval") is None:
1465 tool_args["top_k_retrieval"] = tool_args.pop("topk_retrieval")
1466
1467
1468def _parse_tool_flags(raw: list[str]) -> dict[str, Any]:
1469 """Parse --key value flags into an args dict."""
1470 from aquin.cli_flags import reject_legacy_output_flags
1471
1472 reject_legacy_output_flags(raw)
1473 tool_args: dict[str, Any] = {}
1474 i = 0
1475 while i < len(raw):
1476 a = raw[i]
1477 if a.startswith("--"):
1478 if "=" in a:
1479 key, _, val = a[2:].partition("=")
1480 tool_args[key.replace("-", "_")] = _coerce_flag_value(val)
1481 i += 1
1482 elif i + 1 < len(raw) and not raw[i + 1].startswith("--"):
1483 key = a[2:].replace("-", "_")
1484 tool_args[key] = _coerce_flag_value(raw[i + 1])
1485 i += 2
1486 else:
1487 tool_args[a[2:].replace("-", "_")] = True
1488 i += 1
1489 else:
1490 i += 1
1491 _normalize_topk_flags(tool_args)
1492 return tool_args
1493
1494
1495# Commands where --umap loads the SAE feature map after the primary result.
1496_UMAP_FOLLOWUP_VERBS: frozenset[str] = frozenset({
1497 "trace",
1498 "feature locate",
1499 "feature logit",
1500 "feature neighbor",
1501 "steer",
1502 "multi-steer",
1503 "benchmark",
1504 "sae-stats",
1505 "features",
1506 "contrastive",
1507 "interp",
1508 "browser",
1509 "graph",
1510 "circuit",
1511 "steer",
1512 "absorption",
1513 "polysemy",
1514 "faithfulness",
1515 "decomp",
1516})
1517
1518
1519def _pop_umap_flag(tool_args: dict[str, Any], raw_args: list[str] | None = None) -> bool:
1520 wanted = bool(tool_args.pop("umap", False))
1521 if raw_args and "--umap" in raw_args:
1522 wanted = True
1523 return wanted
1524
1525
1526def _coerce_feature_idx(value: Any) -> int | None:
1527 try:
1528 if value is None or isinstance(value, bool):
1529 return None
1530 return int(value)
1531 except (TypeError, ValueError):
1532 return None
1533
1534
1535def _feature_idxs_from_payload(result: Any, tool_args: dict[str, Any] | None = None) -> list[int]:
1536 """Collect feature indices from a tool payload / args for UMAP highlighting."""
1537 idxs: list[int] = []
1538 seen: set[int] = set()
1540 def _add(raw: Any) -> None:
1541 idx = _coerce_feature_idx(raw)
1542 if idx is None or idx in seen:
1543 return
1544 seen.add(idx)
1545 idxs.append(idx)
1546
1547 args = tool_args or {}
1548 for key in ("feature_idx", "feature", "target_feature_idx", "chosen_feature_idx"):
1549 _add(args.get(key))
1550
1551 if not isinstance(result, dict):
1552 return idxs
1553
1554 payload = result.get("content", result) if isinstance(result.get("content"), dict) else result
1555 if not isinstance(payload, dict):
1556 return idxs
1557
1558 for key in ("feature_idx", "feature", "target_feature_idx", "chosen_feature_idx"):
1559 _add(payload.get(key))
1560
1561 for key in ("top_features", "rankings", "neighbors", "features"):
1562 rows = payload.get(key)
1563 if not isinstance(rows, list):
1564 continue
1565 for row in rows:
1566 if isinstance(row, dict):
1567 _add(row.get("feature_idx") or row.get("feature") or row.get("idx"))
1568
1569 card = result.get("card") if isinstance(result.get("card"), dict) else None
1570 if card:
1571 data = card.get("data") if isinstance(card.get("data"), dict) else {}
1572 _add(data.get("featureIdx") or data.get("chosenFeatureIdx"))
1573 for key in ("topFeatures", "rankings", "neighbors", "features"):
1574 rows = data.get(key)
1575 if not isinstance(rows, list):
1576 continue
1577 for row in rows:
1578 if isinstance(row, dict):
1579 _add(row.get("feature_idx") or row.get("featureIdx") or row.get("feature"))
1580
1581 return idxs
1582
1583
1584def _layer_from_payload(result: Any, tool_args: dict[str, Any] | None = None) -> int | None:
1585 args = tool_args or {}
1586 for key in ("layer", "sae_layer"):
1587 idx = _coerce_feature_idx(args.get(key))
1588 if idx is not None:
1589 return idx
1590 if not isinstance(result, dict):
1591 return None
1592 payload = result.get("content", result) if isinstance(result.get("content"), dict) else result
1593 if isinstance(payload, dict):
1594 for key in ("layer", "sae_layer"):
1595 idx = _coerce_feature_idx(payload.get(key))
1596 if idx is not None:
1597 return idx
1598 card = result.get("card") if isinstance(result.get("card"), dict) else None
1599 if isinstance(card, dict):
1600 data = card.get("data") if isinstance(card.get("data"), dict) else {}
1601 for key in ("layer", "saeLayer", "sae_layer"):
1602 idx = _coerce_feature_idx(data.get(key))
1603 if idx is not None:
1604 return idx
1605 return None
1606
1607
1609 ctx: dict[str, Any],
1610 *,
1611 result: Any = None,
1612 tool_args: dict[str, Any] | None = None,
1613 ensure_model: str | None = None,
1614 layer: int | None = None,
1615 feature_idxs: list[int] | None = None,
1616) -> None:
1617 """After a feature-producing command, load UMAP and sync the explorer card."""
1618 from .cli_output import print_tool_result
1619 from .engine.sync_dispatch import run_dispatch
1620
1621 resolved_layer = layer if layer is not None else _layer_from_payload(result, tool_args)
1622 idxs = feature_idxs if feature_idxs is not None else _feature_idxs_from_payload(result, tool_args)
1623 umap_args: dict[str, Any] = {}
1624 if resolved_layer is not None:
1625 umap_args["layer"] = resolved_layer
1626 if ensure_model:
1627 umap_args["model_id"] = ensure_model
1628
1629 print("\n[--umap] loading UMAP projection...", flush=True)
1630 try:
1631 umap_result = run_dispatch(
1632 "ensure_umap_loaded",
1633 umap_args,
1634 ctx,
1635 command="umap",
1636 ensure_model=ensure_model,
1637 )
1638 except Exception as exc:
1639 print(f"[--umap] failed: {exc}", file=sys.stderr)
1640 return
1641
1642 print_tool_result("umap", umap_result, tool_name="ensure_umap_loaded")
1643 if isinstance(umap_result, dict) and umap_result.get("error"):
1644 return
1645 if idxs:
1646 shown = ", ".join(f"f{i}" for i in idxs[:8])
1647 more = f" (+{len(idxs) - 8} more)" if len(idxs) > 8 else ""
1648 print(f"[--umap] features to inspect: {shown}{more}")
1649
1650
1651def _cmd_simulation_catalog(verb: str, tool_name: str, extra_args: list[str]) -> None:
1652 """List/load/compare saved simulation runs — no GPU model required."""
1653 from .engine.sync_dispatch import dispatch_model_free
1654 from .engine.tools.registry import _load_stubs # noqa: ensure registered
1656 _load_stubs()
1657 tool_args = _parse_tool_flags(extra_args)
1658 ctx = {**_build_tool_ctx(), "cwd": os.getcwd()}
1659
1660 try:
1661 result = dispatch_model_free(tool_name, tool_args, ctx, command=verb)
1662 except Exception as exc:
1663 from .user_errors import die
1664 die(exc=exc, label=verb)
1665
1666 from .cli_output import print_tool_result
1667 print_tool_result(verb, result, tool_name=tool_name)
1668 if isinstance(result, dict) and result.get("error"):
1669 sys.exit(1)
1670
1671
1672def _require_attention_args(verb: str, tool_name: str, tool_args: dict[str, Any]) -> None:
1673 """`aquin check attention` must include --prompt."""
1674 if verb != "attention":
1675 return
1677 if tool_name == "run_attention_routing":
1678 prompt = tool_args.get("prompt") or tool_args.get("text")
1679 if not prompt or not str(prompt).strip():
1680 print("Error: --prompt is required.", file=sys.stderr)
1681 print("Usage: aquin check attention --prompt <string> [--check]", file=sys.stderr)
1682 sys.exit(1)
1683 tool_args["prompt"] = str(prompt).strip()
1684
1685
1686def _save_tool_check(verb: str, result: dict[str, Any], tool_name: str | None) -> None:
1687 """Write JSON + PNG for verbs that support --check."""
1688 if not isinstance(result, dict):
1689 return
1691 from .benchmark_check import has_benchmark_payload
1692 from .audit_check import has_audit_payload
1693 from .consistency_eval_check import has_consistency_eval_payload
1694 from .suppression_eval_check import has_suppression_eval_payload
1695 from .boundary_eval_check import has_boundary_eval_payload
1696 from .red_team_check import has_red_team_payload
1697 from .eval_check import has_eval_payload
1698
1699 if result.get("error"):
1700 if verb in ("benchmark", "benchmarks") and has_benchmark_payload(result):
1701 pass
1702 elif verb == "audit" and has_audit_payload(result):
1703 pass
1704 elif verb == "consistency-eval" and has_consistency_eval_payload(result):
1705 pass
1706 elif verb == "suppression-eval" and has_suppression_eval_payload(result):
1707 pass
1708 elif verb == "boundary-eval" and has_boundary_eval_payload(result):
1709 pass
1710 elif verb == "red-team" and has_red_team_payload(result):
1711 pass
1712 elif verb == "eval" and has_eval_payload(result):
1713 pass
1714 else:
1715 print(f"[{verb} --check] skipped save: {result['error']}", file=sys.stderr)
1716 return
1717
1718 writers: dict[str, tuple[str, str]] = {
1719 "attention": ("aquin.attention_check", "write_attention_check"),
1720 "layer-analysis": ("aquin.layer_analysis_check", "write_layer_analysis_check"),
1721 "perturbation": ("aquin.perturbation_check", "write_perturbation_check"),
1722 "check-weights": ("aquin.check_weights_check", "write_check_weights_check"),
1723 "benchmark": ("aquin.benchmark_check", "write_benchmark_check"),
1724 "benchmarks": ("aquin.benchmark_check", "write_benchmark_check"),
1725 "audit": ("aquin.audit_check", "write_audit_check"),
1726 "consistency-eval": ("aquin.consistency_eval_check", "write_consistency_eval_check"),
1727 "suppression-eval": ("aquin.suppression_eval_check", "write_suppression_eval_check"),
1728 "boundary-eval": ("aquin.boundary_eval_check", "write_boundary_eval_check"),
1729 "red-team": ("aquin.red_team_check", "write_red_team_check"),
1730 "eval": ("aquin.eval_check", "write_eval_check"),
1731 }
1732 spec = writers.get(verb)
1733 if not spec:
1734 return
1735
1736 import importlib
1737
1738 mod = importlib.import_module(spec[0])
1739 write_fn = getattr(mod, spec[1])
1740 try:
1741 json_path, png_path = write_fn(result, tool_name=tool_name, cwd=os.getcwd())
1742 print(f"Saved {json_path}")
1743 print(f"Saved {png_path}")
1744 except Exception as exc:
1745 from .user_errors import die
1746 die(message="Could not save check output files.", exc=exc, label=f"{verb} --check")
1747
1748
1749def cmd_tool(verb: str, raw_args: list[str]) -> None:
1750 """Generic handler for raw CLI tool verbs — thin argv layer over ``aquin.sdk``."""
1751 verb = resolve_legacy_verb(verb)
1752 _guard_tool_verb(verb)
1754
1755 from .engine.tools.registry import _load_stubs # noqa: ensure registered
1756 from .session_mode import tool_requires_model
1757
1758 active_map = _active_cli_tool_map()
1759 tool_name = active_map[verb]
1760 needs_model = tool_requires_model(tool_name)
1761 tool_args = _parse_tool_flags(raw_args)
1762 tool_check = bool(tool_args.pop("check", False)) or "--check" in raw_args
1763 want_umap = _pop_umap_flag(tool_args, raw_args)
1764 tool_args.pop("model_id", None)
1765
1766 if want_umap and verb not in _UMAP_FOLLOWUP_VERBS:
1767 print(
1768 f"Error: --umap is for SAE feature commands "
1769 f"(trace, feature locate/logit/neighbor, steer, benchmark).",
1770 file=sys.stderr,
1771 )
1772 sys.exit(1)
1773
1774 if verb == "steer" and tool_args.get("save") and tool_name == "run_steer_and_show":
1775 if tool_args.get("vector") or tool_args.get("vector_path"):
1776 print("Error: --save exports a feature vector; use --vector to apply a saved vector.", file=sys.stderr)
1777 sys.exit(1)
1778 if tool_args.get("eval"):
1779 print("Error: cannot combine --eval with --save. Export first, then: aquin steer --eval --vector <path>", file=sys.stderr)
1780 sys.exit(1)
1781 if tool_args.get("feature_idx") is None:
1782 print("Usage: aquin steer --feature_idx <n> --save <path> [--layer N]", file=sys.stderr)
1783 sys.exit(1)
1784 tool_name = "extract_steer_vector"
1785 needs_model = tool_requires_model(tool_name)
1786
1787 _require_attention_args(verb, tool_name, tool_args)
1788
1789 from .compute.model_loader import get_active_model_id
1790 from aquin.sdk._runtime import build_ctx, invoke
1791
1792 active_model = get_active_model_id() or ""
1793 if active_model and needs_model:
1794 tool_args.setdefault("model_id", active_model)
1795
1796 try:
1797 result = invoke(
1798 tool_name,
1799 tool_args,
1800 command=verb,
1801 needs_model=needs_model,
1802 model_id=active_model or None,
1803 raise_on_error=False,
1804 )
1805 except NotImplementedError as e:
1806 from .user_errors import die
1807 die(f"This command is not available yet.", exc=e, label=verb)
1808 except Exception as e:
1809 from .user_errors import die
1810 die(exc=e, label=verb)
1811
1812 from .cli_output import print_tool_result
1813 print_tool_result(verb, result, tool_name=tool_name)
1814
1815 if tool_check:
1816 _save_tool_check(verb, result, tool_name)
1817
1818 if want_umap and isinstance(result, dict) and not result.get("error"):
1820 build_ctx(model_id=active_model or None),
1821 result=result,
1822 tool_args=tool_args,
1823 ensure_model=active_model or None,
1824 )
1825
1826 if isinstance(result, dict) and result.get("error"):
1827 sys.exit(1)
1828
1829
1830def _try_simulation_phrase(args: list[str]) -> bool:
1831 """Dispatch aquin list simulation | compare simulation; redirect load simulation."""
1832 if len(args) < 2:
1833 return False
1834 prefix, noun = args[0], args[1]
1835 if prefix == "list" and noun in ("simulation", "simulations"):
1836 cmd_list(["simulation"] + args[2:])
1837 return True
1838 if prefix == "load" and noun == "simulation":
1839 print("Use: aquin replay simulation --run_id <id>", file=sys.stderr)
1840 sys.exit(1)
1841 if prefix == "compare" and noun == "simulation":
1842 _guard_tool_verb("compare simulation")
1843 _cmd_simulation_catalog("compare simulation", "compare_simulations", args[2:])
1844 return True
1845 return False
1846
1847
1848def cmd_help(args: list[str]) -> None:
1849 if args and args[0] == "commands":
1850 from .commands_cli import cmd_commands
1851
1852 cmd_commands(["--help"])
1853 return
1854
1855 if args and args[0] in ("--help", "-h", "help"):
1856 print("Usage: aquin help [commands] [--json] [--plain]")
1857 print("")
1858 print(" Show the CLI command map.")
1859 print(" commands Tracked-command subcommands")
1860 print(" --json Machine-readable sections")
1861 print(" --plain Full ASCII banner layout")
1862 return
1863
1864 as_json = "--json" in args
1865 plain = "--plain" in args
1866
1867 from aquin.compute.model_loader import get_active_model_id
1868
1869 model_id = get_active_model_id()
1870 mode = _session_mode()
1871 show_llm = True
1872
1873 from .help_display_ascii import help_payload, render_help, render_help_quiet
1874
1875 if as_json:
1876 try:
1877 sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
1878 except Exception:
1879 pass
1880 print(json.dumps(help_payload(
1881 connected=True,
1882 mode=mode, # type: ignore[arg-type]
1883 show_llm=show_llm,
1884 model_id=model_id,
1885 ), ensure_ascii=False))
1886 return
1887
1888 if plain:
1889 render_help(
1890 connected=True,
1891 mode=mode, # type: ignore[arg-type]
1892 show_llm=show_llm,
1893 model_id=model_id,
1894 )
1895 return
1896
1897 render_help_quiet(
1898 connected=True,
1899 mode=mode, # type: ignore[arg-type]
1900 show_llm=show_llm,
1901 model_id=model_id,
1902 )
1903
1904
1905
1906def main() -> None:
1907 from aquin.quiet import install_cli_quiet_mode
1908
1909 install_cli_quiet_mode()
1910 try:
1911 _cli_main()
1912 except KeyboardInterrupt:
1913 sys.exit(130)
1914 except SystemExit:
1915 raise
1916 except Exception as exc:
1917 from .user_errors import die
1918 die(exc=exc)
1919
1920
1921def _cli_main() -> None:
1922 args = sys.argv[1:]
1923
1924 if args and args[0] == "--cli-version":
1925 from aquin.version_display import version_payload
1926
1927 print(version_payload()["cli"])
1928 return
1929
1930 if "--accept-license" in args:
1931 from aquin.license_acceptance import accept_license_cli
1932
1933 accept_license_cli()
1934 return
1935
1936 if not args:
1937 from aquin.ascii_layout import print_logo
1938
1939 print_logo()
1940 return
1941
1942 cmd = resolve_legacy_verb(args[0])
1943 # Common typos
1944 _CMD_TYPOS = {"stauts": "status"}
1945 cmd = _CMD_TYPOS.get(cmd, cmd)
1946 rest = args[1:]
1947
1948 from aquin.license_acceptance import ensure_license_accepted
1949
1950 ensure_license_accepted(cmd=cmd)
1951
1952 if _try_simulation_phrase(args):
1953 return
1954
1955 if cmd == "engine":
1956 print("Removed. Use: aquin load model <id>")
1957 sys.exit(1)
1958 elif cmd in ("login", "logout", "switch"):
1959 print(f"Removed. This framework has no account auth ({cmd}).", file=sys.stderr)
1960 sys.exit(1)
1961 elif cmd == "chat":
1962 cmd_chat(rest)
1963 elif cmd in ("prompt", "prompting"):
1964 cmd_prompt(rest)
1965 elif cmd == "load":
1966 cmd_load(rest)
1967 elif cmd == "unload":
1968 cmd_unload(rest)
1969 elif cmd == "commands":
1970 from .commands_cli import cmd_commands
1971 cmd_commands(rest)
1972 elif cmd == "watch":
1973 print("Removed. Training watch is no longer part of the CLI.", file=sys.stderr)
1974 print("Use the SDK: aquin.init() / run.log() / run.checkpoint() → ./aquin_run/", file=sys.stderr)
1975 sys.exit(1)
1976 elif cmd == "dataset-generate":
1977 print("Removed. Pass --dataset <path> or --topic to aqit simulate.", file=sys.stderr)
1978 sys.exit(1)
1979 elif cmd in ("pairs-generate", "embed-pairs-generate"):
1980 print("Removed. Pass --dataset <path> or --topic to aqit simulate.", file=sys.stderr)
1981 sys.exit(1)
1982 elif cmd == "dataset-analyze":
1983 print("Removed. Dataset quality check is no longer available.", file=sys.stderr)
1984 sys.exit(1)
1985 elif cmd == "space-decomp":
1986 print("Removed. Embedding space decomposition is no longer available.", file=sys.stderr)
1987 sys.exit(1)
1988 elif cmd in ("mem", "mem-write", "mem-read"):
1989 print("Removed.", file=sys.stderr)
1990 sys.exit(1)
1991 elif cmd in ("run-code", "save-artifact"):
1992 print("Removed.", file=sys.stderr)
1993 sys.exit(1)
1994 elif cmd == "status":
1995 cmd_status(rest)
1996 elif cmd == "update":
1997 cmd_update(rest)
1998 elif cmd == "desktop":
1999 cmd_desktop(rest)
2000 elif cmd == "setup":
2001 from .setup_cli import cmd_setup
2002 cmd_setup(rest)
2003 elif cmd == "version" or cmd in ("--version", "-V"):
2004 cmd_version(rest)
2005 elif cmd == "license":
2006 cmd_license(rest)
2007 elif cmd in ("help", "--help", "-h"):
2008 cmd_help(rest)
2009 else:
2010 from aqit.cli import dispatch as aqit_dispatch, is_aqit_verb
2011
2012 if is_aqit_verb(cmd):
2013 aqit_dispatch([cmd, *rest])
2014 return
2015 print(f"Unknown command: {cmd}")
2016 print("Run aqit help for Recipe/train/inspect. aquin help for the shell.")
2017 sys.exit(1)
Definition cli.py:1
bool _try_simulation_phrase(list[str] args)
Definition cli.py:1834
str _get_api_key(str|None explicit=None)
Definition cli.py:35
None _model_switch_hint(str|None mode)
Definition cli.py:1413
None cmd_version(list[str] args)
Definition cli.py:78
dict _inject_session_mode(dict state)
Definition cli.py:1396
str load_model_local(str model_id, *, bool as_json=False)
Definition cli.py:955
dict _connected_state()
Definition cli.py:1365
str|None _session_mode()
Definition cli.py:1375
int|None _layer_from_filename_cli(Path path)
Definition cli.py:364
None _guard_llm_only_cmd(str cmd)
Definition cli.py:1405
int|None _layer_from_payload(Any result, dict[str, Any]|None tool_args=None)
Definition cli.py:1588
bool _pop_umap_flag(dict[str, Any] tool_args, list[str]|None raw_args=None)
Definition cli.py:1523
None cmd_prompt(list[str] args)
Definition cli.py:1218
None _assert_no_model_override(list[str] raw_args)
Definition cli.py:575
None _print_load_sae_help()
Definition cli.py:374
None cmd_trace(list[str] args)
Definition cli.py:616
Path|None _default_sae_load_output(str sae_id)
Definition cli.py:347
None cmd_desktop(list[str] args)
Definition cli.py:244
str _require_loaded_model_id()
Definition cli.py:555
None _cmd_simulation_catalog(str verb, str tool_name, list[str] extra_args)
Definition cli.py:1655
dict _build_tool_ctx(str|None model_id=None)
Definition cli.py:585
None _cli_main()
Definition cli.py:1925
list[int] _feature_idxs_from_payload(Any result, dict[str, Any]|None tool_args=None)
Definition cli.py:1539
None _cmd_load_sae(list[str] args)
Definition cli.py:398
None cmd_unload(list[str] args)
Definition cli.py:1070
None main()
Definition cli.py:1910
None _push_log(str line)
Definition cli.py:950
None _warm_daemon_sae(str model_id, int layer)
Definition cli.py:387
None cmd_chat(list[str] args)
Definition cli.py:1192
None _require_attention_args(str verb, str tool_name, dict[str, Any] tool_args)
Definition cli.py:1676
None _guard_tool_verb(str verb)
Definition cli.py:1417
None cmd_load(list[str] args)
Definition cli.py:1140
Any _coerce_flag_value(str val)
Definition cli.py:1438
None cmd_status(list[str] args)
Definition cli.py:41
None _save_tool_check(str verb, dict[str, Any] result, str|None tool_name)
Definition cli.py:1690
None cmd_feature_neighbors(list[str] args)
Definition cli.py:862
None cmd_license(list[str] args)
Definition cli.py:96
str _require_locked_model_id()
Definition cli.py:570
None _save_config(dict data)
Definition cli.py:29
dict _load_config()
Definition cli.py:22
None _normalize_topk_flags(dict[str, Any] tool_args)
Definition cli.py:1465
None cmd_tool(str verb, list[str] raw_args)
Definition cli.py:1753
dict[str, Any] _parse_tool_flags(list[str] raw)
Definition cli.py:1472
None cmd_help(list[str] args)
Definition cli.py:1852
bool _has_loaded_model()
Definition cli.py:1370
None cmd_feature_logits(list[str] args)
Definition cli.py:772
None _run_umap_followup(dict[str, Any] ctx, *, Any result=None, dict[str, Any]|None tool_args=None, str|None ensure_model=None, int|None layer=None, list[int]|None feature_idxs=None)
Definition cli.py:1620
dict[str, str] _active_cli_tool_map()
Definition cli.py:1383
int|None _coerce_feature_idx(Any value)
Definition cli.py:1530
None cmd_info(list[str] args)
Definition cli.py:307
None cmd_update(list[str] args)
Definition cli.py:153
None cmd_list(list[str] args)
Definition cli.py:282
tuple[str, str] _detect_desktop_platform()
Definition cli.py:220