superpowers-manager 0.1.4 → 0.2.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.
@@ -0,0 +1,603 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ root=$(CDPATH= cd -- "$(dirname "$0")/../../.." && pwd)
5
+ . "$root/scripts/core/common.sh"
6
+ . "$root/scripts/adapters/codex/lib.sh"
7
+
8
+ spw_codex_argument_error() {
9
+ operation="$1"
10
+ message="$2"
11
+ spw_codex_failure "$operation" "invalid-arguments" "$message" "" ""
12
+ }
13
+
14
+ spw_codex_require_command() {
15
+ operation="$1"
16
+ command_name="$2"
17
+ if ! command -v "$command_name" >/dev/null 2>&1; then
18
+ spw_codex_failure "$operation" "command-not-found" \
19
+ "required Codex command not found: $command_name" "" ""
20
+ fi
21
+ }
22
+
23
+ spw_codex_require_absolute_path() {
24
+ operation="$1"
25
+ value="$2"
26
+ flag="$3"
27
+ case "$value" in
28
+ /*) ;;
29
+ *) spw_codex_argument_error "$operation" "$flag must be an absolute path" ;;
30
+ esac
31
+ }
32
+
33
+ spw_codex_read_source_from_provenance() {
34
+ provenance_path="$1"
35
+ python3 - "$provenance_path" <<'PY'
36
+ import json
37
+ import sys
38
+
39
+ path = sys.argv[1]
40
+ try:
41
+ with open(path, encoding="utf-8") as handle:
42
+ data = json.load(handle)
43
+ except (OSError, UnicodeError, json.JSONDecodeError, RecursionError, ValueError):
44
+ sys.exit(1)
45
+ if not isinstance(data, dict):
46
+ sys.exit(1)
47
+ source = data.get("source")
48
+ if not isinstance(source, str) or not source:
49
+ sys.exit(1)
50
+ print(source)
51
+ PY
52
+ }
53
+
54
+ spw_codex_capture_command() {
55
+ messages_file="$1"
56
+ stdout_file="$2"
57
+ stderr_file="$3"
58
+ shift 3
59
+ : > "$stdout_file"
60
+ : > "$stderr_file"
61
+ if "$@" >"$stdout_file" 2>"$stderr_file"; then
62
+ spw_codex_append_messages "$messages_file" stderr "$stdout_file"
63
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
64
+ return 0
65
+ fi
66
+ spw_codex_append_messages "$messages_file" stderr "$stdout_file"
67
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
68
+ return 1
69
+ }
70
+
71
+ spw_codex_install_result_json() {
72
+ mismatch_hint="$1"
73
+ missing_hint="$2"
74
+ python3 - "$mismatch_hint" "$missing_hint" <<'PY'
75
+ import json
76
+ import sys
77
+
78
+ mismatch_hint, missing_hint = sys.argv[1:]
79
+ hints = {}
80
+ if mismatch_hint:
81
+ hints["mismatch"] = mismatch_hint
82
+ if missing_hint:
83
+ hints["missing"] = missing_hint
84
+ json.dump({"verification_hints": hints}, sys.stdout, allow_nan=False, separators=(",", ":"))
85
+ sys.stdout.write("\n")
86
+ PY
87
+ }
88
+
89
+ spw_codex_append_message_text() {
90
+ messages_file="$1"
91
+ channel="$2"
92
+ text="$3"
93
+ python3 - "$messages_file" "$channel" "$text" <<'PY'
94
+ import sys
95
+
96
+ path, channel, text = sys.argv[1:]
97
+ escaped = text.encode("unicode_escape").decode("ascii")
98
+ with open(path, "a", encoding="utf-8") as handle:
99
+ handle.write(f"{channel}\t{escaped}\n")
100
+ PY
101
+ }
102
+
103
+ spw_codex_build() {
104
+ upstream_root=
105
+ candidate_root=
106
+ requested_ref=
107
+ resolved_ref=
108
+ commit=
109
+ manager_version=
110
+ upstream_manifest_version=
111
+ fallback_manifest=
112
+ seen_upstream_root=0
113
+ seen_candidate_root=0
114
+ seen_requested_ref=0
115
+ seen_resolved_ref=0
116
+ seen_commit=0
117
+ seen_manager_version=0
118
+ seen_upstream_manifest_version=0
119
+ seen_fallback_manifest=0
120
+
121
+ while [ "$#" -gt 0 ]; do
122
+ flag="$1"
123
+ shift
124
+ case "$flag" in
125
+ --upstream-root)
126
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --upstream-root"
127
+ [ "$seen_upstream_root" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --upstream-root"
128
+ upstream_root="$1"
129
+ seen_upstream_root=1
130
+ shift
131
+ ;;
132
+ --candidate-root)
133
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --candidate-root"
134
+ [ "$seen_candidate_root" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --candidate-root"
135
+ candidate_root="$1"
136
+ seen_candidate_root=1
137
+ shift
138
+ ;;
139
+ --requested-ref)
140
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --requested-ref"
141
+ [ "$seen_requested_ref" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --requested-ref"
142
+ requested_ref="$1"
143
+ seen_requested_ref=1
144
+ shift
145
+ ;;
146
+ --resolved-ref)
147
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --resolved-ref"
148
+ [ "$seen_resolved_ref" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --resolved-ref"
149
+ resolved_ref="$1"
150
+ seen_resolved_ref=1
151
+ shift
152
+ ;;
153
+ --commit)
154
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --commit"
155
+ [ "$seen_commit" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --commit"
156
+ commit="$1"
157
+ seen_commit=1
158
+ shift
159
+ ;;
160
+ --manager-version)
161
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --manager-version"
162
+ [ "$seen_manager_version" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --manager-version"
163
+ manager_version="$1"
164
+ seen_manager_version=1
165
+ shift
166
+ ;;
167
+ --upstream-manifest-version)
168
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --upstream-manifest-version"
169
+ [ "$seen_upstream_manifest_version" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --upstream-manifest-version"
170
+ upstream_manifest_version="$1"
171
+ seen_upstream_manifest_version=1
172
+ shift
173
+ ;;
174
+ --fallback-manifest)
175
+ [ "$#" -gt 0 ] || spw_codex_argument_error build "missing value for --fallback-manifest"
176
+ [ "$seen_fallback_manifest" -eq 0 ] || spw_codex_argument_error build "duplicate flag: --fallback-manifest"
177
+ fallback_manifest="$1"
178
+ seen_fallback_manifest=1
179
+ shift
180
+ ;;
181
+ *)
182
+ spw_codex_argument_error build "unknown flag: $flag"
183
+ ;;
184
+ esac
185
+ done
186
+
187
+ [ "$seen_upstream_root" -eq 1 ] || spw_codex_argument_error build "missing required flag: --upstream-root"
188
+ [ "$seen_candidate_root" -eq 1 ] || spw_codex_argument_error build "missing required flag: --candidate-root"
189
+ [ "$seen_requested_ref" -eq 1 ] || spw_codex_argument_error build "missing required flag: --requested-ref"
190
+ [ "$seen_resolved_ref" -eq 1 ] || spw_codex_argument_error build "missing required flag: --resolved-ref"
191
+ [ "$seen_commit" -eq 1 ] || spw_codex_argument_error build "missing required flag: --commit"
192
+ [ "$seen_manager_version" -eq 1 ] || spw_codex_argument_error build "missing required flag: --manager-version"
193
+ [ "$seen_upstream_manifest_version" -eq 1 ] || spw_codex_argument_error build "missing required flag: --upstream-manifest-version"
194
+ [ "$seen_fallback_manifest" -eq 1 ] || spw_codex_argument_error build "missing required flag: --fallback-manifest"
195
+
196
+ spw_codex_require_absolute_path build "$upstream_root" "--upstream-root"
197
+ spw_codex_require_absolute_path build "$candidate_root" "--candidate-root"
198
+ [ -d "$upstream_root" ] || spw_codex_argument_error build "upstream root not found: $upstream_root"
199
+ [ -d "$candidate_root" ] || spw_codex_argument_error build "candidate root not found: $candidate_root"
200
+ [ -f "$fallback_manifest" ] || spw_codex_argument_error build "fallback manifest not found: $fallback_manifest"
201
+
202
+ validator="$root/scripts/adapters/codex/validate-generated-plugin.py"
203
+ [ -f "$validator" ] || spw_codex_failure build "build-failed" "missing built-in plugin validator: $validator" "" ""
204
+
205
+ tmp_parent="${TMPDIR:-/tmp}"
206
+ if ! scratch=$(spw_make_workspace "$tmp_parent" "superpowers-manager.adapter-build"); then
207
+ spw_codex_failure build "build-failed" "cannot create adapter build workspace under $tmp_parent" "" ""
208
+ fi
209
+ spw_install_workspace_trap "$scratch"
210
+ stdout_file="$scratch/stdout"
211
+ stderr_file="$scratch/stderr"
212
+ messages_file="$scratch/messages"
213
+ candidate_manifest="$candidate_root/.codex-plugin/plugin.json"
214
+ provenance_path="$candidate_root/.superpowers-upstream.json"
215
+
216
+ mkdir -p "$candidate_root/.codex-plugin"
217
+ : > "$messages_file"
218
+
219
+ upstream_manifest="$upstream_root/.codex-plugin/plugin.json"
220
+ if [ -f "$upstream_manifest" ]; then
221
+ if ! cp "$upstream_manifest" "$candidate_manifest"; then
222
+ spw_codex_failure build "build-failed" "cannot copy upstream manifest into candidate" "" "$messages_file"
223
+ fi
224
+ else
225
+ if ! cp "$fallback_manifest" "$candidate_manifest"; then
226
+ spw_codex_failure build "build-failed" "cannot copy fallback manifest into candidate" "" "$messages_file"
227
+ fi
228
+ fi
229
+
230
+ if ! spw_apply_manifest_overlay "$candidate_manifest" "$manager_version"; then
231
+ spw_codex_failure build "build-failed" "failed to apply manager manifest overlay" "" "$messages_file"
232
+ fi
233
+
234
+ if ! cp "$fallback_manifest" "$candidate_root/.codex-plugin/plugin.template.json"; then
235
+ spw_codex_failure build "build-failed" "cannot copy fallback manifest template into candidate" "" "$messages_file"
236
+ fi
237
+
238
+ if ! upstream_source=$(spw_codex_read_source_from_provenance "$provenance_path"); then
239
+ spw_codex_failure build "invalid-provenance" "candidate provenance is missing or invalid" "" "$messages_file"
240
+ fi
241
+
242
+ if python3 -S "$validator" \
243
+ --plugin-root "$candidate_root" \
244
+ --source="$upstream_source" \
245
+ --requested-ref "$requested_ref" \
246
+ --resolved-ref "$resolved_ref" \
247
+ --commit "$commit" \
248
+ --manifest-version "$manager_version" \
249
+ --upstream-manifest-version "$upstream_manifest_version" \
250
+ >"$stdout_file" 2>"$stderr_file"; then
251
+ spw_codex_append_messages "$messages_file" stdout "$stdout_file"
252
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
253
+ spw_codex_success build '{}' "$messages_file"
254
+ else
255
+ spw_codex_append_messages "$messages_file" stdout "$stdout_file"
256
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
257
+ spw_codex_failure build "generated-plugin-validation-failed" "built-in generated plugin validation failed" "" "$messages_file"
258
+ fi
259
+ }
260
+
261
+ spw_codex_install() {
262
+ package_root=
263
+ seen_package_root=0
264
+
265
+ while [ "$#" -gt 0 ]; do
266
+ flag="$1"
267
+ shift
268
+ case "$flag" in
269
+ --package-root)
270
+ [ "$#" -gt 0 ] || spw_codex_argument_error install "missing value for --package-root"
271
+ [ "$seen_package_root" -eq 0 ] || spw_codex_argument_error install "duplicate flag: --package-root"
272
+ package_root="$1"
273
+ seen_package_root=1
274
+ shift
275
+ ;;
276
+ *)
277
+ spw_codex_argument_error install "unknown flag: $flag"
278
+ ;;
279
+ esac
280
+ done
281
+
282
+ [ "$seen_package_root" -eq 1 ] || spw_codex_argument_error install "missing required flag: --package-root"
283
+ spw_codex_require_absolute_path install "$package_root" "--package-root"
284
+ [ -d "$package_root" ] || spw_codex_argument_error install "package root not found: $package_root"
285
+
286
+ codex_bin="${SUPERPOWERS_CODEX:-codex}"
287
+ refresh_mode="${SUPERPOWERS_INSTALL_REFRESH_MODE:-add-only}"
288
+ spw_require_command python3
289
+ spw_codex_require_command install "$codex_bin"
290
+ case "$refresh_mode" in
291
+ add-only|remove-add) ;;
292
+ *) spw_codex_failure install "invalid-arguments" "unsupported SUPERPOWERS_INSTALL_REFRESH_MODE: $refresh_mode" "" "" ;;
293
+ esac
294
+
295
+ tmp_parent="${TMPDIR:-/tmp}"
296
+ if ! scratch=$(spw_make_workspace "$tmp_parent" "superpowers-manager.adapter-install"); then
297
+ spw_codex_failure install "install-failed" "cannot create adapter install workspace under $tmp_parent" "" ""
298
+ fi
299
+ spw_install_workspace_trap "$scratch"
300
+ stdout_file="$scratch/stdout"
301
+ stderr_file="$scratch/stderr"
302
+ messages_file="$scratch/messages"
303
+ hints_file="$scratch/hints"
304
+
305
+ : > "$messages_file"
306
+ : > "$hints_file"
307
+
308
+ : > "$stderr_file"
309
+ if ! listing=$("$codex_bin" plugin marketplace list --json 2>"$stderr_file"); then
310
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
311
+ spw_codex_failure install "install-failed" "cannot list Codex marketplaces via '$codex_bin plugin marketplace list --json'" "$hints_file" "$messages_file"
312
+ fi
313
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
314
+ if ! registered_root=$(spw_marketplace_root_from_json "$listing" "$SPW_MARKETPLACE_NAME"); then
315
+ spw_codex_failure install "install-failed" "cannot parse output of '$codex_bin plugin marketplace list --json'" "$hints_file" "$messages_file"
316
+ fi
317
+
318
+ if [ -z "$registered_root" ]; then
319
+ if ! spw_codex_capture_command "$messages_file" "$stdout_file" "$stderr_file" "$codex_bin" plugin marketplace add "$package_root"; then
320
+ spw_codex_failure install "install-failed" "codex marketplace add failed for $package_root" "$hints_file" "$messages_file"
321
+ fi
322
+ else
323
+ if ! path_relation=$(spw_paths_equal "$package_root" "$registered_root"); then
324
+ spw_codex_failure install "install-failed" "cannot compare registered and requested marketplace roots" "$hints_file" "$messages_file"
325
+ fi
326
+ case "$path_relation" in
327
+ same) ;;
328
+ different)
329
+ spw_codex_append_message_text "$messages_file" stdout \
330
+ "marketplace $SPW_MARKETPLACE_NAME registered at $registered_root; re-registering at $package_root"
331
+ if ! spw_codex_capture_command "$messages_file" "$stdout_file" "$stderr_file" "$codex_bin" plugin marketplace remove "$SPW_MARKETPLACE_NAME"; then
332
+ spw_codex_failure install "install-failed" "codex marketplace remove failed for $SPW_MARKETPLACE_NAME (registered at $registered_root)" "$hints_file" "$messages_file"
333
+ fi
334
+ if ! spw_codex_capture_command "$messages_file" "$stdout_file" "$stderr_file" "$codex_bin" plugin marketplace add "$package_root"; then
335
+ printf 'recover with: %s plugin marketplace add %s\n' "$codex_bin" "$package_root" >> "$hints_file"
336
+ printf 'previous root (last known good): %s\n' "$registered_root" >> "$hints_file"
337
+ spw_codex_failure install "install-failed" "marketplace $SPW_MARKETPLACE_NAME was removed but re-adding failed." "$hints_file" "$messages_file"
338
+ fi
339
+ ;;
340
+ *)
341
+ spw_codex_failure install "install-failed" "invalid marketplace path comparison result" "$hints_file" "$messages_file"
342
+ ;;
343
+ esac
344
+ fi
345
+
346
+ if [ "$refresh_mode" = remove-add ]; then
347
+ spw_codex_capture_command "$messages_file" "$stdout_file" "$stderr_file" "$codex_bin" plugin remove "$SPW_PLUGIN_ID" || true
348
+ fi
349
+
350
+ if ! spw_codex_capture_command "$messages_file" "$stdout_file" "$stderr_file" "$codex_bin" plugin add "$SPW_PLUGIN_ID"; then
351
+ spw_codex_failure install "install-failed" "codex plugin add failed for $SPW_PLUGIN_ID" "$hints_file" "$messages_file"
352
+ fi
353
+
354
+ mismatch_hint=
355
+ case "$refresh_mode" in
356
+ add-only)
357
+ mismatch_hint="retry with SUPERPOWERS_INSTALL_REFRESH_MODE=remove-add"
358
+ ;;
359
+ esac
360
+ missing_hint="verify with 'codex plugin list --json'."
361
+ result_json=$(spw_codex_install_result_json "$mismatch_hint" "$missing_hint")
362
+ spw_codex_success install "$result_json" "$messages_file"
363
+ }
364
+
365
+ spw_codex_uninstall() {
366
+ plugin_present=
367
+ marketplace_present=
368
+ seen_plugin_present=0
369
+ seen_marketplace_present=0
370
+
371
+ while [ "$#" -gt 0 ]; do
372
+ flag="$1"
373
+ shift
374
+ case "$flag" in
375
+ --plugin-present)
376
+ [ "$#" -gt 0 ] || spw_codex_argument_error uninstall "missing value for --plugin-present"
377
+ [ "$seen_plugin_present" -eq 0 ] || spw_codex_argument_error uninstall "duplicate flag: --plugin-present"
378
+ plugin_present="$1"
379
+ seen_plugin_present=1
380
+ shift
381
+ ;;
382
+ --marketplace-present)
383
+ [ "$#" -gt 0 ] || spw_codex_argument_error uninstall "missing value for --marketplace-present"
384
+ [ "$seen_marketplace_present" -eq 0 ] || spw_codex_argument_error uninstall "duplicate flag: --marketplace-present"
385
+ marketplace_present="$1"
386
+ seen_marketplace_present=1
387
+ shift
388
+ ;;
389
+ *)
390
+ spw_codex_argument_error uninstall "unknown flag: $flag"
391
+ ;;
392
+ esac
393
+ done
394
+
395
+ [ "$seen_plugin_present" -eq 1 ] || spw_codex_argument_error uninstall "missing required flag: --plugin-present"
396
+ [ "$seen_marketplace_present" -eq 1 ] || spw_codex_argument_error uninstall "missing required flag: --marketplace-present"
397
+ case "$plugin_present" in
398
+ true|false) ;;
399
+ *) spw_codex_argument_error uninstall "--plugin-present must be true or false" ;;
400
+ esac
401
+ case "$marketplace_present" in
402
+ true|false) ;;
403
+ *) spw_codex_argument_error uninstall "--marketplace-present must be true or false" ;;
404
+ esac
405
+
406
+ codex_bin="${SUPERPOWERS_CODEX:-codex}"
407
+ spw_require_command python3
408
+ spw_codex_require_command uninstall "$codex_bin"
409
+
410
+ tmp_parent="${TMPDIR:-/tmp}"
411
+ if ! scratch=$(spw_make_workspace "$tmp_parent" "superpowers-manager.adapter-uninstall"); then
412
+ spw_codex_failure uninstall "uninstall-failed" "cannot create adapter uninstall workspace under $tmp_parent" "" ""
413
+ fi
414
+ spw_install_workspace_trap "$scratch"
415
+ stdout_file="$scratch/stdout"
416
+ stderr_file="$scratch/stderr"
417
+ messages_file="$scratch/messages"
418
+
419
+ : > "$messages_file"
420
+
421
+ if [ "$plugin_present" = true ]; then
422
+ if ! spw_codex_capture_command "$messages_file" "$stdout_file" "$stderr_file" "$codex_bin" plugin remove "$SPW_PLUGIN_ID"; then
423
+ spw_codex_failure uninstall "uninstall-failed" "codex plugin remove failed for $SPW_PLUGIN_ID" "" "$messages_file"
424
+ fi
425
+ spw_codex_append_message_text "$messages_file" stdout "removed plugin $SPW_PLUGIN_ID"
426
+ else
427
+ spw_codex_append_message_text "$messages_file" stdout "plugin not installed; skipping"
428
+ fi
429
+
430
+ if [ "$marketplace_present" = true ]; then
431
+ if ! spw_codex_capture_command "$messages_file" "$stdout_file" "$stderr_file" "$codex_bin" plugin marketplace remove "$SPW_MARKETPLACE_NAME"; then
432
+ spw_codex_failure uninstall "uninstall-failed" "codex plugin marketplace remove failed for $SPW_MARKETPLACE_NAME" "" "$messages_file"
433
+ fi
434
+ spw_codex_append_message_text "$messages_file" stdout "removed marketplace $SPW_MARKETPLACE_NAME"
435
+ else
436
+ spw_codex_append_message_text "$messages_file" stdout "marketplace not registered; skipping"
437
+ fi
438
+
439
+ spw_codex_success uninstall '{}' "$messages_file"
440
+ }
441
+
442
+ spw_codex_inspect() {
443
+ view=
444
+ seen_view=0
445
+
446
+ while [ "$#" -gt 0 ]; do
447
+ flag="$1"
448
+ shift
449
+ case "$flag" in
450
+ --view)
451
+ [ "$#" -gt 0 ] || spw_codex_argument_error inspect "missing value for --view"
452
+ [ "$seen_view" -eq 0 ] || spw_codex_argument_error inspect "duplicate flag: --view"
453
+ view="$1"
454
+ seen_view=1
455
+ shift
456
+ ;;
457
+ *)
458
+ spw_codex_argument_error inspect "unknown flag: $flag"
459
+ ;;
460
+ esac
461
+ done
462
+
463
+ [ "$seen_view" -eq 1 ] || spw_codex_argument_error inspect "missing required flag: --view"
464
+
465
+ case "$view" in
466
+ fingerprint)
467
+ codex_bin="${SUPERPOWERS_CODEX:-codex}"
468
+ spw_require_command python3
469
+ spw_codex_require_command inspect "$codex_bin"
470
+
471
+ tmp_parent="${TMPDIR:-/tmp}"
472
+ if ! scratch=$(spw_make_workspace "$tmp_parent" "superpowers-manager.adapter-fingerprint"); then
473
+ spw_codex_failure inspect "inspect-failed" "cannot create adapter fingerprint workspace under $tmp_parent" "" ""
474
+ fi
475
+ spw_install_workspace_trap "$scratch"
476
+ stderr_file="$scratch/stderr"
477
+ messages_file="$scratch/messages"
478
+
479
+ : > "$messages_file"
480
+ : > "$stderr_file"
481
+ if ! plugin_listing=$("$codex_bin" plugin list --json 2>"$stderr_file"); then
482
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
483
+ spw_codex_failure inspect "inspect-failed" "cannot list Codex plugins via '$codex_bin plugin list --json'" "" "$messages_file"
484
+ fi
485
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
486
+ if ! active_version=$(spw_active_plugin_version_from_json \
487
+ "$plugin_listing" "$SPW_PLUGIN_ID"); then
488
+ spw_codex_failure inspect "inspect-failed" "cannot parse output of '$codex_bin plugin list --json'" "" "$messages_file"
489
+ fi
490
+ if [ -z "$active_version" ]; then
491
+ spw_codex_success inspect \
492
+ '{"view":"fingerprint","fingerprint":null}' "$messages_file"
493
+ return 0
494
+ fi
495
+
496
+ search_root="${SUPERPOWERS_INSTALLED_SEARCH_ROOT:-$HOME/.codex}"
497
+ active_root=$(spw_installed_root_for_version \
498
+ "$search_root" "$SPW_MARKETPLACE_NAME" "superpowers" "$active_version")
499
+ fingerprint=$(spw_installed_commit_from_root_or_empty "$active_root")
500
+ if [ -z "$fingerprint" ]; then
501
+ spw_codex_failure inspect "inspect-failed" \
502
+ "cannot inspect active Codex plugin fingerprint under $active_root" "" "$messages_file"
503
+ fi
504
+ spw_codex_success inspect \
505
+ "{\"view\":\"fingerprint\",\"fingerprint\":\"$fingerprint\"}" \
506
+ "$messages_file"
507
+ ;;
508
+ update-control)
509
+ # Capability declaration for the fixed Codex local-package topology,
510
+ # backed by Layer-4 A/B evidence; revisit if topology or observed update
511
+ # behavior changes.
512
+ spw_codex_success inspect \
513
+ '{"view":"update-control","update_control":"managed"}' ""
514
+ ;;
515
+ ownership)
516
+ codex_bin="${SUPERPOWERS_CODEX:-codex}"
517
+ spw_require_command python3
518
+ spw_codex_require_command inspect "$codex_bin"
519
+
520
+ tmp_parent="${TMPDIR:-/tmp}"
521
+ if ! scratch=$(spw_make_workspace "$tmp_parent" "superpowers-manager.adapter-inspect"); then
522
+ spw_codex_failure inspect "inspect-failed" "cannot create adapter inspect workspace under $tmp_parent" "" ""
523
+ fi
524
+ spw_install_workspace_trap "$scratch"
525
+ stderr_file="$scratch/stderr"
526
+ messages_file="$scratch/messages"
527
+
528
+ : > "$messages_file"
529
+
530
+ : > "$stderr_file"
531
+ if ! plugin_listing=$("$codex_bin" plugin list --json 2>"$stderr_file"); then
532
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
533
+ spw_codex_failure inspect "inspect-failed" "cannot list Codex plugins via '$codex_bin plugin list --json'" "" "$messages_file"
534
+ fi
535
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
536
+ if ! plugin_result=$(spw_json_array_has "$plugin_listing" "installed" "pluginId" "$SPW_PLUGIN_ID"); then
537
+ spw_codex_failure inspect "inspect-failed" "cannot parse output of '$codex_bin plugin list --json'" "" "$messages_file"
538
+ fi
539
+ if ! legacy_plugin_result=$(spw_json_array_has "$plugin_listing" "installed" "pluginId" "$SPW_LEGACY_PLUGIN_ID"); then
540
+ spw_codex_failure inspect "inspect-failed" "cannot parse output of '$codex_bin plugin list --json'" "" "$messages_file"
541
+ fi
542
+
543
+ : > "$stderr_file"
544
+ if ! marketplace_listing=$("$codex_bin" plugin marketplace list --json 2>"$stderr_file"); then
545
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
546
+ spw_codex_failure inspect "inspect-failed" "cannot list Codex marketplaces via '$codex_bin plugin marketplace list --json'" "" "$messages_file"
547
+ fi
548
+ spw_codex_append_messages "$messages_file" stderr "$stderr_file"
549
+ if ! marketplace_result=$(spw_json_array_has "$marketplace_listing" "marketplaces" "name" "$SPW_MARKETPLACE_NAME"); then
550
+ spw_codex_failure inspect "inspect-failed" "cannot parse output of '$codex_bin plugin marketplace list --json'" "" "$messages_file"
551
+ fi
552
+ if ! legacy_marketplace_result=$(spw_json_array_has "$marketplace_listing" "marketplaces" "name" "$SPW_LEGACY_MARKETPLACE_NAME"); then
553
+ spw_codex_failure inspect "inspect-failed" "cannot parse output of '$codex_bin plugin marketplace list --json'" "" "$messages_file"
554
+ fi
555
+
556
+ manager_present=false
557
+ legacy_present=false
558
+ if [ "$plugin_result" = present ] || [ "$marketplace_result" = present ]; then
559
+ manager_present=true
560
+ fi
561
+ if [ "$legacy_plugin_result" = present ] || [ "$legacy_marketplace_result" = present ]; then
562
+ legacy_present=true
563
+ fi
564
+ case "$manager_present:$legacy_present" in
565
+ false:false) identity_state=neither ;;
566
+ true:false) identity_state=manager ;;
567
+ false:true) identity_state=legacy ;;
568
+ true:true) identity_state=both ;;
569
+ *) spw_codex_failure inspect "inspect-failed" "cannot derive Codex identity state" "" "$messages_file" ;;
570
+ esac
571
+
572
+ spw_codex_success inspect \
573
+ "{\"view\":\"ownership\",\"resources\":{\"plugin\":$([ "$plugin_result" = present ] && printf true || printf false),\"marketplace\":$([ "$marketplace_result" = present ] && printf true || printf false)},\"legacy_resources\":{\"plugin\":$([ "$legacy_plugin_result" = present ] && printf true || printf false),\"marketplace\":$([ "$legacy_marketplace_result" = present ] && printf true || printf false)},\"identity_state\":\"$identity_state\"}" \
574
+ "$messages_file"
575
+ ;;
576
+ *)
577
+ spw_codex_argument_error inspect "unsupported inspect view: $view"
578
+ ;;
579
+ esac
580
+ }
581
+
582
+ operation="${1:-}"
583
+ [ -n "$operation" ] || spw_codex_failure adapter "invalid-arguments" "missing adapter operation" "" ""
584
+ shift
585
+
586
+ case "$operation" in
587
+ build)
588
+ spw_codex_build "$@"
589
+ ;;
590
+ install)
591
+ spw_codex_install "$@"
592
+ ;;
593
+ inspect)
594
+ spw_codex_inspect "$@"
595
+ ;;
596
+ uninstall)
597
+ spw_codex_uninstall "$@"
598
+ ;;
599
+ *)
600
+ spw_codex_emit "$operation" false '{}' "unsupported-operation" "unsupported adapter operation: $operation" "" ""
601
+ exit 1
602
+ ;;
603
+ esac