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.
Files changed (68) hide show
  1. package/README.md +340 -66
  2. package/dist/adapter-result.js +204 -0
  3. package/dist/adapter.js +690 -0
  4. package/dist/atomic.js +142 -0
  5. package/dist/cli-arguments.js +32 -0
  6. package/dist/cli.js +313 -0
  7. package/dist/codex-harness.js +271 -0
  8. package/dist/codex-json.js +89 -0
  9. package/dist/codex-prepare.js +199 -0
  10. package/dist/codex-presentation.js +259 -0
  11. package/dist/codex-state.js +78 -0
  12. package/dist/commands/context.js +1 -0
  13. package/dist/commands/install.js +291 -0
  14. package/dist/commands/pin.js +79 -0
  15. package/dist/commands/prepare.js +420 -0
  16. package/dist/commands/probe.js +257 -0
  17. package/dist/commands/track-latest.js +44 -0
  18. package/dist/commands/uninstall.js +217 -0
  19. package/dist/commands/unpin.js +95 -0
  20. package/dist/commands/update.js +79 -0
  21. package/dist/domain/fingerprint.js +4 -0
  22. package/dist/domain/refs.js +37 -0
  23. package/dist/effective-selection.js +123 -0
  24. package/dist/generated-plugin.js +748 -0
  25. package/dist/git.js +38 -0
  26. package/dist/harness.js +1 -0
  27. package/dist/hooks.js +290 -0
  28. package/dist/lifecycle.js +135 -0
  29. package/dist/manifest-overlay.js +125 -0
  30. package/dist/provenance.js +111 -0
  31. package/dist/python-json-format.js +83 -0
  32. package/dist/python-json.js +29 -0
  33. package/dist/python-text.js +74 -0
  34. package/dist/safe-path.js +137 -0
  35. package/dist/safety-error.js +10 -0
  36. package/dist/selection-state-cli.js +85 -0
  37. package/dist/selection-store.js +172 -0
  38. package/dist/selection.js +200 -0
  39. package/dist/status.js +24 -0
  40. package/dist/strict-json.js +248 -0
  41. package/dist/upstream-cli.js +110 -0
  42. package/dist/upstream-version.js +40 -0
  43. package/dist/upstream.js +253 -0
  44. package/dist/validate-generated-plugin-cli.js +118 -0
  45. package/dist/validator.js +317 -0
  46. package/dist/workspace.js +139 -0
  47. package/package.json +25 -4
  48. package/bin/superpowers-manager.js +0 -290
  49. package/scripts/adapters/codex/adapter +0 -603
  50. package/scripts/adapters/codex/lib.sh +0 -449
  51. package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
  52. package/scripts/core/adapter.sh +0 -103
  53. package/scripts/core/common.sh +0 -59
  54. package/scripts/core/lifecycle.sh +0 -141
  55. package/scripts/core/provenance.sh +0 -135
  56. package/scripts/core/selection-state.py +0 -414
  57. package/scripts/core/selection.sh +0 -168
  58. package/scripts/core/status.sh +0 -27
  59. package/scripts/core/upstream.sh +0 -280
  60. package/scripts/core/validate-adapter-response.py +0 -284
  61. package/scripts/install +0 -59
  62. package/scripts/pin +0 -51
  63. package/scripts/prepare +0 -117
  64. package/scripts/probe +0 -82
  65. package/scripts/track-latest +0 -30
  66. package/scripts/uninstall +0 -35
  67. package/scripts/unpin +0 -41
  68. 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
@@ -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."