AbteeXAILabs commited on
Commit
711af7c
·
verified ·
1 Parent(s): b8d4423

Publish expanded LumynaX product platform package

Browse files
Files changed (5) hide show
  1. PYPI_RELEASE.md +1 -1
  2. README.md +19 -2
  3. marama_route/cli.py +326 -81
  4. marama_route/platform.py +117 -3
  5. pyproject.toml +1 -1
PYPI_RELEASE.md CHANGED
@@ -1,7 +1,7 @@
1
  # PyPI Release: LumynaX MaramaRoute
2
 
3
  Package: `lumynax-marama-route`
4
- Version: `0.7.18`
5
 
6
  ## Local Build
7
 
 
1
  # PyPI Release: LumynaX MaramaRoute
2
 
3
  Package: `lumynax-marama-route`
4
+ Version: `0.8.0`
5
 
6
  ## Local Build
7
 
README.md CHANGED
@@ -95,8 +95,8 @@ MaramaRoute chat
95
  # Or jump straight to a model by id or search fragment.
96
  MaramaRoute chat qwen25-05b
97
 
98
- # Create local config, default alias, and optional agent files.
99
- MaramaRoute init --agent claude-code
100
  MaramaRoute agent doctor --model qwen25-7b
101
 
102
  # Inspect registry categories before choosing a model.
@@ -105,6 +105,7 @@ MaramaRoute categories
105
  # Install local runtimes when you want direct generation.
106
  python -m pip install llama-cpp-python
107
  python -m pip install torch
 
108
 
109
  # Estimate, pull, verify, and chat.
110
  MaramaRoute pull qwen25-05b --estimate --remote-sizes
@@ -209,8 +210,15 @@ Use these commands before putting a machine or workspace into regular use:
209
  # Install, registry, cache, HF tooling, and local runtime readiness.
210
  MaramaRoute doctor --hardware
211
 
 
 
 
 
212
  # Workspace bridge config and optional gateway health probe.
213
  MaramaRoute agent doctor --target claude-code --model qwen25-7b
 
 
 
214
  MaramaRoute agent doctor --target hpe --model qwen25-7b --base-url http://127.0.0.1:8787/v1
215
 
216
  # Exact remote size planning, local hash verification, and registry drift check.
@@ -445,9 +453,18 @@ MaramaRoute matrix
445
  ### Generate agent and HPE/HPC helper configs
446
 
447
  ```bash
 
 
 
448
  # Command bridge JSON for coding-agent workspaces.
449
  MaramaRoute agent-config --target claude-code --model qwen25-7b
 
 
 
 
 
450
  MaramaRoute agent-init --target claude-code --model qwen25-7b
 
451
  MaramaRoute agent init --target claude-code --model qwen25-7b
452
  MaramaRoute agent doctor --target claude-code --model qwen25-7b
453
 
 
95
  # Or jump straight to a model by id or search fragment.
96
  MaramaRoute chat qwen25-05b
97
 
98
+ # Create local config, default alias, coding-agent bridge files, and HPE scaffold.
99
+ MaramaRoute setup --all-targets --hpe
100
  MaramaRoute agent doctor --model qwen25-7b
101
 
102
  # Inspect registry categories before choosing a model.
 
105
  # Install local runtimes when you want direct generation.
106
  python -m pip install llama-cpp-python
107
  python -m pip install torch
108
+ python -m pip install sentencepiece tiktoken tokenizers
109
 
110
  # Estimate, pull, verify, and chat.
111
  MaramaRoute pull qwen25-05b --estimate --remote-sizes
 
210
  # Install, registry, cache, HF tooling, and local runtime readiness.
211
  MaramaRoute doctor --hardware
212
 
213
+ # One-shot production bootstrap: local config, aliases, agent bridge files, and optional HPE bundle.
214
+ MaramaRoute setup qwen25-05b --all-targets --hpe
215
+ MaramaRoute setup qwen25-7b --target claude-code,codex,continue,opencode,litellm,tabby --hpe --backend vllm
216
+
217
  # Workspace bridge config and optional gateway health probe.
218
  MaramaRoute agent doctor --target claude-code --model qwen25-7b
219
+ MaramaRoute agent doctor --target codex --model qwen25-7b
220
+ MaramaRoute agent doctor --target continue --model qwen25-7b
221
+ MaramaRoute agent doctor --target litellm --model qwen25-7b
222
  MaramaRoute agent doctor --target hpe --model qwen25-7b --base-url http://127.0.0.1:8787/v1
223
 
224
  # Exact remote size planning, local hash verification, and registry drift check.
 
453
  ### Generate agent and HPE/HPC helper configs
454
 
455
  ```bash
456
+ # One command writes MaramaRoute config, aliases, agent bridge files, and HPE scaffold.
457
+ MaramaRoute setup qwen25-7b --all-targets --hpe --backend vllm
458
+
459
  # Command bridge JSON for coding-agent workspaces.
460
  MaramaRoute agent-config --target claude-code --model qwen25-7b
461
+ MaramaRoute agent-config --target codex --model qwen25-7b
462
+ MaramaRoute agent-config --target continue --model qwen25-7b
463
+ MaramaRoute agent-config --target opencode --model qwen25-7b
464
+ MaramaRoute agent-config --target litellm --model qwen25-7b
465
+ MaramaRoute agent-config --target tabby --model qwen25-7b
466
  MaramaRoute agent-init --target claude-code --model qwen25-7b
467
+ MaramaRoute agent-init --target codex --model qwen25-7b
468
  MaramaRoute agent init --target claude-code --model qwen25-7b
469
  MaramaRoute agent doctor --target claude-code --model qwen25-7b
470
 
marama_route/cli.py CHANGED
@@ -56,6 +56,7 @@ from .platform import (
56
  build_registry_analytics,
57
  catalog_models,
58
  compare_models,
 
59
  recommend_model,
60
  render_hpe_apptainer_definition,
61
  render_hpe_gateway_config,
@@ -105,6 +106,9 @@ _DEPLOYMENT_TARGET_LABELS = {
105
  "nvidia_nim": "NVIDIA NIM",
106
  "nvidia_nemo": "NVIDIA NeMo/NEM",
107
  }
 
 
 
108
 
109
 
110
  class _ExitConversation(Exception):
@@ -332,28 +336,64 @@ def _doctor(args: argparse.Namespace) -> int:
332
  "path": str(cache_root),
333
  "pulled_count": pulled["count"],
334
  },
 
