@foggy-projects/deepseek-harness-plugin 0.4.0-beta.6 → 0.4.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -6
- package/THIRD-PARTY-RUNTIME-NOTICES.md +16 -0
- package/docs/PUBLIC-BETA-READINESS.md +51 -0
- package/docs/WINDOWS-BETA-ACCEPTANCE.md +71 -0
- package/experience/linux/README.md +5 -6
- package/experience/linux/prepare.sh +7 -9
- package/lib/client.js +132 -6
- package/lib/index.js +157 -28
- package/lib/python-runtime.js +339 -0
- package/lib/remote-descriptor.js +1 -0
- package/lib/version.js +5 -0
- package/package.json +6 -3
- package/skills/foggy-deepseek-onboarding/SKILL.md +24 -5
- package/skills/foggy-deepseek-onboarding/assets/onboarding-state.schema.json +20 -0
- package/skills/foggy-deepseek-onboarding/assets/versions.json +46 -2
- package/skills/foggy-deepseek-onboarding/references/onboarding-workflow.md +18 -4
- package/skills/foggy-deepseek-onboarding/scripts/doctor.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/doctor.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/install.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/install.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.ps1 +30 -0
- package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.sh +24 -0
- package/skills/foggy-deepseek-onboarding/scripts/onboard.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/onboard.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +400 -43
- package/skills/foggy-deepseek-onboarding/scripts/runtime-start.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/runtime-start.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.sh +1 -2
- package/skills/foggy-deepseek-onboarding/scripts/uninstall.ps1 +2 -2
- package/skills/foggy-deepseek-onboarding/scripts/uninstall.sh +1 -2
|
@@ -34,6 +34,7 @@ PROFILE_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,62}$")
|
|
|
34
34
|
ENV_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
35
35
|
OPAQUE_PROFILE_PATTERN = re.compile(r"^fop_[a-f0-9]{32}$")
|
|
36
36
|
OPAQUE_REVISION_PATTERN = re.compile(r"^sha256:[a-f0-9]{64}$")
|
|
37
|
+
OPAQUE_PROFILE_SCHEMA = "foggy-runtime-onboarding-profile/v1"
|
|
37
38
|
MODEL_NAME_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,127}$")
|
|
38
39
|
|
|
39
40
|
|
|
@@ -46,7 +47,7 @@ ACTIVE_PROGRESS: "ProgressReporter | None" = None
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class ProgressReporter:
|
|
49
|
-
total_steps =
|
|
50
|
+
total_steps = 7
|
|
50
51
|
|
|
51
52
|
def __init__(self, path: str | None, operation_id: str | None, kind: str) -> None:
|
|
52
53
|
self.path = Path(path).expanduser() if path else None
|
|
@@ -283,11 +284,20 @@ def materialize(
|
|
|
283
284
|
progress_index: int = 0,
|
|
284
285
|
progress_total: int = 1,
|
|
285
286
|
progress_message: str = "Downloading and verifying asset",
|
|
287
|
+
replace_corrupt: bool = False,
|
|
286
288
|
) -> dict:
|
|
287
289
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
288
290
|
if destination.is_file():
|
|
289
|
-
|
|
290
|
-
|
|
291
|
+
try:
|
|
292
|
+
verify_asset(destination, asset["sha256"])
|
|
293
|
+
return {"file": asset["file"], "path": str(destination), "source": "existing", "sha256": asset["sha256"]}
|
|
294
|
+
except OnboardingError:
|
|
295
|
+
if not replace_corrupt:
|
|
296
|
+
raise
|
|
297
|
+
quarantine = destination.with_name(
|
|
298
|
+
destination.name + f".corrupt-{dt.datetime.now().strftime('%Y%m%d-%H%M%S-%f')}"
|
|
299
|
+
)
|
|
300
|
+
destination.replace(quarantine)
|
|
291
301
|
cached = cached_asset(asset["file"], asset["sha256"], cache_dirs)
|
|
292
302
|
if cached:
|
|
293
303
|
shutil.copy2(cached, destination)
|
|
@@ -461,6 +471,153 @@ def configure_profile_store(data_root: Path, *, create: bool = True) -> Path:
|
|
|
461
471
|
return destination
|
|
462
472
|
|
|
463
473
|
|
|
474
|
+
def legacy_profile_stores(destination: Path) -> list[Path]:
|
|
475
|
+
configured = [
|
|
476
|
+
normalized(item)
|
|
477
|
+
for item in os.environ.get("FOGGY_RUNTIME_PROFILE_LEGACY_STORES", "").split(os.pathsep)
|
|
478
|
+
if item.strip()
|
|
479
|
+
]
|
|
480
|
+
default_legacy = Path(tempfile.gettempdir()) / "foggy-profiles"
|
|
481
|
+
candidates = [*configured, normalized(default_legacy)]
|
|
482
|
+
unique: list[Path] = []
|
|
483
|
+
for candidate in candidates:
|
|
484
|
+
if candidate == destination or candidate in unique:
|
|
485
|
+
continue
|
|
486
|
+
unique.append(candidate)
|
|
487
|
+
return unique
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def validate_opaque_profile_document(payload: dict, path: Path) -> dict:
|
|
491
|
+
allowed = {"schemaVersion", "profileId", "revision", "createdAt", "updatedAt", "connection"}
|
|
492
|
+
unexpected = sorted(set(payload) - allowed)
|
|
493
|
+
if unexpected:
|
|
494
|
+
raise OnboardingError(f"Unsupported opaque profile fields in {path.name}: {', '.join(unexpected)}")
|
|
495
|
+
if payload.get("schemaVersion") != OPAQUE_PROFILE_SCHEMA:
|
|
496
|
+
raise OnboardingError(f"Unexpected opaque profile schema in {path.name}")
|
|
497
|
+
profile_id = payload.get("profileId")
|
|
498
|
+
revision = payload.get("revision")
|
|
499
|
+
if not isinstance(profile_id, str) or not OPAQUE_PROFILE_PATTERN.fullmatch(profile_id):
|
|
500
|
+
raise OnboardingError(f"Invalid opaque profile ID in {path.name}")
|
|
501
|
+
if path.stem != profile_id:
|
|
502
|
+
raise OnboardingError(f"Opaque profile filename does not match its ID: {path.name}")
|
|
503
|
+
if not isinstance(revision, str) or not OPAQUE_REVISION_PATTERN.fullmatch(revision):
|
|
504
|
+
raise OnboardingError(f"Invalid opaque profile revision in {path.name}")
|
|
505
|
+
connection = payload.get("connection")
|
|
506
|
+
if not isinstance(connection, dict):
|
|
507
|
+
raise OnboardingError(f"Opaque profile connection is missing in {path.name}")
|
|
508
|
+
allowed_connection = {"name", "type", "jdbcUrl", "username", "passwordEnv", "namespace"}
|
|
509
|
+
unexpected_connection = sorted(set(connection) - allowed_connection)
|
|
510
|
+
if unexpected_connection:
|
|
511
|
+
raise OnboardingError(
|
|
512
|
+
f"Unsupported opaque connection fields in {path.name}: {', '.join(unexpected_connection)}"
|
|
513
|
+
)
|
|
514
|
+
for name in ("name", "type", "jdbcUrl", "namespace"):
|
|
515
|
+
if not isinstance(connection.get(name), str) or not connection[name].strip():
|
|
516
|
+
raise OnboardingError(f"Opaque profile connection.{name} is invalid in {path.name}")
|
|
517
|
+
password_env = connection.get("passwordEnv")
|
|
518
|
+
if password_env is not None and (
|
|
519
|
+
not isinstance(password_env, str) or not ENV_NAME_PATTERN.fullmatch(password_env)
|
|
520
|
+
):
|
|
521
|
+
raise OnboardingError(f"Opaque profile passwordEnv is invalid in {path.name}")
|
|
522
|
+
jdbc_url = connection["jdbcUrl"]
|
|
523
|
+
if re.search(r"(?i)(?:password|passwd|pwd)\s*=", jdbc_url) or re.search(r"//[^/@:]+:[^/@]+@", jdbc_url):
|
|
524
|
+
raise OnboardingError(f"Opaque profile embeds a password in jdbcUrl: {path.name}")
|
|
525
|
+
return payload
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def profile_migration_inventory(data_root: Path) -> dict:
|
|
529
|
+
destination = configure_profile_store(data_root, create=False)
|
|
530
|
+
entries: list[dict] = []
|
|
531
|
+
for source in legacy_profile_stores(destination):
|
|
532
|
+
if not source.is_dir():
|
|
533
|
+
continue
|
|
534
|
+
for path in sorted(source.glob("fop_*.json")):
|
|
535
|
+
try:
|
|
536
|
+
payload = validate_opaque_profile_document(read_json_object(path, "Opaque profile"), path)
|
|
537
|
+
target = destination / path.name
|
|
538
|
+
status = "pending"
|
|
539
|
+
if target.is_file():
|
|
540
|
+
existing = validate_opaque_profile_document(read_json_object(target, "Opaque profile"), target)
|
|
541
|
+
status = "migrated" if existing == payload else "conflict"
|
|
542
|
+
entries.append({
|
|
543
|
+
"profileId": payload["profileId"],
|
|
544
|
+
"revision": payload["revision"],
|
|
545
|
+
"source": str(source),
|
|
546
|
+
"destination": str(destination),
|
|
547
|
+
"status": status,
|
|
548
|
+
})
|
|
549
|
+
except OnboardingError as exc:
|
|
550
|
+
entries.append({
|
|
551
|
+
"profileId": path.stem if OPAQUE_PROFILE_PATTERN.fullmatch(path.stem) else None,
|
|
552
|
+
"source": str(source),
|
|
553
|
+
"destination": str(destination),
|
|
554
|
+
"status": "invalid",
|
|
555
|
+
"error": str(exc),
|
|
556
|
+
})
|
|
557
|
+
return {
|
|
558
|
+
"schemaVersion": "foggy-deepseek-profile-migration-status/v1",
|
|
559
|
+
"profileStore": str(destination),
|
|
560
|
+
"legacyStores": [str(path) for path in legacy_profile_stores(destination)],
|
|
561
|
+
"entries": entries,
|
|
562
|
+
"pendingCount": sum(item["status"] == "pending" for item in entries),
|
|
563
|
+
"conflictCount": sum(item["status"] in {"conflict", "invalid"} for item in entries),
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def profile_migration_status_command(args: argparse.Namespace) -> dict:
|
|
568
|
+
install_root = normalized(args.install_root or default_install_root())
|
|
569
|
+
install_state = read_install_state(install_root)
|
|
570
|
+
data_root = normalized(args.data_root or install_state["dataRoot"])
|
|
571
|
+
return {"success": True, **profile_migration_inventory(data_root), "productionReady": False}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def profile_migrate_command(args: argparse.Namespace) -> dict:
|
|
575
|
+
if not args.approve:
|
|
576
|
+
raise OnboardingError("Profile migration requires --approve")
|
|
577
|
+
install_root = normalized(args.install_root or default_install_root())
|
|
578
|
+
install_state = read_install_state(install_root)
|
|
579
|
+
data_root = normalized(args.data_root or install_state["dataRoot"])
|
|
580
|
+
inventory = profile_migration_inventory(data_root)
|
|
581
|
+
if inventory["conflictCount"]:
|
|
582
|
+
raise OnboardingError("Legacy profile migration has conflicts or invalid entries; inspect status first")
|
|
583
|
+
destination = configure_profile_store(data_root)
|
|
584
|
+
migrated: list[dict] = []
|
|
585
|
+
for item in inventory["entries"]:
|
|
586
|
+
if item["status"] != "pending":
|
|
587
|
+
continue
|
|
588
|
+
source = normalized(Path(item["source"]) / f"{item['profileId']}.json")
|
|
589
|
+
payload = validate_opaque_profile_document(read_json_object(source, "Opaque profile"), source)
|
|
590
|
+
target = destination / source.name
|
|
591
|
+
atomic_json(target, payload)
|
|
592
|
+
if os.name != "nt":
|
|
593
|
+
target.chmod(0o600)
|
|
594
|
+
if read_json_object(target, "Migrated opaque profile") != payload:
|
|
595
|
+
target.unlink(missing_ok=True)
|
|
596
|
+
raise OnboardingError(f"Migrated profile verification failed: {source.name}")
|
|
597
|
+
backup_root = data_root / "profile-migration-backups" / dt.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
|
|
598
|
+
backup_root.mkdir(parents=True, exist_ok=True)
|
|
599
|
+
if os.name != "nt":
|
|
600
|
+
backup_root.chmod(0o700)
|
|
601
|
+
archived = backup_root / (source.name + ".bak")
|
|
602
|
+
source.replace(archived)
|
|
603
|
+
if os.name != "nt":
|
|
604
|
+
archived.chmod(0o600)
|
|
605
|
+
migrated.append({
|
|
606
|
+
"profileId": payload["profileId"],
|
|
607
|
+
"revision": payload["revision"],
|
|
608
|
+
"destination": str(target),
|
|
609
|
+
"legacyBackup": str(archived),
|
|
610
|
+
})
|
|
611
|
+
return {
|
|
612
|
+
"success": True,
|
|
613
|
+
"schemaVersion": "foggy-deepseek-profile-migration/v1",
|
|
614
|
+
"profileStore": str(destination),
|
|
615
|
+
"migrated": migrated,
|
|
616
|
+
"migratedCount": len(migrated),
|
|
617
|
+
"productionReady": False,
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
|
|
464
621
|
def resolve_profile_name(data_root: Path, requested: str | None) -> str:
|
|
465
622
|
if requested:
|
|
466
623
|
return safe_profile(requested)
|
|
@@ -825,6 +982,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
825
982
|
data_root = normalized(args.data_root or default_data_root())
|
|
826
983
|
cache_dirs = [normalized(item) for item in args.asset_cache_dir]
|
|
827
984
|
components = versions["components"]
|
|
985
|
+
repair_component = getattr(args, "repair_component", None)
|
|
828
986
|
assert_managed_root(install_root, "Install root")
|
|
829
987
|
assert_managed_root(data_root, "Data root")
|
|
830
988
|
if install_root == data_root:
|
|
@@ -837,7 +995,8 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
837
995
|
"profileStore": str(profile_store),
|
|
838
996
|
"workspaceMode": "dsh-session-cwd",
|
|
839
997
|
"versions": {name: value.get("version") for name, value in components.items()},
|
|
840
|
-
"
|
|
998
|
+
"repairComponent": repair_component,
|
|
999
|
+
"operations": ["verify private Python", "install isolated CLI", "verify Launcher assets", "install global analysis Skill", "write install state"],
|
|
841
1000
|
"productionReady": False,
|
|
842
1001
|
}
|
|
843
1002
|
if args.dry_run:
|
|
@@ -849,7 +1008,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
849
1008
|
getattr(args, "operation_kind", "initialize"),
|
|
850
1009
|
)
|
|
851
1010
|
ACTIVE_PROGRESS = progress
|
|
852
|
-
progress.update("
|
|
1011
|
+
progress.update("python", 1, "Managed Python ready", fraction=1.0, current_file=sys.executable)
|
|
853
1012
|
if sys.version_info < (3, 11):
|
|
854
1013
|
raise OnboardingError(f"Python 3.11+ required, got {sys.version.split()[0]}")
|
|
855
1014
|
install_root.mkdir(parents=True, exist_ok=True)
|
|
@@ -861,18 +1020,19 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
861
1020
|
cli_assets = [cli_component[role] for role in ("wheel", "checksums")]
|
|
862
1021
|
for index, asset in enumerate(cli_assets):
|
|
863
1022
|
progress.update(
|
|
864
|
-
"cli",
|
|
1023
|
+
"cli", 2, "Downloading and verifying CLI",
|
|
865
1024
|
fraction=index / len(cli_assets), current_file=asset["file"],
|
|
866
1025
|
completed_files=index, total_files=len(cli_assets),
|
|
867
1026
|
)
|
|
868
1027
|
verified.append(materialize(
|
|
869
1028
|
asset, downloads / "cli" / asset["file"], cache_dirs,
|
|
870
|
-
progress=progress, progress_phase="cli", progress_step=
|
|
1029
|
+
progress=progress, progress_phase="cli", progress_step=2,
|
|
871
1030
|
progress_index=index, progress_total=len(cli_assets),
|
|
872
1031
|
progress_message="Downloading and verifying CLI",
|
|
1032
|
+
replace_corrupt=repair_component == "cli",
|
|
873
1033
|
))
|
|
874
1034
|
progress.update(
|
|
875
|
-
"cli",
|
|
1035
|
+
"cli", 2, "Downloading and verifying CLI",
|
|
876
1036
|
fraction=(index + 1) / len(cli_assets), current_file=asset["file"],
|
|
877
1037
|
completed_files=index + 1, total_files=len(cli_assets),
|
|
878
1038
|
)
|
|
@@ -882,13 +1042,13 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
882
1042
|
if checksum_entries.get(wheel_asset["file"]) != wheel_asset["sha256"]:
|
|
883
1043
|
raise OnboardingError("Pinned CLI SHA256SUMS does not match the pinned wheel hash")
|
|
884
1044
|
if args.skip_cli_install:
|
|
885
|
-
progress.update("cli",
|
|
1045
|
+
progress.update("cli", 2, "Using existing CLI", fraction=0.65, current_file="foggy-runtime")
|
|
886
1046
|
cli_command = normalized(args.cli_command or shutil.which("foggy-runtime") or "")
|
|
887
1047
|
if not cli_command.is_file():
|
|
888
1048
|
raise OnboardingError("--skip-cli-install requires --cli-command or foggy-runtime on PATH")
|
|
889
1049
|
cli_mode = "external"
|
|
890
1050
|
else:
|
|
891
|
-
progress.update("cli",
|
|
1051
|
+
progress.update("cli", 2, "Installing CLI", fraction=0.65, current_file="Python virtual environment")
|
|
892
1052
|
python_path = venv_python(install_root)
|
|
893
1053
|
if not python_path.is_file():
|
|
894
1054
|
venv.EnvBuilder(with_pip=True).create(install_root / "venv")
|
|
@@ -903,64 +1063,66 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
903
1063
|
)
|
|
904
1064
|
cli_command = venv_cli(install_root)
|
|
905
1065
|
cli_mode = "managed-venv"
|
|
906
|
-
progress.update("cli",
|
|
1066
|
+
progress.update("cli", 2, "Verifying CLI version", fraction=0.9, current_file="foggy-runtime")
|
|
907
1067
|
cli_version = command_result([str(cli_command), "--version"], check=True)
|
|
908
1068
|
actual_cli_version = version_tuple(cli_version["stdout"])[:3]
|
|
909
1069
|
pinned_cli_version = version_tuple(cli_component["version"])[:3]
|
|
910
1070
|
version_matches = actual_cli_version >= pinned_cli_version if cli_mode == "external" else actual_cli_version == pinned_cli_version
|
|
911
1071
|
if not version_matches:
|
|
912
1072
|
raise OnboardingError(f"Unexpected CLI version: {cli_version['stdout']}")
|
|
913
|
-
progress.update("cli",
|
|
1073
|
+
progress.update("cli", 2, "CLI ready", fraction=1.0)
|
|
914
1074
|
|
|
915
1075
|
launcher_dir = install_root / "launcher"
|
|
916
1076
|
launcher_assets = components["launcher"]["assets"]
|
|
917
1077
|
for index, asset in enumerate(launcher_assets):
|
|
918
1078
|
progress.update(
|
|
919
|
-
"launcher",
|
|
1079
|
+
"launcher", 3, "Downloading and verifying Launcher",
|
|
920
1080
|
fraction=index / len(launcher_assets), current_file=asset["file"],
|
|
921
1081
|
completed_files=index, total_files=len(launcher_assets),
|
|
922
1082
|
)
|
|
923
1083
|
verified.append(materialize(
|
|
924
1084
|
asset, launcher_dir / asset["file"], cache_dirs,
|
|
925
|
-
progress=progress, progress_phase="launcher", progress_step=
|
|
1085
|
+
progress=progress, progress_phase="launcher", progress_step=3,
|
|
926
1086
|
progress_index=index, progress_total=len(launcher_assets),
|
|
927
1087
|
progress_message="Downloading and verifying Launcher",
|
|
1088
|
+
replace_corrupt=repair_component == "launcher",
|
|
928
1089
|
))
|
|
929
1090
|
progress.update(
|
|
930
|
-
"launcher",
|
|
1091
|
+
"launcher", 3, "Downloading and verifying Launcher",
|
|
931
1092
|
fraction=(index + 1) / len(launcher_assets), current_file=asset["file"],
|
|
932
1093
|
completed_files=index + 1, total_files=len(launcher_assets),
|
|
933
1094
|
)
|
|
934
1095
|
if os.name != "nt":
|
|
935
1096
|
(launcher_dir / "start-foggy-runtime.sh").chmod(0o755)
|
|
936
|
-
progress.update("launcher",
|
|
1097
|
+
progress.update("launcher", 3, "Launcher ready", fraction=1.0)
|
|
937
1098
|
|
|
938
1099
|
analysis_assets = components["analysisSkill"]["assets"]
|
|
939
1100
|
for index, asset in enumerate(analysis_assets):
|
|
940
1101
|
progress.update(
|
|
941
|
-
"analysis-skill",
|
|
1102
|
+
"analysis-skill", 4, "Downloading and verifying analysis Skill",
|
|
942
1103
|
fraction=index / len(analysis_assets), current_file=asset["file"],
|
|
943
1104
|
completed_files=index, total_files=len(analysis_assets),
|
|
944
1105
|
)
|
|
945
1106
|
verified.append(materialize(
|
|
946
1107
|
asset, downloads / "skill" / asset["file"], cache_dirs,
|
|
947
|
-
progress=progress, progress_phase="analysis-skill", progress_step=
|
|
1108
|
+
progress=progress, progress_phase="analysis-skill", progress_step=4,
|
|
948
1109
|
progress_index=index, progress_total=len(analysis_assets),
|
|
949
1110
|
progress_message="Downloading and verifying analysis Skill",
|
|
1111
|
+
replace_corrupt=repair_component == "analysis-skill",
|
|
950
1112
|
))
|
|
951
1113
|
progress.update(
|
|
952
|
-
"analysis-skill",
|
|
1114
|
+
"analysis-skill", 4, "Downloading and verifying analysis Skill",
|
|
953
1115
|
fraction=(index + 1) / len(analysis_assets), current_file=asset["file"],
|
|
954
1116
|
completed_files=index + 1, total_files=len(analysis_assets),
|
|
955
1117
|
)
|
|
956
1118
|
zip_asset = next(item for item in analysis_assets if item["role"] == "zip")
|
|
957
|
-
progress.update("analysis-skill",
|
|
1119
|
+
progress.update("analysis-skill", 4, "Installing analysis Skill", fraction=0.9, current_file=zip_asset["file"])
|
|
958
1120
|
analysis_skill = install_analysis_skill(
|
|
959
1121
|
downloads / "skill" / zip_asset["file"], install_root, components["analysisSkill"]["version"],
|
|
960
|
-
zip_asset["sha256"], versions["packageVersion"], args.replace_skill,
|
|
1122
|
+
zip_asset["sha256"], versions["packageVersion"], args.replace_skill or repair_component == "analysis-skill",
|
|
961
1123
|
)
|
|
962
|
-
progress.update("analysis-skill",
|
|
963
|
-
progress.update("workspace-skills",
|
|
1124
|
+
progress.update("analysis-skill", 4, "Analysis Skill ready", fraction=1.0)
|
|
1125
|
+
progress.update("workspace-skills", 5, "Registering native DSH Skills", fraction=0.1)
|
|
964
1126
|
onboarding_skill = {
|
|
965
1127
|
"path": str(skill_root()),
|
|
966
1128
|
"version": versions["packageVersion"],
|
|
@@ -968,7 +1130,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
968
1130
|
"action": "provided-by-plugin",
|
|
969
1131
|
"provider": "foggy-managed-skills",
|
|
970
1132
|
}
|
|
971
|
-
progress.update("workspace-skills",
|
|
1133
|
+
progress.update("workspace-skills", 5, "Native DSH Skills ready", fraction=1.0)
|
|
972
1134
|
state = {
|
|
973
1135
|
"schemaVersion": STATE_SCHEMA,
|
|
974
1136
|
"installedAt": now_utc(),
|
|
@@ -977,6 +1139,11 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
977
1139
|
"dataRoot": str(data_root),
|
|
978
1140
|
"profileStore": str(profile_store),
|
|
979
1141
|
"workspaceMode": "dsh-session-cwd",
|
|
1142
|
+
"python": {
|
|
1143
|
+
"version": components["python"]["version"],
|
|
1144
|
+
"command": sys.executable,
|
|
1145
|
+
"mode": os.environ.get("FOGGY_ONBOARDING_PYTHON_SOURCE", "managed"),
|
|
1146
|
+
},
|
|
980
1147
|
"cli": {"version": cli_component["version"], "command": str(cli_command), "mode": cli_mode},
|
|
981
1148
|
"launcher": {"version": components["launcher"]["version"], "path": str(launcher_dir)},
|
|
982
1149
|
"skills": {"onboarding": onboarding_skill, "analysis": analysis_skill},
|
|
@@ -984,7 +1151,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
984
1151
|
"securityMode": versions["defaults"]["securityMode"],
|
|
985
1152
|
"productionReady": False,
|
|
986
1153
|
}
|
|
987
|
-
progress.update("state",
|
|
1154
|
+
progress.update("state", 6, "Writing install state", fraction=0.2, current_file="install-state.json")
|
|
988
1155
|
atomic_json(install_root / "install-state.json", state)
|
|
989
1156
|
progress.finish()
|
|
990
1157
|
ACTIVE_PROGRESS = None
|
|
@@ -1088,8 +1255,61 @@ def runtime_start_command(args: argparse.Namespace) -> dict:
|
|
|
1088
1255
|
existing = data_root / "runtime-state.json"
|
|
1089
1256
|
if existing.is_file():
|
|
1090
1257
|
prior = json.loads(existing.read_text(encoding="utf-8"))
|
|
1091
|
-
|
|
1092
|
-
|
|
1258
|
+
prior_info = process_info(int(prior.get("pid", 0)))
|
|
1259
|
+
if prior_info["running"]:
|
|
1260
|
+
expected_jar = f"foggy-runtime-launcher-{state['launcher']['version']}.jar"
|
|
1261
|
+
if prior_info.get("commandLine") and expected_jar not in prior_info["commandLine"]:
|
|
1262
|
+
raise OnboardingError(
|
|
1263
|
+
f"Recorded Runtime PID {prior['pid']} does not match the pinned Launcher"
|
|
1264
|
+
)
|
|
1265
|
+
cli = state["cli"]["command"]
|
|
1266
|
+
namespace = args.namespace or prior.get("namespace") or versions["defaults"]["namespace"]
|
|
1267
|
+
base_url = prior.get("runtimeUrl")
|
|
1268
|
+
if not isinstance(base_url, str) or not base_url:
|
|
1269
|
+
raise OnboardingError("Recorded Runtime does not contain runtimeUrl")
|
|
1270
|
+
wait_result = command_result(
|
|
1271
|
+
[cli, "--base-url", base_url, "--namespace", namespace, "--output", "json", "wait-ready",
|
|
1272
|
+
"--timeout-seconds", str(args.timeout or versions["defaults"]["readinessTimeoutSeconds"]),
|
|
1273
|
+
"--interval-seconds", "1"],
|
|
1274
|
+
timeout=(args.timeout or versions["defaults"]["readinessTimeoutSeconds"]) + 30,
|
|
1275
|
+
)
|
|
1276
|
+
wait_payload = parse_json_output(wait_result, "wait-ready")
|
|
1277
|
+
if wait_payload.get("success") is not True:
|
|
1278
|
+
raise OnboardingError("wait-ready returned success=false for recorded Runtime")
|
|
1279
|
+
capabilities = parse_json_output(
|
|
1280
|
+
command_result(
|
|
1281
|
+
[cli, "--base-url", base_url, "--namespace", namespace, "--output", "json", "capabilities"],
|
|
1282
|
+
timeout=30,
|
|
1283
|
+
),
|
|
1284
|
+
"capabilities",
|
|
1285
|
+
)
|
|
1286
|
+
expected_contract = versions["components"]["launcher"]["runtimeApiContract"]
|
|
1287
|
+
if capabilities.get("success") is not True or capabilities.get("runtimeApiVersion") != expected_contract:
|
|
1288
|
+
raise OnboardingError(f"Unexpected Runtime API contract; expected {expected_contract}")
|
|
1289
|
+
if capabilities.get("data", {}).get("securityMode") != versions["defaults"]["securityMode"]:
|
|
1290
|
+
raise OnboardingError("Recorded Runtime did not report the expected dev/test security mode")
|
|
1291
|
+
verified_at = now_utc()
|
|
1292
|
+
identity = {
|
|
1293
|
+
"engine": capabilities.get("engine"),
|
|
1294
|
+
"runtimeApiVersion": capabilities.get("runtimeApiVersion"),
|
|
1295
|
+
"schemaVersion": capabilities.get("data", {}).get("schemaVersion"),
|
|
1296
|
+
"securityMode": capabilities.get("data", {}).get("securityMode"),
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
"success": True,
|
|
1300
|
+
**prior,
|
|
1301
|
+
"identity": identity,
|
|
1302
|
+
"lastVerifiedAt": verified_at,
|
|
1303
|
+
"verification": {
|
|
1304
|
+
"waitReady": True,
|
|
1305
|
+
"capabilities": identity,
|
|
1306
|
+
"persisted": False,
|
|
1307
|
+
"instruction": "Capture this JSON in the current workspace when fresh verification evidence is required",
|
|
1308
|
+
},
|
|
1309
|
+
"action": "already-running-verified",
|
|
1310
|
+
"resumed": True,
|
|
1311
|
+
"productionReady": False,
|
|
1312
|
+
}
|
|
1093
1313
|
existing.unlink()
|
|
1094
1314
|
port = args.port or int(versions["defaults"]["port"])
|
|
1095
1315
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
|
@@ -1834,7 +2054,9 @@ def semantic_verify_command(args: argparse.Namespace) -> dict:
|
|
|
1834
2054
|
raise OnboardingError("--query-model must be declared in the registered semantic plan")
|
|
1835
2055
|
if not args.query_payload:
|
|
1836
2056
|
raise OnboardingError("--query-payload is required for semantic verification")
|
|
1837
|
-
project_root = normalized(state["projectRoot"])
|
|
2057
|
+
project_root = normalized(getattr(args, "project_root", None) or state["projectRoot"])
|
|
2058
|
+
if not project_root_is_bound(state, project_root):
|
|
2059
|
+
raise OnboardingError("Query projectRoot is not bound to this onboarding profile")
|
|
1838
2060
|
payload_path = normalized(args.query_payload)
|
|
1839
2061
|
if not is_child(payload_path, project_root):
|
|
1840
2062
|
raise OnboardingError("Query payload must stay inside projectRoot")
|
|
@@ -1885,7 +2107,20 @@ def semantic_verify_command(args: argparse.Namespace) -> dict:
|
|
|
1885
2107
|
queryExecuted=True,
|
|
1886
2108
|
rowCount=row_count,
|
|
1887
2109
|
queryPayloadDigest=sha256(payload_path),
|
|
2110
|
+
projectRoot=str(project_root),
|
|
1888
2111
|
)
|
|
2112
|
+
workspace_verifications = state.setdefault("workspaceVerifications", [])
|
|
2113
|
+
workspace_verifications[:] = [
|
|
2114
|
+
item for item in workspace_verifications
|
|
2115
|
+
if not (item.get("projectRoot") == str(project_root) and item.get("queryModel") == query_model)
|
|
2116
|
+
]
|
|
2117
|
+
workspace_verifications.append({
|
|
2118
|
+
"projectRoot": str(project_root),
|
|
2119
|
+
"queryModel": query_model,
|
|
2120
|
+
"queryPayloadDigest": sha256(payload_path),
|
|
2121
|
+
"rowCount": row_count,
|
|
2122
|
+
"verifiedAt": now_utc(),
|
|
2123
|
+
})
|
|
1889
2124
|
state.setdefault("artifacts", {})["semanticVerifyEvidence"] = str(evidence_dir)
|
|
1890
2125
|
path = write_onboarding_state(data_root, state)
|
|
1891
2126
|
return {
|
|
@@ -1944,12 +2179,63 @@ def onboarding_status_command(args: argparse.Namespace) -> dict:
|
|
|
1944
2179
|
"passwordEnv": password_env,
|
|
1945
2180
|
"passwordEnvPresent": bool(password_env and os.environ.get(password_env)),
|
|
1946
2181
|
"steps": state["steps"],
|
|
2182
|
+
"projectRoot": state.get("projectRoot"),
|
|
2183
|
+
"workspaceBindings": [str(path) for path in bound_project_roots(state)],
|
|
2184
|
+
"workspaceVerifications": state.get("workspaceVerifications", []),
|
|
1947
2185
|
"artifacts": state.get("artifacts", {}),
|
|
1948
2186
|
"next": next_onboarding_action(state),
|
|
1949
2187
|
"productionReady": False,
|
|
1950
2188
|
}
|
|
1951
2189
|
|
|
1952
2190
|
|
|
2191
|
+
def onboarding_list_command(args: argparse.Namespace) -> dict:
|
|
2192
|
+
install_root = normalized(args.install_root or default_install_root())
|
|
2193
|
+
install_state = read_install_state(install_root)
|
|
2194
|
+
data_root = normalized(args.data_root or install_state["dataRoot"])
|
|
2195
|
+
profiles_dir = data_root / "onboarding" / "profiles"
|
|
2196
|
+
profiles: list[dict] = []
|
|
2197
|
+
ordered_steps = [
|
|
2198
|
+
"planned", "datasourceConfigured", "datasourceVerified", "schemaDiscovered",
|
|
2199
|
+
"semanticDrafted", "semanticValidated", "semanticPublished", "semanticVerified",
|
|
2200
|
+
]
|
|
2201
|
+
for path in sorted(profiles_dir.glob("*.json")):
|
|
2202
|
+
if not PROFILE_PATTERN.fullmatch(path.stem):
|
|
2203
|
+
continue
|
|
2204
|
+
try:
|
|
2205
|
+
state = read_onboarding_state(data_root, path.stem)
|
|
2206
|
+
steps = state.get("steps", {})
|
|
2207
|
+
completed = sum(steps.get(name, {}).get("status") == "completed" for name in ordered_steps)
|
|
2208
|
+
profiles.append({
|
|
2209
|
+
"profile": state["profile"],
|
|
2210
|
+
"projectRoot": state.get("projectRoot"),
|
|
2211
|
+
"workspaceBindings": [str(item) for item in bound_project_roots(state)],
|
|
2212
|
+
"updatedAt": state.get("updatedAt"),
|
|
2213
|
+
"completedSteps": completed,
|
|
2214
|
+
"totalSteps": len(ordered_steps),
|
|
2215
|
+
"steps": {name: steps.get(name, {"status": "pending"}) for name in ordered_steps},
|
|
2216
|
+
"next": next_onboarding_action(state),
|
|
2217
|
+
})
|
|
2218
|
+
except OnboardingError as exc:
|
|
2219
|
+
profiles.append({
|
|
2220
|
+
"profile": path.stem,
|
|
2221
|
+
"projectRoot": None,
|
|
2222
|
+
"updatedAt": None,
|
|
2223
|
+
"completedSteps": 0,
|
|
2224
|
+
"totalSteps": len(ordered_steps),
|
|
2225
|
+
"steps": {},
|
|
2226
|
+
"next": {"status": "invalid"},
|
|
2227
|
+
"error": str(exc),
|
|
2228
|
+
})
|
|
2229
|
+
profiles.sort(key=lambda item: item.get("updatedAt") or "", reverse=True)
|
|
2230
|
+
return {
|
|
2231
|
+
"success": True,
|
|
2232
|
+
"schemaVersion": "foggy-deepseek-onboarding-list/v1",
|
|
2233
|
+
"profiles": profiles,
|
|
2234
|
+
"profileCount": len(profiles),
|
|
2235
|
+
"productionReady": False,
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
|
|
1953
2239
|
def onboarding_resume_command(args: argparse.Namespace) -> dict:
|
|
1954
2240
|
result = onboarding_status_command(args)
|
|
1955
2241
|
result["schemaVersion"] = "foggy-deepseek-onboarding-resume/v1"
|
|
@@ -1986,6 +2272,37 @@ def step_completed(state: dict, name: str) -> bool:
|
|
|
1986
2272
|
return state.get("steps", {}).get(name, {}).get("status") == "completed"
|
|
1987
2273
|
|
|
1988
2274
|
|
|
2275
|
+
def bound_project_roots(state: dict) -> list[Path]:
|
|
2276
|
+
values = [state.get("projectRoot"), *state.get("workspaceBindings", [])]
|
|
2277
|
+
result: list[Path] = []
|
|
2278
|
+
for value in values:
|
|
2279
|
+
if not isinstance(value, str) or not value:
|
|
2280
|
+
continue
|
|
2281
|
+
path = normalized(value)
|
|
2282
|
+
if path not in result:
|
|
2283
|
+
result.append(path)
|
|
2284
|
+
return result
|
|
2285
|
+
|
|
2286
|
+
|
|
2287
|
+
def project_root_is_bound(state: dict, project_root: Path) -> bool:
|
|
2288
|
+
return normalized(project_root) in bound_project_roots(state)
|
|
2289
|
+
|
|
2290
|
+
|
|
2291
|
+
def bind_completed_workspace(state: dict, data_root: Path, project_root: Path) -> bool:
|
|
2292
|
+
project_root = normalized(project_root)
|
|
2293
|
+
if project_root_is_bound(state, project_root):
|
|
2294
|
+
return False
|
|
2295
|
+
required = ("datasourceConfigured", "datasourceVerified", "schemaDiscovered", "semanticPublished")
|
|
2296
|
+
if not all(step_completed(state, name) for name in required):
|
|
2297
|
+
raise OnboardingError(
|
|
2298
|
+
"Existing onboarding profile belongs to a different projectRoot and is not complete enough for safe reuse; "
|
|
2299
|
+
"resume from its original DSH workspace or choose a new profile name"
|
|
2300
|
+
)
|
|
2301
|
+
state.setdefault("workspaceBindings", []).append(str(project_root))
|
|
2302
|
+
write_onboarding_state(data_root, state)
|
|
2303
|
+
return True
|
|
2304
|
+
|
|
2305
|
+
|
|
1989
2306
|
def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
1990
2307
|
_install_root, install_state, data_root, _runtime_state = onboarding_context(args, require_runtime=True)
|
|
1991
2308
|
project_root = normalized(args.project_root or Path.cwd())
|
|
@@ -2005,16 +2322,14 @@ def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
|
2005
2322
|
if existing:
|
|
2006
2323
|
if existing.get("connection") != requested_connection:
|
|
2007
2324
|
raise OnboardingError("Existing onboarding profile does not match the requested connection plan")
|
|
2008
|
-
|
|
2009
|
-
raise OnboardingError(
|
|
2010
|
-
"Existing onboarding profile belongs to a different projectRoot; resume from that DSH workspace "
|
|
2011
|
-
"or choose a new profile name"
|
|
2012
|
-
)
|
|
2325
|
+
adopted = bind_completed_workspace(existing, data_root, project_root)
|
|
2013
2326
|
plan_result = {
|
|
2014
2327
|
"success": True,
|
|
2015
2328
|
"schemaVersion": "foggy-deepseek-onboarding-plan-result/v1",
|
|
2016
2329
|
"profile": profile,
|
|
2017
2330
|
"resumed": True,
|
|
2331
|
+
"workspaceAdopted": adopted,
|
|
2332
|
+
"projectRoot": str(project_root),
|
|
2018
2333
|
"statePath": str(onboarding_state_path(data_root, profile)),
|
|
2019
2334
|
"next": next_onboarding_action(existing),
|
|
2020
2335
|
"productionReady": False,
|
|
@@ -2119,8 +2434,8 @@ def datasource_run_command(args: argparse.Namespace) -> dict:
|
|
|
2119
2434
|
}
|
|
2120
2435
|
|
|
2121
2436
|
|
|
2122
|
-
def semantic_plan_snapshot(state: dict, plan: dict) -> tuple[Path, dict, bool]:
|
|
2123
|
-
project_root = normalized(state["projectRoot"])
|
|
2437
|
+
def semantic_plan_snapshot(state: dict, plan: dict, project_root: Path | None = None) -> tuple[Path, dict, bool]:
|
|
2438
|
+
project_root = normalized(project_root or state["projectRoot"])
|
|
2124
2439
|
draft_dir = normalized(project_root / plan["draftDir"])
|
|
2125
2440
|
if not is_child(draft_dir, project_root) or draft_dir == project_root:
|
|
2126
2441
|
raise OnboardingError("draftDir must stay inside projectRoot and cannot equal it")
|
|
@@ -2142,6 +2457,22 @@ def published_digest_matches(state: dict, digest: str) -> bool:
|
|
|
2142
2457
|
return published.get("status") == "completed" and published.get("digest") == digest
|
|
2143
2458
|
|
|
2144
2459
|
|
|
2460
|
+
def workspace_query_verification(state: dict, project_root: Path, query_model: str, digest: str) -> dict | None:
|
|
2461
|
+
requested_root = str(normalized(project_root))
|
|
2462
|
+
candidates = list(state.get("workspaceVerifications", []))
|
|
2463
|
+
legacy = state.get("steps", {}).get("semanticVerified", {})
|
|
2464
|
+
if legacy.get("status") == "completed" and legacy.get("projectRoot"):
|
|
2465
|
+
candidates.append(legacy)
|
|
2466
|
+
for item in candidates:
|
|
2467
|
+
if (
|
|
2468
|
+
item.get("projectRoot") == requested_root
|
|
2469
|
+
and item.get("queryModel") == query_model
|
|
2470
|
+
and item.get("queryPayloadDigest") == digest
|
|
2471
|
+
):
|
|
2472
|
+
return item
|
|
2473
|
+
return None
|
|
2474
|
+
|
|
2475
|
+
|
|
2145
2476
|
def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
2146
2477
|
approved_plan = validate_semantic_plan(read_json_object(normalized(args.semantic_plan), "Semantic plan"))
|
|
2147
2478
|
if not approved_plan.get("profile"):
|
|
@@ -2152,7 +2483,11 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2152
2483
|
profile_args = argparse.Namespace(**vars(args))
|
|
2153
2484
|
profile_args.profile = profile
|
|
2154
2485
|
state, _install_state, _data_root, _runtime_state = require_profile(profile_args, require_runtime=True)
|
|
2155
|
-
project_root = normalized(state["projectRoot"])
|
|
2486
|
+
project_root = normalized(getattr(args, "project_root", None) or state["projectRoot"])
|
|
2487
|
+
if not project_root_is_bound(state, project_root):
|
|
2488
|
+
raise OnboardingError(
|
|
2489
|
+
"Current projectRoot is not bound to this completed profile; run onboard-datasource-run from this workspace first"
|
|
2490
|
+
)
|
|
2156
2491
|
payload_path = normalized(args.query_payload)
|
|
2157
2492
|
if not is_child(payload_path, project_root):
|
|
2158
2493
|
raise OnboardingError(
|
|
@@ -2171,8 +2506,13 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2171
2506
|
evidence_dir = composite_evidence_dir(project_root, profile, str(project_root / contract_evidence) if contract_evidence else args.evidence_dir)
|
|
2172
2507
|
files: list[str] = []
|
|
2173
2508
|
|
|
2174
|
-
_draft_dir, manifest, draft_matches = semantic_plan_snapshot(state, approved_plan)
|
|
2509
|
+
_draft_dir, manifest, draft_matches = semantic_plan_snapshot(state, approved_plan, project_root)
|
|
2175
2510
|
published_matches = published_digest_matches(state, manifest["digest"])
|
|
2511
|
+
if normalized(state["projectRoot"]) != project_root and not published_matches:
|
|
2512
|
+
raise OnboardingError(
|
|
2513
|
+
"A secondary workspace may reuse an identical published semantic layer but cannot replace it; "
|
|
2514
|
+
"publish changes from the original projectRoot or choose a new profile"
|
|
2515
|
+
)
|
|
2176
2516
|
if draft_matches or published_matches:
|
|
2177
2517
|
drafted = resumed_phase(profile, "semanticDrafted", digest=manifest["digest"])
|
|
2178
2518
|
else:
|
|
@@ -2265,13 +2605,9 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2265
2605
|
))
|
|
2266
2606
|
save_composite_result(evidence_dir, "11-semantic-publish-apply.json", published, files)
|
|
2267
2607
|
state = read_onboarding_state(normalized(state["dataRoot"]), profile)
|
|
2268
|
-
verified_step = state.get("steps", {}).get("semanticVerified", {})
|
|
2269
2608
|
payload_digest = sha256(payload_path)
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
and verified_step.get("queryModel") == declared_query_model
|
|
2273
|
-
and verified_step.get("queryPayloadDigest") == payload_digest
|
|
2274
|
-
):
|
|
2609
|
+
verified_step = workspace_query_verification(state, project_root, declared_query_model, payload_digest)
|
|
2610
|
+
if verified_step:
|
|
2275
2611
|
resumed = resumed_phase(
|
|
2276
2612
|
profile,
|
|
2277
2613
|
"semanticVerified",
|
|
@@ -2307,6 +2643,7 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2307
2643
|
profile=profile,
|
|
2308
2644
|
query_model=declared_query_model,
|
|
2309
2645
|
query_payload=args.query_payload,
|
|
2646
|
+
project_root=str(project_root),
|
|
2310
2647
|
execute=False,
|
|
2311
2648
|
))
|
|
2312
2649
|
save_composite_result(evidence_dir, "12-query-validate.json", query_validated, files)
|
|
@@ -2330,6 +2667,7 @@ def semantic_run_command(args: argparse.Namespace) -> dict:
|
|
|
2330
2667
|
profile=profile,
|
|
2331
2668
|
query_model=declared_query_model,
|
|
2332
2669
|
query_payload=args.query_payload,
|
|
2670
|
+
project_root=str(project_root),
|
|
2333
2671
|
execute=True,
|
|
2334
2672
|
))
|
|
2335
2673
|
save_composite_result(evidence_dir, "13-query-execute.json", executed, files)
|
|
@@ -2464,6 +2802,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2464
2802
|
install.add_argument("--project-root")
|
|
2465
2803
|
install.add_argument("--asset-cache-dir", action="append", default=[])
|
|
2466
2804
|
install.add_argument("--replace-skill", action="store_true")
|
|
2805
|
+
install.add_argument("--repair-component", choices=("cli", "launcher", "analysis-skill"))
|
|
2467
2806
|
install.add_argument("--skip-cli-install", action="store_true")
|
|
2468
2807
|
install.add_argument("--cli-command")
|
|
2469
2808
|
install.add_argument("--progress-file")
|
|
@@ -2516,6 +2855,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2516
2855
|
resume.add_argument("--profile")
|
|
2517
2856
|
resume.set_defaults(handler=onboarding_resume_command)
|
|
2518
2857
|
|
|
2858
|
+
onboarding_list = sub.add_parser("onboard-list")
|
|
2859
|
+
onboarding_list.add_argument("--install-root")
|
|
2860
|
+
onboarding_list.add_argument("--data-root")
|
|
2861
|
+
onboarding_list.set_defaults(handler=onboarding_list_command)
|
|
2862
|
+
|
|
2863
|
+
migration_status = sub.add_parser("profile-migration-status")
|
|
2864
|
+
migration_status.add_argument("--install-root")
|
|
2865
|
+
migration_status.add_argument("--data-root")
|
|
2866
|
+
migration_status.set_defaults(handler=profile_migration_status_command)
|
|
2867
|
+
|
|
2868
|
+
migrate = sub.add_parser("profile-migrate")
|
|
2869
|
+
migrate.add_argument("--install-root")
|
|
2870
|
+
migrate.add_argument("--data-root")
|
|
2871
|
+
migrate.add_argument("--approve", action="store_true")
|
|
2872
|
+
migrate.set_defaults(handler=profile_migrate_command)
|
|
2873
|
+
|
|
2519
2874
|
datasource_run = sub.add_parser("onboard-datasource-run")
|
|
2520
2875
|
datasource_run.add_argument("--install-root")
|
|
2521
2876
|
datasource_run.add_argument("--data-root")
|
|
@@ -2536,6 +2891,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2536
2891
|
semantic_run = sub.add_parser("onboard-semantic-run")
|
|
2537
2892
|
semantic_run.add_argument("--install-root")
|
|
2538
2893
|
semantic_run.add_argument("--data-root")
|
|
2894
|
+
semantic_run.add_argument("--project-root")
|
|
2539
2895
|
semantic_run.add_argument("--profile")
|
|
2540
2896
|
semantic_run.add_argument("--semantic-plan", required=True)
|
|
2541
2897
|
semantic_run.add_argument("--query-payload", required=True)
|
|
@@ -2602,6 +2958,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2602
2958
|
semantic_verify = sub.add_parser("semantic-verify")
|
|
2603
2959
|
semantic_verify.add_argument("--install-root")
|
|
2604
2960
|
semantic_verify.add_argument("--data-root")
|
|
2961
|
+
semantic_verify.add_argument("--project-root")
|
|
2605
2962
|
semantic_verify.add_argument("--profile")
|
|
2606
2963
|
semantic_verify.add_argument("--query-model")
|
|
2607
2964
|
semantic_verify.add_argument("--query-payload", required=True)
|