@acidicsoil/portable-capabilities 0.1.9 → 0.1.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.10
4
+
5
+ ### Patch Changes
6
+
7
+ - [#75](https://github.com/AcidicSoil/portable-capabilities/pull/75) [`169d407`](https://github.com/AcidicSoil/portable-capabilities/commit/169d407d8b7e544db6f9b71f1521bac87df0d459) Thanks [@AcidicSoil](https://github.com/AcidicSoil)! - ● @acidicsoil/portable-capabilities@0.1.9
8
+
3
9
  ## 0.1.9
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -122,6 +122,7 @@ CONSUMER=/tmp/portable-capabilities-consumer
122
122
  mkdir -p "$CONSUMER"
123
123
  pnpm cli -- install \
124
124
  --runtime codex \
125
+ --runtime-version 0.147.0 \
125
126
  --capability convention-mining \
126
127
  --project "$CONSUMER" \
127
128
  --approval-policy preapproved
@@ -11733,7 +11733,7 @@ function writeEnvelope(envelope, format2) {
11733
11733
  }
11734
11734
 
11735
11735
  // packages/cli/src/release.ts
11736
- var cliVersion = true ? "0.1.9" : createRequire(import.meta.url)("../../../package.json").version;
11736
+ var cliVersion = true ? "0.1.10" : createRequire(import.meta.url)("../../../package.json").version;
11737
11737
 
11738
11738
  // packages/adapters/src/antigravity/materialize.ts
11739
11739
  import { resolve as resolve2 } from "node:path";
@@ -13073,6 +13073,7 @@ var codexRuntimeEvidence = Object.freeze([
13073
13073
  ]);
13074
13074
 
13075
13075
  // packages/adapters/src/dcode/materialize.ts
13076
+ import { readFile as readFile3 } from "node:fs/promises";
13076
13077
  import { resolve as resolve5 } from "node:path";
13077
13078
  var marker4 = "<!-- portable-capabilities:owned dcode -->";
13078
13079
  function destination4(value) {
@@ -13118,7 +13119,32 @@ function registration3(operation) {
13118
13119
  return `${JSON.stringify({ adapterId: operation.adapterId, kind: operation.kind, target: operation.target }, null, 2)}
13119
13120
  `;
13120
13121
  }
13121
- function prepareDCodeMaterialization(plan, root) {
13122
+ async function mergedInstructions(root, path9, operations, removedIds) {
13123
+ let existing;
13124
+ try {
13125
+ existing = await readFile3(resolve5(root, path9), "utf8");
13126
+ } catch (error) {
13127
+ if (error.code !== "ENOENT") throw error;
13128
+ }
13129
+ if (!existing && operations.length === 0) return void 0;
13130
+ const selectedIds = new Set(operations.map((operation) => operation.id));
13131
+ const existingText = existing ?? "";
13132
+ const firstHeading = existingText.search(/^## .+$/mu);
13133
+ const preamble = firstHeading >= 0 ? existingText.slice(0, firstHeading).trim() : existingText.trim();
13134
+ const sections = firstHeading >= 0 ? existingText.slice(firstHeading).split(/(?=^## .+$)/mu) : [];
13135
+ const retained = sections.filter((section) => {
13136
+ const id = section.match(/^## ([^\n]+)/u)?.[1] ?? "";
13137
+ return !selectedIds.has(id) && !removedIds.some((removedId) => id.startsWith(`${removedId}:`));
13138
+ }).map((section) => section.trim());
13139
+ const generated = operations.slice().sort((left, right) => left.id.localeCompare(right.id)).map((operation) => `## ${operation.id}
13140
+
13141
+ ${operation.content.trim()}`);
13142
+ const sectionsToWrite = [...retained, ...generated];
13143
+ return sectionsToWrite.length > 0 ? `${preamble || marker4}
13144
+ ${sectionsToWrite.join("\n\n")}
13145
+ ` : void 0;
13146
+ }
13147
+ async function prepareDCodeMaterialization(plan, root) {
13122
13148
  if (plan.runtimeId !== "dcode") throw new Error(`DCode adapter cannot prepare ${plan.runtimeId}`);
13123
13149
  const mutations = [];
13124
13150
  const diagnostics = [];
@@ -13220,28 +13246,39 @@ function prepareDCodeMaterialization(plan, root) {
13220
13246
  );
13221
13247
  }
13222
13248
  }
13223
- if (instructionOperations.length > 0) {
13249
+ const instructionRemovals = plan.operations.filter(
13250
+ (operation) => operation.kind === "RemoveOwnedArtifact" && /^\.deepagents\/skills\/[^/]+\/SKILL\.md$/u.test(destination4(operation.detail))
13251
+ );
13252
+ if (instructionOperations.length > 0 || instructionRemovals.length > 0) {
13224
13253
  const instructionPath = ".deepagents/AGENTS.md";
13225
13254
  confined4(root, instructionPath);
13226
- const content = instructionOperations.sort((left, right) => left.id.localeCompare(right.id)).map((operation) => `## ${operation.id}
13227
-
13228
- ${operation.content.trim()}`).join("\n\n");
13229
- const first = instructionOperations[0];
13230
- mutations.push({
13231
- path: instructionPath,
13232
- kind: "write",
13233
- content: `<!-- portable-capabilities:owned dcode -->
13234
- ${content}
13235
- `,
13236
- mode: 420,
13237
- operationId: "dcode:shared-instructions",
13238
- sourceProjectionOperationId: first.sourceProjectionOperationId,
13239
- ownershipIntent: first.ownershipIntent,
13240
- approval: first.approval,
13241
- rollback: first.rollback,
13242
- upgrade: first.upgrade,
13243
- uninstall: first.uninstall
13255
+ const first = instructionOperations[0] ?? instructionRemovals[0];
13256
+ if (!first) throw new Error("DCode shared instruction operation is missing");
13257
+ const removedIds = instructionRemovals.flatMap((operation) => {
13258
+ const match = operation.detail.match(/^\.deepagents\/skills\/([^/]+)\/SKILL\.md$/u);
13259
+ return match?.[1] ? [match[1]] : [];
13244
13260
  });
13261
+ const content = await mergedInstructions(
13262
+ root,
13263
+ instructionPath,
13264
+ instructionOperations,
13265
+ removedIds
13266
+ );
13267
+ mutations.push(
13268
+ content ? {
13269
+ path: instructionPath,
13270
+ kind: "write",
13271
+ content,
13272
+ mode: 420,
13273
+ operationId: "dcode:shared-instructions",
13274
+ sourceProjectionOperationId: first.sourceProjectionOperationId,
13275
+ ownershipIntent: first.ownershipIntent,
13276
+ approval: first.approval,
13277
+ rollback: first.rollback,
13278
+ upgrade: first.upgrade,
13279
+ uninstall: first.uninstall
13280
+ } : { path: instructionPath, kind: "remove", ...metadata3(first) }
13281
+ );
13245
13282
  }
13246
13283
  return Object.freeze({
13247
13284
  runtimeId: plan.runtimeId,
@@ -15253,19 +15290,19 @@ function normalizeSetupRequest(input) {
15253
15290
  // packages/cli/src/setup/service.ts
15254
15291
  import { createHash as createHash16 } from "node:crypto";
15255
15292
  import { existsSync as existsSync2 } from "node:fs";
15256
- import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
15293
+ import { readdir as readdir3, readFile as readFile6 } from "node:fs/promises";
15257
15294
  import { dirname as dirname4, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve11 } from "node:path";
15258
15295
  import { fileURLToPath as fileURLToPath4 } from "node:url";
15259
15296
  import { parse as parse3 } from "yaml";
15260
15297
 
15261
15298
  // packages/cli/src/installation/lifecycle.ts
15262
15299
  import { createHash as createHash15 } from "node:crypto";
15263
- import { chmod, copyFile, lstat as lstat2, mkdir as mkdir3, readFile as readFile4, rename as rename2, rm, writeFile as writeFile3 } from "node:fs/promises";
15300
+ import { chmod, copyFile, lstat as lstat2, mkdir as mkdir3, readFile as readFile5, rename as rename2, rm, writeFile as writeFile3 } from "node:fs/promises";
15264
15301
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join3, relative as relative2, resolve as resolve10, sep } from "node:path";
15265
15302
 
15266
15303
  // packages/cli/src/installation/manifest.ts
15267
15304
  import { createHash as createHash14 } from "node:crypto";
15268
- import { mkdir as mkdir2, readFile as readFile3, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
15305
+ import { mkdir as mkdir2, readFile as readFile4, rename, stat, writeFile as writeFile2 } from "node:fs/promises";
15269
15306
  import { dirname as dirname2, join as join2 } from "node:path";
15270
15307
  var installationManifestName = ".portable-capabilities-manifest.json";
15271
15308
  var installationJournalName = ".portable-capabilities-journal.json";
@@ -15319,7 +15356,7 @@ function verifyInstallationManifestDigest(manifest) {
15319
15356
  }
15320
15357
  }
15321
15358
  async function digestFile(path9) {
15322
- return digestBytes(await readFile3(path9));
15359
+ return digestBytes(await readFile4(path9));
15323
15360
  }
15324
15361
  function manifestPath(targetRoot) {
15325
15362
  return join2(targetRoot, installationManifestName);
@@ -15330,7 +15367,7 @@ function journalPath(targetRoot) {
15330
15367
  async function readInstallationManifest(targetRoot) {
15331
15368
  try {
15332
15369
  const parsed = JSON.parse(
15333
- await readFile3(manifestPath(targetRoot), "utf8")
15370
+ await readFile4(manifestPath(targetRoot), "utf8")
15334
15371
  );
15335
15372
  if (parsed.schemaVersion !== "1.1.0" || !Array.isArray(parsed.entries)) {
15336
15373
  throw new Error("Invalid installation manifest shape");
@@ -15607,6 +15644,19 @@ async function assertPlanPathsSafe(targetRoot, plan) {
15607
15644
  }
15608
15645
  async function executePreparedInstallation(options) {
15609
15646
  const manifest = await readInstallationManifest(options.targetRoot);
15647
+ const operation = options.preparedOperation ?? "install";
15648
+ if (options.preparedDiagnostics?.length) {
15649
+ return result(operation, "degraded", {
15650
+ ok: false,
15651
+ diagnostics: options.preparedDiagnostics
15652
+ });
15653
+ }
15654
+ if (options.preparedMutations.length === 0) {
15655
+ return result(operation, "degraded", {
15656
+ ok: false,
15657
+ diagnostics: ["No prepared mutations can be installed"]
15658
+ });
15659
+ }
15610
15660
  const duplicatePaths = /* @__PURE__ */ new Set();
15611
15661
  for (const mutation of options.preparedMutations) {
15612
15662
  if (duplicatePaths.has(mutation.path)) {
@@ -15635,7 +15685,7 @@ async function executePreparedInstallation(options) {
15635
15685
  const exists = await pathExists(target);
15636
15686
  if (mutation.ownershipIntent === "preserve-user" && exists) continue;
15637
15687
  if (exists && mutation.kind === "write" && mutation.ownershipIntent === "own") {
15638
- const current = await readFile4(target, "utf8");
15688
+ const current = await readFile5(target, "utf8");
15639
15689
  const ownedEntry = manifest?.entries.find((entry) => entry.path === mutation.path);
15640
15690
  const isManifestOwned = ownedEntry !== void 0 && await digestFile(target) === ownedEntry.digest;
15641
15691
  if (!current.includes("portable-capabilities:owned") && !isManifestOwned)
@@ -15681,7 +15731,6 @@ async function executePreparedInstallation(options) {
15681
15731
  packageIdentities,
15682
15732
  entries: nextEntries
15683
15733
  };
15684
- const operation = options.preparedOperation ?? "install";
15685
15734
  if (options.dryRun)
15686
15735
  return result(operation, manifest ? "healthy" : "missing", {
15687
15736
  dryRun: true,
@@ -15716,63 +15765,6 @@ async function statusInstallation(targetRoot) {
15716
15765
  manifest
15717
15766
  });
15718
15767
  }
15719
- async function removeInstallation(targetRoot, faultInjector, capabilityIds) {
15720
- const manifest = await readInstallationManifest(targetRoot);
15721
- if (!manifest) return result("remove", "missing", { ok: false });
15722
- const selected = new Set(capabilityIds ?? []);
15723
- const scoped = (path9) => {
15724
- if (selected.size === 0) return true;
15725
- const normalized = path9.replaceAll("\\", "/");
15726
- return [...selected].some(
15727
- (id) => normalized.includes(`/skills/${id}/`) || normalized.endsWith(`/skills/${id}`)
15728
- );
15729
- };
15730
- const classifications = await classifyManifestEntries(manifest);
15731
- const unchanged = new Set(
15732
- classifications.unchanged.filter((entry) => scoped(entry.path)).map((entry) => entry.path)
15733
- );
15734
- const removes = manifest.entries.filter((entry) => unchanged.has(entry.path)).map((entry) => entry.path);
15735
- const preserved = manifest.entries.filter((entry) => !unchanged.has(entry.path));
15736
- const nextManifest = preserved.length ? (() => {
15737
- const entries = Object.freeze(preserved);
15738
- const preservedPaths = new Set(entries.map((entry) => entry.path));
15739
- const packageIdentities = Object.freeze(
15740
- manifest.packageIdentities.filter((identity3) => preservedPaths.has(identity3.lockPath))
15741
- );
15742
- const packageDigest = packageEntriesDigest(entries, packageIdentities);
15743
- const base = {
15744
- runtimeId: manifest.runtimeId,
15745
- packageVersion: manifest.packageVersion,
15746
- targetRoot: manifest.targetRoot,
15747
- journalVersion: manifest.journalVersion,
15748
- lifecycleState: "modified",
15749
- packageIdentities,
15750
- entries
15751
- };
15752
- return {
15753
- ...manifest,
15754
- ...base,
15755
- packageDigest,
15756
- installationDigest: installationOwnershipDigest(packageDigest, base)
15757
- };
15758
- })() : void 0;
15759
- return commitPlan(
15760
- "remove",
15761
- {
15762
- sourceRoot: targetRoot,
15763
- targetRoot,
15764
- runtimeId: manifest.runtimeId,
15765
- packageVersion: manifest.packageVersion,
15766
- ...faultInjector ? { faultInjector } : {}
15767
- },
15768
- {
15769
- writes: [],
15770
- removes,
15771
- preserved: preserved.map((entry) => entry.path),
15772
- ...nextManifest ? { nextManifest } : {}
15773
- }
15774
- );
15775
- }
15776
15768
 
15777
15769
  // packages/cli/src/setup/service.ts
15778
15770
  function roleFacet(role, name) {
@@ -15833,7 +15825,7 @@ async function loadCanonicalArtifact(catalogRoot, capabilityId) {
15833
15825
  if (!capabilityId || /[\\/]/u.test(capabilityId))
15834
15826
  throw new Error("capability IDs must be single path segments");
15835
15827
  const file = resolve11(catalogRoot, "roles", `${capabilityId}.yaml`);
15836
- const role = parse3(await readFile5(file, "utf8"));
15828
+ const role = parse3(await readFile6(file, "utf8"));
15837
15829
  if (role.schemaVersion !== "1.0" || role.id !== capabilityId || !role.purpose || !Array.isArray(role.inputs) || !role.output || !role.family || !role.taxonomy || !Array.isArray(role.precedence) || !Array.isArray(role.requiredBehavior) || !Array.isArray(role.forbiddenBehavior) || !Array.isArray(role.evaluation?.criteria)) {
15838
15830
  throw new Error(`Invalid canonical capability: ${file}`);
15839
15831
  }
@@ -15842,7 +15834,7 @@ async function loadCanonicalArtifact(catalogRoot, capabilityId) {
15842
15834
  const schemaRelativePath = relative3(resolve11(catalogRoot), schemaFile);
15843
15835
  if (isAbsolute3(schemaRelativePath) || schemaRelativePath.startsWith(".."))
15844
15836
  throw new Error(`Canonical output schema escapes catalog: ${role.output.schema}`);
15845
- const schemaContent = await readFile5(schemaFile, "utf8");
15837
+ const schemaContent = await readFile6(schemaFile, "utf8");
15846
15838
  const contract = operationalContract(role);
15847
15839
  const contractContent = `${JSON.stringify(
15848
15840
  { ...role, output: { ...role.output, schema: "resources/output.schema.json" } },
@@ -15915,6 +15907,27 @@ function previewSetup(request, artifact, descriptor, claims, ownershipManifest)
15915
15907
  const body = { ...plan, selectedSurfaces, conversions, conflicts };
15916
15908
  return { ...body, digest: stableDigest(body) };
15917
15909
  }
15910
+ function composeInstallationPlan(previews, artifacts, runtimeId) {
15911
+ const first = previews[0]?.installationPlan;
15912
+ if (!first) throw new Error("at least one installation plan is required");
15913
+ const operations = previews.flatMap((preview, index) => {
15914
+ const capabilityId = artifacts[index]?.id;
15915
+ if (!capabilityId) throw new Error("installation plan is missing its capability");
15916
+ return preview.installationPlan.operations.map((operation) => ({
15917
+ ...operation,
15918
+ id: `${capabilityId}:${operation.id}`,
15919
+ sourceProjectionOperationId: `${capabilityId}:${operation.sourceProjectionOperationId}`
15920
+ }));
15921
+ });
15922
+ const base = {
15923
+ schemaVersion: first.schemaVersion,
15924
+ id: `installation:${runtimeId}:${artifacts.map((artifact) => artifact.id).join(",")}`,
15925
+ runtimeId,
15926
+ scope: first.scope,
15927
+ operations
15928
+ };
15929
+ return { ...base, digest: stableDigest(base) };
15930
+ }
15918
15931
  function defaultCatalogRoot() {
15919
15932
  const moduleDir = dirname4(fileURLToPath4(import.meta.url));
15920
15933
  const packagedCatalog = [resolve11(moduleDir, "catalog"), resolve11(moduleDir, "../catalog")].find(
@@ -15973,23 +15986,33 @@ async function executeSetupSelection(request, artifacts, descriptor, claims, con
15973
15986
  ...context.faultInjector ? { faultInjector: context.faultInjector } : {}
15974
15987
  };
15975
15988
  const runtime = resolveProductionRuntime(executionRequest.runtimeId);
15976
- const lifecycle = executionRequest.intent === "setup" || executionRequest.intent === "update" ? await executePreparedInstallation({
15977
- ...lifecycleOptions,
15978
- preparedOperation: executionRequest.intent === "update" ? "upgrade" : "install",
15979
- preparedMutations: (await Promise.all(
15980
- previews.map((preview) => runtime.prepare(preview.installationPlan, targetRoot))
15981
- )).flatMap((prepared) => prepared.mutations)
15982
- }) : executionRequest.intent === "remove" ? await removeInstallation(
15983
- targetRoot,
15984
- context.faultInjector,
15985
- executionRequest.capabilityIds
15986
- ) : await statusInstallation(targetRoot);
15989
+ const lifecycle = executionRequest.intent === "setup" || executionRequest.intent === "update" || executionRequest.intent === "remove" ? await (async () => {
15990
+ const installationPlan = composeInstallationPlan(
15991
+ previews,
15992
+ planningArtifacts,
15993
+ executionRequest.runtimeId
15994
+ );
15995
+ const prepared = await runtime.prepare(installationPlan, targetRoot);
15996
+ if (prepared.runtimeId !== installationPlan.runtimeId || prepared.planId !== installationPlan.id) {
15997
+ throw new Error(`Materializer returned an invalid ${executionRequest.runtimeId} plan`);
15998
+ }
15999
+ const duplicatePath = prepared.mutations.map((mutation) => mutation.path).find((path9, index, paths) => paths.indexOf(path9) !== index);
16000
+ if (duplicatePath)
16001
+ throw new Error(`Selection produced duplicate prepared path: ${duplicatePath}`);
16002
+ return executePreparedInstallation({
16003
+ ...lifecycleOptions,
16004
+ preparedOperation: executionRequest.intent === "update" ? "upgrade" : executionRequest.intent === "remove" ? "remove" : "install",
16005
+ preparedMutations: prepared.mutations,
16006
+ ...prepared.diagnostics ? {
16007
+ preparedDiagnostics: prepared.diagnostics.map(
16008
+ (diagnostic6) => `${diagnostic6.kind}: ${diagnostic6.message}`
16009
+ )
16010
+ } : {}
16011
+ });
16012
+ })() : await statusInstallation(targetRoot);
15987
16013
  return { request, previews, lifecycle };
15988
16014
  }
15989
16015
 
15990
- // packages/cli/src/wizard.ts
15991
- import { execFileSync } from "node:child_process";
15992
-
15993
16016
  // node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.mjs
15994
16017
  import { styleText } from "node:util";
15995
16018
  import { stdout, stdin } from "node:process";
@@ -17197,6 +17220,73 @@ ${r2}
17197
17220
  }
17198
17221
  }).prompt();
17199
17222
 
17223
+ // packages/cli/src/setup/probe.ts
17224
+ import { execFileSync } from "node:child_process";
17225
+ var executables = Object.freeze({
17226
+ antigravity: "agy",
17227
+ "claude-code": "claude",
17228
+ codex: "codex",
17229
+ dcode: "dcode",
17230
+ "oh-my-pi": "omp",
17231
+ opencode: "opencode",
17232
+ pi: "pi"
17233
+ });
17234
+ function resolveRuntimeProbe(runtimeId) {
17235
+ const runtime = resolveProductionRuntime(runtimeId);
17236
+ const canonicalId = runtime.descriptor.id;
17237
+ const executable = executables[canonicalId];
17238
+ if (!executable) throw new Error(`No runtime probe configured for ${canonicalId}`);
17239
+ return {
17240
+ runtimeId: canonicalId,
17241
+ executable,
17242
+ args: ["--version"],
17243
+ supportedVersions: runtime.descriptor.supportedVersions
17244
+ };
17245
+ }
17246
+ function parseVersion2(output) {
17247
+ return output.match(/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/u)?.[1];
17248
+ }
17249
+ function defaultRun(probe) {
17250
+ return execFileSync(probe.executable, probe.args, {
17251
+ encoding: "utf8",
17252
+ stdio: ["ignore", "pipe", "pipe"],
17253
+ timeout: 5e3
17254
+ });
17255
+ }
17256
+ function probeRuntimeVersion(runtimeId, options = {}) {
17257
+ const probe = resolveRuntimeProbe(runtimeId);
17258
+ let output;
17259
+ try {
17260
+ output = (options.run ?? defaultRun)(probe);
17261
+ } catch (error) {
17262
+ const code = error.code;
17263
+ const unavailable = code === "ENOENT" || code === "EACCES";
17264
+ return {
17265
+ status: unavailable ? "unavailable" : "failed",
17266
+ runtimeId: probe.runtimeId,
17267
+ executable: probe.executable,
17268
+ reason: unavailable ? `${probe.executable} is not installed or is not executable` : error instanceof Error ? error.message : String(error)
17269
+ };
17270
+ }
17271
+ const version = parseVersion2(output);
17272
+ if (!version)
17273
+ return {
17274
+ status: "unknown-version",
17275
+ runtimeId: probe.runtimeId,
17276
+ executable: probe.executable,
17277
+ reason: "version probe returned no parseable semantic version"
17278
+ };
17279
+ if (!probe.supportedVersions.some((range) => isRuntimeVersionInRange(range, version)))
17280
+ return {
17281
+ status: "unsupported-version",
17282
+ runtimeId: probe.runtimeId,
17283
+ executable: probe.executable,
17284
+ version,
17285
+ reason: `version ${version} is outside the supported runtime ranges`
17286
+ };
17287
+ return { status: "detected", runtimeId: probe.runtimeId, executable: probe.executable, version };
17288
+ }
17289
+
17200
17290
  // packages/cli/src/wizard.ts
17201
17291
  var supportedRuntimes = [...productionRuntimeRegistry.keys()];
17202
17292
  function unwrap(value) {
@@ -17207,17 +17297,6 @@ function unwrap(value) {
17207
17297
  }
17208
17298
  return value;
17209
17299
  }
17210
- function detectRuntimeVersion(runtime) {
17211
- try {
17212
- const output = execFileSync(runtime, ["--version"], {
17213
- encoding: "utf8",
17214
- stdio: ["ignore", "pipe", "pipe"]
17215
- });
17216
- return output.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0] ?? "unknown";
17217
- } catch {
17218
- return "unknown";
17219
- }
17220
- }
17221
17300
  async function runInstallWizard() {
17222
17301
  intro("Portable Capabilities setup");
17223
17302
  try {
@@ -17237,6 +17316,12 @@ async function runInstallWizard() {
17237
17316
  options: supportedRuntimes.map((value) => ({ value, label: value }))
17238
17317
  })
17239
17318
  );
17319
+ const probe = probeRuntimeVersion(runtime);
17320
+ if (probe.status !== "detected" || !probe.version) {
17321
+ log.error(`${runtime}: ${probe.reason ?? probe.status}`);
17322
+ process.exitCode = 4;
17323
+ return;
17324
+ }
17240
17325
  const target = String(
17241
17326
  unwrap(
17242
17327
  await text({
@@ -17254,7 +17339,7 @@ async function runInstallWizard() {
17254
17339
  );
17255
17340
  const request = normalizeSetupRequest({
17256
17341
  runtimeId: runtime,
17257
- runtimeVersion: detectRuntimeVersion(runtime),
17342
+ runtimeVersion: probe.version,
17258
17343
  scope: "project",
17259
17344
  projectDirectory: target,
17260
17345
  capabilityIds,
@@ -17276,7 +17361,7 @@ async function runInstallWizard() {
17276
17361
  runtimeEntry.claims,
17277
17362
  { sourceRoot: process.cwd(), packageVersion: cliVersion }
17278
17363
  );
17279
- if (!result2.lifecycle || !result2.lifecycle.ok) {
17364
+ if (!result2.lifecycle?.ok) {
17280
17365
  for (const diagnostic6 of result2.lifecycle?.diagnostics ?? ["Approval required."])
17281
17366
  log.error(diagnostic6);
17282
17367
  process.exitCode = 4;