335
  "huggingface_hub": {
336
  "ok": importlib.util.find_spec("huggingface_hub") is not None,
337
  "hf_cli": shutil.which("hf") or shutil.which("huggingface-cli") or "",
338
  "token_env_present": any(os.getenv(name) for name in _HF_TOKEN_ENV_NAMES),
 
339
  },
340
  "llama_cpp": {
341
  "ok": importlib.util.find_spec("llama_cpp") is not None,
342
  "needed_for": "local GGUF chat and run",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  },
344
  }
345
  if args.online:
346
  checks["huggingface_hub"]["whoami"] = _hf_whoami_status()
 
347
  result = {
348
- "ok": all(bool(item.get("ok")) for item in checks.values()),
349
  "product": "LumynaX MaramaRoute",
350
  "checks": checks,
 
351
  "hardware": hardware,
352
  "next_commands": {
 
353
  "install_runtime": "python -m pip install llama-cpp-python",
 
354
  "choose_model": "MaramaRoute chat",
355
  "pull_small_model": "MaramaRoute pull qwen25-05b",
356
- "run_gateway": "MaramaRoute serve --port 8787",
 
 
357
  },
358
  }
359
  if args.json:
@@ -492,6 +532,231 @@ def _init_self_test(models: tuple[Any, ...], model: Any, args: argparse.Namespac
492
  return {"enabled": True, "ok": not blocking, "checks": checks}
493
 
494
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  def _writable_dir(path: Path) -> dict[str, Any]:
496
  try:
497
  path.mkdir(parents=True, exist_ok=True)
@@ -651,7 +916,6 @@ def _agent_init(args: argparse.Namespace) -> int:
651
  model_ref = args.model or args.model_option
652
  model = _resolve_cli_model(models, model_ref) if model_ref else _recommended_model(models)
653
  target_dir = args.output_dir.resolve()
654
- target_dir.mkdir(parents=True, exist_ok=True)
655
  config = build_agent_bridge_config(
656
  models,
657
  target=args.target,
@@ -661,28 +925,16 @@ def _agent_init(args: argparse.Namespace) -> int:
661
  cache_dir=args.cache_dir,
662
  model_id=model.model_id,
663
  )
664
- config_path = target_dir / "marama-route.agent.json"
665
- config_path.write_text(json.dumps(config, indent=2, sort_keys=True), encoding="utf-8")
666
- written = [str(config_path)]
667
- if args.target == "claude-code":
668
- claude_path = target_dir / "CLAUDE.md"
669
- claude_path.write_text(
670
- "\n".join(
671
- (
672
- "# MaramaRoute",
673
- "",
674
- "Use MaramaRoute for LumynaX model selection, local chat, and local-live gateway calls.",
675
- f"Default model: `{model.model_id}`",
676
- f"Start gateway: `{config['commands']['start_gateway']}`",
677
- f"Recommend model: `{config['commands']['recommend']}`",
678
- "",
679
- ),
680
- ),
681
- encoding="utf-8",
682
- )
683
- written.append(str(claude_path))
684
- print(json.dumps({"ok": True, "target": args.target, "model_id": model.model_id, "written": written}, indent=2, sort_keys=True))
685
- return 0
686
 
687
 
688
  def _agent_doctor(args: argparse.Namespace) -> int:
@@ -701,7 +953,7 @@ def _agent_doctor(args: argparse.Namespace) -> int:
701
  health = _gateway_health(args.base_url)
702
  checks = {
703
  "registry": {"ok": bool(models), "count": len(models), "path": str(_registry_path(args))},
704
- "target": {"ok": config.get("target") in {"generic", "claude-code", "opencode", "hpe-slurm"}, "value": config.get("target")},
705
  "cache": {"ok": True, "path": str(args.cache_dir or default_cache_root())},
706
  "gateway": health,
707
  }
@@ -745,56 +997,22 @@ def _hpe(args: argparse.Namespace) -> int:
745
  cache_dir="$MARAMA_ROUTE_CACHE",
746
  )
747
  if args.hpe_action == "init":
748
- script = output_dir / "marama-route.slurm"
749
- script.write_text(
750
- render_hpe_slurm_script(
751
- model_id=model.model_id,
752
- repo_id=model.repo_id,
753
- model_runtime=model.runtime,
754
- port=args.port,
755
- backend=args.backend,
756
- backend_port=args.backend_port,
757
- backend_base_url=args.backend_base_url,
758
- backend_model=args.backend_model,
759
- backend_command=args.backend_command,
760
- api_key_env=args.api_key_env,
761
- vllm_args=args.vllm_args,
762
- gpus=args.gpus,
763
- memory=args.memory,
764
- time_limit=args.time,
765
- partition=args.partition,
766
- ),
767
- encoding="utf-8",
768
- )
769
- gateway = output_dir / "gateway.hpe.json"
770
- gateway.write_text(json.dumps(gateway_config, indent=2, sort_keys=True), encoding="utf-8")
771
- env = output_dir / "marama-route.env"
772
- env.write_text(
773
- "\n".join(
774
- (
775
- "MARAMA_ROUTE_CACHE=${SCRATCH:-$HOME}/marama-route/models",
776
- f"MARAMA_ROUTE_PORT={args.port}",
777
- f"MARAMA_BACKEND_PORT={args.backend_port}",
778
- f"MARAMA_BACKEND_BASE_URL={args.backend_base_url or f'http://127.0.0.1:{args.backend_port}/v1'}",
779
- f"MARAMA_BACKEND_MODEL={args.backend_model or model.model_id}",
780
- f"MARAMA_BACKEND={args.backend}",
781
- "",
782
- ),
783
- ),
784
- encoding="utf-8",
785
  )
