superpowers-manager 0.2.0 → 0.4.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/README.md +340 -66
- package/dist/adapter-result.js +204 -0
- package/dist/adapter.js +690 -0
- package/dist/atomic.js +142 -0
- package/dist/cli-arguments.js +32 -0
- package/dist/cli.js +313 -0
- package/dist/codex-harness.js +271 -0
- package/dist/codex-json.js +89 -0
- package/dist/codex-prepare.js +199 -0
- package/dist/codex-presentation.js +259 -0
- package/dist/codex-state.js +78 -0
- package/dist/commands/context.js +1 -0
- package/dist/commands/install.js +291 -0
- package/dist/commands/pin.js +79 -0
- package/dist/commands/prepare.js +420 -0
- package/dist/commands/probe.js +257 -0
- package/dist/commands/track-latest.js +44 -0
- package/dist/commands/uninstall.js +217 -0
- package/dist/commands/unpin.js +95 -0
- package/dist/commands/update.js +79 -0
- package/dist/domain/fingerprint.js +4 -0
- package/dist/domain/refs.js +37 -0
- package/dist/effective-selection.js +123 -0
- package/dist/generated-plugin.js +748 -0
- package/dist/git.js +38 -0
- package/dist/harness.js +1 -0
- package/dist/hooks.js +290 -0
- package/dist/lifecycle.js +135 -0
- package/dist/manifest-overlay.js +125 -0
- package/dist/provenance.js +111 -0
- package/dist/python-json-format.js +83 -0
- package/dist/python-json.js +29 -0
- package/dist/python-text.js +74 -0
- package/dist/safe-path.js +137 -0
- package/dist/safety-error.js +10 -0
- package/dist/selection-state-cli.js +85 -0
- package/dist/selection-store.js +172 -0
- package/dist/selection.js +200 -0
- package/dist/status.js +24 -0
- package/dist/strict-json.js +248 -0
- package/dist/upstream-cli.js +110 -0
- package/dist/upstream-version.js +40 -0
- package/dist/upstream.js +253 -0
- package/dist/validate-generated-plugin-cli.js +118 -0
- package/dist/validator.js +317 -0
- package/dist/workspace.js +139 -0
- package/package.json +25 -4
- package/bin/superpowers-manager.js +0 -290
- package/scripts/adapters/codex/adapter +0 -603
- package/scripts/adapters/codex/lib.sh +0 -449
- package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
- package/scripts/core/adapter.sh +0 -103
- package/scripts/core/common.sh +0 -59
- package/scripts/core/lifecycle.sh +0 -141
- package/scripts/core/provenance.sh +0 -135
- package/scripts/core/selection-state.py +0 -414
- package/scripts/core/selection.sh +0 -168
- package/scripts/core/status.sh +0 -27
- package/scripts/core/upstream.sh +0 -280
- package/scripts/core/validate-adapter-response.py +0 -284
- package/scripts/install +0 -59
- package/scripts/pin +0 -51
- package/scripts/prepare +0 -117
- package/scripts/probe +0 -82
- package/scripts/track-latest +0 -30
- package/scripts/uninstall +0 -35
- package/scripts/unpin +0 -41
- package/scripts/update +0 -34
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import json
|
|
6
|
-
import re
|
|
7
|
-
import sys
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
ENVELOPE_KEYS = {"protocol", "operation", "ok", "messages", "result", "error"}
|
|
11
|
-
MESSAGE_KEYS = {"channel", "text"}
|
|
12
|
-
ERROR_KEYS = {"code", "message", "hints"}
|
|
13
|
-
OWNERSHIP_KEYS = {"plugin", "marketplace"}
|
|
14
|
-
VERIFICATION_HINT_KEYS = {"mismatch", "missing"}
|
|
15
|
-
OPERATIONS = {"build", "inspect", "install", "uninstall"}
|
|
16
|
-
FINGERPRINT_RE = re.compile(r"(?:[0-9a-fA-F]{7}|[0-9a-fA-F]{40})\Z")
|
|
17
|
-
MAX_NESTING = 64
|
|
18
|
-
MAX_RESPONSE_BYTES = 1_048_576
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class ProtocolError(ValueError):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def reject_constant(value: str) -> None:
|
|
26
|
-
raise ProtocolError(f"non-standard JSON constant: {value}")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def reject_duplicate_keys(
|
|
30
|
-
pairs: list[tuple[str, object]],
|
|
31
|
-
) -> dict[str, object]:
|
|
32
|
-
obj: dict[str, object] = {}
|
|
33
|
-
for key, value in pairs:
|
|
34
|
-
if key in obj:
|
|
35
|
-
raise ProtocolError("duplicate object key")
|
|
36
|
-
obj[key] = value
|
|
37
|
-
return obj
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def nesting_exceeds_limit(value: object) -> bool:
|
|
41
|
-
stack = [(value, 0)]
|
|
42
|
-
while stack:
|
|
43
|
-
current, depth = stack.pop()
|
|
44
|
-
if isinstance(current, dict):
|
|
45
|
-
next_depth = depth + 1
|
|
46
|
-
if next_depth > MAX_NESTING:
|
|
47
|
-
return True
|
|
48
|
-
stack.extend((child, next_depth) for child in current.values())
|
|
49
|
-
elif isinstance(current, list):
|
|
50
|
-
next_depth = depth + 1
|
|
51
|
-
if next_depth > MAX_NESTING:
|
|
52
|
-
return True
|
|
53
|
-
stack.extend((child, next_depth) for child in current)
|
|
54
|
-
return False
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def require_object(value: object, label: str) -> dict[str, object]:
|
|
58
|
-
if not isinstance(value, dict):
|
|
59
|
-
raise ProtocolError(f"{label} must be an object")
|
|
60
|
-
return value
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def require_exact_keys(value: dict[str, object], expected: set[str], label: str) -> None:
|
|
64
|
-
actual = set(value)
|
|
65
|
-
if actual != expected:
|
|
66
|
-
raise ProtocolError(
|
|
67
|
-
f"{label} keys must be {sorted(expected)}, got {sorted(actual)}"
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def contains_terminal_control(value: str) -> bool:
|
|
72
|
-
return any(
|
|
73
|
-
ord(character) < 0x20
|
|
74
|
-
or 0x7F <= ord(character) <= 0x9F
|
|
75
|
-
or 0xD800 <= ord(character) <= 0xDFFF
|
|
76
|
-
for character in value
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def require_non_empty_string(value: object, label: str) -> str:
|
|
81
|
-
if not isinstance(value, str) or not value or "\n" in value or "\r" in value:
|
|
82
|
-
raise ProtocolError(f"{label} must be a non-empty single-line string")
|
|
83
|
-
if contains_terminal_control(value):
|
|
84
|
-
raise ProtocolError(f"{label} must not contain terminal control characters")
|
|
85
|
-
return value
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def validate_messages(value: object) -> list[dict[str, str]]:
|
|
89
|
-
if not isinstance(value, list):
|
|
90
|
-
raise ProtocolError("messages must be an array")
|
|
91
|
-
messages: list[dict[str, str]] = []
|
|
92
|
-
for index, item in enumerate(value):
|
|
93
|
-
message = require_object(item, f"messages[{index}]")
|
|
94
|
-
require_exact_keys(message, MESSAGE_KEYS, f"messages[{index}]")
|
|
95
|
-
channel = message.get("channel")
|
|
96
|
-
if channel not in {"stdout", "stderr"}:
|
|
97
|
-
raise ProtocolError(f"messages[{index}].channel is invalid")
|
|
98
|
-
text = require_non_empty_string(message.get("text"), f"messages[{index}].text")
|
|
99
|
-
messages.append({"channel": str(channel), "text": text})
|
|
100
|
-
return messages
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def validate_error(value: object) -> dict[str, object]:
|
|
104
|
-
error = require_object(value, "error")
|
|
105
|
-
require_exact_keys(error, ERROR_KEYS, "error")
|
|
106
|
-
code = require_non_empty_string(error.get("code"), "error.code")
|
|
107
|
-
message = require_non_empty_string(error.get("message"), "error.message")
|
|
108
|
-
hints_value = error.get("hints")
|
|
109
|
-
if not isinstance(hints_value, list):
|
|
110
|
-
raise ProtocolError("error.hints must be an array")
|
|
111
|
-
hints = [
|
|
112
|
-
require_non_empty_string(hint, f"error.hints[{index}]")
|
|
113
|
-
for index, hint in enumerate(hints_value)
|
|
114
|
-
]
|
|
115
|
-
return {"code": code, "message": message, "hints": hints}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def validate_result(
|
|
119
|
-
operation: str, value: object, inspect_view: str | None
|
|
120
|
-
) -> dict[str, object]:
|
|
121
|
-
result = require_object(value, "result")
|
|
122
|
-
if operation in {"build", "uninstall"}:
|
|
123
|
-
require_exact_keys(result, set(), f"{operation} result")
|
|
124
|
-
return {}
|
|
125
|
-
if operation == "inspect":
|
|
126
|
-
if inspect_view == "fingerprint":
|
|
127
|
-
require_exact_keys(result, {"view", "fingerprint"}, "inspect result")
|
|
128
|
-
if result.get("view") != "fingerprint":
|
|
129
|
-
raise ProtocolError("inspect result view must be fingerprint")
|
|
130
|
-
fingerprint = result.get("fingerprint")
|
|
131
|
-
if fingerprint is not None and (
|
|
132
|
-
not isinstance(fingerprint, str)
|
|
133
|
-
or FINGERPRINT_RE.fullmatch(fingerprint) is None
|
|
134
|
-
):
|
|
135
|
-
raise ProtocolError("fingerprint must be null, 7 hex, or 40 hex")
|
|
136
|
-
return {"view": "fingerprint", "fingerprint": fingerprint}
|
|
137
|
-
if inspect_view == "ownership":
|
|
138
|
-
require_exact_keys(
|
|
139
|
-
result,
|
|
140
|
-
{"view", "resources", "legacy_resources", "identity_state"},
|
|
141
|
-
"inspect result",
|
|
142
|
-
)
|
|
143
|
-
if result.get("view") != "ownership":
|
|
144
|
-
raise ProtocolError("inspect result view must be ownership")
|
|
145
|
-
resources = require_object(result.get("resources"), "resources")
|
|
146
|
-
legacy_resources = require_object(
|
|
147
|
-
result.get("legacy_resources"), "legacy_resources"
|
|
148
|
-
)
|
|
149
|
-
for label, group in (
|
|
150
|
-
("resources", resources),
|
|
151
|
-
("legacy_resources", legacy_resources),
|
|
152
|
-
):
|
|
153
|
-
require_exact_keys(group, OWNERSHIP_KEYS, label)
|
|
154
|
-
if not all(isinstance(group[key], bool) for key in OWNERSHIP_KEYS):
|
|
155
|
-
raise ProtocolError(f"{label} values must be Boolean")
|
|
156
|
-
manager_present = any(resources.values())
|
|
157
|
-
legacy_present = any(legacy_resources.values())
|
|
158
|
-
expected_state = {
|
|
159
|
-
(False, False): "neither",
|
|
160
|
-
(True, False): "manager",
|
|
161
|
-
(False, True): "legacy",
|
|
162
|
-
(True, True): "both",
|
|
163
|
-
}[(manager_present, legacy_present)]
|
|
164
|
-
identity_state = result.get("identity_state")
|
|
165
|
-
if identity_state != expected_state:
|
|
166
|
-
raise ProtocolError(
|
|
167
|
-
f"identity_state must be {expected_state} for the reported resources"
|
|
168
|
-
)
|
|
169
|
-
return {
|
|
170
|
-
"view": "ownership",
|
|
171
|
-
"resources": resources,
|
|
172
|
-
"legacy_resources": legacy_resources,
|
|
173
|
-
"identity_state": identity_state,
|
|
174
|
-
}
|
|
175
|
-
if inspect_view == "update-control":
|
|
176
|
-
require_exact_keys(
|
|
177
|
-
result,
|
|
178
|
-
{"view", "update_control"},
|
|
179
|
-
"update-control inspect result",
|
|
180
|
-
)
|
|
181
|
-
if result.get("view") != "update-control":
|
|
182
|
-
raise ProtocolError("inspect result view must be update-control")
|
|
183
|
-
value = result.get("update_control")
|
|
184
|
-
if value not in {"managed", "unsupported"}:
|
|
185
|
-
raise ProtocolError(
|
|
186
|
-
"update-control value must be managed or unsupported"
|
|
187
|
-
)
|
|
188
|
-
return {"view": "update-control", "update_control": value}
|
|
189
|
-
raise ProtocolError(
|
|
190
|
-
"inspect view must be fingerprint or ownership, or update-control"
|
|
191
|
-
)
|
|
192
|
-
if operation == "install":
|
|
193
|
-
require_exact_keys(result, {"verification_hints"}, "install result")
|
|
194
|
-
hints = require_object(result.get("verification_hints"), "verification_hints")
|
|
195
|
-
unknown = set(hints) - VERIFICATION_HINT_KEYS
|
|
196
|
-
if unknown:
|
|
197
|
-
raise ProtocolError(f"unknown verification hint keys: {sorted(unknown)}")
|
|
198
|
-
normalized = {
|
|
199
|
-
key: require_non_empty_string(value, f"verification_hints.{key}")
|
|
200
|
-
for key, value in hints.items()
|
|
201
|
-
}
|
|
202
|
-
return {"verification_hints": normalized}
|
|
203
|
-
raise ProtocolError(f"unsupported operation: {operation}")
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def validate_envelope(
|
|
207
|
-
raw: object, operation: str, adapter_exit: int, inspect_view: str | None
|
|
208
|
-
) -> tuple[list[dict[str, str]], dict[str, object] | None, dict[str, object] | None]:
|
|
209
|
-
envelope = require_object(raw, "response")
|
|
210
|
-
require_exact_keys(envelope, ENVELOPE_KEYS, "response")
|
|
211
|
-
protocol = envelope.get("protocol")
|
|
212
|
-
if type(protocol) is not int or protocol != 1:
|
|
213
|
-
raise ProtocolError("protocol must equal integer 1")
|
|
214
|
-
if envelope.get("operation") != operation:
|
|
215
|
-
raise ProtocolError("response operation does not match invocation")
|
|
216
|
-
ok = envelope.get("ok")
|
|
217
|
-
if not isinstance(ok, bool):
|
|
218
|
-
raise ProtocolError("ok must be Boolean")
|
|
219
|
-
messages = validate_messages(envelope.get("messages"))
|
|
220
|
-
if ok:
|
|
221
|
-
if adapter_exit != 0:
|
|
222
|
-
raise ProtocolError("successful response requires adapter exit 0")
|
|
223
|
-
if envelope.get("error") is not None:
|
|
224
|
-
raise ProtocolError("successful response error must be null")
|
|
225
|
-
result = validate_result(operation, envelope.get("result"), inspect_view)
|
|
226
|
-
return messages, result, None
|
|
227
|
-
if adapter_exit == 0:
|
|
228
|
-
raise ProtocolError("failure response requires nonzero adapter exit")
|
|
229
|
-
if envelope.get("result") is not None:
|
|
230
|
-
raise ProtocolError("failure response result must be null")
|
|
231
|
-
error = validate_error(envelope.get("error"))
|
|
232
|
-
return messages, None, error
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def replay(messages: list[dict[str, str]]) -> None:
|
|
236
|
-
for message in messages:
|
|
237
|
-
stream = sys.stdout if message["channel"] == "stdout" else sys.stderr
|
|
238
|
-
print(message["text"], file=stream)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def main() -> int:
|
|
242
|
-
parser = argparse.ArgumentParser()
|
|
243
|
-
parser.add_argument("--operation", choices=sorted(OPERATIONS), required=True)
|
|
244
|
-
parser.add_argument("--adapter-exit", type=int, required=True)
|
|
245
|
-
parser.add_argument("--response", type=Path, required=True)
|
|
246
|
-
parser.add_argument("--result", type=Path, required=True)
|
|
247
|
-
parser.add_argument(
|
|
248
|
-
"--inspect-view", choices=("fingerprint", "ownership", "update-control")
|
|
249
|
-
)
|
|
250
|
-
args = parser.parse_args()
|
|
251
|
-
try:
|
|
252
|
-
response_size = args.response.stat().st_size
|
|
253
|
-
if response_size > MAX_RESPONSE_BYTES:
|
|
254
|
-
raise ProtocolError(
|
|
255
|
-
f"response exceeds {MAX_RESPONSE_BYTES}-byte limit"
|
|
256
|
-
)
|
|
257
|
-
with args.response.open(encoding="utf-8") as handle:
|
|
258
|
-
raw = json.load(
|
|
259
|
-
handle,
|
|
260
|
-
parse_constant=reject_constant,
|
|
261
|
-
object_pairs_hook=reject_duplicate_keys,
|
|
262
|
-
)
|
|
263
|
-
if nesting_exceeds_limit(raw):
|
|
264
|
-
raise ProtocolError("response JSON nesting exceeds limit")
|
|
265
|
-
messages, result, error = validate_envelope(
|
|
266
|
-
raw, args.operation, args.adapter_exit, args.inspect_view
|
|
267
|
-
)
|
|
268
|
-
replay(messages)
|
|
269
|
-
if error is not None:
|
|
270
|
-
print(f"error: {error['message']}", file=sys.stderr)
|
|
271
|
-
for hint in error["hints"]:
|
|
272
|
-
print(f"hint: {hint}", file=sys.stderr)
|
|
273
|
-
return 1
|
|
274
|
-
with args.result.open("w", encoding="utf-8") as handle:
|
|
275
|
-
json.dump(result, handle, allow_nan=False, separators=(",", ":"))
|
|
276
|
-
handle.write("\n")
|
|
277
|
-
return 0
|
|
278
|
-
except (OSError, UnicodeError, json.JSONDecodeError, ProtocolError, RecursionError) as exc:
|
|
279
|
-
print(f"error: invalid adapter response: {exc}", file=sys.stderr)
|
|
280
|
-
return 2
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if __name__ == "__main__":
|
|
284
|
-
raise SystemExit(main())
|
package/scripts/install
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
|
5
|
-
. "$root/scripts/core/common.sh"
|
|
6
|
-
. "$root/scripts/core/provenance.sh"
|
|
7
|
-
. "$root/scripts/core/status.sh"
|
|
8
|
-
. "$root/scripts/core/lifecycle.sh"
|
|
9
|
-
. "$root/scripts/core/adapter.sh"
|
|
10
|
-
|
|
11
|
-
spw_require_command python3
|
|
12
|
-
|
|
13
|
-
echo "Note: remove or disable conflicting Superpowers providers yourself before relying on manager skills."
|
|
14
|
-
|
|
15
|
-
# Prepare-before-mutate: probe, and (re)generate the plugin tree if it is
|
|
16
|
-
# missing or stale — including validation — entirely before any Codex
|
|
17
|
-
# marketplace or plugin mutation. A failed prepare leaves Codex untouched.
|
|
18
|
-
probe_output=$(sh "$root/scripts/probe" --porcelain)
|
|
19
|
-
identity_state=$(spw_probe_field "$probe_output" identity_state)
|
|
20
|
-
[ -n "$identity_state" ] || spw_die "probe did not report adapter identity state"
|
|
21
|
-
spw_require_no_legacy_state "$identity_state"
|
|
22
|
-
status=$(spw_probe_field "$probe_output" status)
|
|
23
|
-
case "$status" in
|
|
24
|
-
needs\ prepare)
|
|
25
|
-
sh "$root/scripts/prepare"
|
|
26
|
-
;;
|
|
27
|
-
needs\ install|current)
|
|
28
|
-
;;
|
|
29
|
-
*)
|
|
30
|
-
printf '%s\n' "$probe_output"
|
|
31
|
-
spw_die "unknown probe status: $status"
|
|
32
|
-
;;
|
|
33
|
-
esac
|
|
34
|
-
|
|
35
|
-
desired_commit=$(spw_metadata_commit_or_empty "$root/plugins/superpowers/.superpowers-upstream.json" || true)
|
|
36
|
-
[ -n "$desired_commit" ] || spw_die "generated metadata missing desired commit after prepare"
|
|
37
|
-
|
|
38
|
-
tmp_parent="${TMPDIR:-/tmp}"
|
|
39
|
-
if ! install_workspace=$(spw_make_workspace "$tmp_parent" "superpowers-manager.install"); then
|
|
40
|
-
spw_die "cannot create install workspace under $tmp_parent"
|
|
41
|
-
fi
|
|
42
|
-
spw_install_workspace_trap "$install_workspace"
|
|
43
|
-
|
|
44
|
-
TMPDIR="$install_workspace"
|
|
45
|
-
export TMPDIR
|
|
46
|
-
install_result="$install_workspace/install.json"
|
|
47
|
-
inspect_result="$install_workspace/inspect.json"
|
|
48
|
-
ownership_result="$install_workspace/ownership.json"
|
|
49
|
-
update_control_result="$install_workspace/update-control.json"
|
|
50
|
-
spw_inspect_ownership "$ownership_result"
|
|
51
|
-
spw_inspect_update_control "$update_control_result"
|
|
52
|
-
identity_state=$(spw_adapter_result_get "$ownership_result" "identity_state")
|
|
53
|
-
update_control=$(spw_adapter_result_get "$update_control_result" "update_control")
|
|
54
|
-
spw_require_no_legacy_state "$identity_state"
|
|
55
|
-
spw_require_managed_update_control "$update_control"
|
|
56
|
-
spw_adapter_install "$install_result" "$root"
|
|
57
|
-
if ! spw_verify_installed_fingerprint "$desired_commit" "$install_result" "$inspect_result"; then
|
|
58
|
-
exit 1
|
|
59
|
-
fi
|
package/scripts/pin
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
|
5
|
-
. "$root/scripts/core/common.sh"
|
|
6
|
-
. "$root/scripts/core/upstream.sh"
|
|
7
|
-
. "$root/scripts/core/selection.sh"
|
|
8
|
-
|
|
9
|
-
[ "$#" -eq 1 ] || spw_usage_error 'usage: superpowers-manager pin REF'
|
|
10
|
-
requested_ref="$1"
|
|
11
|
-
|
|
12
|
-
if spw_is_pinnable_tag "$requested_ref"; then
|
|
13
|
-
requested_kind=tag
|
|
14
|
-
elif spw_is_full_commit "$requested_ref"; then
|
|
15
|
-
requested_kind=raw-commit
|
|
16
|
-
else
|
|
17
|
-
spw_usage_error 'pin REF must be an exact v-prefixed SemVer tag or full 40-hex commit'
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
spw_require_command python3
|
|
21
|
-
state_path=$(spw_selection_state_path)
|
|
22
|
-
tmp_parent="${TMPDIR:-/tmp}"
|
|
23
|
-
if ! pin_workspace=$(spw_make_workspace "$tmp_parent" "superpowers-manager.pin"); then
|
|
24
|
-
spw_die "cannot create pin workspace under $tmp_parent"
|
|
25
|
-
fi
|
|
26
|
-
spw_install_workspace_trap "$pin_workspace"
|
|
27
|
-
|
|
28
|
-
normalized="$pin_workspace/selection.json"
|
|
29
|
-
spw_selection_state "$root" read \
|
|
30
|
-
--path="$state_path" --output="$normalized"
|
|
31
|
-
|
|
32
|
-
source="${SUPERPOWERS_UPSTREAM_URL:-$SPW_UPSTREAM_URL_DEFAULT}"
|
|
33
|
-
spw_selection_state "$root" \
|
|
34
|
-
validate-source --source="$source"
|
|
35
|
-
|
|
36
|
-
if [ "$requested_kind" = tag ]; then
|
|
37
|
-
spw_require_command git
|
|
38
|
-
commit=$(spw_resolve_exact_tag "$source" "$requested_ref")
|
|
39
|
-
resolved_ref="$requested_ref"
|
|
40
|
-
else
|
|
41
|
-
spw_require_command git
|
|
42
|
-
requested_ref=$(printf '%s' "$requested_ref" | tr '[:upper:]' '[:lower:]')
|
|
43
|
-
resolved_ref="$requested_ref"
|
|
44
|
-
commit=$(spw_verify_raw_commit "$source" "$requested_ref" "$pin_workspace")
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
spw_selection_state "$root" write-pinned \
|
|
48
|
-
--path="$state_path" --source="$source" --requested-ref="$requested_ref" \
|
|
49
|
-
--resolved-ref="$resolved_ref" --commit="$commit"
|
|
50
|
-
|
|
51
|
-
printf 'pinned upstream selection to %s at %s\n' "$requested_ref" "$commit"
|
package/scripts/prepare
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
|
5
|
-
. "$root/scripts/core/common.sh"
|
|
6
|
-
. "$root/scripts/core/upstream.sh"
|
|
7
|
-
. "$root/scripts/core/selection.sh"
|
|
8
|
-
. "$root/scripts/core/provenance.sh"
|
|
9
|
-
. "$root/scripts/core/lifecycle.sh"
|
|
10
|
-
. "$root/scripts/core/adapter.sh"
|
|
11
|
-
|
|
12
|
-
cache_parent="${SUPERPOWERS_CACHE_DIR:-$root/.cache/upstream}"
|
|
13
|
-
plugin_root="${SUPERPOWERS_PLUGIN_ROOT:-$root/plugins/superpowers}"
|
|
14
|
-
manifest_template="${SUPERPOWERS_MANIFEST_TEMPLATE:-$root/plugins/superpowers/.codex-plugin/plugin.template.json}"
|
|
15
|
-
additional_validator="${SUPERPOWERS_VALIDATOR:-}"
|
|
16
|
-
invocation_root=$(pwd -P)
|
|
17
|
-
case "$cache_parent" in
|
|
18
|
-
/*) ;;
|
|
19
|
-
*) cache_parent="$invocation_root/$cache_parent" ;;
|
|
20
|
-
esac
|
|
21
|
-
case "$plugin_root" in
|
|
22
|
-
/*) ;;
|
|
23
|
-
*) plugin_root="$invocation_root/$plugin_root" ;;
|
|
24
|
-
esac
|
|
25
|
-
cache="$cache_parent/superpowers"
|
|
26
|
-
tmp_parent=$(dirname "$plugin_root")
|
|
27
|
-
mkdir -p "$tmp_parent"
|
|
28
|
-
if ! prepare_workspace=$(spw_make_workspace "$tmp_parent" ".superpowers.prepare"); then
|
|
29
|
-
spw_die "cannot create prepare workspace under $tmp_parent"
|
|
30
|
-
fi
|
|
31
|
-
spw_install_workspace_trap "$prepare_workspace"
|
|
32
|
-
tmp_plugin="$prepare_workspace/superpowers"
|
|
33
|
-
adapter_result="$prepare_workspace/adapter-build.json"
|
|
34
|
-
|
|
35
|
-
TMPDIR="$prepare_workspace"
|
|
36
|
-
export TMPDIR
|
|
37
|
-
|
|
38
|
-
spw_require_command python3
|
|
39
|
-
spw_compute_effective_selection "$root" "$prepare_workspace"
|
|
40
|
-
spw_require_command git
|
|
41
|
-
spw_require_command "$SPW_ADAPTER"
|
|
42
|
-
[ -f "$manifest_template" ] || spw_die "missing fallback manifest template: $manifest_template"
|
|
43
|
-
|
|
44
|
-
mkdir -p "$cache_parent" "$tmp_parent"
|
|
45
|
-
if [ "$SPW_SELECTION_MODE" = "pinned" ]; then
|
|
46
|
-
spw_fetch_exact_commit \
|
|
47
|
-
"$SPW_EFFECTIVE_SOURCE" "$SPW_DESIRED_COMMIT" "$cache" "$prepare_workspace"
|
|
48
|
-
else
|
|
49
|
-
git_source=$(spw_git_safe_source "$SPW_EFFECTIVE_SOURCE")
|
|
50
|
-
if [ -d "$cache/.git" ]; then
|
|
51
|
-
if ! git -C "$cache" fetch --tags --prune -- "$git_source"; then
|
|
52
|
-
spw_die "cannot fetch upstream repo: $SPW_EFFECTIVE_SOURCE"
|
|
53
|
-
fi
|
|
54
|
-
else
|
|
55
|
-
if ! git clone -- "$git_source" "$cache"; then
|
|
56
|
-
spw_die "cannot clone upstream repo: $SPW_EFFECTIVE_SOURCE"
|
|
57
|
-
fi
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
60
|
-
if ! git -C "$cache" checkout --detach "$SPW_DESIRED_COMMIT" >/dev/null; then
|
|
61
|
-
spw_die "cannot check out upstream commit: $SPW_DESIRED_COMMIT"
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
spw_require_upstream_path "$cache/skills" "skills/"
|
|
65
|
-
spw_require_upstream_path "$cache/LICENSE" "LICENSE"
|
|
66
|
-
spw_require_upstream_path "$cache/README.md" "README.md"
|
|
67
|
-
spw_require_upstream_path "$cache/CODE_OF_CONDUCT.md" "CODE_OF_CONDUCT.md"
|
|
68
|
-
|
|
69
|
-
rm -rf "$tmp_plugin"
|
|
70
|
-
mkdir -p "$tmp_plugin/.codex-plugin"
|
|
71
|
-
upstream_manifest="$cache/.codex-plugin/plugin.json"
|
|
72
|
-
|
|
73
|
-
spw_copy_path_if_present "$cache/skills" "$tmp_plugin/skills"
|
|
74
|
-
spw_copy_path_if_present "$cache/assets" "$tmp_plugin/assets"
|
|
75
|
-
spw_copy_path_if_present "$cache/LICENSE" "$tmp_plugin/LICENSE"
|
|
76
|
-
spw_copy_path_if_present "$cache/README.md" "$tmp_plugin/README.md"
|
|
77
|
-
spw_copy_path_if_present "$cache/CODE_OF_CONDUCT.md" "$tmp_plugin/CODE_OF_CONDUCT.md"
|
|
78
|
-
|
|
79
|
-
upstream_manifest_version=""
|
|
80
|
-
if [ -f "$upstream_manifest" ]; then
|
|
81
|
-
upstream_manifest_version=$(spw_json_get "$upstream_manifest" "version")
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
spw_write_metadata_json \
|
|
85
|
-
"$tmp_plugin/.superpowers-upstream.json" \
|
|
86
|
-
"$SPW_EFFECTIVE_SOURCE" \
|
|
87
|
-
"$SPW_REQUESTED_REF" \
|
|
88
|
-
"$SPW_RESOLVED_REF" \
|
|
89
|
-
"$SPW_DESIRED_COMMIT" \
|
|
90
|
-
"$upstream_manifest_version"
|
|
91
|
-
|
|
92
|
-
manager_version=$(
|
|
93
|
-
spw_manifest_version_for_ref \
|
|
94
|
-
"$SPW_REQUESTED_REF" "$SPW_RESOLUTION_KIND" \
|
|
95
|
-
"$SPW_RESOLVED_REF" "$SPW_DESIRED_COMMIT"
|
|
96
|
-
)
|
|
97
|
-
spw_invoke_adapter build "$adapter_result" "" -- \
|
|
98
|
-
--upstream-root "$cache" \
|
|
99
|
-
--candidate-root "$tmp_plugin" \
|
|
100
|
-
--requested-ref "$SPW_REQUESTED_REF" \
|
|
101
|
-
--resolved-ref "$SPW_RESOLVED_REF" \
|
|
102
|
-
--commit "$SPW_DESIRED_COMMIT" \
|
|
103
|
-
--manager-version "$manager_version" \
|
|
104
|
-
--upstream-manifest-version "$upstream_manifest_version" \
|
|
105
|
-
--fallback-manifest "$manifest_template"
|
|
106
|
-
|
|
107
|
-
if [ -n "$additional_validator" ]; then
|
|
108
|
-
[ -f "$additional_validator" ] || \
|
|
109
|
-
spw_die "additional plugin validator not found: $additional_validator"
|
|
110
|
-
if ! python3 "$additional_validator" "$tmp_plugin"; then
|
|
111
|
-
spw_die "additional plugin validation failed"
|
|
112
|
-
fi
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
spw_replace_generated_tree "$tmp_plugin" "$plugin_root"
|
|
116
|
-
|
|
117
|
-
echo "prepared $SPW_RESOLVED_REF at $SPW_DESIRED_COMMIT"
|
package/scripts/probe
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
|
5
|
-
. "$root/scripts/core/common.sh"
|
|
6
|
-
. "$root/scripts/core/upstream.sh"
|
|
7
|
-
. "$root/scripts/core/selection.sh"
|
|
8
|
-
. "$root/scripts/core/provenance.sh"
|
|
9
|
-
. "$root/scripts/core/status.sh"
|
|
10
|
-
. "$root/scripts/core/lifecycle.sh"
|
|
11
|
-
. "$root/scripts/core/adapter.sh"
|
|
12
|
-
|
|
13
|
-
tmp_parent="${TMPDIR:-/tmp}"
|
|
14
|
-
if ! probe_workspace=$(spw_make_workspace "$tmp_parent" "superpowers-manager.probe"); then
|
|
15
|
-
spw_die "cannot create probe workspace under $tmp_parent"
|
|
16
|
-
fi
|
|
17
|
-
spw_install_workspace_trap "$probe_workspace"
|
|
18
|
-
fingerprint_result="$probe_workspace/fingerprint.json"
|
|
19
|
-
ownership_result="$probe_workspace/ownership.json"
|
|
20
|
-
update_control_result="$probe_workspace/update-control.json"
|
|
21
|
-
|
|
22
|
-
TMPDIR="$probe_workspace"
|
|
23
|
-
export TMPDIR
|
|
24
|
-
spw_compute_effective_selection "$root" "$probe_workspace"
|
|
25
|
-
effective_source=$(spw_display_source "$SPW_EFFECTIVE_SOURCE")
|
|
26
|
-
if [ -n "$SPW_SAVED_SOURCE" ]; then
|
|
27
|
-
saved_source=$(spw_display_source "$SPW_SAVED_SOURCE")
|
|
28
|
-
else
|
|
29
|
-
saved_source=""
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
generated_commit=$(spw_generated_commit_or_empty "$root")
|
|
33
|
-
spw_inspect_fingerprint "$fingerprint_result"
|
|
34
|
-
installed_commit=$(spw_adapter_result_get "$fingerprint_result" "fingerprint")
|
|
35
|
-
spw_inspect_ownership "$ownership_result"
|
|
36
|
-
identity_state=$(spw_adapter_result_get "$ownership_result" "identity_state")
|
|
37
|
-
spw_inspect_update_control "$update_control_result"
|
|
38
|
-
update_control=$(spw_adapter_result_get "$update_control_result" "update_control")
|
|
39
|
-
|
|
40
|
-
status=$(spw_status_for_commits "$SPW_DESIRED_COMMIT" "$generated_commit" "$installed_commit")
|
|
41
|
-
|
|
42
|
-
if [ "${1:-}" = "--porcelain" ]; then
|
|
43
|
-
printf 'requested_ref=%s\n' "$SPW_REQUESTED_REF"
|
|
44
|
-
printf 'resolved_ref=%s\n' "$SPW_RESOLVED_REF"
|
|
45
|
-
printf 'desired_commit=%s\n' "$SPW_DESIRED_COMMIT"
|
|
46
|
-
printf 'generated_commit=%s\n' "$generated_commit"
|
|
47
|
-
printf 'installed_commit=%s\n' "$installed_commit"
|
|
48
|
-
printf 'identity_state=%s\n' "$identity_state"
|
|
49
|
-
printf 'status=%s\n' "$status"
|
|
50
|
-
printf 'selection_origin=%s\n' "$SPW_SELECTION_ORIGIN"
|
|
51
|
-
printf 'selection_mode=%s\n' "$SPW_SELECTION_MODE"
|
|
52
|
-
printf 'upstream_source_origin=%s\n' "$SPW_UPSTREAM_SOURCE_ORIGIN"
|
|
53
|
-
printf 'effective_source=%s\n' "$effective_source"
|
|
54
|
-
printf 'saved_mode=%s\n' "$SPW_SAVED_MODE"
|
|
55
|
-
printf 'saved_source=%s\n' "$saved_source"
|
|
56
|
-
printf 'saved_requested_ref=%s\n' "$SPW_SAVED_REQUESTED_REF"
|
|
57
|
-
printf 'saved_resolved_ref=%s\n' "$SPW_SAVED_RESOLVED_REF"
|
|
58
|
-
printf 'saved_commit=%s\n' "$SPW_SAVED_COMMIT"
|
|
59
|
-
printf 'update_control=%s\n' "$update_control"
|
|
60
|
-
else
|
|
61
|
-
printf 'requested ref: %s\n' "$SPW_REQUESTED_REF"
|
|
62
|
-
printf 'resolved ref: %s\n' "$SPW_RESOLVED_REF"
|
|
63
|
-
printf 'desired commit: %s\n' "$SPW_DESIRED_COMMIT"
|
|
64
|
-
printf 'generated plugin commit: %s\n' "${generated_commit:-not present}"
|
|
65
|
-
printf 'installed manager commit or fingerprint: %s\n' "${installed_commit:-not detected}"
|
|
66
|
-
printf 'Codex identity state: %s\n' "$identity_state"
|
|
67
|
-
printf 'status: %s\n' "$status"
|
|
68
|
-
printf 'selection origin: %s\n' "$SPW_SELECTION_ORIGIN"
|
|
69
|
-
printf 'selection mode: %s\n' "$SPW_SELECTION_MODE"
|
|
70
|
-
printf 'upstream source origin: %s\n' "$SPW_UPSTREAM_SOURCE_ORIGIN"
|
|
71
|
-
printf 'effective source: %s\n' "$effective_source"
|
|
72
|
-
printf 'saved mode: %s\n' "$SPW_SAVED_MODE"
|
|
73
|
-
printf 'saved source: %s\n' "$saved_source"
|
|
74
|
-
printf 'saved requested ref: %s\n' "$SPW_SAVED_REQUESTED_REF"
|
|
75
|
-
printf 'saved resolved ref: %s\n' "$SPW_SAVED_RESOLVED_REF"
|
|
76
|
-
printf 'saved commit: %s\n' "$SPW_SAVED_COMMIT"
|
|
77
|
-
printf 'update control: %s\n' "$update_control"
|
|
78
|
-
if [ "$SPW_SELECTION_ORIGIN" != "$SPW_UPSTREAM_SOURCE_ORIGIN" ]; then
|
|
79
|
-
printf 'warning: effective ref and source have mixed origins (ref: %s, source: %s)\n' \
|
|
80
|
-
"$SPW_SELECTION_ORIGIN" "$SPW_UPSTREAM_SOURCE_ORIGIN"
|
|
81
|
-
fi
|
|
82
|
-
fi
|
package/scripts/track-latest
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
|
5
|
-
. "$root/scripts/core/common.sh"
|
|
6
|
-
. "$root/scripts/core/upstream.sh"
|
|
7
|
-
. "$root/scripts/core/selection.sh"
|
|
8
|
-
|
|
9
|
-
[ "$#" -eq 0 ] || spw_usage_error 'usage: superpowers-manager track-latest'
|
|
10
|
-
|
|
11
|
-
spw_require_command python3
|
|
12
|
-
state_path=$(spw_selection_state_path)
|
|
13
|
-
tmp_parent="${TMPDIR:-/tmp}"
|
|
14
|
-
if ! track_workspace=$(spw_make_workspace "$tmp_parent" "superpowers-manager.track-latest"); then
|
|
15
|
-
spw_die "cannot create track-latest workspace under $tmp_parent"
|
|
16
|
-
fi
|
|
17
|
-
spw_install_workspace_trap "$track_workspace"
|
|
18
|
-
|
|
19
|
-
normalized="$track_workspace/selection.json"
|
|
20
|
-
spw_selection_state "$root" read \
|
|
21
|
-
--path="$state_path" --output="$normalized"
|
|
22
|
-
|
|
23
|
-
source="${SUPERPOWERS_UPSTREAM_URL:-$SPW_UPSTREAM_URL_DEFAULT}"
|
|
24
|
-
spw_selection_state "$root" \
|
|
25
|
-
validate-source --source="$source"
|
|
26
|
-
|
|
27
|
-
spw_selection_state "$root" write-track-latest \
|
|
28
|
-
--path="$state_path" --source="$source"
|
|
29
|
-
|
|
30
|
-
printf '%s\n' 'saved upstream selection: latest stable release'
|
package/scripts/uninstall
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
set -eu
|
|
3
|
-
|
|
4
|
-
root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
|
5
|
-
. "$root/scripts/core/common.sh"
|
|
6
|
-
. "$root/scripts/core/provenance.sh"
|
|
7
|
-
. "$root/scripts/core/lifecycle.sh"
|
|
8
|
-
. "$root/scripts/core/adapter.sh"
|
|
9
|
-
|
|
10
|
-
spw_require_command python3
|
|
11
|
-
|
|
12
|
-
tmp_parent="${TMPDIR:-/tmp}"
|
|
13
|
-
if ! uninstall_workspace=$(spw_make_workspace "$tmp_parent" "superpowers-manager.uninstall"); then
|
|
14
|
-
spw_die "cannot create uninstall workspace under $tmp_parent"
|
|
15
|
-
fi
|
|
16
|
-
spw_install_workspace_trap "$uninstall_workspace"
|
|
17
|
-
inspect_result="$uninstall_workspace/inspect.json"
|
|
18
|
-
uninstall_result="$uninstall_workspace/uninstall.json"
|
|
19
|
-
|
|
20
|
-
TMPDIR="$uninstall_workspace"
|
|
21
|
-
export TMPDIR
|
|
22
|
-
|
|
23
|
-
spw_inspect_ownership "$inspect_result"
|
|
24
|
-
plugin_present=$(spw_adapter_result_boolean "$inspect_result" "resources.plugin")
|
|
25
|
-
marketplace_present=$(spw_adapter_result_boolean "$inspect_result" "resources.marketplace")
|
|
26
|
-
|
|
27
|
-
spw_adapter_uninstall "$uninstall_result" "$plugin_present" "$marketplace_present"
|
|
28
|
-
|
|
29
|
-
spw_inspect_ownership "$inspect_result"
|
|
30
|
-
spw_verify_uninstalled_resources "$inspect_result"
|
|
31
|
-
identity_state=$(spw_adapter_result_get "$inspect_result" "identity_state")
|
|
32
|
-
spw_report_legacy_state "$identity_state"
|
|
33
|
-
|
|
34
|
-
echo "uninstall complete"
|
|
35
|
-
echo "note: local generated artifacts under plugins/superpowers/ and .cache/upstream/ were left in place; remove them manually or regenerate with scripts/prepare."
|