superpowers-manager 0.1.6 → 0.3.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 (59) hide show
  1. package/README.md +184 -64
  2. package/bin/superpowers-manager.js +9 -237
  3. package/dist/adapter-result.js +204 -0
  4. package/dist/adapter.js +649 -0
  5. package/dist/atomic.js +142 -0
  6. package/dist/cli-arguments.js +32 -0
  7. package/dist/cli.js +293 -0
  8. package/dist/codex-json.js +89 -0
  9. package/dist/codex-state.js +78 -0
  10. package/dist/commands/context.js +1 -0
  11. package/dist/commands/install.js +376 -0
  12. package/dist/commands/pin.js +79 -0
  13. package/dist/commands/prepare.js +527 -0
  14. package/dist/commands/probe.js +392 -0
  15. package/dist/commands/track-latest.js +44 -0
  16. package/dist/commands/uninstall.js +308 -0
  17. package/dist/commands/unpin.js +95 -0
  18. package/dist/commands/update.js +138 -0
  19. package/dist/domain/fingerprint.js +4 -0
  20. package/dist/domain/refs.js +37 -0
  21. package/dist/effective-selection.js +123 -0
  22. package/dist/generated-plugin.js +748 -0
  23. package/dist/git.js +38 -0
  24. package/dist/hooks.js +290 -0
  25. package/dist/lifecycle.js +220 -0
  26. package/dist/manifest-overlay.js +125 -0
  27. package/dist/provenance.js +111 -0
  28. package/dist/python-json-format.js +83 -0
  29. package/dist/python-json.js +29 -0
  30. package/dist/python-text.js +74 -0
  31. package/dist/safe-path.js +137 -0
  32. package/dist/safety-error.js +10 -0
  33. package/dist/selection-state-cli.js +85 -0
  34. package/dist/selection-store.js +172 -0
  35. package/dist/selection.js +200 -0
  36. package/dist/status.js +24 -0
  37. package/dist/strict-json.js +248 -0
  38. package/dist/upstream-cli.js +110 -0
  39. package/dist/upstream-version.js +40 -0
  40. package/dist/upstream.js +253 -0
  41. package/dist/validate-generated-plugin-cli.js +118 -0
  42. package/dist/validator.js +317 -0
  43. package/dist/workspace.js +139 -0
  44. package/package.json +25 -2
  45. package/scripts/adapters/codex/adapter +0 -598
  46. package/scripts/adapters/codex/lib.sh +0 -391
  47. package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
  48. package/scripts/core/adapter.sh +0 -92
  49. package/scripts/core/common.sh +0 -35
  50. package/scripts/core/lifecycle.sh +0 -127
  51. package/scripts/core/provenance.sh +0 -135
  52. package/scripts/core/status.sh +0 -27
  53. package/scripts/core/upstream.sh +0 -146
  54. package/scripts/core/validate-adapter-response.py +0 -245
  55. package/scripts/install +0 -60
  56. package/scripts/prepare +0 -126
  57. package/scripts/probe +0 -61
  58. package/scripts/uninstall +0 -44
  59. package/scripts/update +0 -31