786
- definition = output_dir / "marama-route.def"
787
- definition.write_text(render_hpe_apptainer_definition(backend=args.backend), encoding="utf-8")
788
- readme = output_dir / "README.hpe.md"
789
- readme.write_text(render_hpe_readme(model_id=model.model_id, port=args.port, backend=args.backend), encoding="utf-8")
790
- result = {
791
- "ok": True,
792
- "written": [str(script), str(gateway), str(env), str(definition), str(readme)],
793
- "model_id": model.model_id,
794
- "repo_id": model.repo_id,
795
- "backend": args.backend,
796
- "gateway_config": gateway_config,
797
- }
798
  elif args.hpe_action == "submit":
799
  script = output_dir / "marama-route.slurm"
800
  script.write_text(
@@ -1892,7 +2110,7 @@ def _print_startup_guide(models: tuple[Any, ...], cache_dir: Path | None, *, ani
1892
  "",
1893
  "Start here: press Enter for hardware-suitable models, or type /help any time.",
1894
  "Offline flow: /models -> choose -> /pull -> chat. Use /switch <text> to change.",
1895
- "Production commands: doctor | verify --deep | serve --live-local | agent-init | hpe init",
1896
  "MaramaRoute serve --live-local --port 8787",
1897
  ],
1898
  )
@@ -2196,7 +2414,9 @@ def _print_doctor(result: dict[str, Any]) -> None:
2196
  print(f"- {name}: {mark}{detail}")
2197
  if not result["checks"]["llama_cpp"]["ok"]:
2198
  print("Install local GGUF runtime: python -m pip install llama-cpp-python")
2199
- print("Next: MaramaRoute chat | MaramaRoute pull qwen25-05b | MaramaRoute serve --port 8787")
 
 
2200
 
2201
 
2202
  def _ensure_model_ready(
@@ -2403,12 +2623,37 @@ def build_parser() -> argparse.ArgumentParser:
2403
  init.add_argument("--host", default="127.0.0.1")
2404
  init.add_argument("--port", type=int, default=8787)
2405
  init.add_argument("--live-local", action=argparse.BooleanOptionalAction, default=True)
2406
- init.add_argument("--agent", default="", choices=["", "generic", "claude-code", "opencode", "hpe", "hpe-slurm"])
2407
  init.add_argument("--hpe", action=argparse.BooleanOptionalAction, default=False)
2408
  init.add_argument("--pull", action=argparse.BooleanOptionalAction, default=False)
2409
  init.add_argument("--self-test", action=argparse.BooleanOptionalAction, default=True)
2410
  init.set_defaults(handler=_init)
2411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2412
  hardware = subparsers.add_parser("hardware", help="Inspect local hardware and recommend runnable LumynaX models.")
2413
  hardware.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2414
  hardware.add_argument("--cache-dir", type=Path, default=default_cache_root(), help="Local model cache directory.")
@@ -2505,7 +2750,7 @@ def build_parser() -> argparse.ArgumentParser:
2505
  help="Emit command-bridge config for coding agents, local gateways, or HPE/Slurm jobs.",
2506
  )
2507
  agent.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2508
- agent.add_argument("--target", default="generic", choices=["generic", "claude-code", "opencode", "hpe", "hpe-slurm"])
2509
  agent.add_argument("--model", default="", help="Default model id, repo id, or unique search fragment.")
2510
  agent.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
2511
  agent.add_argument("--host", default="127.0.0.1")
@@ -2518,7 +2763,7 @@ def build_parser() -> argparse.ArgumentParser:
2518
  agent_init.add_argument("model", nargs="?", help="Optional default model id, repo id, alias, or search fragment.")
2519
  agent_init.add_argument("--model", dest="model_option", default="", help="Default model id, repo id, alias, or search fragment.")
2520
  agent_init.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2521
- agent_init.add_argument("--target", default="claude-code", choices=["generic", "claude-code", "opencode", "hpe", "hpe-slurm"])
2522
  agent_init.add_argument("--output-dir", type=Path, default=Path("."))
2523
  agent_init.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
2524
  agent_init.add_argument("--host", default="127.0.0.1")
@@ -2528,7 +2773,7 @@ def build_parser() -> argparse.ArgumentParser:
2528
 
2529
  agent_doctor = subparsers.add_parser("agent-doctor", help="Check agent bridge config and local gateway readiness.")
2530
  agent_doctor.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2531
- agent_doctor.add_argument("--target", default="claude-code", choices=["generic", "claude-code", "opencode", "hpe", "hpe-slurm"])
2532
  agent_doctor.add_argument("--model", default="", help="Default model id, repo id, alias, or search fragment.")
2533
  agent_doctor.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
2534
  agent_doctor.add_argument("--host", default="127.0.0.1")
 
56
  build_registry_analytics,
57
  catalog_models,
58
  compare_models,
59
+ normalize_agent_target,
60
  recommend_model,
61
  render_hpe_apptainer_definition,
62
  render_hpe_gateway_config,
 
106
  "nvidia_nim": "NVIDIA NIM",
107
  "nvidia_nemo": "NVIDIA NeMo/NEM",
108
  }
109
+ _AGENT_TARGET_CHOICES = ("generic", "claude-code", "codex", "continue", "opencode", "litellm", "tabby", "hpe", "hpe-slurm")
110
+ _AGENT_SETUP_DEFAULT_TARGETS = ("claude-code", "codex", "continue", "opencode", "litellm", "tabby")
111
+ _AGENT_CANONICAL_TARGETS = {"generic", "claude-code", "codex", "continue", "opencode", "litellm", "tabby", "hpe-slurm"}
112
 
113
 
114
  class _ExitConversation(Exception):
 
336
  "path": str(cache_root),
337
  "pulled_count": pulled["count"],
338
  },
339
+ "cache_writable": _writable_dir(cache_root),
340
  "huggingface_hub": {
341
  "ok": importlib.util.find_spec("huggingface_hub") is not None,
342
  "hf_cli": shutil.which("hf") or shutil.which("huggingface-cli") or "",
343
  "token_env_present": any(os.getenv(name) for name in _HF_TOKEN_ENV_NAMES),
344
+ "needed_for": "pull, update-registry, and private HF access",
345
  },
