|
21 | 21 | from datetime import datetime, timezone |
22 | 22 | from pathlib import Path |
23 | 23 |
|
24 | | -from . import auth |
| 24 | +from . import auth, config |
25 | 25 | from .catalog import catalog |
26 | | -from .devices import active_handles, resolve_for_capability |
| 26 | +from .devices import CAPABILITIES, active_handles, resolve_for_capability |
27 | 27 | from .environment import ( |
28 | 28 | compose_device_config, resolve_bearer_token, setup_environment, |
29 | 29 | ) |
@@ -154,6 +154,39 @@ def do_catalog(args) -> None: |
154 | 154 | raise ValueError(f"Unknown catalog subcommand: {args.subcmd!r}") |
155 | 155 |
|
156 | 156 |
|
| 157 | +def do_device(args) -> None: |
| 158 | + if args.device_command == "list": |
| 159 | + for name in catalog.names(): |
| 160 | + handle = catalog[name] |
| 161 | + caps = [cap for cap, (predicate, _) in CAPABILITIES.items() |
| 162 | + if predicate(handle)] |
| 163 | + print(f"{name}\t{handle.description}\t" |
| 164 | + f"[{', '.join(caps) if caps else 'none'}]") |
| 165 | + elif args.device_command == "show": |
| 166 | + import yaml |
| 167 | + print(yaml.safe_dump(catalog[args.name].schema.model_dump(), |
| 168 | + sort_keys=False)) |
| 169 | + elif args.device_command == "use": |
| 170 | + if args.clear: |
| 171 | + config.set_default_device(None) |
| 172 | + print("Cleared the default device.") |
| 173 | + return |
| 174 | + if args.name is None: |
| 175 | + print("Error: `fdp device use` needs a device name " |
| 176 | + "(or --clear).", file=sys.stderr) |
| 177 | + sys.exit(1) |
| 178 | + if args.name not in catalog: |
| 179 | + print(f"Error: unknown device {args.name!r}. Registered: " |
| 180 | + f"{', '.join(catalog.names())}", file=sys.stderr) |
| 181 | + sys.exit(1) |
| 182 | + config.set_default_device(args.name) |
| 183 | + print(f"Default device set to '{args.name}' in " |
| 184 | + f"{config.config_path()}.") |
| 185 | + else: |
| 186 | + raise ValueError( |
| 187 | + f"Unknown device subcommand: {args.device_command!r}") |
| 188 | + |
| 189 | + |
157 | 190 | def do_skills(args) -> None: |
158 | 191 | skill_dirs = discover_skill_dirs() |
159 | 192 | backend_arg = getattr(args, "backend", "claude") |
@@ -312,13 +345,26 @@ def build_parser() -> argparse.ArgumentParser: |
312 | 345 | help="The path whose contents will be listed") |
313 | 346 | p_ls.set_defaults(func=do_ls) |
314 | 347 |
|
315 | | - p_cat = sub.add_parser("catalog", help="Inspect the tokamak catalog") |
| 348 | + p_cat = sub.add_parser("catalog", |
| 349 | + help="(deprecated) alias for `fdp device`") |
316 | 350 | cat_sub = p_cat.add_subparsers(dest="subcmd", required=True) |
317 | 351 | cat_sub.add_parser("list", help="List tokamak names and descriptions") |
318 | 352 | show = cat_sub.add_parser("show", help="Print a tokamak's full catalog YAML") |
319 | 353 | show.add_argument("name") |
320 | 354 | p_cat.set_defaults(func=do_catalog, needs_env=False) |
321 | 355 |
|
| 356 | + p_dev = sub.add_parser("device", help="Inspect and select devices") |
| 357 | + dev_sub = p_dev.add_subparsers(dest="device_command", required=True) |
| 358 | + dev_sub.add_parser("list", help="List devices and their capabilities") |
| 359 | + dev_show = dev_sub.add_parser("show", help="Print a device's catalog YAML") |
| 360 | + dev_show.add_argument("name") |
| 361 | + dev_use = dev_sub.add_parser( |
| 362 | + "use", help="Record a default device in ~/.fdp/config.toml") |
| 363 | + dev_use.add_argument("name", nargs="?", default=None) |
| 364 | + dev_use.add_argument("--clear", action="store_true", |
| 365 | + help="Remove the recorded default device.") |
| 366 | + p_dev.set_defaults(func=do_device, needs_env=False) |
| 367 | + |
322 | 368 | p_sk = sub.add_parser("skills", |
323 | 369 | help="Manage AI assistant skills") |
324 | 370 | sk_sub = p_sk.add_subparsers(dest="skills_command", required=True) |
|
0 commit comments