Repository navigation
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·175 lines (162 loc) · 7.15 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·175 lines (162 loc) · 7.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env bash
# Install On Point into whichever agents a project actually uses.
#
# ./install.sh detect agents in the current directory, install those
# ./install.sh --all install every surface regardless of detection
# ./install.sh --dry-run print what would change, touch nothing
# ./install.sh --target DIR install into DIR instead of the current directory
# ./install.sh --uninstall remove what this installed
#
# Two classes of destination, handled differently on purpose:
#
# DEDICATED .cursor/rules/on-point.mdc, .clinerules/on-point.md, …
# Ours alone. Written whole.
#
# SHARED AGENTS.md, CLAUDE.md, GEMINI.md, .github/copilot-instructions.md
# Usually already full of the user's own instructions. NEVER
# overwritten — spliced between markers, idempotently, so a re-run
# replaces our block and leaves everything else byte-identical.
set -euo pipefail
SRC="$(cd "$(dirname "$0")" && pwd)"
TARGET="$PWD"
MODE=detect
DRY=0
while [ $# -gt 0 ]; do
case "$1" in
--all) MODE=all ;;
--uninstall) MODE=uninstall ;;
--dry-run|-n) DRY=1 ;;
--target) TARGET="$2"; shift ;;
-h|--help) sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) echo "unknown flag: $1 (try --help)" >&2; exit 2 ;;
esac
shift
done
[ -d "$TARGET" ] || { echo "no such directory: $TARGET" >&2; exit 1; }
[ -f "$SRC/personas/00-doctrine.md" ] || { echo "run this from an On Point checkout" >&2; exit 1; }
# agent | marker that says "this project uses it" | source in checkout | destination
# src and dest differ where an agent wants the file somewhere else (claude-code).
AGENTS="
cursor|.cursor|.cursor/rules/on-point.mdc|.cursor/rules/on-point.mdc
windsurf|.windsurf|.windsurf/rules/on-point.md|.windsurf/rules/on-point.md
cline|.clinerules|.clinerules/on-point.md|.clinerules/on-point.md
kiro|.kiro|.kiro/steering/on-point.md|.kiro/steering/on-point.md
qoder|.qoder|.qoder/rules/on-point.md|.qoder/rules/on-point.md
opencode|.opencode|.opencode/command/on-point.md|.opencode/command/on-point.md
openclaw|.openclaw|.openclaw/skills/on-point/SKILL.md|.openclaw/skills/on-point/SKILL.md
claude-code|.claude|skills/on-point/SKILL.md|.claude/skills/on-point/SKILL.md
codex|.codex|.codex/config.toml|.codex/config.toml
codex-hooks|.codex|.codex/hooks.json|.codex/hooks.json
"
# shared files: agent | marker | destination | which generated file supplies the body
SHARED="
agents-md|AGENTS.md|AGENTS.md|AGENTS.md
claude-md|CLAUDE.md|CLAUDE.md|CLAUDE.md
gemini|GEMINI.md|GEMINI.md|GEMINI.md
copilot|.github|.github/copilot-instructions.md|.github/copilot-instructions.md
"
# The Claude Code plugin's hook already injects the whole body at the tail of every
# session. Splicing it into CLAUDE.md as well puts the same ~12 KB in the cached
# system prefix — the same instruction paid for twice, in the position this project
# argues against. Same for a second copy of the skill, which the plugin also ships.
# ON_POINT_FORCE_CLAUDE=1 installs them anyway.
plugin_installed() {
[ "${ON_POINT_FORCE_CLAUDE:-0}" = 1 ] && return 1
if command -v claude >/dev/null 2>&1 &&
claude plugin list --json 2>/dev/null | grep -q '"on-point@'; then return 0; fi
[ -d "$HOME/.claude/plugins/cache/on-point" ] && return 0
return 1
}
BEGIN="<!-- on-point:begin — generated, edit personas/ upstream instead -->"
END="<!-- on-point:end -->"
say() { printf ' %-12s %s\n' "$1" "$2"; }
splice() { # splice <destfile> <bodyfile>
BEGIN="$BEGIN" END="$END" python3 - "$1" "$2" <<'PY'
import os, sys, pathlib
dest, body_src = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2])
b, e = os.environ["BEGIN"], os.environ["END"]
block = f"{b}\n{body_src.read_text(encoding='utf-8').strip()}\n{e}\n"
old = dest.read_text(encoding="utf-8") if dest.exists() else ""
if b in old and e in old: # idempotent replace
head, rest = old.split(b, 1)
_, tail = rest.split(e, 1)
new = head + block + tail.lstrip("\n")
else: # append, never clobber
new = (old.rstrip("\n") + "\n\n" if old.strip() else "") + block
if new != old:
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(new, encoding="utf-8")
print("changed")
else:
print("unchanged")
PY
}
unsplice() { # unsplice <destfile>
BEGIN="$BEGIN" END="$END" python3 - "$1" <<'PY'
import os, sys, pathlib
dest = pathlib.Path(sys.argv[1])
b, e = os.environ["BEGIN"], os.environ["END"]
if not dest.exists(): print("absent"); raise SystemExit
old = dest.read_text(encoding="utf-8")
if b not in old or e not in old: print("absent"); raise SystemExit
head, rest = old.split(b, 1)
_, tail = rest.split(e, 1)
new = (head.rstrip("\n") + "\n" + tail.lstrip("\n")).strip()
dest.write_text(new + "\n" if new else "", encoding="utf-8")
print("removed")
PY
}
echo "on-point → $TARGET"
[ "$DRY" = 1 ] && echo " (dry run — nothing will be written)"
n=0
# ── dedicated files ──────────────────────────────────────────────────────────
while IFS='|' read -r name marker src dest; do
[ -n "$name" ] || continue
if [ "$MODE" = detect ] && [ ! -e "$TARGET/$marker" ]; then continue; fi
if [ "$MODE" = uninstall ]; then
if [ -e "$TARGET/$dest" ]; then
[ "$DRY" = 1 ] || rm -f "$TARGET/$dest"
say "$name" "removed $dest"; n=$((n+1))
fi
continue
fi
if [ "$name" = claude-code ] && [ "$MODE" != uninstall ] && plugin_installed; then
say "$name" "SKIP — the plugin already ships this skill"; continue
fi
[ -f "$SRC/$src" ] || { say "$name" "SKIP — $src missing from checkout (run ./build.sh)"; continue; }
if [ "$DRY" = 1 ]; then say "$name" "would write $dest"
else
mkdir -p "$TARGET/$(dirname "$dest")"
cp "$SRC/$src" "$TARGET/$dest"
say "$name" "wrote $dest"
fi
n=$((n+1))
done <<< "$AGENTS"
# ── shared files: splice, never overwrite ────────────────────────────────────
while IFS='|' read -r name marker dest body; do
[ -n "$name" ] || continue
if [ "$MODE" = detect ] && [ ! -e "$TARGET/$marker" ]; then continue; fi
if [ "$MODE" = uninstall ]; then
r=$([ "$DRY" = 1 ] && echo "would unsplice" || unsplice "$TARGET/$dest")
[ "$r" = absent ] || { say "$name" "$r block from $dest"; n=$((n+1)); }
continue
fi
if [ "$name" = claude-md ] && [ "$MODE" != uninstall ] && plugin_installed; then
say "$name" "SKIP — the plugin's hook already injects this; a spliced CLAUDE.md would double it"
continue
fi
[ -f "$SRC/$body" ] || { say "$name" "SKIP — $body missing (run ./build.sh)"; continue; }
if [ "$DRY" = 1 ]; then
say "$name" "would splice block into $dest$([ -f "$TARGET/$dest" ] && echo ' (existing file preserved)')"
else
r=$(splice "$TARGET/$dest" "$SRC/$body")
say "$name" "$r $dest"
fi
n=$((n+1))
done <<< "$SHARED"
if [ "$n" = 0 ]; then
echo " no agents detected here. Use --all to install every surface,"
echo " or --help to see the list."
else
echo " $n surface(s). Re-run any time — shared files are spliced, not overwritten."
fi