346
  "llama_cpp": {
347
  "ok": importlib.util.find_spec("llama_cpp") is not None,
348
  "needed_for": "local GGUF chat and run",
349
+ "severity": "warning",
350
+ },
351
+ "transformers": {
352
+ "ok": importlib.util.find_spec("transformers") is not None,
353
+ "needed_for": "offline Transformers text-generation snapshots",
354
+ "severity": "warning",
355
+ },
356
+ "tokenizer_extras": {
357
+ "ok": any(importlib.util.find_spec(name) is not None for name in ("sentencepiece", "tiktoken", "tokenizers")),
358
+ "needed_for": "slow-tokenizer conversion and some Transformers snapshots",
359
+ "severity": "warning",
360
+ },
361
+ "git": {
362
+ "ok": shutil.which("git") is not None,
363
+ "executable": shutil.which("git") or "",
364
+ "severity": "warning",
365
+ },
366
+ "node": {
367
+ "ok": shutil.which("node") is not None,
368
+ "executable": shutil.which("node") or "",
369
+ "needed_for": "npm wrapper usage",
370
+ "severity": "warning",
371
+ },
372
+ "npm": {
373
+ "ok": shutil.which("npm") is not None,
374
+ "executable": shutil.which("npm") or "",
375
+ "needed_for": "npm wrapper usage",
376
+ "severity": "warning",
377
  },
378
  }
379
  if args.online:
380
  checks["huggingface_hub"]["whoami"] = _hf_whoami_status()
381
+ blocking = [name for name, item in checks.items() if not item.get("ok") and item.get("severity") != "warning"]
382
  result = {
383
+ "ok": not blocking,
384
  "product": "LumynaX MaramaRoute",
385
  "checks": checks,
386
+ "blocking_checks": blocking,
387
  "hardware": hardware,
388
  "next_commands": {
389
+ "setup": "MaramaRoute setup --all-targets --hpe",
390
  "install_runtime": "python -m pip install llama-cpp-python",
391
+ "install_transformers_runtime": "python -m pip install transformers torch sentencepiece tiktoken tokenizers",
392
  "choose_model": "MaramaRoute chat",
393
  "pull_small_model": "MaramaRoute pull qwen25-05b",
394
+ "run_gateway": "MaramaRoute serve --live-local --port 8787",
395
+ "agent_doctor": "MaramaRoute agent doctor --target claude-code",
396
+ "hpe_init": "MaramaRoute hpe init qwen25-05b --backend auto",
397
  },
398
  }
399
  if args.json:
 
532
  return {"enabled": True, "ok": not blocking, "checks": checks}
533
 
534
 