@@ -1,92 +0,0 @@
1
- #!/bin/sh
2
- # Sourced module; callers own set -eu.
3
-
4
- spw_adapter_package_root() {
5
- if [ -n "${SPW_PACKAGE_ROOT:-}" ]; then
6
- printf '%s\n' "$SPW_PACKAGE_ROOT"
7
- elif [ -n "${root:-}" ]; then
8
- printf '%s\n' "$root"
9
- else
10
- spw_root
11
- fi
12
- }
13
-
14
- SPW_ADAPTER_ROOT=$(spw_adapter_package_root)
15
- SPW_ADAPTER="${SPW_ADAPTER:-$SPW_ADAPTER_ROOT/scripts/adapters/codex/adapter}"
16
- SPW_ADAPTER_RESPONSE_VALIDATOR="${SPW_ADAPTER_RESPONSE_VALIDATOR:-$SPW_ADAPTER_ROOT/scripts/core/validate-adapter-response.py}"
17
-
18
- spw_invoke_adapter() {
19
- operation="$1"
20
- result_file="$2"
21
- inspect_view="$3"
22
- shift 3
23
- [ "${1:-}" = "--" ] || spw_die "internal adapter invocation missing --"
24
- shift
25
-
26
- response_file="${result_file}.response"
27
- adapter_exit=0
28
- "$SPW_ADAPTER" "$operation" "$@" > "$response_file" || adapter_exit=$?
29
-
30
- args=""
31
- if [ -n "$inspect_view" ]; then
32
- args="$inspect_view"
33
- fi
34
- if [ -n "$args" ]; then
35
- python3 -S "$SPW_ADAPTER_RESPONSE_VALIDATOR" \
36
- --operation "$operation" --adapter-exit "$adapter_exit" \
37
- --response "$response_file" --result "$result_file" \
38
- --inspect-view "$inspect_view"
39
- else
40
- python3 -S "$SPW_ADAPTER_RESPONSE_VALIDATOR" \
41
- --operation "$operation" --adapter-exit "$adapter_exit" \
42
- --response "$response_file" --result "$result_file"
43
- fi
44
- }
45
-
46
- spw_adapter_result_get() {
47
- result_file="$1"
48
- dotted_key="$2"
49
- spw_json_get "$result_file" "$dotted_key"
50
- }
51
-
52
- spw_adapter_result_boolean() {
53
- result_file="$1"
54
- dotted_key="$2"
55
- value=$(spw_adapter_result_get "$result_file" "$dotted_key")
56
- case "$value" in
57
- true|True)
58
- printf 'true\n'
59
- ;;
60
- false|False)
61
- printf 'false\n'
62
- ;;
63
- *)
64
- spw_die "expected Boolean adapter result at $dotted_key in $result_file"
65
- ;;
66
- esac
67
- }
68
-
69
- spw_inspect_fingerprint() {
70
- result_file="$1"
71
- spw_invoke_adapter inspect "$result_file" fingerprint -- --view fingerprint
72
- }
73
-
74
- spw_inspect_ownership() {
75
- result_file="$1"
76
- spw_invoke_adapter inspect "$result_file" ownership -- --view ownership
77
- }
78
-
79
- spw_adapter_install() {
80
- result_file="$1"
81
- package_root="$2"
82
- spw_invoke_adapter install "$result_file" "" -- --package-root "$package_root"
83
- }
84
-
85
- spw_adapter_uninstall() {
86
- result_file="$1"
87
- plugin_present="$2"
88
- marketplace_present="$3"
89
- spw_invoke_adapter uninstall "$result_file" "" -- \
90
- --plugin-present "$plugin_present" \
91
- --marketplace-present "$marketplace_present"
92
- }
@@ -1,35 +0,0 @@
1
- #!/bin/sh
2
- # Sourced module; callers own set -eu.
3
-
4
- spw_die() {
5
- echo "error: $*" >&2
6
- exit 1
7
- }
8
-
9
- spw_require_command() {
10
- command_name="$1"
11
- if ! command -v "$command_name" >/dev/null 2>&1; then
12
- spw_die "required command not found: $command_name"
13
- fi
14
- }
15
-
16
- spw_root() {
17
- CDPATH= cd -- "$(dirname "$0")/.." && pwd
18
- }
19
-
20
- spw_copy_path_if_present() {
21
- src="$1"
22
- dst="$2"
23
- if [ -e "$src" ]; then
24
- rm -rf "$dst"
25
- cp -R "$src" "$dst"
26
- fi
27
- }
28
-
29
- spw_require_upstream_path() {
30
- path="$1"
31
- label="$2"
32
- if [ ! -e "$path" ]; then
33
- spw_die "required upstream path missing: $label"
34
- fi
35
- }
@@ -1,127 +0,0 @@
1
- #!/bin/sh
2
- # Sourced module; callers own set -eu.
3
-
4
- # Replace the live tree with a single atomic rename, keeping a backup so a
5
- # failed swap restores the previous working generated tree. candidate and
6
- # live_root must be same-filesystem siblings for atomic renames.
7
- spw_replace_generated_tree() {
8
- candidate="$1"
9
- live_root="$2"
10
- parent=$(dirname "$live_root")
11
- backup="$parent/.superpowers.bak.$$"
12
-
13
- rm -rf "$backup"
14
- if [ -e "$live_root" ]; then
15
- mv "$live_root" "$backup"
16
- fi
17
- if mv "$candidate" "$live_root"; then
18
- rm -rf "$backup"
19
- else
20
- if [ -e "$backup" ]; then
21
- mv "$backup" "$live_root"
22
- fi
23
- rm -rf "$candidate"
24
- spw_die "failed to install generated tree into $live_root; previous tree restored"
25
- fi
26
- }
27
-
28
- spw_generated_metadata_path() (
29
- generated_root="$1"
30
- printf '%s\n' "$generated_root/plugins/superpowers/.superpowers-upstream.json"
31
- )
32
-
33
- spw_generated_commit_or_empty() (
34
- generated_root="$1"
35
- generated_metadata=$(spw_generated_metadata_path "$generated_root")
36
- spw_metadata_commit_lenient_or_empty "$generated_metadata"
37
- )
38
-
39
- spw_require_no_legacy_state() {
40
- identity_state="$1"
41
- case "$identity_state" in
42
- neither|manager)
43
- return 0
44
- ;;
45
- legacy|both)
46
- printf '%s\n' \
47
- 'Legacy superpowers-wrapper Codex state is installed.' \
48
- 'Run: npx superpowers-wrapper@0.1.1 uninstall' \
49
- 'Then run: npx superpowers-manager install' >&2
50
- return 1
51
- ;;
52
- *)
53
- spw_die "unknown adapter identity state: $identity_state"
54
- ;;
55
- esac
56
- }
57
-
58
- spw_report_legacy_state() {
59
- case "$1" in
60
- legacy|both)
61
- printf '%s\n' \
62
- 'Legacy superpowers-wrapper Codex state remains installed.' \
63
- 'Run: npx superpowers-wrapper@0.1.1 uninstall'
64
- ;;
65
- neither|manager)
66
- ;;
67
- *)
68
- spw_die "unknown adapter identity state: $1"
69
- ;;
70
- esac
71
- }
72
-
73
- spw_verify_installed_fingerprint() {
74
- desired_commit="$1"
75
- install_result="$2"
76
- inspect_result="$3"
77
- if ! spw_inspect_fingerprint "$inspect_result"; then
78
- echo "error: installed manager fingerprint inspection failed after install." >&2
79
- return 1
80
- fi
81
- if ! installed_commit=$(spw_adapter_result_get "$inspect_result" "fingerprint"); then
82
- echo "error: cannot parse installed manager fingerprint inspection result after install." >&2
83
- return 1
84
- fi
85
- printf 'desired_commit=%s\n' "$desired_commit"
86
- printf 'installed_commit=%s\n' "$installed_commit"
87
- if [ -n "$installed_commit" ] && spw_commit_matches "$desired_commit" "$installed_commit"; then
88
- echo "manager updated"
89
- return 0
90
- fi
91
-
92
- hint=""
93
- if [ -f "$install_result" ]; then
94
- if [ -n "$installed_commit" ]; then
95
- hint=$(spw_adapter_result_get "$install_result" "verification_hints.mismatch" || true)
96
- else
97
- hint=$(spw_adapter_result_get "$install_result" "verification_hints.missing" || true)
98
- fi
99
- fi
100
-
101
- if [ -n "$installed_commit" ]; then
102
- echo "error: installed manager fingerprint does not match the prepared plugin after install." >&2
103
- else
104
- echo "error: installed manager fingerprint is not detectable after install." >&2
105
- fi
106
- if [ -n "$hint" ]; then
107
- echo "hint: $hint" >&2
108
- fi
109
- return 1
110
- }
111
-
112
- spw_verify_uninstalled_resources() {
113
- inspect_result="$1"
114
-
115
- if ! plugin_present=$(spw_adapter_result_boolean "$inspect_result" "resources.plugin"); then
116
- return 1
117
- fi
118
- if ! marketplace_present=$(spw_adapter_result_boolean "$inspect_result" "resources.marketplace"); then
119
- return 1
120
- fi
121
- if [ "$plugin_present" = true ]; then
122
- spw_die "owned plugin resource is still installed after removal"
123
- fi
124
- if [ "$marketplace_present" = true ]; then
125
- spw_die "owned marketplace resource is still registered after removal"
126
- fi
127
- }
@@ -1,135 +0,0 @@
1
- #!/bin/sh
2
- # Sourced module; callers own set -eu.
3
-
4
- spw_json_get() {
5
- file="$1"
6
- key="$2"
7
- if ! value=$(
8
- python3 - "$file" "$key" 2>&1 <<'PY'
9
- import json
10
- import sys
11
-
12
- path, dotted_key = sys.argv[1:]
13
- MAX_JSON_NESTING = 256
14
-
15
- def reject_constant(constant):
16
- raise ValueError(f"non-standard numeric constant: {constant}")
17
-
18
- def nesting_exceeds_limit(value):
19
- stack = [(value, 0)]
20
- while stack:
21
- current, depth = stack.pop()
22
- if isinstance(current, dict):
23
- next_depth = depth + 1
24
- if next_depth > MAX_JSON_NESTING:
25
- return True
26
- stack.extend((child, next_depth) for child in current.values())
27
- elif isinstance(current, list):
28
- next_depth = depth + 1
29
- if next_depth > MAX_JSON_NESTING:
30
- return True
31
- stack.extend((child, next_depth) for child in current)
32
- return False
33
-
34
- try:
35
- with open(path, "r", encoding="utf-8") as f:
36
- data = json.load(f, parse_constant=reject_constant)
37
- except RecursionError:
38
- sys.exit(f"JSON nesting exceeds limit in {path}")
39
- except json.JSONDecodeError as exc:
40
- sys.exit(
41
- f"invalid JSON in {path}: "
42
- f"line {exc.lineno} column {exc.colno}: {exc.msg}"
43
- )
44
- except (OSError, UnicodeError) as exc:
45
- sys.exit(f"cannot read JSON in {path}: {exc}")
46
- except ValueError as exc:
47
- sys.exit(f"invalid JSON in {path}: {exc}")
48
-
49
- if nesting_exceeds_limit(data):
50
- sys.exit(f"JSON nesting exceeds limit in {path}")
51
-
52
- if not isinstance(data, dict):
53
- sys.exit(f"JSON value must be an object in {path}")
54
-
55
- value = data
56
- for part in dotted_key.split("."):
57
- if not isinstance(value, dict):
58
- value = ""
59
- break
60
- value = value.get(part, "")
61
- try:
62
- print(value if value is not None else "")
63
- except (OSError, UnicodeError) as exc:
64
- sys.exit(f"cannot output JSON value from {path}: {exc}")
65
- PY
66
- ); then
67
- spw_die "$value"
68
- fi
69
- printf '%s\n' "$value"
70
- }
71
-
72
- spw_write_metadata_json() {
73
- file="$1"
74
- source="$2"
75
- requested_ref="$3"
76
- resolved_ref="$4"
77
- commit="$5"
78
- upstream_manifest_version="$6"
79
- python3 - "$file" "$source" "$requested_ref" "$resolved_ref" "$commit" "$upstream_manifest_version" <<'PY'
80
- import json, sys
81
- path, source, requested, resolved, commit, version = sys.argv[1:]
82
- data = {
83
- "source": source,
84
- "requested_ref": requested,
85
- "resolved_ref": resolved,
86
- "commit": commit,
87
- "upstream_manifest_version": version,
88
- }
89
- try:
90
- with open(path, "w", encoding="utf-8") as f:
91
- json.dump(data, f, indent=2, allow_nan=False)
92
- f.write("\n")
93
- except RecursionError as exc:
94
- sys.exit(f"JSON nesting exceeds limit while writing {path}: {exc}")
95
- except (OSError, UnicodeError, ValueError) as exc:
96
- sys.exit(f"cannot write JSON to {path}: {exc}")
97
- PY
98
- }
99
-
100
- spw_metadata_commit_or_empty() {
101
- file="$1"
102
- if [ -f "$file" ]; then
103
- spw_json_get "$file" "commit"
104
- fi
105
- }
106
-
107
- spw_metadata_commit_lenient_or_empty() {
108
- file="$1"
109
- [ -f "$file" ] || return 0
110
-
111
- if value=$(
112
- python3 -S - "$file" 2>/dev/null <<'PY'
113
- import json
114
- import re
115
- import sys
116
-
117
- path = sys.argv[1]
118
-
119
- def reject_constant(constant):
120
- raise ValueError(f"non-standard numeric constant: {constant}")
121
-
122
- try:
123
- with open(path, "r", encoding="utf-8") as handle:
124
- data = json.load(handle, parse_constant=reject_constant)
125
- except (OSError, UnicodeError, ValueError, RecursionError):
126
- raise SystemExit(1)
127
-
128
- commit = data.get("commit") if isinstance(data, dict) else None
129
- if isinstance(commit, str) and re.fullmatch(r"[0-9a-fA-F]{40}", commit):
130
- print(commit)
131
- PY
132
- ); then
133
- printf '%s\n' "$value"
134
- fi
135
- }
@@ -1,27 +0,0 @@
1
- #!/bin/sh
2
- # Sourced module; callers own set -eu.
3
-
4
- spw_commit_matches() {
5
- desired="$1"
6
- observed="$2"
7
- short=$(printf '%s' "$desired" | cut -c 1-7)
8
- [ -n "$observed" ] && { [ "$observed" = "$desired" ] || [ "$observed" = "$short" ]; }
9
- }
10
-
11
- spw_status_for_commits() {
12
- desired="$1"
13
- generated="$2"
14
- installed="$3"
15
-
16
- if [ -z "$generated" ]; then
17
- printf '%s\n' "needs prepare"
18
- elif ! spw_commit_matches "$desired" "$generated"; then
19
- printf '%s\n' "needs prepare"
20
- elif [ -z "$installed" ]; then
21
- printf '%s\n' "needs install"
22
- elif ! spw_commit_matches "$desired" "$installed"; then
23
- printf '%s\n' "needs install"
24
- else
25
- printf '%s\n' "current"
26
- fi
27
- }
@@ -1,146 +0,0 @@
1
- #!/bin/sh
2
- # Sourced module; callers own set -eu.
3
-
4
- SPW_UPSTREAM_URL_DEFAULT="https://github.com/obra/superpowers"
5
-
6
- spw_config_ref() (
7
- config_root="$1"
8
- if [ -n "${SUPERPOWERS_REF:-}" ]; then
9
- printf '%s\n' "$SUPERPOWERS_REF"
10
- return
11
- fi
12
- sed -n '1{s/[[:space:]]*$//;p;}' "$config_root/config/upstream-ref"
13
- )
14
-
15
- spw_short_commit() {
16
- commit="$1"
17
- printf '%s' "$commit" | cut -c 1-7
18
- }
19
-
20
- spw_is_semver_base() {
21
- version="$1"
22
- printf '%s' "$version" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?$'
23
- }
24
-
25
- spw_sanitize_ref_for_version() {
26
- ref="$1"
27
- sanitized=$(printf '%s' "$ref" | sed 's/[^0-9A-Za-z-][^0-9A-Za-z-]*/-/g; s/^-*//; s/-*$//')
28
- sanitized=$(printf '%s' "$sanitized" | cut -c 1-48 | sed 's/^-*//; s/-*$//')
29
- if [ -z "$sanitized" ]; then
30
- sanitized="unknown"
31
- fi
32
- printf '%s' "$sanitized"
33
- }
34
-
35
- spw_manifest_version_for_ref() {
36
- requested_ref="$1"
37
- resolution_kind="$2"
38
- resolved_ref="$3"
39
- commit="$4"
40
- short=$(spw_short_commit "$commit")
41
-
42
- case "$resolution_kind" in
43
- latest-release|tag)
44
- base=$(printf '%s' "$resolved_ref" | sed -n 's/^v//p')
45
- if [ -n "$base" ] && spw_is_semver_base "$base"; then
46
- printf '%s+manager.%s\n' "$base" "$short"
47
- return
48
- fi
49
- ;;
50
- ref)
51
- if [ "$requested_ref" = "main" ]; then
52
- printf '0.0.0-main+manager.%s\n' "$short"
53
- return
54
- fi
55
- sanitized=$(spw_sanitize_ref_for_version "$requested_ref")
56
- printf '0.0.0-ref-%s+manager.%s\n' "$sanitized" "$short"
57
- return
58
- ;;
59
- raw-commit)
60
- ;;
61
- esac
62
-
63
- printf '0.0.0+manager.%s\n' "$short"
64
- }
65
-
66
- spw_manifest_version_for_commit() {
67
- commit="$1"
68
- spw_manifest_version_for_ref "$commit" "raw-commit" "$commit" "$commit"
69
- }
70
-
71
- spw_select_latest_release_from_ls_remote() {
72
- awk '
73
- $2 ~ /^refs\/tags\/v[0-9]+\.[0-9]+\.[0-9]+(\^\{\})?$/ {
74
- ref = $2
75
- peeled = 0
76
- if (ref ~ /\^\{\}$/) {
77
- peeled = 1
78
- sub(/\^\{\}$/, "", ref)
79
- }
80
- tag = ref
81
- sub(/^refs\/tags\//, "", tag)
82
- split(substr(tag, 2), parts, ".")
83
- key = sprintf("%010d.%010d.%010d", parts[1], parts[2], parts[3])
84
- tag_by_key[key] = tag
85
- if (peeled || !(tag in sha_by_tag)) {
86
- sha_by_tag[tag] = $1
87
- }
88
- }
89
- END {
90
- for (key in tag_by_key) {
91
- tag = tag_by_key[key]
92
- print key, tag, sha_by_tag[tag]
93
- }
94
- }
95
- ' | sort | tail -n 1 | awk '
96
- NF == 3 { print $2 " " $3; found = 1 }
97
- END { if (!found) exit 1 }
98
- '
99
- }
100
-
101
- spw_resolve_ref() {
102
- upstream_url="$1"
103
- requested_ref="$2"
104
- spw_require_command git
105
-
106
- if [ "$requested_ref" = "latest-release" ]; then
107
- if ! output=$(git ls-remote --tags "$upstream_url" 'refs/tags/v*' 2>&1); then
108
- spw_die "cannot query upstream tags from $upstream_url: $output"
109
- fi
110
- if ! selected=$(printf '%s\n' "$output" | spw_select_latest_release_from_ls_remote); then
111
- spw_die "no stable semver tag found for latest-release"
112
- fi
113
- printf 'latest-release %s\n' "$selected"
114
- return
115
- fi
116
-
117
- if printf '%s' "$requested_ref" | grep -Eq '^[0-9a-fA-F]{40}$'; then
118
- printf 'raw-commit %s %s\n' "$requested_ref" "$requested_ref"
119
- return
120
- fi
121
-
122
- if ! tag_output=$(git ls-remote --tags "$upstream_url" "refs/tags/$requested_ref" "refs/tags/$requested_ref^{}" 2>&1); then
123
- spw_die "cannot query upstream tag $requested_ref from $upstream_url: $tag_output"
124
- fi
125
- resolved=$(printf '%s\n' "$tag_output" | awk -v tag_ref="refs/tags/$requested_ref" '
126
- NF >= 2 && ($2 == tag_ref || $2 == tag_ref "^{}") {
127
- sha = $1
128
- }
129
- END { if (sha != "") print sha }
130
- ')
131
- if [ -n "$resolved" ]; then
132
- printf 'tag %s %s\n' "$requested_ref" "$resolved"
133
- return
134
- fi
135
-
136
- if ! ref_output=$(git ls-remote "$upstream_url" "$requested_ref" 2>&1); then
137
- spw_die "cannot query upstream ref $requested_ref from $upstream_url: $ref_output"
138
- fi
139
- resolved=$(printf '%s\n' "$ref_output" | awk 'NF >= 2 { print $1; exit }')
140
- if [ -n "$resolved" ]; then
141
- printf 'ref %s %s\n' "$requested_ref" "$resolved"
142
- return
143
- fi
144
-
145
- spw_die "cannot resolve upstream ref: $requested_ref"
146
- }