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
@@ -0,0 +1,204 @@
1
+ function byteEscape(byte) {
2
+ return `\\x${byte.toString(16).padStart(2, "0")}`;
3
+ }
4
+ function decodeBackslashReplace(bytes) {
5
+ let result = "";
6
+ for (let index = 0; index < bytes.length;) {
7
+ const first = bytes[index];
8
+ if (first < 0x80) {
9
+ result += String.fromCodePoint(first);
10
+ index += 1;
11
+ continue;
12
+ }
13
+ let length = 0;
14
+ let codePoint = 0;
15
+ let secondMinimum = 0x80;
16
+ let secondMaximum = 0xbf;
17
+ if (first >= 0xc2 && first <= 0xdf) {
18
+ length = 2;
19
+ codePoint = first & 0x1f;
20
+ }
21
+ else if (first >= 0xe0 && first <= 0xef) {
22
+ length = 3;
23
+ codePoint = first & 0x0f;
24
+ if (first === 0xe0)
25
+ secondMinimum = 0xa0;
26
+ if (first === 0xed)
27
+ secondMaximum = 0x9f;
28
+ }
29
+ else if (first >= 0xf0 && first <= 0xf4) {
30
+ length = 4;
31
+ codePoint = first & 0x07;
32
+ if (first === 0xf0)
33
+ secondMinimum = 0x90;
34
+ if (first === 0xf4)
35
+ secondMaximum = 0x8f;
36
+ }
37
+ const second = bytes[index + 1];
38
+ let valid = length > 0 &&
39
+ index + length <= bytes.length &&
40
+ second !== undefined &&
41
+ second >= secondMinimum &&
42
+ second <= secondMaximum;
43
+ for (let offset = 2; valid && offset < length; offset += 1) {
44
+ const byte = bytes[index + offset];
45
+ valid = byte >= 0x80 && byte <= 0xbf;
46
+ }
47
+ if (!valid) {
48
+ result += byteEscape(first);
49
+ index += 1;
50
+ continue;
51
+ }
52
+ for (let offset = 1; offset < length; offset += 1) {
53
+ codePoint = (codePoint << 6) | (bytes[index + offset] & 0x3f);
54
+ }
55
+ result += String.fromCodePoint(codePoint);
56
+ index += length;
57
+ }
58
+ return result;
59
+ }
60
+ function pythonUnicodeEscape(value) {
61
+ let result = "";
62
+ for (const character of value) {
63
+ const code = character.codePointAt(0);
64
+ if (character === "\\")
65
+ result += "\\\\";
66
+ else if (code === 0x09)
67
+ result += "\\t";
68
+ else if (code === 0x0a)
69
+ result += "\\n";
70
+ else if (code === 0x0d)
71
+ result += "\\r";
72
+ else if (code >= 0x20 && code <= 0x7e)
73
+ result += character;
74
+ else if (code <= 0xff)
75
+ result += `\\x${code.toString(16).padStart(2, "0")}`;
76
+ else if (code <= 0xffff)
77
+ result += `\\u${code.toString(16).padStart(4, "0")}`;
78
+ else
79
+ result += `\\U${code.toString(16).padStart(8, "0")}`;
80
+ }
81
+ return result;
82
+ }
83
+ export function pythonUnicodeEscapeBytes(bytes) {
84
+ return pythonUnicodeEscape(decodeBackslashReplace(bytes));
85
+ }
86
+ export class AdapterMessageLog {
87
+ #messages = [];
88
+ appendBytes(channel, bytes) {
89
+ let start = 0;
90
+ for (let index = 0; index <= bytes.length; index += 1) {
91
+ if (index !== bytes.length && bytes[index] !== 0x0a)
92
+ continue;
93
+ const chunk = bytes.slice(start, index);
94
+ start = index + 1;
95
+ if (chunk.length === 0)
96
+ continue;
97
+ this.#messages.push({
98
+ channel,
99
+ text: pythonUnicodeEscapeBytes(chunk),
100
+ });
101
+ }
102
+ }
103
+ appendText(channel, text) {
104
+ const escaped = pythonUnicodeEscape(text);
105
+ if (escaped.length > 0)
106
+ this.#messages.push({ channel, text: escaped });
107
+ }
108
+ snapshot() {
109
+ return this.#messages.map((message) => ({ ...message }));
110
+ }
111
+ }
112
+ export function successResult(operation, result, messages) {
113
+ return {
114
+ status: 0,
115
+ outcome: {
116
+ operation,
117
+ ok: true,
118
+ messages,
119
+ result,
120
+ error: null,
121
+ },
122
+ };
123
+ }
124
+ export function failureResult(operation, code, message, hints, messages) {
125
+ return {
126
+ status: 1,
127
+ outcome: {
128
+ operation,
129
+ ok: false,
130
+ messages,
131
+ result: null,
132
+ error: { code, message, hints },
133
+ },
134
+ };
135
+ }
136
+ export function hasTerminalControl(value) {
137
+ for (const character of value) {
138
+ const code = character.codePointAt(0);
139
+ if (code < 0x20 ||
140
+ (code >= 0x7f && code <= 0x9f) ||
141
+ (code >= 0xd800 && code <= 0xdfff)) {
142
+ return true;
143
+ }
144
+ }
145
+ return false;
146
+ }
147
+ // D8b's enforcement point on the LIVE product path. replayOutcome -- which
148
+ // every command replays through -- wrote these three strings to the terminal
149
+ // unfiltered.
150
+ //
151
+ // NOT requireProtocolString: that one message
152
+ // ("protocol strings must not contain terminal control characters") names no
153
+ // member, and D8b requires the thrown message to name the failing one. So this
154
+ // scans with the predicate underneath it instead.
155
+ //
156
+ // Order is code, then message, then hints by ascending index, so the thrown
157
+ // message is a function of the outcome rather than of iteration order.
158
+ //
159
+ // The offending value is never interpolated, never sanitized, and never
160
+ // truncated into the message: the contract is that an unsafe string does not
161
+ // reach the terminal, not that it arrives altered. The hint INDEX is the one
162
+ // interpolated value, and it is a bounded integer. src/cli.ts's handler catch
163
+ // emits `error: <this module's own hand-written message>` with exit 1, which
164
+ // is the sanctioned interpolation under AGENTS.md.
165
+ //
166
+ // Exported separately from writeAdapterFailure because replayOutcome writes
167
+ // every message BEFORE the error line: a caller that validated only at the
168
+ // write would already have put the context lines on the stream when the guard
169
+ // fired. Hoisting this above the message loop is what makes a refused failure
170
+ // leave both streams untouched.
171
+ export function assertFailureWritable(outcome) {
172
+ if (outcome.ok)
173
+ return;
174
+ if (hasTerminalControl(outcome.error.code)) {
175
+ throw new Error("adapter failure code contains a terminal control character");
176
+ }
177
+ if (hasTerminalControl(outcome.error.message)) {
178
+ throw new Error("adapter failure message contains a terminal control character");
179
+ }
180
+ for (const [index, hint] of outcome.error.hints.entries()) {
181
+ if (hasTerminalControl(hint)) {
182
+ throw new Error(`adapter failure hint[${index}] contains a terminal control character`);
183
+ }
184
+ }
185
+ }
186
+ // Validates the WHOLE failure before the first write, so a hint that fails
187
+ // after two safe ones leaves neither of those two on the stream. `ctx` is
188
+ // typed structurally rather than as CommandContext: this reads exactly one of
189
+ // that interface's five members, and naming the wide type would point this
190
+ // module at one that already imports from it.
191
+ export function writeAdapterFailure(ctx, outcome) {
192
+ assertFailureWritable(outcome);
193
+ if (outcome.ok)
194
+ return;
195
+ ctx.stderr.write(`error: ${outcome.error.message}\n`);
196
+ for (const hint of outcome.error.hints) {
197
+ ctx.stderr.write(`hint: ${hint}\n`);
198
+ }
199
+ }
200
+ export function requireProtocolString(value) {
201
+ if (hasTerminalControl(value)) {
202
+ throw new Error("protocol strings must not contain terminal control characters");
203
+ }
204
+ }