535
+ def _setup(args: argparse.Namespace) -> int:
536
+ models = load_model_registry(_registry_path(args))
537
+ model = _resolve_cli_model(models, args.model) if args.model else _recommended_model(models)
538
+ state_root = args.state_dir or default_state_root()
539
+ state_root.mkdir(parents=True, exist_ok=True)
540
+ targets = _expand_agent_targets(args.target, all_targets=args.all_targets, include_hpe=args.hpe)
541
+ hardware = inspect_hardware(args.cache_dir)
542
+ self_test = _init_self_test(models, model, args)
543
+ base_url = f"http://{args.host}:{args.port}/v1"
544
+ config = {
545
+ "product": "LumynaX MaramaRoute",
546
+ "mode": "production_setup",
547
+ "version": _installed_version(),
548
+ "state_dir": str(state_root),
549
+ "cache_dir": str(args.cache_dir),
550
+ "default_model": model.model_id,
551
+ "default_repo": model.repo_id,
552
+ "default_runtime": model.runtime,
553
+ "hardware": hardware,
554
+ "agent_targets": targets,
555
+ "gateway": {
556
+ "host": args.host,
557
+ "port": args.port,
558
+ "base_url": base_url,
559
+ "live_local": True,
560
+ },
561
+ "next_commands": {
562
+ "doctor": "MaramaRoute doctor --hardware",
563
+ "pull": f"MaramaRoute pull {model.model_id}",
564
+ "chat": f"MaramaRoute chat {model.model_id}",
565
+ "serve": f"MaramaRoute serve --host {args.host} --port {args.port} --live-local",
566
+ "agent_doctor": "MaramaRoute agent doctor --target claude-code",
567
+ "hpe": f"MaramaRoute hpe init {model.model_id} --backend {args.backend}",
568
+ "compat_vllm": "MaramaRoute compat --target vllm --status usable",
569
+ "compat_nim": "MaramaRoute compat --target nim --status usable",
570
+ "compat_nemo": "MaramaRoute compat --target nemo --status pathway",
571
+ },
572
+ "self_test": self_test,
573
+ }
574
+ config_path = state_root / "marama-route.json"
575
+ config_path.write_text(json.dumps(config, indent=2, sort_keys=True), encoding="utf-8")
576
+ set_alias("default", model.model_id, state_root)
577
+ set_favorite(model.model_id, state_root)
578
+
579
+ artifacts: dict[str, Any] = {
580
+ "config": str(config_path),
581
+ "state_dir": str(state_root),
582
+ "aliases": str(state_root / "aliases.json"),
583
+ }
584
+ agent_artifacts: dict[str, Any] = {}
585
+ for target in targets:
586
+ if target == "hpe-slurm":
587
+ continue
588
+ agent_config = build_agent_bridge_config(
589
+ models,
590
+ target=target,
591
+ base_url=base_url,
592
+ host=args.host,
593
+ port=args.port,
594
+ cache_dir=args.cache_dir,
595
+ model_id=model.model_id,
596
+ )
597
+ target_dir = state_root / "agents" / target
598
+ agent_artifacts[target] = _write_agent_workspace_files(target_dir, agent_config)
599
+ if agent_artifacts:
600
+ artifacts["agents"] = agent_artifacts
601
+
602
+ if "hpe-slurm" in targets:
603
+ hpe_result = _write_hpe_init_bundle(
604
+ state_root / "hpe",
605
+ model,
606
+ port=args.port,
607
+ backend=args.backend,
608
+ backend_port=args.backend_port,
609
+ backend_base_url=args.backend_base_url,
610
+ backend_model=args.backend_model,
611
+ backend_command=args.backend_command,
612
+ api_key_env=args.api_key_env,
613
+ vllm_args=args.vllm_args,
614
+ gpus=args.gpus,
615
+ memory=args.memory,
616
+ time_limit=args.time,
617
+ partition=args.partition,
618
+ )
619
+ artifacts["hpe"] = hpe_result["written"]
620
+
621
+ if args.pull:
622
+ pulled = pull_model(models, model.model_id, cache_root=args.cache_dir)
623
+ artifacts["pulled_model"] = pulled.to_dict()
624
+
625
+ result = {
626
+ "ok": self_test.get("ok", True),
627
+ "model_id": model.model_id,
628
+ "repo_id": model.repo_id,
629
+ "runtime": model.runtime,
630
+ "targets": targets,
631
+ "artifacts": artifacts,
632
+ "config": config,
633
+ }
634
+ print(json.dumps(result, indent=2, sort_keys=True))
635
+ return 0 if result["ok"] or args.allow_warnings else 2
636
+
637
+
638
+ def _expand_agent_targets(raw_targets: Sequence[str], *, all_targets: bool, include_hpe: bool) -> list[str]:
639
+ selected: list[str] = []
640
+ raw_values: list[str] = []
641
+ for value in raw_targets:
642
+ raw_values.extend(part.strip() for part in value.split(",") if part.strip())
643
+ if all_targets or not raw_values:
644
+ raw_values.extend(_AGENT_SETUP_DEFAULT_TARGETS)
645
+ if include_hpe:
646
+ raw_values.append("hpe-slurm")
647
+ for value in raw_values:
648
+ if value.lower() == "all":
649
+ expanded = list(_AGENT_SETUP_DEFAULT_TARGETS)
650
+ else:
651
+ expanded = [value]
652
+ for item in expanded:
653
+ normalized = normalize_agent_target(item)
654
+ if normalized not in _AGENT_CANONICAL_TARGETS:
655
+ choices = ", ".join(_AGENT_TARGET_CHOICES)
656
+ raise ValueError(f"Unsupported agent target: {item}. Choices: {choices}")
657
+ if normalized not in selected:
658
+ selected.append(normalized)
659
+ return selected
660
+
661
+
662
+ def _write_agent_workspace_files(target_dir: Path, config: dict[str, Any]) -> list[str]:
663
+ target_dir.mkdir(parents=True, exist_ok=True)
664
+ config_path = target_dir / "marama-route.agent.json"
665
+ config_path.write_text(json.dumps(config, indent=2, sort_keys=True), encoding="utf-8")
666
+ written = [str(config_path)]
667
+ workspace_files = config.get("workspace_files", {})
668
+ if isinstance(workspace_files, dict):
669
+ for name, content in workspace_files.items():
670
+ output_path = target_dir / Path(str(name)).name
671
+ if isinstance(content, list):
672
+ text = "\n".join(str(line) for line in content).rstrip() + "\n"
673
+ else:
674
+ text = str(content).rstrip() + "\n"
675
+ output_path.write_text(text, encoding="utf-8")
676
+ written.append(str(output_path))
677
+ return written
678
+
679
+
680
+ def _write_hpe_init_bundle(
681
+ output_dir: Path,
682
+ model: Any,
683
+ *,
684
+ port: int,
685
+ backend: str,
686
+ backend_port: int = 8000,
687
+ backend_base_url: str = "",
688
+ backend_model: str = "",
689
+ backend_command: str = "",
690
+ api_key_env: str = "",
691
+ vllm_args: str = "",
692
+ gpus: int = 0,
693
+ memory: str = "32G",
694
+ time_limit: str = "02:00:00",
695
+ partition: str = "",
696
+ ) -> dict[str, Any]:
697
+ output_dir.mkdir(parents=True, exist_ok=True)
698
+ backend_base = backend_base_url or f"http://127.0.0.1:{backend_port}/v1"
699
+ gateway_config = render_hpe_gateway_config(
700
+ model_id=model.model_id,
701
+ backend=backend,
702
+ model_runtime=model.runtime,
703
+ backend_base_url=backend_base,
704
+ backend_model=backend_model or model.model_id,
705
+ api_key_env=api_key_env,
706
+ cache_dir="$MARAMA_ROUTE_CACHE",
707
+ )
708
+ script = output_dir / "marama-route.slurm"
709
+ script.write_text(
710
+ render_hpe_slurm_script(
711
+ model_id=model.model_id,
712
+ repo_id=model.repo_id,
713
+ model_runtime=model.runtime,
714
+ port=port,
715
+ backend=backend,
716
+ backend_port=backend_port,
717
+ backend_base_url=backend_base_url,
718
+ backend_model=backend_model,
719
+ backend_command=backend_command,
720
+ api_key_env=api_key_env,
721
+ vllm_args=vllm_args,
722
+ gpus=gpus,
723
+ memory=memory,
724
+ time_limit=time_limit,
725
+ partition=partition,
726
+ ),
727
+ encoding="utf-8",
728
+ )
729
+ gateway = output_dir / "gateway.hpe.json"
730
+ gateway.write_text(json.dumps(gateway_config, indent=2, sort_keys=True), encoding="utf-8")
731
+ env = output_dir / "marama-route.env"
732
+ env.write_text(
733
+ "\n".join(
734
+ (
735
+ "MARAMA_ROUTE_CACHE=${SCRATCH:-$HOME}/marama-route/models",
736
+ f"MARAMA_ROUTE_PORT={port}",
737
+ f"MARAMA_BACKEND_PORT={backend_port}",
738
+ f"MARAMA_BACKEND_BASE_URL={backend_base}",
739
+ f"MARAMA_BACKEND_MODEL={backend_model or model.model_id}",
740
+ f"MARAMA_BACKEND={backend}",
741
+ "",
742
+ ),
743
+ ),
744
+ encoding="utf-8",
745
+ )
746
+ definition = output_dir / "marama-route.def"
747
+ definition.write_text(render_hpe_apptainer_definition(backend=backend), encoding="utf-8")
748
+ readme = output_dir / "README.hpe.md"
749
+ readme.write_text(render_hpe_readme(model_id=model.model_id, port=port, backend=backend), encoding="utf-8")
750
+ return {
751
+ "ok": True,
752
+ "written": [str(script), str(gateway), str(env), str(definition), str(readme)],
753
+ "model_id": model.model_id,
754
+ "repo_id": model.repo_id,
755
+ "backend": backend,
756
+ "gateway_config": gateway_config,
757
+ }
758
+
759
+
760
  def _writable_dir(path: Path) -> dict[str, Any]:
