superpowers-manager 0.5.2 → 0.6.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 +12 -10
- package/dist/cli.js +8 -13
- package/dist/commands/install.js +2 -3
- package/dist/commands/mutation.js +7 -0
- package/dist/commands/prepare.js +5 -51
- package/dist/commands/probe.js +8 -10
- package/dist/commands/uninstall.js +2 -2
- package/dist/effective-selection.js +3 -3
- package/dist/harnesses/codex/adapter.js +259 -509
- package/dist/harnesses/codex/compatibility.js +2 -17
- package/dist/harnesses/codex/harness.js +18 -263
- package/dist/harnesses/codex/lifecycle.js +110 -54
- package/dist/harnesses/codex/manifest-overlay.js +22 -17
- package/dist/harnesses/codex/presentation.js +6 -14
- package/dist/harnesses/codex/publication.js +13 -31
- package/dist/python-json-format.js +0 -69
- package/dist/python-json.js +2 -27
- package/dist/python-text.js +9 -69
- package/dist/validator.js +35 -72
- package/package.json +1 -1
- package/dist/selection-state-cli.js +0 -85
- package/dist/upstream-cli.js +0 -110
package/README.md
CHANGED
|
@@ -113,18 +113,20 @@ The [usage reference](https://github.com/j7an/superpowers-manager/blob/main/docs
|
|
|
113
113
|
The installed package requires Node >=24. Native source, tests, and packaging tooling require Node >=24.12.0. Every other requirement is command-specific and is checked before dispatch. This table is derived from production by `tests/bin/readme-requirements.test.ts`.
|
|
114
114
|
|
|
115
115
|
<!-- requirements:begin -->
|
|
116
|
-
| Command | git |
|
|
117
|
-
|
|
118
|
-
| `pin` | yes | no | no |
|
|
119
|
-
| `track-latest` | no | no | no |
|
|
120
|
-
| `unpin` | no | no | no |
|
|
121
|
-
| `prepare` | yes |
|
|
122
|
-
| `probe` | yes |
|
|
123
|
-
| `install` | yes |
|
|
124
|
-
| `update` | yes |
|
|
125
|
-
| `uninstall` | no |
|
|
116
|
+
| Command | git | Codex CLI (default) | Pi CLI (`--harness pi`) |
|
|
117
|
+
|---|---|---|---|
|
|
118
|
+
| `pin` | yes | no | no |
|
|
119
|
+
| `track-latest` | no | no | no |
|
|
120
|
+
| `unpin` | no | no | no |
|
|
121
|
+
| `prepare` | yes | no | no |
|
|
122
|
+
| `probe` | yes | yes | no |
|
|
123
|
+
| `install` | yes | yes | yes |
|
|
124
|
+
| `update` | yes | yes | yes |
|
|
125
|
+
| `uninstall` | no | yes | yes |
|
|
126
126
|
<!-- requirements:end -->
|
|
127
127
|
|
|
128
|
+
Generated Codex manifests preserve upstream JSON number tokens while rejecting non-finite decimal or exponent values.
|
|
129
|
+
|
|
128
130
|
macOS and Linux are tested. WSL2 is supported for the established Codex path; Pi in WSL2 and native Windows remain untested. The manager runs in-process and does not require Git Bash. The [Pi reference](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md#runtime-compatibility) explains its runtime qualification and admission checks.
|
|
129
131
|
|
|
130
132
|
Native test versions qualify integration mechanisms; they are not runtime allowlists, and an untested runtime is not automatically certified as compatible.
|
package/dist/cli.js
CHANGED
|
@@ -46,8 +46,8 @@ const IN_PROCESS_HANDLERS = {
|
|
|
46
46
|
// required because `spw_invoke_adapter` ran validate-adapter-response.py once
|
|
47
47
|
// per adapter call
|
|
48
48
|
// (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:37-44::--response "$response_file" --result "$result_file" \`);
|
|
49
|
-
// the in-process path
|
|
50
|
-
//
|
|
49
|
+
// the in-process path no longer uses that adapter-response validator.
|
|
50
|
+
// No command has a Python requirement in commandRequirements(env).
|
|
51
51
|
// No command requires a POSIX shell any more.
|
|
52
52
|
const SHARED_COMMAND_REQUIREMENTS = {
|
|
53
53
|
pin: ["git"],
|
|
@@ -170,13 +170,6 @@ function findTool(name, env, platform) {
|
|
|
170
170
|
}
|
|
171
171
|
return null;
|
|
172
172
|
}
|
|
173
|
-
// python3 is required by `prepare` only when SUPERPOWERS_VALIDATOR names one:
|
|
174
|
-
// after the port, that optional spawn (runValidator, in src/validator.ts,
|
|
175
|
-
// called from src/commands/prepare.ts) is Python's only remaining consumer
|
|
176
|
-
// on the prepare path. The conditional lives here, in the accessor preflight reads,
|
|
177
|
-
// rather than inside preflight — an accessor that under-reports what
|
|
178
|
-
// preflight enforces is the blind spot slice 2 closed when it made
|
|
179
|
-
// CLI-PREFLIGHT-01 derive its map from production.
|
|
180
173
|
function sharedRequirement(name) {
|
|
181
174
|
return {
|
|
182
175
|
name,
|
|
@@ -188,9 +181,6 @@ function sharedRequirement(name) {
|
|
|
188
181
|
function commandRequirementsFor(env, adapter) {
|
|
189
182
|
const forCommand = (command) => {
|
|
190
183
|
const shared = SHARED_COMMAND_REQUIREMENTS[command].map(sharedRequirement);
|
|
191
|
-
if (command === "prepare" && env.SUPERPOWERS_VALIDATOR) {
|
|
192
|
-
shared.push(sharedRequirement("python3"));
|
|
193
|
-
}
|
|
194
184
|
return [...shared, ...adapter.requirements(command, env)];
|
|
195
185
|
};
|
|
196
186
|
return {
|
|
@@ -217,6 +207,8 @@ function commandRequirements(env) {
|
|
|
217
207
|
// spawned command in-process, so there is no shell to discover.
|
|
218
208
|
function preflightFor(cmd, env, platform, adapter) {
|
|
219
209
|
const errors = [...configurationErrors(cmd, env)];
|
|
210
|
+
if (errors.length > 0)
|
|
211
|
+
return { ok: false, errors };
|
|
220
212
|
for (const requirement of commandRequirementsFor(env, adapter)[cmd]) {
|
|
221
213
|
const found = requirement.lookup === "explicit-path-or-path" &&
|
|
222
214
|
requirement.executable.includes(path.sep)
|
|
@@ -260,9 +252,12 @@ function usage() {
|
|
|
260
252
|
"SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,",
|
|
261
253
|
"SUPERPOWERS_CONFIG_DIR, XDG_CONFIG_HOME,",
|
|
262
254
|
"SUPERPOWERS_PLUGIN_ROOT, SUPERPOWERS_MANIFEST_TEMPLATE,",
|
|
263
|
-
"
|
|
255
|
+
"SUPERPOWERS_VALIDATOR_EXECUTABLE,",
|
|
264
256
|
"SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE",
|
|
265
257
|
"",
|
|
258
|
+
"SUPERPOWERS_VALIDATOR is removed; unset it and use",
|
|
259
|
+
"SUPERPOWERS_VALIDATOR_EXECUTABLE with an executable validator.",
|
|
260
|
+
"",
|
|
266
261
|
"Selection state uses SUPERPOWERS_CONFIG_DIR when set; otherwise it uses",
|
|
267
262
|
"$XDG_CONFIG_HOME/superpowers-manager, then $HOME/.config/superpowers-manager.",
|
|
268
263
|
].join("\n");
|
package/dist/commands/install.js
CHANGED
|
@@ -163,9 +163,8 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
163
163
|
// is what turns a failed inspection into "error: installed manager
|
|
164
164
|
// fingerprint inspection failed after install."
|
|
165
165
|
// renderInstallVerification's failed-inspection arm
|
|
166
|
-
// (`src/harnesses/codex/presentation.ts:
|
|
167
|
-
// exists for this result-bearing path
|
|
168
|
-
// export delegates through the same arm. Returning
|
|
166
|
+
// (`src/harnesses/codex/presentation.ts:305::if (inspection.status !== 0 || !inspection.outcome.ok) {`)
|
|
167
|
+
// exists for this result-bearing path. Returning
|
|
169
168
|
// failed() instead reported the adapter's own generic diagnostic and
|
|
170
169
|
// dropped the post-install verification claim -- a mutation had
|
|
171
170
|
// already been issued at stage 3,
|
|
@@ -3,6 +3,7 @@ import { upstreamCacheRoot } from "../upstream-workspace.js";
|
|
|
3
3
|
import { SafetyError } from "../safety-error.js";
|
|
4
4
|
import { isAbsolute } from "node:path";
|
|
5
5
|
import { oneLine } from "../cli-arguments.js";
|
|
6
|
+
import { configurationErrors } from "../validator.js";
|
|
6
7
|
export async function withMutation(command, ctx, action) {
|
|
7
8
|
// Selection validation precedes every adapter call and lock creation. A
|
|
8
9
|
// nested mutation receives the already resolved value and cannot observe a
|
|
@@ -50,6 +51,12 @@ export async function withMutation(command, ctx, action) {
|
|
|
50
51
|
return await ctx.coordination.withResources(resources, async () => action(scoped));
|
|
51
52
|
}
|
|
52
53
|
export async function runWithMutation(command, ctx, action) {
|
|
54
|
+
const errors = configurationErrors(command, ctx.env);
|
|
55
|
+
if (errors.length > 0) {
|
|
56
|
+
for (const message of errors)
|
|
57
|
+
ctx.stderr.write(`error: ${message}\n`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
53
60
|
let actionThrew = false;
|
|
54
61
|
try {
|
|
55
62
|
return await withMutation(command, ctx, async (scoped) => {
|
package/dist/commands/prepare.js
CHANGED
|
@@ -7,7 +7,7 @@ import { runGit } from "../git.js";
|
|
|
7
7
|
import { SafetyError } from "../safety-error.js";
|
|
8
8
|
import { fetchExactCommit, gitSafeSource } from "../upstream.js";
|
|
9
9
|
import { upstreamCacheRoot } from "../upstream-workspace.js";
|
|
10
|
-
import { BOUNDED_EXECUTABLE,
|
|
10
|
+
import { BOUNDED_EXECUTABLE, launchFailureMessage, resolveValidator, runValidator, displayPath, } from "../validator.js";
|
|
11
11
|
import { withWorkspace, workspaceRemovalFailure } from "../workspace.js";
|
|
12
12
|
import { replayOutcome } from "./probe.js";
|
|
13
13
|
import { runWithMutation } from "./mutation.js";
|
|
@@ -18,18 +18,6 @@ import { runWithMutation } from "./mutation.js";
|
|
|
18
18
|
function prepareError(message, cause) {
|
|
19
19
|
return new SafetyError("prepare", message, { cause });
|
|
20
20
|
}
|
|
21
|
-
// `[ -f ]` — regular file. `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:108::[ -f "$additional_validator` uses -f, and
|
|
22
|
-
// tests/baseline/cli-parity.test.js's "CLI-ENV-MANIFEST-TEMPLATE-01 fallback
|
|
23
|
-
// template bytes and non-file rejection" also covers the separately extracted
|
|
24
|
-
// fallback check.
|
|
25
|
-
async function regularFileExists(path) {
|
|
26
|
-
try {
|
|
27
|
-
return (await stat(path)).isFile();
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
21
|
// `[ -d ]` — `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:50::if [ -d`. A regular file named `.git` is what a git
|
|
34
22
|
// worktree or `clone --separate-git-dir` leaves behind; `-e` would take the
|
|
35
23
|
// fetch branch and let git follow its `gitdir:` pointer, where the shell took
|
|
@@ -112,7 +100,6 @@ async function gatherPrepare(ctx) {
|
|
|
112
100
|
throw prepareError("adapter returned an invalid preparation staging leaf");
|
|
113
101
|
}
|
|
114
102
|
const pluginRoot = location.destinationRoot;
|
|
115
|
-
const additionalValidator = env.SUPERPOWERS_VALIDATOR || "";
|
|
116
103
|
const executableValidator = env.SUPERPOWERS_VALIDATOR_EXECUTABLE || "";
|
|
117
104
|
const tmpParent = dirname(pluginRoot);
|
|
118
105
|
await owned(`cannot create directory: ${tmpParent}`, () => mkdir(tmpParent, { recursive: true }));
|
|
@@ -214,37 +201,7 @@ async function gatherPrepare(ctx) {
|
|
|
214
201
|
return failed(prepared.outcome.result.compatibility.reason);
|
|
215
202
|
}
|
|
216
203
|
let validator = NO_VALIDATOR_OUTPUT;
|
|
217
|
-
if (
|
|
218
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:108::[ -f "$additional_validator` — `[ -f ]`.
|
|
219
|
-
if (!(await regularFileExists(additionalValidator))) {
|
|
220
|
-
return {
|
|
221
|
-
kind: "failed",
|
|
222
|
-
outcomes,
|
|
223
|
-
validator,
|
|
224
|
-
message: `additional plugin validator not found: ${additionalValidator}`,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
const ran = await runValidator(["python3", additionalValidator, candidate], UNBOUNDED_LEGACY, env, workspace);
|
|
228
|
-
if (ran.kind === "launchFailed") {
|
|
229
|
-
// PARITY, and it must stay a THROW. The legacy path rejects the workspace
|
|
230
|
-
// callback on a spawn failure; withWorkspace returns the callback error
|
|
231
|
-
// and the `outcomes` collected above are LOST. That loss is a recorded,
|
|
232
|
-
// deliberately unassigned defect. Returning a failed outcome here would
|
|
233
|
-
// replay those outcomes instead -- observably different control flow for
|
|
234
|
-
// CLI-ENV-VALIDATOR-01, which this PR is not authorized to change.
|
|
235
|
-
throw prepareError(`cannot execute additional plugin validator: ${additionalValidator}`, ran.cause);
|
|
236
|
-
}
|
|
237
|
-
validator = { stdout: ran.stdout.text, stderr: ran.stderr.text };
|
|
238
|
-
if (ran.kind !== "exited" || ran.code !== 0) {
|
|
239
|
-
return {
|
|
240
|
-
kind: "failed",
|
|
241
|
-
outcomes,
|
|
242
|
-
validator,
|
|
243
|
-
message: "additional plugin validation failed",
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
else if (executableValidator.length > 0) {
|
|
204
|
+
if (executableValidator.length > 0) {
|
|
248
205
|
const resolution = await resolveValidator(executableValidator);
|
|
249
206
|
const ran = await runValidator([executableValidator, candidate], BOUNDED_EXECUTABLE, env, workspace);
|
|
250
207
|
// The disclosure lands BEFORE any early return, so a launch failure still
|
|
@@ -336,10 +293,7 @@ async function performPrepare(argv, ctx) {
|
|
|
336
293
|
catch (cause) {
|
|
337
294
|
// Hand-written messages, per AGENTS.md's reader-diagnostics rule.
|
|
338
295
|
// Reachable here: prepareError(), from this module's owned() wrappers,
|
|
339
|
-
// its two manifest-version checks,
|
|
340
|
-
// additional-validator branch's own throw on a legacy-validator launch
|
|
341
|
-
// failure (its cause is the shared runner's (src/validator.ts) captured
|
|
342
|
-
// spawn error, which oneLine never reads -- it takes .message only);
|
|
296
|
+
// its two manifest-version checks, and asResolutionKind;
|
|
343
297
|
// readManifest's three hookError messages
|
|
344
298
|
// (`src/harnesses/codex/hooks.ts:113-138::readManifest`), pinned by
|
|
345
299
|
// `tests/unit/harnesses/codex/hooks.test.ts:95::void test("readManifest diagnostics` as carrying no reader vocabulary or
|
|
@@ -382,7 +336,7 @@ async function performPrepare(argv, ctx) {
|
|
|
382
336
|
// only, so it bounds how much of that text lands, not what it may
|
|
383
337
|
// contain.
|
|
384
338
|
//
|
|
385
|
-
//
|
|
339
|
+
// runCodexOperation's closing `throw cause` (src/harnesses/codex/adapter.ts) does NOT arrive
|
|
386
340
|
// here -- the call site catches it and converts it to a hand-written
|
|
387
341
|
// message per AGENTS.md's reader-diagnostics rule.
|
|
388
342
|
//
|
|
@@ -418,7 +372,7 @@ async function performPrepare(argv, ctx) {
|
|
|
418
372
|
// completed before cleanup ran, so it is not being reported as unverified
|
|
419
373
|
// -- but something did still go wrong, and AGENTS.md's fail-closed rule
|
|
420
374
|
// extends to it. Mirrors
|
|
421
|
-
// `src/commands/install.ts:
|
|
375
|
+
// `src/commands/install.ts:561-570::if (cleanupWarning`.
|
|
422
376
|
ctx.stderr.write(`error: ${cleanupWarning}\n`);
|
|
423
377
|
return 1;
|
|
424
378
|
}
|
package/dist/commands/probe.js
CHANGED
|
@@ -7,7 +7,7 @@ export const PROBE_USAGE = "error: usage: superpowers-manager probe [--porcelain
|
|
|
7
7
|
// response, so adapter messages reached the operator on their declared streams
|
|
8
8
|
// in array order, and a controlled failure printed `error:` plus one `hint:`
|
|
9
9
|
// per hint. DIAG-ADAPTER-01 retains that contract
|
|
10
|
-
// (docs/
|
|
10
|
+
// (docs/adapter-result-contract.md); dropping it here would be a
|
|
11
11
|
// silent diagnostics regression, not a simplification.
|
|
12
12
|
//
|
|
13
13
|
// Interpolating error.message and each hint is the sanctioned form (AGENTS.md):
|
|
@@ -113,14 +113,14 @@ function validControl(value) {
|
|
|
113
113
|
candidate.probeEligibility.kind === "blocked" &&
|
|
114
114
|
candidate.mutationEligibility.kind === "blocked")));
|
|
115
115
|
}
|
|
116
|
-
// `
|
|
116
|
+
// `runCodexOperation` reports a CONTROLLED failure by RETURN VALUE, not by throwing
|
|
117
117
|
// (`src/adapter-result.ts:32-35::export interface AdapterResult`). The shell got
|
|
118
118
|
// fail-closed behaviour for free: spw_invoke_adapter returned 1 and
|
|
119
119
|
// scripts/probe ran under `set -eu`.
|
|
120
120
|
// Omitting the status check here would read a failed inspection as absent
|
|
121
121
|
// evidence and report it as success.
|
|
122
122
|
//
|
|
123
|
-
// It does still THROW for a non-AdapterFailure cause (
|
|
123
|
+
// It does still THROW for a non-AdapterFailure cause (runCodexOperation's closing
|
|
124
124
|
// `throw cause`, src/harnesses/codex/adapter.ts). That is caught here rather than in runProbe's
|
|
125
125
|
// outer catch, because the two need different diagnostics -- see spec §3.3a.
|
|
126
126
|
//
|
|
@@ -287,8 +287,7 @@ export async function gatherProbe(ctx) {
|
|
|
287
287
|
export async function runProbe(argv, ctx) {
|
|
288
288
|
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/probe:42::porcelain` tested only `[ "${1:-}" = "--porcelain" ]`, so a typo'd
|
|
289
289
|
// flag silently produced human output. Rejecting it is a deliberate
|
|
290
|
-
// narrowing
|
|
291
|
-
// tests/migration-inventory/probe.md.
|
|
290
|
+
// narrowing from the historical shell behavior.
|
|
292
291
|
//
|
|
293
292
|
// This guard is NOT the production path. src/cli.ts's parseArgs rejects the
|
|
294
293
|
// same inputs first, before preflight, with the usage block the CLI's other
|
|
@@ -365,10 +364,9 @@ export async function runProbe(argv, ctx) {
|
|
|
365
364
|
//
|
|
366
365
|
// fetchExactCommit is deliberately NOT in this list, unlike prepare's
|
|
367
366
|
// fetchExactCommit exception, which runPrepare's catch block documents.
|
|
368
|
-
// Its only
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
// never reaches it and
|
|
367
|
+
// Its only remaining caller is gatherPrepare's call in
|
|
368
|
+
// src/commands/prepare.ts (git grep -n fetchExactCommit -- src/ is the
|
|
369
|
+
// check that keeps "only" true), so probe never reaches it and
|
|
372
370
|
// its splice sites cannot appear on this stream. Do not add it back by
|
|
373
371
|
// symmetry with prepare.
|
|
374
372
|
//
|
|
@@ -376,7 +374,7 @@ export async function runProbe(argv, ctx) {
|
|
|
376
374
|
// all. It (src/provenance.ts) delegates to readGeneratedCommitLenient,
|
|
377
375
|
// which catches every failure and returns "".
|
|
378
376
|
//
|
|
379
|
-
// A non-AdapterFailure re-thrown by
|
|
377
|
+
// A non-AdapterFailure re-thrown by runCodexOperation's closing `throw cause`
|
|
380
378
|
// (src/harnesses/codex/adapter.ts) does NOT reach here: inspect() catches it and converts
|
|
381
379
|
// it to a hand-written message per AGENTS.md's reader-diagnostics rule -- a
|
|
382
380
|
// rethrown cause is exactly the failure src/harnesses/codex/adapter.ts declined to own, so
|
|
@@ -125,7 +125,7 @@ async function gatherUninstall(ctx) {
|
|
|
125
125
|
catch (cause) {
|
|
126
126
|
// Reachable only for mkdtemp failure, with nothing collected yet: this
|
|
127
127
|
// callback never throws -- every ctx.adapter throw is already caught
|
|
128
|
-
// inside invoke(), and presenceFlag/
|
|
128
|
+
// inside invoke(), and presenceFlag/
|
|
129
129
|
// reportLegacyState are pure (src/harnesses/codex/lifecycle.ts's header comment) -- so a
|
|
130
130
|
// post-success cleanup failure is handled by onCleanupFailure above and
|
|
131
131
|
// cannot reach here. Wrapping with `outcomes` anyway keeps the class
|
|
@@ -163,7 +163,7 @@ async function performUninstall(argv, ctx) {
|
|
|
163
163
|
// blindly.
|
|
164
164
|
//
|
|
165
165
|
// A cause outside ctx.adapter's AdapterFailure guard
|
|
166
|
-
// (`src/harnesses/codex/adapter.ts:
|
|
166
|
+
// (`src/harnesses/codex/adapter.ts:829::async function runCodexOperation<T = JsonValue>(`) does NOT
|
|
167
167
|
// reach here: invoke() catches it inside gatherUninstall and converts it
|
|
168
168
|
// to a hand-written message carried as UninstallOutcome data, exactly as
|
|
169
169
|
// src/commands/probe.ts's inspect() does for the same cause.
|
|
@@ -34,9 +34,9 @@ export function selectionConfigDir(env) {
|
|
|
34
34
|
export function selectionStatePath(env) {
|
|
35
35
|
return `${selectionConfigDir(env)}/selection.json`;
|
|
36
36
|
}
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// five
|
|
37
|
+
// Normalizes the record returned by readSelectionState directly. The shell
|
|
38
|
+
// wrote a normalized document to a mktemp file and read five fields back with
|
|
39
|
+
// five python3 invocations; in-process this is one call.
|
|
40
40
|
export async function loadSavedSelection(env) {
|
|
41
41
|
return normalizeSaved(await readSelectionState(selectionStatePath(env)));
|
|
42
42
|
}
|