@jetrabbits/agentic 0.0.4 → 0.1.0
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/AGENTS.md +16 -0
- package/Makefile +40 -0
- package/README.md +3 -0
- package/UPGRADE.md +61 -0
- package/agentic +1236 -13
- package/areas/software/full-stack/AGENTS.md +1 -4
- package/areas/software/full-stack/workflows/debug-issue.md +2 -2
- package/docs/agentic-lifecycle.md +114 -0
- package/docs/agentic-token-minimization/README.md +81 -0
- package/docs/agentic-usage.md +157 -0
- package/docs/catalog.schema.json +203 -0
- package/docs/guidance-updates/2026-04-10-software-devops-best-practices.md +26 -0
- package/docs/opencode_prepare_agents.md +40 -0
- package/docs/opencode_setup.md +48 -0
- package/docs/prompt-format.md +80 -0
- package/docs/site/README.md +44 -0
- package/docs/site/app.js +127 -0
- package/docs/site/catalog.json +5002 -0
- package/docs/site/index.html +52 -0
- package/docs/site/styles.css +177 -0
- package/extensions/codex/agents/developer.toml +1 -1
- package/extensions/codex/agents/devops-engineer.toml +1 -1
- package/extensions/codex/agents/product-owner.toml +1 -1
- package/extensions/codex/agents/team-lead.toml +1 -1
- package/extensions/opencode/plugins/model-checker.json +2 -3
- package/extensions/opencode/plugins/model-checker.ts +23 -0
- package/extensions/opencode/plugins/telegram-notification.ts +33 -5
- package/package.json +6 -2
- package/scripts/assess_area_quality.py +216 -0
- package/scripts/build_docs_catalog.py +283 -0
- package/scripts/lint_prompts.py +113 -0
- package/areas/software/full-stack/skills/bash-pro/SKILL.md +0 -310
- package/areas/software/full-stack/skills/python-pro/SKILL.md +0 -158
- package/areas/software/full-stack/skills/skill-creator/LICENSE.txt +0 -202
- package/areas/software/full-stack/skills/skill-creator/SKILL.md +0 -356
- package/areas/software/full-stack/skills/skill-creator/references/output-patterns.md +0 -82
- package/areas/software/full-stack/skills/skill-creator/references/workflows.md +0 -28
- package/areas/software/full-stack/skills/skill-creator/scripts/init_skill.py +0 -303
- package/areas/software/full-stack/skills/skill-creator/scripts/package_skill.py +0 -110
- package/areas/software/full-stack/skills/skill-creator/scripts/quick_validate.py +0 -95
- package/extensions/codex/skills/babysit-pr/SKILL.md +0 -187
- package/extensions/codex/skills/babysit-pr/agents/openai.yaml +0 -4
- package/extensions/codex/skills/babysit-pr/references/github-api-notes.md +0 -72
- package/extensions/codex/skills/babysit-pr/references/heuristics.md +0 -58
- package/extensions/codex/skills/babysit-pr/scripts/gh_pr_watch.py +0 -806
- package/extensions/codex/skills/babysit-pr/scripts/test_gh_pr_watch.py +0 -155
- package/extensions/opencode/skills/code_review_expert/SKILL.md +0 -144
- package/extensions/opencode/skills/design_expert/SKILL.md +0 -42
- package/extensions/opencode/skills/qa_expert/SKILL.md +0 -116
package/agentic
CHANGED
|
@@ -11,6 +11,8 @@ APP_NAME="agentic"
|
|
|
11
11
|
APP_TITLE="Agentic Installer"
|
|
12
12
|
APP_TUI_TITLE="Agentic installer (TUI mode)"
|
|
13
13
|
APP_REPO_URL="https://github.com/sawrus/agent-guides.git"
|
|
14
|
+
APP_REPO_LINK="https://github.com/sawrus/agent-guides"
|
|
15
|
+
PROJECT_MANIFEST_NAME=".agentic.json"
|
|
14
16
|
|
|
15
17
|
DEFAULT_AGENT_OS="default"
|
|
16
18
|
STATIC_AGENT_OS=(default opencode codex claude antigravity cursor agents)
|
|
@@ -21,6 +23,7 @@ XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
|
21
23
|
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
22
24
|
APP_CONFIG_DIR="$XDG_CONFIG_HOME/$APP_NAME"
|
|
23
25
|
APP_CONFIG_FILE="$APP_CONFIG_DIR/config"
|
|
26
|
+
OPENCODE_PLUGIN_CONFIG_FILE="$APP_CONFIG_DIR/opencode-plugins.json"
|
|
24
27
|
APP_DATA_DIR="$XDG_DATA_HOME/$APP_NAME"
|
|
25
28
|
APP_REPO_DIR="$APP_DATA_DIR/repo"
|
|
26
29
|
|
|
@@ -47,8 +50,12 @@ SELF_INSTALL_WITH_FZF=false
|
|
|
47
50
|
|
|
48
51
|
CREATED_PATHS=()
|
|
49
52
|
COPIED_PATHS=()
|
|
53
|
+
MANAGED_RECORDS=()
|
|
54
|
+
SKIPPED_MANAGED_PATHS=()
|
|
50
55
|
WARNINGS=()
|
|
51
56
|
|
|
57
|
+
CONTEXT7_API_KEY="${CONTEXT7_API_KEY:-}"
|
|
58
|
+
|
|
52
59
|
COLOR_RESET=""
|
|
53
60
|
COLOR_HEADER=""
|
|
54
61
|
COLOR_INFO=""
|
|
@@ -505,6 +512,385 @@ ensure_dir() {
|
|
|
505
512
|
unique_append "$path" CREATED_PATHS
|
|
506
513
|
}
|
|
507
514
|
|
|
515
|
+
ensure_python_available() {
|
|
516
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
517
|
+
error "python3 is required for managed install metadata and generated markers"
|
|
518
|
+
exit 1
|
|
519
|
+
fi
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
selected_agent_os_contains() {
|
|
523
|
+
local expected="$1"
|
|
524
|
+
local agent
|
|
525
|
+
for agent in "${SELECTED_AGENT_OS[@]}"; do
|
|
526
|
+
if [[ "$agent" == "$expected" ]]; then
|
|
527
|
+
return 0
|
|
528
|
+
fi
|
|
529
|
+
done
|
|
530
|
+
return 1
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
project_manifest_path() {
|
|
534
|
+
printf '%s\n' "$PROJECT_DIR/$PROJECT_MANIFEST_NAME"
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
project_rel_path() {
|
|
538
|
+
local path="$1"
|
|
539
|
+
local rel="${path#"$PROJECT_DIR"/}"
|
|
540
|
+
printf '%s\n' "$rel"
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
hash_file() {
|
|
544
|
+
local path="$1"
|
|
545
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
546
|
+
shasum -a 256 "$path" | awk '{print $1}'
|
|
547
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
548
|
+
sha256sum "$path" | awk '{print $1}'
|
|
549
|
+
else
|
|
550
|
+
error "shasum or sha256sum is required to track managed files"
|
|
551
|
+
exit 1
|
|
552
|
+
fi
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
manifest_has_path() {
|
|
556
|
+
local rel="$1"
|
|
557
|
+
local manifest
|
|
558
|
+
manifest="$(project_manifest_path)"
|
|
559
|
+
[[ -f "$manifest" ]] || return 1
|
|
560
|
+
python3 - "$manifest" "$rel" <<'PY'
|
|
561
|
+
import json
|
|
562
|
+
import sys
|
|
563
|
+
from pathlib import Path
|
|
564
|
+
|
|
565
|
+
manifest = Path(sys.argv[1])
|
|
566
|
+
rel = sys.argv[2]
|
|
567
|
+
try:
|
|
568
|
+
data = json.loads(manifest.read_text(encoding="utf-8"))
|
|
569
|
+
except Exception:
|
|
570
|
+
sys.exit(1)
|
|
571
|
+
for item in data.get("managed_files", []):
|
|
572
|
+
if item.get("path") == rel:
|
|
573
|
+
sys.exit(0)
|
|
574
|
+
sys.exit(1)
|
|
575
|
+
PY
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
manifest_hash_for_path() {
|
|
579
|
+
local rel="$1"
|
|
580
|
+
local manifest
|
|
581
|
+
manifest="$(project_manifest_path)"
|
|
582
|
+
[[ -f "$manifest" ]] || return 0
|
|
583
|
+
python3 - "$manifest" "$rel" <<'PY'
|
|
584
|
+
import json
|
|
585
|
+
import sys
|
|
586
|
+
from pathlib import Path
|
|
587
|
+
|
|
588
|
+
manifest = Path(sys.argv[1])
|
|
589
|
+
rel = sys.argv[2]
|
|
590
|
+
try:
|
|
591
|
+
data = json.loads(manifest.read_text(encoding="utf-8"))
|
|
592
|
+
except Exception:
|
|
593
|
+
sys.exit(0)
|
|
594
|
+
for item in data.get("managed_files", []):
|
|
595
|
+
if item.get("path") == rel:
|
|
596
|
+
print(item.get("content_hash", ""))
|
|
597
|
+
break
|
|
598
|
+
PY
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
can_write_managed_file() {
|
|
602
|
+
local dest="$1"
|
|
603
|
+
local rel
|
|
604
|
+
rel="$(project_rel_path "$dest")"
|
|
605
|
+
|
|
606
|
+
if [[ -f "$(project_manifest_path)" ]]; then
|
|
607
|
+
if ! manifest_has_path "$rel"; then
|
|
608
|
+
if [[ -e "$dest" ]]; then
|
|
609
|
+
warn "Skipping unmanaged target on rerun: $rel"
|
|
610
|
+
unique_append "$rel" SKIPPED_MANAGED_PATHS
|
|
611
|
+
return 1
|
|
612
|
+
fi
|
|
613
|
+
return 0
|
|
614
|
+
fi
|
|
615
|
+
|
|
616
|
+
if [[ -f "$dest" ]]; then
|
|
617
|
+
local expected_hash current_hash
|
|
618
|
+
expected_hash="$(manifest_hash_for_path "$rel")"
|
|
619
|
+
if [[ -n "$expected_hash" ]]; then
|
|
620
|
+
current_hash="$(hash_file "$dest")"
|
|
621
|
+
if [[ "$current_hash" != "$expected_hash" ]]; then
|
|
622
|
+
warn "Skipping user-modified managed file: $rel"
|
|
623
|
+
unique_append "$rel" SKIPPED_MANAGED_PATHS
|
|
624
|
+
return 1
|
|
625
|
+
fi
|
|
626
|
+
fi
|
|
627
|
+
fi
|
|
628
|
+
fi
|
|
629
|
+
|
|
630
|
+
return 0
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
register_managed_file() {
|
|
634
|
+
local dest="$1"
|
|
635
|
+
local source_ref="$2"
|
|
636
|
+
local marker="$3"
|
|
637
|
+
local rel
|
|
638
|
+
rel="$(project_rel_path "$dest")"
|
|
639
|
+
local digest
|
|
640
|
+
digest="$(hash_file "$dest")"
|
|
641
|
+
MANAGED_RECORDS+=("$rel|$source_ref|$digest|$marker")
|
|
642
|
+
unique_append "$dest" COPIED_PATHS
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
record_agentic_event() {
|
|
646
|
+
local kind="$1"
|
|
647
|
+
local value="${2:-}"
|
|
648
|
+
|
|
649
|
+
case "$kind" in
|
|
650
|
+
DIR)
|
|
651
|
+
unique_append "$value" CREATED_PATHS
|
|
652
|
+
;;
|
|
653
|
+
COPIED)
|
|
654
|
+
unique_append "$value" COPIED_PATHS
|
|
655
|
+
;;
|
|
656
|
+
RECORD)
|
|
657
|
+
MANAGED_RECORDS+=("$value")
|
|
658
|
+
;;
|
|
659
|
+
SKIP)
|
|
660
|
+
unique_append "$value" SKIPPED_MANAGED_PATHS
|
|
661
|
+
;;
|
|
662
|
+
WARN)
|
|
663
|
+
warn "$value"
|
|
664
|
+
;;
|
|
665
|
+
esac
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
write_file_with_agentic_marker() {
|
|
669
|
+
local src="$1"
|
|
670
|
+
local dest="$2"
|
|
671
|
+
local source_ref="$3"
|
|
672
|
+
|
|
673
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
674
|
+
log "DRY-RUN write managed file $dest"
|
|
675
|
+
unique_append "$dest" COPIED_PATHS
|
|
676
|
+
return
|
|
677
|
+
fi
|
|
678
|
+
|
|
679
|
+
can_write_managed_file "$dest" || return 0
|
|
680
|
+
|
|
681
|
+
ensure_dir "$(dirname -- "$dest")"
|
|
682
|
+
python3 - "$src" "$dest" "$source_ref" "$APP_REPO_LINK" <<'PY'
|
|
683
|
+
import json
|
|
684
|
+
import sys
|
|
685
|
+
from pathlib import Path
|
|
686
|
+
|
|
687
|
+
src = Path(sys.argv[1])
|
|
688
|
+
dest = Path(sys.argv[2])
|
|
689
|
+
source_ref = sys.argv[3]
|
|
690
|
+
repo = sys.argv[4]
|
|
691
|
+
text = src.read_text(encoding="utf-8")
|
|
692
|
+
suffix = dest.suffix.lower()
|
|
693
|
+
marker = f"Generated by agentic; source: {source_ref}; repository: {repo}"
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def yaml_quote(value: str) -> str:
|
|
697
|
+
return json.dumps(value, ensure_ascii=False)
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def markdown_with_marker(body: str) -> str:
|
|
701
|
+
block = (
|
|
702
|
+
"agentic:\n"
|
|
703
|
+
" generated_by: agentic\n"
|
|
704
|
+
f" source: {yaml_quote(source_ref)}\n"
|
|
705
|
+
f" repository: {yaml_quote(repo)}\n"
|
|
706
|
+
)
|
|
707
|
+
if body.startswith("---\n"):
|
|
708
|
+
end = body.find("\n---", 4)
|
|
709
|
+
if end != -1:
|
|
710
|
+
return body[: end + 1] + block + body[end + 1 :]
|
|
711
|
+
return (
|
|
712
|
+
"---\n"
|
|
713
|
+
+ block
|
|
714
|
+
+ "---\n"
|
|
715
|
+
+ body
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def commented(body: str, prefix: str) -> str:
|
|
720
|
+
line = f"{prefix} {marker}\n"
|
|
721
|
+
if body.startswith("#!"):
|
|
722
|
+
first, sep, rest = body.partition("\n")
|
|
723
|
+
if sep:
|
|
724
|
+
return first + sep + line + rest
|
|
725
|
+
return line + body
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
if suffix == ".md":
|
|
729
|
+
output = markdown_with_marker(text)
|
|
730
|
+
elif suffix == ".json":
|
|
731
|
+
data = json.loads(text)
|
|
732
|
+
if not isinstance(data, dict):
|
|
733
|
+
raise SystemExit(f"Cannot add agentic metadata to non-object JSON: {dest}")
|
|
734
|
+
output = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
735
|
+
elif suffix in {".ts", ".tsx", ".js", ".jsx", ".css"}:
|
|
736
|
+
output = commented(text, "//")
|
|
737
|
+
elif suffix in {".sh", ".toml", ".py", ".yml", ".yaml"}:
|
|
738
|
+
output = commented(text, "#")
|
|
739
|
+
else:
|
|
740
|
+
output = commented(text, "#")
|
|
741
|
+
|
|
742
|
+
dest.write_text(output, encoding="utf-8")
|
|
743
|
+
PY
|
|
744
|
+
register_managed_file "$dest" "$source_ref" "internal"
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
write_agentic_manifest() {
|
|
748
|
+
local project_dir="$1"
|
|
749
|
+
local manifest="$project_dir/$PROJECT_MANIFEST_NAME"
|
|
750
|
+
|
|
751
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
752
|
+
log "DRY-RUN write $manifest"
|
|
753
|
+
return
|
|
754
|
+
fi
|
|
755
|
+
|
|
756
|
+
local records_file skipped_file
|
|
757
|
+
records_file="$(mktemp "${TMPDIR:-/tmp}/agentic-records.XXXXXX")"
|
|
758
|
+
skipped_file="$(mktemp "${TMPDIR:-/tmp}/agentic-skipped.XXXXXX")"
|
|
759
|
+
if [[ "${#MANAGED_RECORDS[@]}" -gt 0 ]]; then
|
|
760
|
+
printf '%s\n' "${MANAGED_RECORDS[@]}" > "$records_file"
|
|
761
|
+
else
|
|
762
|
+
: > "$records_file"
|
|
763
|
+
fi
|
|
764
|
+
if [[ "${#SKIPPED_MANAGED_PATHS[@]}" -gt 0 ]]; then
|
|
765
|
+
printf '%s\n' "${SKIPPED_MANAGED_PATHS[@]}" > "$skipped_file"
|
|
766
|
+
else
|
|
767
|
+
: > "$skipped_file"
|
|
768
|
+
fi
|
|
769
|
+
|
|
770
|
+
local agent_os_csv areas_csv specs_csv
|
|
771
|
+
local old_ifs="$IFS"
|
|
772
|
+
IFS=,
|
|
773
|
+
agent_os_csv="${SELECTED_AGENT_OS[*]}"
|
|
774
|
+
areas_csv="${SELECTED_AREAS[*]}"
|
|
775
|
+
specs_csv="${SELECTED_SPECS[*]}"
|
|
776
|
+
IFS="$old_ifs"
|
|
777
|
+
|
|
778
|
+
python3 - "$manifest" "$records_file" "$skipped_file" "$APP_REPO_LINK" "$REPO_ROOT" "$agent_os_csv" "$areas_csv" "$specs_csv" <<'PY'
|
|
779
|
+
import json
|
|
780
|
+
import sys
|
|
781
|
+
from datetime import datetime, timezone
|
|
782
|
+
from pathlib import Path
|
|
783
|
+
|
|
784
|
+
manifest = Path(sys.argv[1])
|
|
785
|
+
records_file = Path(sys.argv[2])
|
|
786
|
+
skipped_file = Path(sys.argv[3])
|
|
787
|
+
repo_link = sys.argv[4]
|
|
788
|
+
repo_root = sys.argv[5]
|
|
789
|
+
agent_os = [x for x in sys.argv[6].split(",") if x]
|
|
790
|
+
areas = [x for x in sys.argv[7].split(",") if x]
|
|
791
|
+
specs = [x for x in sys.argv[8].split(",") if x]
|
|
792
|
+
now = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
793
|
+
|
|
794
|
+
existing = {}
|
|
795
|
+
created_at = now
|
|
796
|
+
if manifest.exists():
|
|
797
|
+
try:
|
|
798
|
+
old = json.loads(manifest.read_text(encoding="utf-8"))
|
|
799
|
+
created_at = old.get("created_at", created_at)
|
|
800
|
+
for item in old.get("managed_files", []):
|
|
801
|
+
if item.get("path"):
|
|
802
|
+
existing[item["path"]] = item
|
|
803
|
+
except Exception:
|
|
804
|
+
existing = {}
|
|
805
|
+
|
|
806
|
+
for line in records_file.read_text(encoding="utf-8").splitlines():
|
|
807
|
+
if not line:
|
|
808
|
+
continue
|
|
809
|
+
path, source, digest, marker = (line.split("|", 3) + ["", "", "", ""])[:4]
|
|
810
|
+
existing[path] = {
|
|
811
|
+
"path": path,
|
|
812
|
+
"source": source,
|
|
813
|
+
"content_hash": digest,
|
|
814
|
+
"marker": marker,
|
|
815
|
+
"updated_at": now,
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
skipped = [x for x in skipped_file.read_text(encoding="utf-8").splitlines() if x]
|
|
819
|
+
data = {
|
|
820
|
+
"_agentic": {
|
|
821
|
+
"generated_by": "agentic",
|
|
822
|
+
"repository": repo_link,
|
|
823
|
+
},
|
|
824
|
+
"version": 1,
|
|
825
|
+
"created_at": created_at,
|
|
826
|
+
"updated_at": now,
|
|
827
|
+
"settings": {
|
|
828
|
+
"agent_os": agent_os,
|
|
829
|
+
"areas": areas,
|
|
830
|
+
"specializations": specs,
|
|
831
|
+
"source_repo": repo_link,
|
|
832
|
+
"source_checkout": repo_root,
|
|
833
|
+
},
|
|
834
|
+
"managed_files": sorted(existing.values(), key=lambda x: x["path"]),
|
|
835
|
+
"skipped_files": skipped,
|
|
836
|
+
}
|
|
837
|
+
manifest.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
838
|
+
PY
|
|
839
|
+
rm -f "$records_file" "$skipped_file"
|
|
840
|
+
unique_append "$manifest" COPIED_PATHS
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
load_install_settings_from_manifest() {
|
|
844
|
+
local manifest="$1"
|
|
845
|
+
[[ -f "$manifest" ]] || return 1
|
|
846
|
+
ensure_python_available
|
|
847
|
+
|
|
848
|
+
local values=()
|
|
849
|
+
readlines values < <(python3 - "$manifest" <<'PY'
|
|
850
|
+
import json
|
|
851
|
+
import sys
|
|
852
|
+
from pathlib import Path
|
|
853
|
+
|
|
854
|
+
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
855
|
+
settings = data.get("settings", {})
|
|
856
|
+
for key in ("agent_os", "areas", "specializations"):
|
|
857
|
+
print("::" + key)
|
|
858
|
+
for value in settings.get(key, []):
|
|
859
|
+
print(value)
|
|
860
|
+
PY
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
local section=""
|
|
864
|
+
local loaded_agent_os=()
|
|
865
|
+
local loaded_areas=()
|
|
866
|
+
local loaded_specs=()
|
|
867
|
+
local value
|
|
868
|
+
for value in "${values[@]}"; do
|
|
869
|
+
case "$value" in
|
|
870
|
+
"::agent_os") section="agent_os" ;;
|
|
871
|
+
"::areas") section="areas" ;;
|
|
872
|
+
"::specializations") section="specializations" ;;
|
|
873
|
+
*)
|
|
874
|
+
case "$section" in
|
|
875
|
+
agent_os) loaded_agent_os+=("$value") ;;
|
|
876
|
+
areas) loaded_areas+=("$value") ;;
|
|
877
|
+
specializations) loaded_specs+=("$value") ;;
|
|
878
|
+
esac
|
|
879
|
+
;;
|
|
880
|
+
esac
|
|
881
|
+
done
|
|
882
|
+
|
|
883
|
+
if [[ "${#SELECTED_AGENT_OS[@]}" -eq 1 && "${SELECTED_AGENT_OS[0]}" == "$DEFAULT_AGENT_OS" && "${#loaded_agent_os[@]}" -gt 0 ]]; then
|
|
884
|
+
SELECTED_AGENT_OS=("${loaded_agent_os[@]}")
|
|
885
|
+
fi
|
|
886
|
+
if [[ "${#SELECTED_AREAS[@]}" -eq 0 && "${#loaded_areas[@]}" -gt 0 ]]; then
|
|
887
|
+
SELECTED_AREAS=("${loaded_areas[@]}")
|
|
888
|
+
fi
|
|
889
|
+
if [[ "${#SELECTED_SPECS[@]}" -eq 0 && "${#loaded_specs[@]}" -gt 0 ]]; then
|
|
890
|
+
SELECTED_SPECS=("${loaded_specs[@]}")
|
|
891
|
+
fi
|
|
892
|
+
}
|
|
893
|
+
|
|
508
894
|
path_ref_for_shell_export() {
|
|
509
895
|
local dir="$1"
|
|
510
896
|
if [[ "$dir" == "$HOME/"* ]]; then
|
|
@@ -583,11 +969,676 @@ copy_dir_contents() {
|
|
|
583
969
|
local dest="$2"
|
|
584
970
|
ensure_dir "$dest"
|
|
585
971
|
if [[ "$DRY_RUN" == true ]]; then
|
|
586
|
-
log "DRY-RUN
|
|
972
|
+
log "DRY-RUN copy managed contents $src -> $dest"
|
|
973
|
+
unique_append "$dest" COPIED_PATHS
|
|
974
|
+
return
|
|
975
|
+
fi
|
|
976
|
+
|
|
977
|
+
local event kind value events_file
|
|
978
|
+
events_file="$(mktemp "${TMPDIR:-/tmp}/agentic-copy-events.XXXXXX")"
|
|
979
|
+
python3 - "$src" "$dest" "$REPO_ROOT" "$PROJECT_DIR" "$(project_manifest_path)" "$APP_REPO_LINK" > "$events_file" <<'PY'
|
|
980
|
+
import hashlib
|
|
981
|
+
import json
|
|
982
|
+
import sys
|
|
983
|
+
from pathlib import Path
|
|
984
|
+
|
|
985
|
+
src = Path(sys.argv[1])
|
|
986
|
+
dest_root = Path(sys.argv[2])
|
|
987
|
+
repo_root = Path(sys.argv[3])
|
|
988
|
+
project_dir = Path(sys.argv[4])
|
|
989
|
+
manifest = Path(sys.argv[5])
|
|
990
|
+
repo = sys.argv[6]
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def emit(kind: str, value: str) -> None:
|
|
994
|
+
print(f"{kind}\t{value}")
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
def sha256(path: Path) -> str:
|
|
998
|
+
h = hashlib.sha256()
|
|
999
|
+
with path.open("rb") as fh:
|
|
1000
|
+
for chunk in iter(lambda: fh.read(1024 * 1024), b""):
|
|
1001
|
+
h.update(chunk)
|
|
1002
|
+
return h.hexdigest()
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
managed = None
|
|
1006
|
+
if manifest.exists():
|
|
1007
|
+
managed = {}
|
|
1008
|
+
try:
|
|
1009
|
+
data = json.loads(manifest.read_text(encoding="utf-8"))
|
|
1010
|
+
for item in data.get("managed_files", []):
|
|
1011
|
+
rel = item.get("path")
|
|
1012
|
+
if rel:
|
|
1013
|
+
managed[rel] = item.get("content_hash", "")
|
|
1014
|
+
except Exception:
|
|
1015
|
+
managed = {}
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def rel_to_project(path: Path) -> str:
|
|
1019
|
+
try:
|
|
1020
|
+
return str(path.relative_to(project_dir))
|
|
1021
|
+
except ValueError:
|
|
1022
|
+
return str(path)
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
def rel_to_repo(path: Path) -> str:
|
|
1026
|
+
try:
|
|
1027
|
+
return str(path.relative_to(repo_root))
|
|
1028
|
+
except ValueError:
|
|
1029
|
+
return str(path)
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def yaml_quote(value: str) -> str:
|
|
1033
|
+
return json.dumps(value, ensure_ascii=False)
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def markdown_with_marker(body: str, source_ref: str) -> str:
|
|
1037
|
+
block = (
|
|
1038
|
+
"agentic:\n"
|
|
1039
|
+
" generated_by: agentic\n"
|
|
1040
|
+
f" source: {yaml_quote(source_ref)}\n"
|
|
1041
|
+
f" repository: {yaml_quote(repo)}\n"
|
|
1042
|
+
)
|
|
1043
|
+
if body.startswith("---\n"):
|
|
1044
|
+
end = body.find("\n---", 4)
|
|
1045
|
+
if end != -1:
|
|
1046
|
+
return body[: end + 1] + block + body[end + 1 :]
|
|
1047
|
+
return "---\n" + block + "---\n" + body
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
def commented(body: str, prefix: str, source_ref: str) -> str:
|
|
1051
|
+
marker = f"Generated by agentic; source: {source_ref}; repository: {repo}"
|
|
1052
|
+
line = f"{prefix} {marker}\n"
|
|
1053
|
+
if body.startswith("#!"):
|
|
1054
|
+
first, sep, rest = body.partition("\n")
|
|
1055
|
+
if sep:
|
|
1056
|
+
return first + sep + line + rest
|
|
1057
|
+
return line + body
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def add_marker(file_path: Path, target: Path, source_ref: str) -> str:
|
|
1061
|
+
text = file_path.read_text(encoding="utf-8")
|
|
1062
|
+
suffix = target.suffix.lower()
|
|
1063
|
+
if suffix == ".md":
|
|
1064
|
+
return markdown_with_marker(text, source_ref)
|
|
1065
|
+
if suffix == ".json":
|
|
1066
|
+
data = json.loads(text)
|
|
1067
|
+
if not isinstance(data, dict):
|
|
1068
|
+
raise SystemExit(f"Cannot add agentic metadata to non-object JSON: {target}")
|
|
1069
|
+
return json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
1070
|
+
if suffix in {".ts", ".tsx", ".js", ".jsx", ".css"}:
|
|
1071
|
+
return commented(text, "//", source_ref)
|
|
1072
|
+
if suffix in {".sh", ".toml", ".py", ".yml", ".yaml"}:
|
|
1073
|
+
return commented(text, "#", source_ref)
|
|
1074
|
+
return commented(text, "#", source_ref)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
emit("DIR", str(dest_root))
|
|
1078
|
+
for file_path in sorted(p for p in src.rglob("*") if p.is_file()):
|
|
1079
|
+
rel = file_path.relative_to(src)
|
|
1080
|
+
target = dest_root / rel
|
|
1081
|
+
project_rel = rel_to_project(target)
|
|
1082
|
+
source_ref = rel_to_repo(file_path)
|
|
1083
|
+
|
|
1084
|
+
if managed is not None:
|
|
1085
|
+
if project_rel not in managed:
|
|
1086
|
+
if target.exists():
|
|
1087
|
+
emit("WARN", f"Skipping unmanaged target on rerun: {project_rel}")
|
|
1088
|
+
emit("SKIP", project_rel)
|
|
1089
|
+
continue
|
|
1090
|
+
expected_hash = managed.get(project_rel, "")
|
|
1091
|
+
if target.exists() and expected_hash and sha256(target) != expected_hash:
|
|
1092
|
+
emit("WARN", f"Skipping user-modified managed file: {project_rel}")
|
|
1093
|
+
emit("SKIP", project_rel)
|
|
1094
|
+
continue
|
|
1095
|
+
|
|
1096
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
1097
|
+
emit("DIR", str(target.parent))
|
|
1098
|
+
target.write_text(add_marker(file_path, target, source_ref), encoding="utf-8")
|
|
1099
|
+
digest = sha256(target)
|
|
1100
|
+
emit("RECORD", f"{project_rel}|{source_ref}|{digest}|internal")
|
|
1101
|
+
emit("COPIED", str(target))
|
|
1102
|
+
PY
|
|
1103
|
+
while IFS= read -r event || [[ -n "$event" ]]; do
|
|
1104
|
+
kind="${event%% *}"
|
|
1105
|
+
if [[ "$kind" == "$event" ]]; then
|
|
1106
|
+
value=""
|
|
1107
|
+
else
|
|
1108
|
+
value="${event#* }"
|
|
1109
|
+
fi
|
|
1110
|
+
record_agentic_event "$kind" "$value"
|
|
1111
|
+
done < "$events_file"
|
|
1112
|
+
rm -f "$events_file"
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
write_generated_text_file() {
|
|
1116
|
+
local dest="$1"
|
|
1117
|
+
local source_ref="$2"
|
|
1118
|
+
local content="$3"
|
|
1119
|
+
|
|
1120
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
1121
|
+
log "DRY-RUN write generated file $dest"
|
|
1122
|
+
unique_append "$dest" COPIED_PATHS
|
|
1123
|
+
return
|
|
1124
|
+
fi
|
|
1125
|
+
|
|
1126
|
+
can_write_managed_file "$dest" || return 0
|
|
1127
|
+
ensure_dir "$(dirname -- "$dest")"
|
|
1128
|
+
|
|
1129
|
+
local tmp
|
|
1130
|
+
tmp="$(mktemp "${TMPDIR:-/tmp}/agentic-generated.XXXXXX")"
|
|
1131
|
+
printf '%s' "$content" > "$tmp"
|
|
1132
|
+
write_file_with_agentic_marker "$tmp" "$dest" "$source_ref"
|
|
1133
|
+
rm -f "$tmp"
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
write_json_file_with_agentic_metadata() {
|
|
1137
|
+
local dest="$1"
|
|
1138
|
+
local source_ref="$2"
|
|
1139
|
+
local python_body="$3"
|
|
1140
|
+
|
|
1141
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
1142
|
+
log "DRY-RUN update JSON managed file $dest"
|
|
1143
|
+
unique_append "$dest" COPIED_PATHS
|
|
1144
|
+
return
|
|
1145
|
+
fi
|
|
1146
|
+
|
|
1147
|
+
can_write_managed_file "$dest" || return 0
|
|
1148
|
+
ensure_dir "$(dirname -- "$dest")"
|
|
1149
|
+
python3 - "$dest" "$source_ref" "$APP_REPO_LINK" "$CONTEXT7_API_KEY" "$python_body" <<'PY'
|
|
1150
|
+
import json
|
|
1151
|
+
import sys
|
|
1152
|
+
from pathlib import Path
|
|
1153
|
+
|
|
1154
|
+
path = Path(sys.argv[1])
|
|
1155
|
+
source_ref = sys.argv[2]
|
|
1156
|
+
repo = sys.argv[3]
|
|
1157
|
+
context7_api_key = sys.argv[4]
|
|
1158
|
+
body = sys.argv[5]
|
|
1159
|
+
|
|
1160
|
+
data = {}
|
|
1161
|
+
if path.exists():
|
|
1162
|
+
try:
|
|
1163
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
1164
|
+
except Exception:
|
|
1165
|
+
data = {}
|
|
1166
|
+
if not isinstance(data, dict):
|
|
1167
|
+
data = {}
|
|
1168
|
+
|
|
1169
|
+
namespace = {
|
|
1170
|
+
"data": data,
|
|
1171
|
+
"context7_api_key": context7_api_key,
|
|
1172
|
+
}
|
|
1173
|
+
exec(body, namespace)
|
|
1174
|
+
data = namespace["data"]
|
|
1175
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
1176
|
+
PY
|
|
1177
|
+
register_managed_file "$dest" "$source_ref" "internal"
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
write_json_config_file() {
|
|
1181
|
+
local dest="$1"
|
|
1182
|
+
local source_ref="$2"
|
|
1183
|
+
local python_body="$3"
|
|
1184
|
+
|
|
1185
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
1186
|
+
log "DRY-RUN update JSON config file $dest"
|
|
1187
|
+
unique_append "$dest" COPIED_PATHS
|
|
1188
|
+
return
|
|
1189
|
+
fi
|
|
1190
|
+
|
|
1191
|
+
can_write_managed_file "$dest" || return 0
|
|
1192
|
+
ensure_dir "$(dirname -- "$dest")"
|
|
1193
|
+
python3 - "$dest" "$CONTEXT7_API_KEY" "$python_body" <<'PY'
|
|
1194
|
+
import json
|
|
1195
|
+
import sys
|
|
1196
|
+
from pathlib import Path
|
|
1197
|
+
|
|
1198
|
+
path = Path(sys.argv[1])
|
|
1199
|
+
context7_api_key = sys.argv[2]
|
|
1200
|
+
body = sys.argv[3]
|
|
1201
|
+
|
|
1202
|
+
data = {}
|
|
1203
|
+
if path.exists():
|
|
1204
|
+
try:
|
|
1205
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
1206
|
+
except Exception:
|
|
1207
|
+
data = {}
|
|
1208
|
+
if not isinstance(data, dict):
|
|
1209
|
+
data = {}
|
|
1210
|
+
|
|
1211
|
+
namespace = {
|
|
1212
|
+
"data": data,
|
|
1213
|
+
"context7_api_key": context7_api_key,
|
|
1214
|
+
}
|
|
1215
|
+
exec(body, namespace)
|
|
1216
|
+
data = namespace["data"]
|
|
1217
|
+
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
1218
|
+
PY
|
|
1219
|
+
register_managed_file "$dest" "$source_ref" "config"
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
write_context7_opencode_config() {
|
|
1223
|
+
local dest="$PROJECT_DIR/opencode.json"
|
|
1224
|
+
local body
|
|
1225
|
+
body='
|
|
1226
|
+
mcp = data.setdefault("mcp", {})
|
|
1227
|
+
context7 = {
|
|
1228
|
+
"type": "remote",
|
|
1229
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1230
|
+
"enabled": True,
|
|
1231
|
+
}
|
|
1232
|
+
if context7_api_key:
|
|
1233
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1234
|
+
mcp["context7"] = context7
|
|
1235
|
+
'
|
|
1236
|
+
write_json_config_file "$dest" "generated:context7-opencode-config" "$body"
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
write_context7_opencode_legacy_config() {
|
|
1240
|
+
local dest="$PROJECT_DIR/.opencode/opencode.json"
|
|
1241
|
+
local body
|
|
1242
|
+
body='
|
|
1243
|
+
mcp = data.setdefault("mcp", {})
|
|
1244
|
+
context7 = {
|
|
1245
|
+
"type": "remote",
|
|
1246
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1247
|
+
"enabled": True,
|
|
1248
|
+
}
|
|
1249
|
+
if context7_api_key:
|
|
1250
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1251
|
+
mcp["context7"] = context7
|
|
1252
|
+
'
|
|
1253
|
+
write_json_file_with_agentic_metadata "$dest" "generated:context7-opencode-legacy-config" "$body"
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
write_context7_codex_config() {
|
|
1257
|
+
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
1258
|
+
local headers=""
|
|
1259
|
+
if [[ -n "$CONTEXT7_API_KEY" ]]; then
|
|
1260
|
+
local escaped_key
|
|
1261
|
+
escaped_key="${CONTEXT7_API_KEY//\\/\\\\}"
|
|
1262
|
+
escaped_key="${escaped_key//\"/\\\"}"
|
|
1263
|
+
headers="http_headers = { \"CONTEXT7_API_KEY\" = \"$escaped_key\" }
|
|
1264
|
+
"
|
|
1265
|
+
fi
|
|
1266
|
+
write_generated_text_file "$dest" "generated:context7-codex-config" "[mcp_servers.context7]
|
|
1267
|
+
url = \"https://mcp.context7.com/mcp\"
|
|
1268
|
+
${headers}"
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
write_context7_claude_config() {
|
|
1272
|
+
local dest="$PROJECT_DIR/.mcp.json"
|
|
1273
|
+
local body
|
|
1274
|
+
body='
|
|
1275
|
+
mcp_servers = data.setdefault("mcpServers", {})
|
|
1276
|
+
context7 = {
|
|
1277
|
+
"type": "http",
|
|
1278
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1279
|
+
}
|
|
1280
|
+
if context7_api_key:
|
|
1281
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1282
|
+
mcp_servers["context7"] = context7
|
|
1283
|
+
'
|
|
1284
|
+
write_json_config_file "$dest" "generated:context7-claude-config" "$body"
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
write_context7_cursor_config() {
|
|
1288
|
+
local dest="$PROJECT_DIR/.cursor/mcp.json"
|
|
1289
|
+
local body
|
|
1290
|
+
body='
|
|
1291
|
+
mcp_servers = data.setdefault("mcpServers", {})
|
|
1292
|
+
context7 = {
|
|
1293
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1294
|
+
}
|
|
1295
|
+
if context7_api_key:
|
|
1296
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1297
|
+
mcp_servers["context7"] = context7
|
|
1298
|
+
'
|
|
1299
|
+
write_json_config_file "$dest" "generated:context7-cursor-config" "$body"
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
write_context7_kilocode_config() {
|
|
1304
|
+
local dest="$PROJECT_DIR/.kilocode/mcp.json"
|
|
1305
|
+
local body
|
|
1306
|
+
body='
|
|
1307
|
+
mcp_servers = data.setdefault("mcpServers", {})
|
|
1308
|
+
context7 = {
|
|
1309
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1310
|
+
}
|
|
1311
|
+
if context7_api_key:
|
|
1312
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1313
|
+
mcp_servers["context7"] = context7
|
|
1314
|
+
'
|
|
1315
|
+
write_json_config_file "$dest" "generated:context7-kilocode-config" "$body"
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
write_context7_antigravity_config() {
|
|
1319
|
+
local dest="$HOME/.gemini/antigravity/mcp_config.json"
|
|
1320
|
+
local body
|
|
1321
|
+
body='
|
|
1322
|
+
mcp_servers = data.setdefault("mcpServers", {})
|
|
1323
|
+
context7 = {
|
|
1324
|
+
"url": "https://mcp.context7.com/mcp",
|
|
1325
|
+
}
|
|
1326
|
+
if context7_api_key:
|
|
1327
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1328
|
+
mcp_servers["context7"] = context7
|
|
1329
|
+
'
|
|
1330
|
+
write_json_config_file "$dest" "generated:context7-antigravity-config" "$body"
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
write_context7_gemini_config() {
|
|
1334
|
+
local dest="$PROJECT_DIR/.gemini/settings.json"
|
|
1335
|
+
local body
|
|
1336
|
+
body='
|
|
1337
|
+
mcp_servers = data.setdefault("mcpServers", {})
|
|
1338
|
+
context7 = {
|
|
1339
|
+
"httpUrl": "https://mcp.context7.com/mcp",
|
|
1340
|
+
}
|
|
1341
|
+
if context7_api_key:
|
|
1342
|
+
context7["headers"] = {"CONTEXT7_API_KEY": context7_api_key}
|
|
1343
|
+
mcp_servers["context7"] = context7
|
|
1344
|
+
'
|
|
1345
|
+
write_json_config_file "$dest" "generated:context7-gemini-config" "$body"
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
write_mempalace_opencode_config() {
|
|
1349
|
+
local dest="$1"
|
|
1350
|
+
local body
|
|
1351
|
+
body='
|
|
1352
|
+
mcp = data.setdefault("mcp", {})
|
|
1353
|
+
mcp["mempalace"] = {"type": "local", "command": ["mempalace-mcp", "--palace", ".mempalace"]}
|
|
1354
|
+
'
|
|
1355
|
+
write_json_config_file "$dest" "generated:mempalace-opencode-config" "$body"
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
write_mempalace_codex_config() {
|
|
1359
|
+
local dest="$PROJECT_DIR/.codex/config.toml"
|
|
1360
|
+
local body
|
|
1361
|
+
body="$(python3 - "$dest" <<'PYCODE'
|
|
1362
|
+
import pathlib, sys
|
|
1363
|
+
path = pathlib.Path(sys.argv[1])
|
|
1364
|
+
text = path.read_text(encoding='utf-8') if path.exists() else ''
|
|
1365
|
+
block = "[mcp_servers.mempalace]\ncommand = \"mempalace-mcp\"\nargs = [\"--palace\", \".mempalace\"]\n"
|
|
1366
|
+
if block not in text:
|
|
1367
|
+
if text and not text.endswith("\n"):
|
|
1368
|
+
text += "\n"
|
|
1369
|
+
text += block
|
|
1370
|
+
print(text, end="")
|
|
1371
|
+
PYCODE
|
|
1372
|
+
)"
|
|
1373
|
+
write_generated_text_file "$dest" "generated:mempalace-codex-config" "$body"
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
write_mempalace_generic_json_config() {
|
|
1377
|
+
local dest="$1"
|
|
1378
|
+
local marker="$2"
|
|
1379
|
+
local body
|
|
1380
|
+
body='
|
|
1381
|
+
servers = data.setdefault("mcpServers", {})
|
|
1382
|
+
servers["mempalace"] = {"command": "mempalace-mcp", "args": ["--palace", ".mempalace"]}
|
|
1383
|
+
'
|
|
1384
|
+
write_json_config_file "$dest" "$marker" "$body"
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
print_mempalace_project_setup_instructions() {
|
|
1388
|
+
log "MemPalace setup instructions for target project: $PROJECT_DIR"
|
|
1389
|
+
cat <<EOF
|
|
1390
|
+
1) Ensure Python is installed and available in PATH.
|
|
1391
|
+
2) Install MemPalace:
|
|
1392
|
+
pip install mempalace
|
|
1393
|
+
3) Initialize project-local MemPalace cache:
|
|
1394
|
+
mkdir -p "$PROJECT_DIR/.mempalace"
|
|
1395
|
+
mempalace init "$PROJECT_DIR/.mempalace" --yes --auto-mine
|
|
1396
|
+
4) Index existing project memory:
|
|
1397
|
+
# optional if --auto-mine was skipped
|
|
1398
|
+
mempalace mine "$PROJECT_DIR/.mempalace"
|
|
1399
|
+
5) Verify in your IDE/agent that MemPalace MCP tools are connected.
|
|
1400
|
+
Note: Ollama at localhost:11434 is optional; MemPalace can run heuristics-only without it.
|
|
1401
|
+
EOF
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
setup_mempalace_for_agentic_opencode() {
|
|
1405
|
+
local step_prefix="MemPalace setup"
|
|
1406
|
+
|
|
1407
|
+
log "$step_prefix [1/4] Checking Python availability"
|
|
1408
|
+
if ! command -v python3 >/dev/null 2>&1 && ! command -v python >/dev/null 2>&1; then
|
|
1409
|
+
warn "Python is not installed. Install Python 3 first, then run: pip install mempalace"
|
|
1410
|
+
warn "Install help: https://www.python.org/downloads/"
|
|
1411
|
+
return 1
|
|
1412
|
+
fi
|
|
1413
|
+
log "$step_prefix [1/4] Python check passed"
|
|
1414
|
+
|
|
1415
|
+
log "$step_prefix [2/4] Checking pip availability"
|
|
1416
|
+
if ! command -v pip >/dev/null 2>&1 && ! command -v pip3 >/dev/null 2>&1; then
|
|
1417
|
+
warn "pip is not available. Install pip for Python 3, then run: pip install mempalace"
|
|
1418
|
+
return 1
|
|
1419
|
+
fi
|
|
1420
|
+
log "$step_prefix [2/4] pip check passed"
|
|
1421
|
+
|
|
1422
|
+
log "$step_prefix [3/4] Installing mempalace package"
|
|
1423
|
+
if pip install mempalace >/dev/null 2>&1; then
|
|
1424
|
+
log "MemPalace package installed via 'pip install mempalace'"
|
|
587
1425
|
else
|
|
588
|
-
|
|
1426
|
+
warn "Unable to auto-install mempalace via pip; continuing with manual setup instructions"
|
|
1427
|
+
print_mempalace_project_setup_instructions
|
|
1428
|
+
return 1
|
|
1429
|
+
fi
|
|
1430
|
+
|
|
1431
|
+
log "$step_prefix [4/4] Initializing project memory at $PROJECT_DIR/.mempalace"
|
|
1432
|
+
if command -v mempalace >/dev/null 2>&1; then
|
|
1433
|
+
mkdir -p "$PROJECT_DIR/.mempalace" || warn "Failed: mkdir -p \"$PROJECT_DIR/.mempalace\""
|
|
1434
|
+
if mempalace init "$PROJECT_DIR/.mempalace" --yes --auto-mine >/dev/null 2>&1; then
|
|
1435
|
+
log "MemPalace init completed"
|
|
1436
|
+
else
|
|
1437
|
+
warn "Failed: mempalace init \"$PROJECT_DIR/.mempalace\" --yes --auto-mine"
|
|
1438
|
+
fi
|
|
1439
|
+
if mempalace mine "$PROJECT_DIR/.mempalace" >/dev/null 2>&1; then
|
|
1440
|
+
log "MemPalace mine completed"
|
|
1441
|
+
else
|
|
1442
|
+
warn "Failed: mempalace mine \"$PROJECT_DIR/.mempalace\""
|
|
1443
|
+
fi
|
|
1444
|
+
log "$step_prefix [4/4] Initialization step finished"
|
|
1445
|
+
else
|
|
1446
|
+
warn "mempalace command is unavailable after install; please run setup manually"
|
|
1447
|
+
print_mempalace_project_setup_instructions
|
|
1448
|
+
return 1
|
|
1449
|
+
fi
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
configure_mempalace_if_needed() {
|
|
1453
|
+
if ! selected_agent_os_contains "opencode" \
|
|
1454
|
+
&& ! selected_agent_os_contains "codex" \
|
|
1455
|
+
&& ! selected_agent_os_contains "claude" \
|
|
1456
|
+
&& ! selected_agent_os_contains "cursor" \
|
|
1457
|
+
&& ! selected_agent_os_contains "gemini" \
|
|
1458
|
+
&& ! selected_agent_os_contains "antigravity"; then
|
|
1459
|
+
return
|
|
1460
|
+
fi
|
|
1461
|
+
|
|
1462
|
+
local enable_mempalace="N"
|
|
1463
|
+
if [[ -n "${AGENTIC_ENABLE_MEMPALACE:-}" ]]; then
|
|
1464
|
+
enable_mempalace="$(trim "${AGENTIC_ENABLE_MEMPALACE}")"
|
|
1465
|
+
elif is_interactive_terminal && [[ -z "${AGENTIC_TEST_SOURCE_AGENTIC:-}" ]]; then
|
|
1466
|
+
read -r -p "Enable MemPalace MCP memory integration? [y/N]: " enable_mempalace
|
|
1467
|
+
enable_mempalace="$(trim "${enable_mempalace:-y}")"
|
|
1468
|
+
if [[ -z "$enable_mempalace" ]]; then enable_mempalace="y"; fi
|
|
1469
|
+
fi
|
|
1470
|
+
if [[ "$enable_mempalace" =~ ^[Nn]$ ]]; then
|
|
1471
|
+
log "Skipped MemPalace MCP configuration"
|
|
1472
|
+
return
|
|
1473
|
+
fi
|
|
1474
|
+
|
|
1475
|
+
if selected_agent_os_contains "opencode"; then
|
|
1476
|
+
setup_mempalace_for_agentic_opencode || true
|
|
1477
|
+
else
|
|
1478
|
+
print_mempalace_project_setup_instructions
|
|
1479
|
+
fi
|
|
1480
|
+
|
|
1481
|
+
if command -v mempalace-mcp >/dev/null 2>&1; then
|
|
1482
|
+
if mempalace-mcp --help >/dev/null 2>&1; then
|
|
1483
|
+
log "MemPalace MCP runtime check succeeded via 'mempalace-mcp'"
|
|
1484
|
+
else
|
|
1485
|
+
warn "MemPalace MCP runtime check failed; continuing without runtime validation"
|
|
1486
|
+
fi
|
|
1487
|
+
else
|
|
1488
|
+
warn "mempalace-mcp is unavailable; install/repair MemPalace and re-run setup"
|
|
1489
|
+
fi
|
|
1490
|
+
|
|
1491
|
+
if selected_agent_os_contains "opencode"; then
|
|
1492
|
+
write_mempalace_opencode_config "$PROJECT_DIR/opencode.json"
|
|
1493
|
+
write_mempalace_opencode_config "$PROJECT_DIR/.opencode/opencode.json"
|
|
1494
|
+
fi
|
|
1495
|
+
if selected_agent_os_contains "codex"; then write_mempalace_codex_config; fi
|
|
1496
|
+
if selected_agent_os_contains "claude"; then
|
|
1497
|
+
write_mempalace_generic_json_config "$PROJECT_DIR/.mcp.json" "generated:mempalace-claude-config"
|
|
1498
|
+
fi
|
|
1499
|
+
if selected_agent_os_contains "cursor"; then
|
|
1500
|
+
write_mempalace_generic_json_config "$PROJECT_DIR/.cursor/mcp.json" "generated:mempalace-cursor-config"
|
|
1501
|
+
fi
|
|
1502
|
+
if selected_agent_os_contains "gemini"; then
|
|
1503
|
+
write_mempalace_generic_json_config "$PROJECT_DIR/.gemini/settings.json" "generated:mempalace-gemini-config"
|
|
1504
|
+
fi
|
|
1505
|
+
if selected_agent_os_contains "antigravity"; then
|
|
1506
|
+
write_mempalace_generic_json_config "$HOME/.gemini/antigravity/mcp_config.json" "generated:mempalace-antigravity-config"
|
|
589
1507
|
fi
|
|
590
|
-
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
configure_context7_if_needed() {
|
|
1511
|
+
if ! selected_agent_os_contains "opencode" \
|
|
1512
|
+
&& ! selected_agent_os_contains "codex" \
|
|
1513
|
+
&& ! selected_agent_os_contains "claude" \
|
|
1514
|
+
&& ! selected_agent_os_contains "cursor" \
|
|
1515
|
+
&& ! selected_agent_os_contains "gemini" \
|
|
1516
|
+
&& ! selected_agent_os_contains "kilocode" \
|
|
1517
|
+
&& ! selected_agent_os_contains "antigravity"; then
|
|
1518
|
+
return
|
|
1519
|
+
fi
|
|
1520
|
+
|
|
1521
|
+
if is_interactive_terminal; then
|
|
1522
|
+
local enable_context7 answer
|
|
1523
|
+
read -r -p "Enable Context7 MCP configuration? [y/N]: " enable_context7
|
|
1524
|
+
enable_context7="$(trim "$enable_context7")"
|
|
1525
|
+
if [[ ! "$enable_context7" =~ ^[Yy]$ ]]; then
|
|
1526
|
+
log "Context7 MCP configuration disabled"
|
|
1527
|
+
return
|
|
1528
|
+
fi
|
|
1529
|
+
|
|
1530
|
+
if [[ -z "$CONTEXT7_API_KEY" ]]; then
|
|
1531
|
+
read -r -p "Context7 API key (optional, empty = no key): " answer
|
|
1532
|
+
CONTEXT7_API_KEY="$(trim "$answer")"
|
|
1533
|
+
fi
|
|
1534
|
+
elif [[ -z "$CONTEXT7_API_KEY" ]]; then
|
|
1535
|
+
log "Context7 MCP configuration skipped; set CONTEXT7_API_KEY or use an interactive install to enable it"
|
|
1536
|
+
return
|
|
1537
|
+
fi
|
|
1538
|
+
|
|
1539
|
+
if selected_agent_os_contains "opencode"; then
|
|
1540
|
+
write_context7_opencode_config
|
|
1541
|
+
write_context7_opencode_legacy_config
|
|
1542
|
+
fi
|
|
1543
|
+
|
|
1544
|
+
if selected_agent_os_contains "codex"; then
|
|
1545
|
+
write_context7_codex_config
|
|
1546
|
+
fi
|
|
1547
|
+
|
|
1548
|
+
if selected_agent_os_contains "claude"; then
|
|
1549
|
+
write_context7_claude_config
|
|
1550
|
+
fi
|
|
1551
|
+
|
|
1552
|
+
if selected_agent_os_contains "cursor"; then
|
|
1553
|
+
write_context7_cursor_config
|
|
1554
|
+
fi
|
|
1555
|
+
|
|
1556
|
+
if selected_agent_os_contains "gemini"; then
|
|
1557
|
+
write_context7_gemini_config
|
|
1558
|
+
fi
|
|
1559
|
+
|
|
1560
|
+
if selected_agent_os_contains "kilocode"; then
|
|
1561
|
+
write_context7_kilocode_config
|
|
1562
|
+
fi
|
|
1563
|
+
|
|
1564
|
+
if selected_agent_os_contains "antigravity"; then
|
|
1565
|
+
write_context7_antigravity_config
|
|
1566
|
+
fi
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
write_default_opencode_plugin_config() {
|
|
1570
|
+
ensure_dir "$APP_CONFIG_DIR"
|
|
1571
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
1572
|
+
log "DRY-RUN write disabled opencode plugin config to $OPENCODE_PLUGIN_CONFIG_FILE"
|
|
1573
|
+
else
|
|
1574
|
+
python3 - "$OPENCODE_PLUGIN_CONFIG_FILE" <<'PY'
|
|
1575
|
+
import json
|
|
1576
|
+
import sys
|
|
1577
|
+
from pathlib import Path
|
|
1578
|
+
|
|
1579
|
+
path = Path(sys.argv[1])
|
|
1580
|
+
path.write_text(json.dumps({
|
|
1581
|
+
"telegram": {"enabled": False, "botToken": "", "chatId": ""},
|
|
1582
|
+
"modelChecker": {"enabled": False},
|
|
1583
|
+
}, indent=2) + "\n", encoding="utf-8")
|
|
1584
|
+
PY
|
|
1585
|
+
fi
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
configure_opencode_plugins_if_needed() {
|
|
1589
|
+
selected_agent_os_contains "opencode" || return 0
|
|
1590
|
+
|
|
1591
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
1592
|
+
log "DRY-RUN configure optional opencode plugins"
|
|
1593
|
+
return
|
|
1594
|
+
fi
|
|
1595
|
+
|
|
1596
|
+
ensure_python_available
|
|
1597
|
+
ensure_dir "$APP_CONFIG_DIR"
|
|
1598
|
+
|
|
1599
|
+
if ! is_interactive_terminal; then
|
|
1600
|
+
if [[ ! -f "$OPENCODE_PLUGIN_CONFIG_FILE" ]]; then
|
|
1601
|
+
write_default_opencode_plugin_config
|
|
1602
|
+
fi
|
|
1603
|
+
return
|
|
1604
|
+
fi
|
|
1605
|
+
|
|
1606
|
+
local enable_telegram telegram_token telegram_chat enable_model_checker
|
|
1607
|
+
read -r -p "Enable OpenCode Telegram notifications? [y/N]: " enable_telegram
|
|
1608
|
+
enable_telegram="$(trim "$enable_telegram")"
|
|
1609
|
+
telegram_token=""
|
|
1610
|
+
telegram_chat=""
|
|
1611
|
+
if [[ "$enable_telegram" =~ ^[Yy]$ ]]; then
|
|
1612
|
+
read -r -p "Telegram bot token (empty disables plugin): " telegram_token
|
|
1613
|
+
read -r -p "Telegram chat id (empty disables plugin): " telegram_chat
|
|
1614
|
+
telegram_token="$(trim "$telegram_token")"
|
|
1615
|
+
telegram_chat="$(trim "$telegram_chat")"
|
|
1616
|
+
fi
|
|
1617
|
+
|
|
1618
|
+
read -r -p "Enable OpenCode model checker plugin? [y/N]: " enable_model_checker
|
|
1619
|
+
enable_model_checker="$(trim "$enable_model_checker")"
|
|
1620
|
+
|
|
1621
|
+
python3 - "$OPENCODE_PLUGIN_CONFIG_FILE" "$telegram_token" "$telegram_chat" "$enable_model_checker" <<'PY'
|
|
1622
|
+
import json
|
|
1623
|
+
import sys
|
|
1624
|
+
from pathlib import Path
|
|
1625
|
+
|
|
1626
|
+
path = Path(sys.argv[1])
|
|
1627
|
+
token = sys.argv[2]
|
|
1628
|
+
chat = sys.argv[3]
|
|
1629
|
+
enable_model = sys.argv[4].lower() == "y"
|
|
1630
|
+
data = {
|
|
1631
|
+
"telegram": {
|
|
1632
|
+
"enabled": bool(token and chat),
|
|
1633
|
+
"botToken": token,
|
|
1634
|
+
"chatId": chat,
|
|
1635
|
+
},
|
|
1636
|
+
"modelChecker": {
|
|
1637
|
+
"enabled": enable_model,
|
|
1638
|
+
},
|
|
1639
|
+
}
|
|
1640
|
+
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
1641
|
+
PY
|
|
591
1642
|
}
|
|
592
1643
|
|
|
593
1644
|
normalize_selected_agent_os() {
|
|
@@ -747,24 +1798,50 @@ append_root_agents_template() {
|
|
|
747
1798
|
|
|
748
1799
|
generate_agents_md() {
|
|
749
1800
|
local project_dir="$1"
|
|
750
|
-
local
|
|
1801
|
+
local outputs=()
|
|
1802
|
+
local needs_root=false
|
|
1803
|
+
local agent_os
|
|
1804
|
+
|
|
1805
|
+
if selected_agent_os_contains "opencode"; then
|
|
1806
|
+
unique_append "$project_dir/.opencode/AGENTS.md" outputs
|
|
1807
|
+
fi
|
|
1808
|
+
|
|
1809
|
+
for agent_os in "${SELECTED_AGENT_OS[@]}"; do
|
|
1810
|
+
if [[ "$agent_os" != "opencode" ]]; then
|
|
1811
|
+
needs_root=true
|
|
1812
|
+
break
|
|
1813
|
+
fi
|
|
1814
|
+
done
|
|
1815
|
+
|
|
1816
|
+
if [[ "$needs_root" == true ]] || ! selected_agent_os_contains "opencode"; then
|
|
1817
|
+
unique_append "$project_dir/AGENTS.md" outputs
|
|
1818
|
+
fi
|
|
751
1819
|
|
|
752
1820
|
if [[ "$DRY_RUN" == true ]]; then
|
|
753
|
-
|
|
754
|
-
|
|
1821
|
+
local dry_run_out
|
|
1822
|
+
for dry_run_out in "${outputs[@]}"; do
|
|
1823
|
+
log "DRY-RUN generate $dry_run_out"
|
|
1824
|
+
unique_append "$dry_run_out" COPIED_PATHS
|
|
1825
|
+
done
|
|
755
1826
|
return
|
|
756
1827
|
fi
|
|
757
1828
|
|
|
758
1829
|
ensure_dir "$project_dir"
|
|
759
|
-
|
|
760
|
-
|
|
1830
|
+
local tmp
|
|
1831
|
+
tmp="$(mktemp "${TMPDIR:-/tmp}/agentic-agents.XXXXXX")"
|
|
1832
|
+
build_header "$tmp"
|
|
1833
|
+
append_root_agents_template "$tmp"
|
|
761
1834
|
|
|
762
1835
|
local spec_key
|
|
763
1836
|
for spec_key in "${SELECTED_SPECS[@]}"; do
|
|
764
|
-
append_specialization_template "$
|
|
1837
|
+
append_specialization_template "$tmp" "$spec_key"
|
|
765
1838
|
done
|
|
766
1839
|
|
|
767
|
-
|
|
1840
|
+
local out
|
|
1841
|
+
for out in "${outputs[@]}"; do
|
|
1842
|
+
write_file_with_agentic_marker "$tmp" "$out" "generated:AGENTS.md"
|
|
1843
|
+
done
|
|
1844
|
+
rm -f "$tmp"
|
|
768
1845
|
}
|
|
769
1846
|
|
|
770
1847
|
validate_inputs() {
|
|
@@ -868,16 +1945,86 @@ print_report() {
|
|
|
868
1945
|
fi
|
|
869
1946
|
}
|
|
870
1947
|
|
|
1948
|
+
detect_runtime_platform_label() {
|
|
1949
|
+
local platform
|
|
1950
|
+
platform="$(detect_platform)"
|
|
1951
|
+
if [[ "$platform" == "linux" ]] && grep -qiE "(microsoft|wsl)" /proc/version 2>/dev/null; then
|
|
1952
|
+
echo "wsl"
|
|
1953
|
+
return
|
|
1954
|
+
fi
|
|
1955
|
+
echo "$platform"
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
get_agent_binary_name() {
|
|
1959
|
+
local agent_os="$1"
|
|
1960
|
+
case "$agent_os" in
|
|
1961
|
+
codex) echo "codex" ;;
|
|
1962
|
+
claude) echo "claude" ;;
|
|
1963
|
+
opencode) echo "opencode" ;;
|
|
1964
|
+
cursor) echo "cursor-agent" ;;
|
|
1965
|
+
gemini) echo "gemini" ;;
|
|
1966
|
+
antigravity) echo "antigravity" ;;
|
|
1967
|
+
*) echo "" ;;
|
|
1968
|
+
esac
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
print_missing_agent_binary_guides() {
|
|
1972
|
+
local platform_label
|
|
1973
|
+
platform_label="$(detect_runtime_platform_label)"
|
|
1974
|
+
local missing_lines=()
|
|
1975
|
+
local agent_os
|
|
1976
|
+
|
|
1977
|
+
for agent_os in "${SELECTED_AGENT_OS[@]}"; do
|
|
1978
|
+
local binary_name
|
|
1979
|
+
binary_name="$(get_agent_binary_name "$agent_os")"
|
|
1980
|
+
if [[ -z "$binary_name" ]]; then
|
|
1981
|
+
continue
|
|
1982
|
+
fi
|
|
1983
|
+
if command -v "$binary_name" >/dev/null 2>&1; then
|
|
1984
|
+
continue
|
|
1985
|
+
fi
|
|
1986
|
+
|
|
1987
|
+
local install_link=""
|
|
1988
|
+
case "$agent_os" in
|
|
1989
|
+
codex) install_link="https://github.com/openai/codex" ;;
|
|
1990
|
+
claude) install_link="https://docs.anthropic.com/en/docs/claude-code/quickstart" ;;
|
|
1991
|
+
opencode) install_link="https://opencode.ai/docs" ;;
|
|
1992
|
+
cursor) install_link="https://docs.cursor.com/get-started/installation" ;;
|
|
1993
|
+
gemini) install_link="https://github.com/google-gemini/gemini-cli" ;;
|
|
1994
|
+
antigravity) install_link="https://github.com/getantigravity/antigravity" ;;
|
|
1995
|
+
esac
|
|
1996
|
+
|
|
1997
|
+
missing_lines+=("- $agent_os: binary '$binary_name' is not installed on $platform_label.")
|
|
1998
|
+
if [[ -n "$install_link" ]]; then
|
|
1999
|
+
missing_lines+=(" Install guide: $install_link")
|
|
2000
|
+
fi
|
|
2001
|
+
done
|
|
2002
|
+
|
|
2003
|
+
if [[ "${#missing_lines[@]}" -eq 0 ]]; then
|
|
2004
|
+
return
|
|
2005
|
+
fi
|
|
2006
|
+
|
|
2007
|
+
echo
|
|
2008
|
+
echo "${COLOR_HEADER}=== Agent binary setup recommendations ===${COLOR_RESET}"
|
|
2009
|
+
printf '%s\n' "${missing_lines[@]}"
|
|
2010
|
+
}
|
|
2011
|
+
|
|
871
2012
|
run_install() {
|
|
872
2013
|
ensure_repo_layout
|
|
2014
|
+
ensure_python_available
|
|
873
2015
|
normalize_selected_agent_os
|
|
874
2016
|
validate_inputs
|
|
875
2017
|
|
|
876
2018
|
ensure_dir "$PROJECT_DIR"
|
|
2019
|
+
configure_opencode_plugins_if_needed
|
|
877
2020
|
copy_extensions "$PROJECT_DIR"
|
|
878
2021
|
copy_specialization_assets "$PROJECT_DIR"
|
|
879
2022
|
generate_agents_md "$PROJECT_DIR"
|
|
2023
|
+
configure_context7_if_needed
|
|
2024
|
+
configure_mempalace_if_needed
|
|
2025
|
+
write_agentic_manifest "$PROJECT_DIR"
|
|
880
2026
|
print_report
|
|
2027
|
+
print_missing_agent_binary_guides
|
|
881
2028
|
}
|
|
882
2029
|
|
|
883
2030
|
ascii_banner() {
|
|
@@ -1256,7 +2403,9 @@ run_tui() {
|
|
|
1256
2403
|
if [[ "$use_fzf" == true ]]; then
|
|
1257
2404
|
readlines picked_agent_os < <(choose_multi_fzf "Select Agent OS target(s):" "${agentos_choices[@]}")
|
|
1258
2405
|
else
|
|
1259
|
-
|
|
2406
|
+
local picked_agent_os_output
|
|
2407
|
+
picked_agent_os_output="$(choose_multi_by_index "Select Agent OS target(s):" "${agentos_choices[@]}")"
|
|
2408
|
+
readlines picked_agent_os <<< "$picked_agent_os_output"
|
|
1260
2409
|
fi
|
|
1261
2410
|
if [[ "${#picked_agent_os[@]}" -eq 0 ]]; then
|
|
1262
2411
|
SELECTED_AGENT_OS=("$DEFAULT_AGENT_OS")
|
|
@@ -1271,7 +2420,9 @@ run_tui() {
|
|
|
1271
2420
|
if [[ "$use_fzf" == true ]]; then
|
|
1272
2421
|
readlines picked_areas < <(choose_multi_fzf "Select area(s):" "${areas[@]}")
|
|
1273
2422
|
else
|
|
1274
|
-
|
|
2423
|
+
local picked_areas_output
|
|
2424
|
+
picked_areas_output="$(choose_multi_by_index "Select area(s):" "${areas[@]}")"
|
|
2425
|
+
readlines picked_areas <<< "$picked_areas_output"
|
|
1275
2426
|
fi
|
|
1276
2427
|
|
|
1277
2428
|
if [[ "${#picked_areas[@]}" -eq 0 ]]; then
|
|
@@ -1290,7 +2441,9 @@ run_tui() {
|
|
|
1290
2441
|
if [[ "$use_fzf" == true ]]; then
|
|
1291
2442
|
readlines chosen_specs < <(choose_multi_fzf "Select specialization(s) for '$area':" "${specs[@]}")
|
|
1292
2443
|
else
|
|
1293
|
-
|
|
2444
|
+
local chosen_specs_output
|
|
2445
|
+
chosen_specs_output="$(choose_multi_by_index "Select specialization(s) for '$area':" "${specs[@]}")"
|
|
2446
|
+
readlines chosen_specs <<< "$chosen_specs_output"
|
|
1294
2447
|
fi
|
|
1295
2448
|
|
|
1296
2449
|
if [[ "${#chosen_specs[@]}" -eq 0 ]]; then
|
|
@@ -1325,6 +2478,22 @@ self_install() {
|
|
|
1325
2478
|
|
|
1326
2479
|
ensure_dir "$bin_dir"
|
|
1327
2480
|
|
|
2481
|
+
if [[ -e "$source_path" && -e "$target" ]]; then
|
|
2482
|
+
local source_real target_real
|
|
2483
|
+
source_real="$(cd -- "$(dirname -- "$source_path")" && pwd -P)/$(basename -- "$source_path")"
|
|
2484
|
+
target_real="$(cd -- "$(dirname -- "$target")" && pwd -P)/$(basename -- "$target")"
|
|
2485
|
+
if [[ "$source_real" == "$target_real" ]]; then
|
|
2486
|
+
if [[ -e "$APP_REPO_DIR/agentic" ]]; then
|
|
2487
|
+
source_path="$APP_REPO_DIR/agentic"
|
|
2488
|
+
else
|
|
2489
|
+
log "Source and target are already the same file: $target"
|
|
2490
|
+
log "Nothing to copy. Run '$APP_NAME upgrade' first to refresh the knowledge base checkout, or run self-install from a checkout."
|
|
2491
|
+
self_install_fzf_optional
|
|
2492
|
+
return
|
|
2493
|
+
fi
|
|
2494
|
+
fi
|
|
2495
|
+
fi
|
|
2496
|
+
|
|
1328
2497
|
if [[ -e "$target" ]] && [[ "$SELF_INSTALL_FORCE" != true ]]; then
|
|
1329
2498
|
error "Target already exists: $target"
|
|
1330
2499
|
error "Use --force to overwrite"
|
|
@@ -1363,6 +2532,35 @@ self_install() {
|
|
|
1363
2532
|
echo "Dry-run: $DRY_RUN"
|
|
1364
2533
|
}
|
|
1365
2534
|
|
|
2535
|
+
update_installed_binary_from_repo() {
|
|
2536
|
+
local source_path="$APP_REPO_DIR/agentic"
|
|
2537
|
+
local target="$SCRIPT_SOURCE"
|
|
2538
|
+
|
|
2539
|
+
if [[ ! -r "$source_path" ]]; then
|
|
2540
|
+
warn "Cannot self-update installed binary; source not found: $source_path"
|
|
2541
|
+
return
|
|
2542
|
+
fi
|
|
2543
|
+
|
|
2544
|
+
if [[ "$DRY_RUN" == true ]]; then
|
|
2545
|
+
log "DRY-RUN self-update installed binary $target from $source_path"
|
|
2546
|
+
return
|
|
2547
|
+
fi
|
|
2548
|
+
|
|
2549
|
+
if cmp -s "$source_path" "$target"; then
|
|
2550
|
+
log "Installed binary already up to date: $target"
|
|
2551
|
+
return
|
|
2552
|
+
fi
|
|
2553
|
+
|
|
2554
|
+
local target_dir target_tmp
|
|
2555
|
+
target_dir="$(dirname "$target")"
|
|
2556
|
+
target_tmp="$(mktemp "$target_dir/.agentic-update.XXXXXX")"
|
|
2557
|
+
|
|
2558
|
+
cp "$source_path" "$target_tmp"
|
|
2559
|
+
chmod +x "$target_tmp"
|
|
2560
|
+
mv -f "$target_tmp" "$target"
|
|
2561
|
+
log "Updated installed binary: $target"
|
|
2562
|
+
}
|
|
2563
|
+
|
|
1366
2564
|
upgrade_repo_checkout() {
|
|
1367
2565
|
refresh_repo_paths
|
|
1368
2566
|
|
|
@@ -1392,6 +2590,21 @@ upgrade_repo_checkout() {
|
|
|
1392
2590
|
git -C "$APP_REPO_DIR" pull --ff-only
|
|
1393
2591
|
refresh_repo_paths
|
|
1394
2592
|
ensure_repo_layout
|
|
2593
|
+
update_installed_binary_from_repo
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
sync_current_project_after_upgrade() {
|
|
2597
|
+
local manifest="$PWD/$PROJECT_MANIFEST_NAME"
|
|
2598
|
+
if [[ ! -f "$manifest" ]]; then
|
|
2599
|
+
log "No $PROJECT_MANIFEST_NAME in current directory; knowledge base upgrade complete"
|
|
2600
|
+
return
|
|
2601
|
+
fi
|
|
2602
|
+
|
|
2603
|
+
log "Detected managed project in $PWD; syncing from upgraded knowledge base"
|
|
2604
|
+
PROJECT_DIR="$(pwd -P)"
|
|
2605
|
+
load_install_settings_from_manifest "$manifest"
|
|
2606
|
+
ensure_repo_layout
|
|
2607
|
+
run_install
|
|
1395
2608
|
}
|
|
1396
2609
|
|
|
1397
2610
|
parse_theme_option() {
|
|
@@ -1465,6 +2678,9 @@ case "$COMMAND" in
|
|
|
1465
2678
|
shift 2
|
|
1466
2679
|
;;
|
|
1467
2680
|
--agent-os)
|
|
2681
|
+
if [[ "${#SELECTED_AGENT_OS[@]}" -eq 1 && "${SELECTED_AGENT_OS[0]}" == "$DEFAULT_AGENT_OS" ]]; then
|
|
2682
|
+
SELECTED_AGENT_OS=()
|
|
2683
|
+
fi
|
|
1468
2684
|
split_csv "$2" SELECTED_AGENT_OS
|
|
1469
2685
|
shift 2
|
|
1470
2686
|
;;
|
|
@@ -1499,6 +2715,12 @@ case "$COMMAND" in
|
|
|
1499
2715
|
;;
|
|
1500
2716
|
esac
|
|
1501
2717
|
done
|
|
2718
|
+
if [[ -z "$PROJECT_DIR" && -f "$PWD/$PROJECT_MANIFEST_NAME" ]]; then
|
|
2719
|
+
PROJECT_DIR="$PWD"
|
|
2720
|
+
load_install_settings_from_manifest "$PWD/$PROJECT_MANIFEST_NAME"
|
|
2721
|
+
elif [[ -n "$PROJECT_DIR" && -f "$PROJECT_DIR/$PROJECT_MANIFEST_NAME" ]]; then
|
|
2722
|
+
load_install_settings_from_manifest "$PROJECT_DIR/$PROJECT_MANIFEST_NAME"
|
|
2723
|
+
fi
|
|
1502
2724
|
ensure_repo_available_for_command
|
|
1503
2725
|
ensure_repo_layout
|
|
1504
2726
|
set_theme_colors
|
|
@@ -1557,6 +2779,7 @@ case "$COMMAND" in
|
|
|
1557
2779
|
esac
|
|
1558
2780
|
done
|
|
1559
2781
|
upgrade_repo_checkout
|
|
2782
|
+
sync_current_project_after_upgrade
|
|
1560
2783
|
;;
|
|
1561
2784
|
|
|
1562
2785
|
self-install)
|