761
  try:
762
  path.mkdir(parents=True, exist_ok=True)
 
916
  model_ref = args.model or args.model_option
917
  model = _resolve_cli_model(models, model_ref) if model_ref else _recommended_model(models)
918
  target_dir = args.output_dir.resolve()
 
919
  config = build_agent_bridge_config(
920
  models,
921
  target=args.target,
 
925
  cache_dir=args.cache_dir,
926
  model_id=model.model_id,
927
  )
928
+ written = _write_agent_workspace_files(target_dir, config)
929
+ ok = bool(config.get("ok", True))
930
+ print(
931
+ json.dumps(
932
+ {"ok": ok, "target": config.get("target", args.target), "model_id": model.model_id, "written": written},
933
+ indent=2,
934
+ sort_keys=True,
935
+ ),
936
+ )
937
+ return 0 if ok else 2
 
 
 
 
 
 
 
 
 
 
 
 
938
 
939
 
940
  def _agent_doctor(args: argparse.Namespace) -> int:
 
953
  health = _gateway_health(args.base_url)
954
  checks = {
955
  "registry": {"ok": bool(models), "count": len(models), "path": str(_registry_path(args))},
956
+ "target": {"ok": config.get("target") in _AGENT_CANONICAL_TARGETS, "value": config.get("target")},
957
  "cache": {"ok": True, "path": str(args.cache_dir or default_cache_root())},
958
  "gateway": health,
959
  }
 
997
  cache_dir="$MARAMA_ROUTE_CACHE",
998
  )
999
  if args.hpe_action == "init":
1000
+ result = _write_hpe_init_bundle(
1001
+ output_dir,
1002
+ model,
1003
+ port=args.port,
1004
+ backend=args.backend,
1005
+ backend_port=args.backend_port,
1006
+ backend_base_url=args.backend_base_url,
1007
+ backend_model=args.backend_model,
1008
+ backend_command=args.backend_command,
1009
+ api_key_env=args.api_key_env,
1010
+ vllm_args=args.vllm_args,
1011
+ gpus=args.gpus,
1012
+ memory=args.memory,
1013
+ time_limit=args.time,
1014
+ partition=args.partition,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1015
  )
 
 
 
 
 
 
 
 
 
 
 
 
1016
  elif args.hpe_action == "submit":
1017
  script = output_dir / "marama-route.slurm"
1018
  script.write_text(
 
2110
  "",
2111
  "Start here: press Enter for hardware-suitable models, or type /help any time.",
2112
  "Offline flow: /models -> choose -> /pull -> chat. Use /switch <text> to change.",
2113
+ "Production commands: setup | doctor | verify --deep | serve --live-local | agent-init | hpe init",
2114
  "MaramaRoute serve --live-local --port 8787",
2115
  ],
2116
  )
 
2414
  print(f"- {name}: {mark}{detail}")
2415
  if not result["checks"]["llama_cpp"]["ok"]:
2416
  print("Install local GGUF runtime: python -m pip install llama-cpp-python")
2417
+ if not result["checks"]["tokenizer_extras"]["ok"]:
2418
+ print("Install tokenizer extras: python -m pip install sentencepiece tiktoken tokenizers")
2419
+ print("Next: MaramaRoute setup --all-targets --hpe | MaramaRoute chat | MaramaRoute serve --live-local --port 8787")
2420
 
2421
 
2422
  def _ensure_model_ready(
 
2623
  init.add_argument("--host", default="127.0.0.1")
2624
  init.add_argument("--port", type=int, default=8787)
2625
  init.add_argument("--live-local", action=argparse.BooleanOptionalAction, default=True)
2626
+ init.add_argument("--agent", default="", choices=("", *_AGENT_TARGET_CHOICES))
2627
  init.add_argument("--hpe", action=argparse.BooleanOptionalAction, default=False)
2628
  init.add_argument("--pull", action=argparse.BooleanOptionalAction, default=False)
2629
  init.add_argument("--self-test", action=argparse.BooleanOptionalAction, default=True)
2630
  init.set_defaults(handler=_init)
2631
 
2632
+ setup = subparsers.add_parser("setup", help="Create a production-ready MaramaRoute local, agent, and HPE setup.")
2633
+ setup.add_argument("model", nargs="?", help="Optional default model id, repo id, alias, or search fragment.")
2634
+ setup.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2635
+ setup.add_argument("--cache-dir", type=Path, default=default_cache_root(), help="Local model cache directory.")
2636
+ setup.add_argument("--state-dir", type=Path, default=None, help="MaramaRoute state directory.")
2637
+ setup.add_argument("--host", default="127.0.0.1")
2638
+ setup.add_argument("--port", type=int, default=8787)
2639
+ setup.add_argument("--target", action="append", default=[], help="Agent target. Repeat or comma-separate values; use all for every coding target.")
2640
+ setup.add_argument("--all-targets", action=argparse.BooleanOptionalAction, default=False)
2641
+ setup.add_argument("--hpe", action=argparse.BooleanOptionalAction, default=False)
2642
+ setup.add_argument("--backend", choices=["auto", "local-live", "vllm", "nim", "nemo", "external"], default="auto")
2643
+ setup.add_argument("--backend-port", type=int, default=8000)
2644
+ setup.add_argument("--backend-base-url", default="")
2645
+ setup.add_argument("--backend-model", default="")
2646
+ setup.add_argument("--backend-command", default="")
2647
+ setup.add_argument("--api-key-env", default="")
2648
+ setup.add_argument("--vllm-args", default="")
2649
+ setup.add_argument("--gpus", type=int, default=0)
2650
+ setup.add_argument("--memory", default="32G")
2651
+ setup.add_argument("--time", default="02:00:00")
2652
+ setup.add_argument("--partition", default="")
2653
+ setup.add_argument("--pull", action=argparse.BooleanOptionalAction, default=False)
2654
+ setup.add_argument("--allow-warnings", action=argparse.BooleanOptionalAction, default=True)
2655
+ setup.set_defaults(handler=_setup)
2656
+
2657
  hardware = subparsers.add_parser("hardware", help="Inspect local hardware and recommend runnable LumynaX models.")
2658
  hardware.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2659
  hardware.add_argument("--cache-dir", type=Path, default=default_cache_root(), help="Local model cache directory.")
 
2750
  help="Emit command-bridge config for coding agents, local gateways, or HPE/Slurm jobs.",
2751
  )
2752
  agent.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2753
+ agent.add_argument("--target", default="generic", choices=_AGENT_TARGET_CHOICES)
2754
  agent.add_argument("--model", default="", help="Default model id, repo id, or unique search fragment.")
2755
  agent.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
2756
  agent.add_argument("--host", default="127.0.0.1")
 
2763
  agent_init.add_argument("model", nargs="?", help="Optional default model id, repo id, alias, or search fragment.")
2764
  agent_init.add_argument("--model", dest="model_option", default="", help="Default model id, repo id, alias, or search fragment.")
2765
  agent_init.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2766
+ agent_init.add_argument("--target", default="claude-code", choices=_AGENT_TARGET_CHOICES)
2767
  agent_init.add_argument("--output-dir", type=Path, default=Path("."))
2768
  agent_init.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
2769
  agent_init.add_argument("--host", default="127.0.0.1")
 
2773
 
2774
  agent_doctor = subparsers.add_parser("agent-doctor", help="Check agent bridge config and local gateway readiness.")
2775
  agent_doctor.add_argument("--registry", type=Path, default=None, help="MaramaRoute model registry JSON.")
2776
+ agent_doctor.add_argument("--target", default="claude-code", choices=_AGENT_TARGET_CHOICES)
2777
  agent_doctor.add_argument("--model", default="", help="Default model id, repo id, alias, or search fragment.")
2778
  agent_doctor.add_argument("--base-url", default="http://127.0.0.1:8787/v1")
2779
  agent_doctor.add_argument("--host", default="127.0.0.1")
marama_route/platform.py CHANGED
@@ -283,6 +283,23 @@ def recommend_model(
283
  }
284
 
285
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  def build_agent_bridge_config(
287
  models: tuple[ModelEndpoint, ...],
288
  *,
@@ -293,7 +310,7 @@ def build_agent_bridge_config(
293
  cache_dir: Path | None = None,
294
  model_id: str = "",
295
  ) -> dict[str, Any]:
296
- normalized = target.strip().lower().replace("_", "-")
297
  selected = _select_export_model(models, model_id)
298
  cache_value = str(cache_dir) if cache_dir is not None else "${MARAMA_ROUTE_CACHE:-~/.cache/abteex-ai/marama-route/models}"
299
  commands = {
@@ -303,12 +320,22 @@ def build_agent_bridge_config(
303
  "recommend": "MaramaRoute recommend --task code --sensitivity restricted --prompt-text \"Describe the task\"",
304
  "pull": f"MaramaRoute pull {selected.model_id}" if selected is not None else "MaramaRoute pull <model-id>",
305
  "chat": f"MaramaRoute chat {selected.model_id}" if selected is not None else "MaramaRoute chat <model-id>",
 
 
 
 
 
 
 
306
  }
 
 
307
  config: dict[str, Any] = {
308
  "ok": True,
309
  "target": normalized,
310
  "product": "LumynaX MaramaRoute",
311
  "mode": "local_command_bridge",
 
312
  "base_url": base_url,
313
  "cache_dir": cache_value,
314
  "default_model": model_summary(selected) if selected is not None else None,
@@ -319,7 +346,7 @@ def build_agent_bridge_config(
319
  "ABTEEX_MARAMA_API_KEY": "local-dev",
320
  },
321
  }
322
- if normalized in {"claude-code", "claude"}:
323
  config["target"] = "claude-code"
324
  config["workspace_files"] = {
325
  "CLAUDE.md": [
@@ -332,7 +359,82 @@ def build_agent_bridge_config(
332
  "This export is a command bridge. It does not replace the coding agent runtime.",
333
  "Keep sensitive prompts on the local MaramaRoute path when project policy requires it.",
334
  ]
335
- elif normalized in {"hpe", "hpe-slurm", "slurm", "hpc"}:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  config["target"] = "hpe-slurm"
337
  config["environment"].update(
338
  {
@@ -359,6 +461,18 @@ def build_agent_bridge_config(
359
  }
360
  elif normalized == "opencode":
361
  config["provider_config"] = build_opencode_provider_config(models, base_url=base_url)
 
 
 
 
 
 
 
 
 
 
 
 
362
  return config
363
 
364
 
 
283
  }
284
 
285
 
286
+ def normalize_agent_target(target: str) -> str:
287
+ normalized = target.strip().lower().replace("_", "-")
288
+ aliases = {
289
+ "claude": "claude-code",
290
+ "claude-code-cli": "claude-code",
291
+ "codex-cli": "codex",
292
+ "continue-dev": "continue",
293
+ "lite-llm": "litellm",
294
+ "lite-llm-proxy": "litellm",
295
+ "tabbyml": "tabby",
296
+ "hpe": "hpe-slurm",
297
+ "hpc": "hpe-slurm",
298
+ "slurm": "hpe-slurm",
299
+ }
300
+ return aliases.get(normalized, normalized or "generic")
301
+
302
+
303
  def build_agent_bridge_config(
304
  models: tuple[ModelEndpoint, ...],
305
  *,
 
310
  cache_dir: Path | None = None,
311
  model_id: str = "",
312
  ) -> dict[str, Any]:
313
+ normalized = normalize_agent_target(target)
314
  selected = _select_export_model(models, model_id)
315
  cache_value = str(cache_dir) if cache_dir is not None else "${MARAMA_ROUTE_CACHE:-~/.cache/abteex-ai/marama-route/models}"
316
  commands = {
 
320
  "recommend": "MaramaRoute recommend --task code --sensitivity restricted --prompt-text \"Describe the task\"",
321
  "pull": f"MaramaRoute pull {selected.model_id}" if selected is not None else "MaramaRoute pull <model-id>",
322
  "chat": f"MaramaRoute chat {selected.model_id}" if selected is not None else "MaramaRoute chat <model-id>",
323
+ "doctor": "MaramaRoute doctor --hardware",
324
+ "agent_doctor": f"MaramaRoute agent doctor --target {normalized}",
325
+ "setup": "MaramaRoute setup --all-targets --hpe",
326
+ "hpe_init": f"MaramaRoute hpe init {selected.model_id if selected else '<model-id>'} --backend auto",
327
+ "compat_vllm": "MaramaRoute compat --target vllm --status usable",
328
+ "compat_nim": "MaramaRoute compat --target nim --status usable",
329
+ "compat_nemo": "MaramaRoute compat --target nemo --status pathway",
330
  }
331
+ selected_id = selected.model_id if selected is not None else "<model-id>"
332
+ agent_targets = ("generic", "claude-code", "codex", "continue", "opencode", "litellm", "tabby", "hpe-slurm")
333
  config: dict[str, Any] = {
334
  "ok": True,
335
  "target": normalized,
336
  "product": "LumynaX MaramaRoute",
337
  "mode": "local_command_bridge",
338
+ "supported_targets": list(agent_targets),
339
  "base_url": base_url,
340
  "cache_dir": cache_value,
341
  "default_model": model_summary(selected) if selected is not None else None,
 
346
  "ABTEEX_MARAMA_API_KEY": "local-dev",
347
  },
348
  }
349
+ if normalized == "claude-code":
350
  config["target"] = "claude-code"
351
  config["workspace_files"] = {
352
  "CLAUDE.md": [
 
359
  "This export is a command bridge. It does not replace the coding agent runtime.",
360
  "Keep sensitive prompts on the local MaramaRoute path when project policy requires it.",
361
  ]
362
+ elif normalized == "codex":
363
+ config["workspace_files"] = {
364
+ "AGENTS.md": [
365
+ "# MaramaRoute",
366
+ "",
367
+ "Use MaramaRoute for LumynaX model selection, local chat, and local-live gateway work.",
368
+ f"Default model: `{selected_id}`",
369
+ f"Start gateway: `{commands['start_gateway']}`",
370
+ f"Pull model files: `{commands['pull']}`",
371
+ f"Run local chat: `{commands['chat']}`",
372
+ ],
373
+ }
374
+ config["notes"] = [
375
+ "This export gives coding tools project-local commands and policy hints.",
376
+ "Start the MaramaRoute gateway only when a project needs a local route API.",
377
+ ]
378
+ elif normalized == "continue":
379
+ config["continue_config"] = {
380
+ "name": "LumynaX MaramaRoute",
381
+ "model": selected_id,
382
+ "base_url": base_url,
383
+ "api_key_env": "ABTEEX_MARAMA_API_KEY",
384
+ "start_command": commands["start_gateway"],
385
+ }
386
+ config["workspace_files"] = {
387
+ "continue.marama-route.json": [
388
+ json.dumps(config["continue_config"], indent=2, sort_keys=True),
389
+ ],
390
+ }
391
+ config["notes"] = [
392
+ "Use this as the project-local Continue bridge reference.",
393
+ "Run the gateway command before pointing Continue at the local MaramaRoute endpoint.",
394
+ ]
395
+ elif normalized == "litellm":
396
+ config["litellm_config"] = {
397
+ "model_list": [
398
+ {
399
+ "model_name": selected_id,
400
+ "litellm_params": {
401
+ "model": f"marama-route/{selected_id}",
402
+ "api_base": base_url,
403
+ "api_key": "os.environ/ABTEEX_MARAMA_API_KEY",
404
+ },
405
+ },
406
+ ],
407
+ "router_settings": {
408
+ "routing_strategy": "simple-shuffle",
409
+ "num_retries": 1,
410
+ },
411
+ }
412
+ config["workspace_files"] = {
413
+ "litellm.marama-route.json": [
414
+ json.dumps(config["litellm_config"], indent=2, sort_keys=True),
415
+ ],
416
+ }
417
+ config["notes"] = [
418
+ "This file is a local gateway reference for LiteLLM-style proxy deployments.",
419
+ "Keep the MaramaRoute gateway running while the proxy sends model traffic.",
420
+ ]
421
+ elif normalized == "tabby":
422
+ config["tabby_config"] = {
423
+ "model": selected_id,
424
+ "base_url": base_url,
425
+ "start_command": commands["start_gateway"],
426
+ "pull_command": commands["pull"],
427
+ }
428
+ config["workspace_files"] = {
429
+ "tabby.marama-route.json": [
430
+ json.dumps(config["tabby_config"], indent=2, sort_keys=True),
431
+ ],
432
+ }
433
+ config["notes"] = [
434
+ "Use this as a Tabby workspace note for routing local coding tasks through LumynaX models.",
435
+ "Verify model quality with `MaramaRoute bench` before production use.",
436
+ ]
437
+ elif normalized == "hpe-slurm":
438
  config["target"] = "hpe-slurm"
439
  config["environment"].update(
440
  {
 
461
  }
462
  elif normalized == "opencode":
463
  config["provider_config"] = build_opencode_provider_config(models, base_url=base_url)
464
+ config["workspace_files"] = {
465
+ "opencode.marama-route.json": [
466
+ json.dumps(config["provider_config"], indent=2, sort_keys=True),
467
+ ],
468
+ }
469
+ config["notes"] = [
470
+ "Drop the provider config into the workspace or user config directory used by OpenCode.",
471
+ "Run the MaramaRoute gateway first when using live local routing.",
472
+ ]
473
+ elif normalized not in agent_targets:
474
+ config["ok"] = False
475
+ config["error"] = f"Unsupported agent target: {target}"
476
  return config
477
 
478
 
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "lumynax-marama-route"
7
- version = "0.7.18"
8
  description = "LumynaX MaramaRoute: conversational AbteeX AI Labs model CLI, downloader, and local runtime for LumynaX releases."
9
  readme = "README.md"
10
  requires-python = ">=3.11"
 
4
 
5
  [project]
6
  name = "lumynax-marama-route"
7
+ version = "0.8.0"
8
  description = "LumynaX MaramaRoute: conversational AbteeX AI Labs model CLI, downloader, and local runtime for LumynaX releases."
9
  readme = "README.md"
10
  requires-python = ">=3.11"