@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.
@@ -4155,6 +4155,19 @@ async function assertPlanPathsSafe(targetRoot, plan) {
4155
4155
  }
4156
4156
  async function executePreparedInstallation(options) {
4157
4157
  const manifest = await readInstallationManifest(options.targetRoot);
4158
+ const operation = options.preparedOperation ?? "install";
4159
+ if (options.preparedDiagnostics?.length) {
4160
+ return result(operation, "degraded", {
4161
+ ok: false,
4162
+ diagnostics: options.preparedDiagnostics
4163
+ });
4164
+ }
4165
+ if (options.preparedMutations.length === 0) {
4166
+ return result(operation, "degraded", {
4167
+ ok: false,
4168
+ diagnostics: ["No prepared mutations can be installed"]
4169
+ });
4170
+ }
4158
4171
  const duplicatePaths = /* @__PURE__ */ new Set();
4159
4172
  for (const mutation of options.preparedMutations) {
4160
4173
  if (duplicatePaths.has(mutation.path)) {
@@ -4229,7 +4242,6 @@ async function executePreparedInstallation(options) {
4229
4242
  packageIdentities,
4230
4243
  entries: nextEntries
4231
4244
  };
4232
- const operation = options.preparedOperation ?? "install";
4233
4245
  if (options.dryRun)
4234
4246
  return result(operation, manifest ? "healthy" : "missing", {
4235
4247
  dryRun: true,
@@ -12628,7 +12640,7 @@ async function resolveVerifiedCapabilityEntrypoint(options) {
12628
12640
  }
12629
12641
 
12630
12642
  // packages/cli/src/release.ts
12631
- var cliVersion = true ? "0.1.9" : createRequire(import.meta.url)("../../../package.json").version;
12643
+ var cliVersion = true ? "0.1.10" : createRequire(import.meta.url)("../../../package.json").version;
12632
12644
 
12633
12645
  // packages/adapters/src/antigravity/materialize.ts
12634
12646
  import { resolve as resolve4 } from "node:path";
@@ -13968,6 +13980,7 @@ var codexRuntimeEvidence = Object.freeze([
13968
13980
  ]);
13969
13981
 
13970
13982
  // packages/adapters/src/dcode/materialize.ts
13983
+ import { readFile as readFile5 } from "node:fs/promises";
13971
13984
  import { resolve as resolve7 } from "node:path";
13972
13985
  var marker4 = "<!-- portable-capabilities:owned dcode -->";
13973
13986
  function destination4(value) {
@@ -14013,7 +14026,32 @@ function registration3(operation) {
14013
14026
  return `${JSON.stringify({ adapterId: operation.adapterId, kind: operation.kind, target: operation.target }, null, 2)}
14014
14027
  `;
14015
14028
  }
14016
- function prepareDCodeMaterialization(plan, root) {
14029
+ async function mergedInstructions(root, path9, operations, removedIds) {
14030
+ let existing;
14031
+ try {
14032
+ existing = await readFile5(resolve7(root, path9), "utf8");
14033
+ } catch (error) {
14034
+ if (error.code !== "ENOENT") throw error;
14035
+ }
14036
+ if (!existing && operations.length === 0) return void 0;
14037
+ const selectedIds = new Set(operations.map((operation) => operation.id));
14038
+ const existingText = existing ?? "";
14039
+ const firstHeading = existingText.search(/^## .+$/mu);
14040
+ const preamble = firstHeading >= 0 ? existingText.slice(0, firstHeading).trim() : existingText.trim();
14041
+ const sections = firstHeading >= 0 ? existingText.slice(firstHeading).split(/(?=^## .+$)/mu) : [];
14042
+ const retained = sections.filter((section) => {
14043
+ const id = section.match(/^## ([^\n]+)/u)?.[1] ?? "";
14044
+ return !selectedIds.has(id) && !removedIds.some((removedId) => id.startsWith(`${removedId}:`));
14045
+ }).map((section) => section.trim());
14046
+ const generated = operations.slice().sort((left, right) => left.id.localeCompare(right.id)).map((operation) => `## ${operation.id}
14047
+
14048
+ ${operation.content.trim()}`);
14049
+ const sectionsToWrite = [...retained, ...generated];
14050
+ return sectionsToWrite.length > 0 ? `${preamble || marker4}
14051
+ ${sectionsToWrite.join("\n\n")}
14052
+ ` : void 0;
14053
+ }
14054
+ async function prepareDCodeMaterialization(plan, root) {
14017
14055
  if (plan.runtimeId !== "dcode") throw new Error(`DCode adapter cannot prepare ${plan.runtimeId}`);
14018
14056
  const mutations = [];
14019
14057
  const diagnostics = [];
@@ -14115,28 +14153,39 @@ function prepareDCodeMaterialization(plan, root) {
14115
14153
  );
14116
14154
  }
14117
14155
  }
14118
- if (instructionOperations.length > 0) {
14156
+ const instructionRemovals = plan.operations.filter(
14157
+ (operation) => operation.kind === "RemoveOwnedArtifact" && /^\.deepagents\/skills\/[^/]+\/SKILL\.md$/u.test(destination4(operation.detail))
14158
+ );
14159
+ if (instructionOperations.length > 0 || instructionRemovals.length > 0) {
14119
14160
  const instructionPath = ".deepagents/AGENTS.md";
14120
14161
  confined4(root, instructionPath);
14121
- const content = instructionOperations.sort((left, right) => left.id.localeCompare(right.id)).map((operation) => `## ${operation.id}
14122
-
14123
- ${operation.content.trim()}`).join("\n\n");
14124
- const first = instructionOperations[0];
14125
- mutations.push({
14126
- path: instructionPath,
14127
- kind: "write",
14128
- content: `<!-- portable-capabilities:owned dcode -->
14129
- ${content}
14130
- `,
14131
- mode: 420,
14132
- operationId: "dcode:shared-instructions",
14133
- sourceProjectionOperationId: first.sourceProjectionOperationId,
14134
- ownershipIntent: first.ownershipIntent,
14135
- approval: first.approval,
14136
- rollback: first.rollback,
14137
- upgrade: first.upgrade,
14138
- uninstall: first.uninstall
14162
+ const first = instructionOperations[0] ?? instructionRemovals[0];
14163
+ if (!first) throw new Error("DCode shared instruction operation is missing");
14164
+ const removedIds = instructionRemovals.flatMap((operation) => {
14165
+ const match = operation.detail.match(/^\.deepagents\/skills\/([^/]+)\/SKILL\.md$/u);
14166
+ return match?.[1] ? [match[1]] : [];
14139
14167
  });
14168
+ const content = await mergedInstructions(
14169
+ root,
14170
+ instructionPath,
14171
+ instructionOperations,
14172
+ removedIds
14173
+ );
14174
+ mutations.push(
14175
+ content ? {
14176
+ path: instructionPath,
14177
+ kind: "write",
14178
+ content,
14179
+ mode: 420,
14180
+ operationId: "dcode:shared-instructions",
14181
+ sourceProjectionOperationId: first.sourceProjectionOperationId,
14182
+ ownershipIntent: first.ownershipIntent,
14183
+ approval: first.approval,
14184
+ rollback: first.rollback,
14185
+ upgrade: first.upgrade,
14186
+ uninstall: first.uninstall
14187
+ } : { path: instructionPath, kind: "remove", ...metadata3(first) }
14188
+ );
14140
14189
  }
14141
14190
  return Object.freeze({
14142
14191
  runtimeId: plan.runtimeId,
@@ -16148,7 +16197,7 @@ function normalizeSetupRequest(input) {
16148
16197
  // packages/cli/src/setup/service.ts
16149
16198
  import { createHash as createHash16 } from "node:crypto";
16150
16199
  import { existsSync as existsSync2 } from "node:fs";
16151
- import { readdir as readdir4, readFile as readFile5 } from "node:fs/promises";
16200
+ import { readdir as readdir4, readFile as readFile6 } from "node:fs/promises";
16152
16201
  import { dirname as dirname5, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve12 } from "node:path";
16153
16202
  import { fileURLToPath as fileURLToPath4 } from "node:url";
16154
16203
  import { parse as parse3 } from "yaml";
@@ -16210,7 +16259,7 @@ async function loadCanonicalArtifact(catalogRoot, capabilityId) {
16210
16259
  if (!capabilityId || /[\\/]/u.test(capabilityId))
16211
16260
  throw new Error("capability IDs must be single path segments");
16212
16261
  const file = resolve12(catalogRoot, "roles", `${capabilityId}.yaml`);
16213
- const role = parse3(await readFile5(file, "utf8"));
16262
+ const role = parse3(await readFile6(file, "utf8"));
16214
16263
  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)) {
16215
16264
  throw new Error(`Invalid canonical capability: ${file}`);
16216
16265
  }
@@ -16219,7 +16268,7 @@ async function loadCanonicalArtifact(catalogRoot, capabilityId) {
16219
16268
  const schemaRelativePath = relative4(resolve12(catalogRoot), schemaFile);
16220
16269
  if (isAbsolute3(schemaRelativePath) || schemaRelativePath.startsWith(".."))
16221
16270
  throw new Error(`Canonical output schema escapes catalog: ${role.output.schema}`);
16222
- const schemaContent = await readFile5(schemaFile, "utf8");
16271
+ const schemaContent = await readFile6(schemaFile, "utf8");
16223
16272
  const contract = operationalContract(role);
16224
16273
  const contractContent = `${JSON.stringify(
16225
16274
  { ...role, output: { ...role.output, schema: "resources/output.schema.json" } },
@@ -16303,6 +16352,27 @@ function renderVerifiedUsage(preview, descriptor, claims, runtimeVersion) {
16303
16352
  return verified ? `${descriptor.id}: ${surface.invocation}` : `${descriptor.id}: native usage unavailable; use manual guidance`;
16304
16353
  });
16305
16354
  }
16355
+ function composeInstallationPlan(previews, artifacts, runtimeId) {
16356
+ const first = previews[0]?.installationPlan;
16357
+ if (!first) throw new Error("at least one installation plan is required");
16358
+ const operations = previews.flatMap((preview, index) => {
16359
+ const capabilityId = artifacts[index]?.id;
16360
+ if (!capabilityId) throw new Error("installation plan is missing its capability");
16361
+ return preview.installationPlan.operations.map((operation) => ({
16362
+ ...operation,
16363
+ id: `${capabilityId}:${operation.id}`,
16364
+ sourceProjectionOperationId: `${capabilityId}:${operation.sourceProjectionOperationId}`
16365
+ }));
16366
+ });
16367
+ const base = {
16368
+ schemaVersion: first.schemaVersion,
16369
+ id: `installation:${runtimeId}:${artifacts.map((artifact) => artifact.id).join(",")}`,
16370
+ runtimeId,
16371
+ scope: first.scope,
16372
+ operations
16373
+ };
16374
+ return { ...base, digest: stableDigest(base) };
16375
+ }
16306
16376
  function defaultCatalogRoot() {
16307
16377
  const moduleDir = dirname5(fileURLToPath4(import.meta.url));
16308
16378
  const packagedCatalog = [resolve12(moduleDir, "catalog"), resolve12(moduleDir, "../catalog")].find(
@@ -16361,30 +16431,41 @@ async function executeSetupSelection(request, artifacts, descriptor, claims, con
16361
16431
  ...context.faultInjector ? { faultInjector: context.faultInjector } : {}
16362
16432
  };
16363
16433
  const runtime = resolveProductionRuntime(executionRequest.runtimeId);
16364
- const lifecycle = executionRequest.intent === "setup" || executionRequest.intent === "update" ? await executePreparedInstallation({
16365
- ...lifecycleOptions,
16366
- preparedOperation: executionRequest.intent === "update" ? "upgrade" : "install",
16367
- preparedMutations: (await Promise.all(
16368
- previews.map((preview) => runtime.prepare(preview.installationPlan, targetRoot))
16369
- )).flatMap((prepared) => prepared.mutations)
16370
- }) : executionRequest.intent === "remove" ? await removeInstallation(
16371
- targetRoot,
16372
- context.faultInjector,
16373
- executionRequest.capabilityIds
16374
- ) : await statusInstallation(targetRoot);
16434
+ const lifecycle = executionRequest.intent === "setup" || executionRequest.intent === "update" || executionRequest.intent === "remove" ? await (async () => {
16435
+ const installationPlan = composeInstallationPlan(
16436
+ previews,
16437
+ planningArtifacts,
16438
+ executionRequest.runtimeId
16439
+ );
16440
+ const prepared = await runtime.prepare(installationPlan, targetRoot);
16441
+ if (prepared.runtimeId !== installationPlan.runtimeId || prepared.planId !== installationPlan.id) {
16442
+ throw new Error(`Materializer returned an invalid ${executionRequest.runtimeId} plan`);
16443
+ }
16444
+ const duplicatePath = prepared.mutations.map((mutation) => mutation.path).find((path9, index, paths) => paths.indexOf(path9) !== index);
16445
+ if (duplicatePath)
16446
+ throw new Error(`Selection produced duplicate prepared path: ${duplicatePath}`);
16447
+ return executePreparedInstallation({
16448
+ ...lifecycleOptions,
16449
+ preparedOperation: executionRequest.intent === "update" ? "upgrade" : executionRequest.intent === "remove" ? "remove" : "install",
16450
+ preparedMutations: prepared.mutations,
16451
+ ...prepared.diagnostics ? {
16452
+ preparedDiagnostics: prepared.diagnostics.map(
16453
+ (diagnostic6) => `${diagnostic6.kind}: ${diagnostic6.message}`
16454
+ )
16455
+ } : {}
16456
+ });
16457
+ })() : await statusInstallation(targetRoot);
16375
16458
  return { request, previews, lifecycle };
16376
16459
  }
16377
16460
  async function executeSetupLifecycle(request, artifact, descriptor, claims, context) {
16378
16461
  const result2 = await executeSetupSelection(request, [artifact], descriptor, claims, context);
16379
16462
  const preview = result2.previews[0];
16463
+ if (!preview) throw new Error("at least one capability is required");
16380
16464
  if (!result2.lifecycle)
16381
16465
  throw new Error("approval required; rerun with approvalPolicy=preapproved");
16382
16466
  return { request, preview, lifecycle: result2.lifecycle };
16383
16467
  }
16384
16468
 
16385
- // packages/cli/src/wizard.ts
16386
- import { execFileSync } from "node:child_process";
16387
-
16388
16469
  // node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.mjs
16389
16470
  import { styleText } from "node:util";
16390
16471
  import { stdout, stdin } from "node:process";
@@ -17592,6 +17673,73 @@ ${r2}
17592
17673
  }
17593
17674
  }).prompt();
17594
17675
 
17676
+ // packages/cli/src/setup/probe.ts
17677
+ import { execFileSync } from "node:child_process";
17678
+ var executables = Object.freeze({
17679
+ antigravity: "agy",
17680
+ "claude-code": "claude",
17681
+ codex: "codex",
17682
+ dcode: "dcode",
17683
+ "oh-my-pi": "omp",
17684
+ opencode: "opencode",
17685
+ pi: "pi"
17686
+ });
17687
+ function resolveRuntimeProbe(runtimeId) {
17688
+ const runtime = resolveProductionRuntime(runtimeId);
17689
+ const canonicalId = runtime.descriptor.id;
17690
+ const executable = executables[canonicalId];
17691
+ if (!executable) throw new Error(`No runtime probe configured for ${canonicalId}`);
17692
+ return {
17693
+ runtimeId: canonicalId,
17694
+ executable,
17695
+ args: ["--version"],
17696
+ supportedVersions: runtime.descriptor.supportedVersions
17697
+ };
17698
+ }
17699
+ function parseVersion2(output) {
17700
+ return output.match(/\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\b/u)?.[1];
17701
+ }
17702
+ function defaultRun(probe) {
17703
+ return execFileSync(probe.executable, probe.args, {
17704
+ encoding: "utf8",
17705
+ stdio: ["ignore", "pipe", "pipe"],
17706
+ timeout: 5e3
17707
+ });
17708
+ }
17709
+ function probeRuntimeVersion(runtimeId, options = {}) {
17710
+ const probe = resolveRuntimeProbe(runtimeId);
17711
+ let output;
17712
+ try {
17713
+ output = (options.run ?? defaultRun)(probe);
17714
+ } catch (error) {
17715
+ const code = error.code;
17716
+ const unavailable = code === "ENOENT" || code === "EACCES";
17717
+ return {
17718
+ status: unavailable ? "unavailable" : "failed",
17719
+ runtimeId: probe.runtimeId,
17720
+ executable: probe.executable,
17721
+ reason: unavailable ? `${probe.executable} is not installed or is not executable` : error instanceof Error ? error.message : String(error)
17722
+ };
17723
+ }
17724
+ const version = parseVersion2(output);
17725
+ if (!version)
17726
+ return {
17727
+ status: "unknown-version",
17728
+ runtimeId: probe.runtimeId,
17729
+ executable: probe.executable,
17730
+ reason: "version probe returned no parseable semantic version"
17731
+ };
17732
+ if (!probe.supportedVersions.some((range) => isRuntimeVersionInRange(range, version)))
17733
+ return {
17734
+ status: "unsupported-version",
17735
+ runtimeId: probe.runtimeId,
17736
+ executable: probe.executable,
17737
+ version,
17738
+ reason: `version ${version} is outside the supported runtime ranges`
17739
+ };
17740
+ return { status: "detected", runtimeId: probe.runtimeId, executable: probe.executable, version };
17741
+ }
17742
+
17595
17743
  // packages/cli/src/wizard.ts
17596
17744
  var supportedRuntimes = [...productionRuntimeRegistry.keys()];
17597
17745
  function unwrap(value) {
@@ -17602,17 +17750,6 @@ function unwrap(value) {
17602
17750
  }
17603
17751
  return value;
17604
17752
  }
17605
- function detectRuntimeVersion(runtime) {
17606
- try {
17607
- const output = execFileSync(runtime, ["--version"], {
17608
- encoding: "utf8",
17609
- stdio: ["ignore", "pipe", "pipe"]
17610
- });
17611
- return output.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0] ?? "unknown";
17612
- } catch {
17613
- return "unknown";
17614
- }
17615
- }
17616
17753
  async function runInstallWizard() {
17617
17754
  intro("Portable Capabilities setup");
17618
17755
  try {
@@ -17632,6 +17769,12 @@ async function runInstallWizard() {
17632
17769
  options: supportedRuntimes.map((value) => ({ value, label: value }))
17633
17770
  })
17634
17771
  );
17772
+ const probe = probeRuntimeVersion(runtime);
17773
+ if (probe.status !== "detected" || !probe.version) {
17774
+ log.error(`${runtime}: ${probe.reason ?? probe.status}`);
17775
+ process.exitCode = 4;
17776
+ return;
17777
+ }
17635
17778
  const target = String(
17636
17779
  unwrap(
17637
17780
  await text({
@@ -17649,7 +17792,7 @@ async function runInstallWizard() {
17649
17792
  );
17650
17793
  const request = normalizeSetupRequest({
17651
17794
  runtimeId: runtime,
17652
- runtimeVersion: detectRuntimeVersion(runtime),
17795
+ runtimeVersion: probe.version,
17653
17796
  scope: "project",
17654
17797
  projectDirectory: target,
17655
17798
  capabilityIds,
@@ -17671,7 +17814,7 @@ async function runInstallWizard() {
17671
17814
  runtimeEntry.claims,
17672
17815
  { sourceRoot: process.cwd(), packageVersion: cliVersion }
17673
17816
  );
17674
- if (!result2.lifecycle || !result2.lifecycle.ok) {
17817
+ if (!result2.lifecycle?.ok) {
17675
17818
  for (const diagnostic6 of result2.lifecycle?.diagnostics ?? ["Approval required."])
17676
17819
  log.error(diagnostic6);
17677
17820
  process.exitCode = 4;
@@ -17876,11 +18019,13 @@ export {
17876
18019
  normalizeSetupRequest,
17877
18020
  persistInvocation,
17878
18021
  previewSetup,
18022
+ probeRuntimeVersion,
17879
18023
  productionRuntimeRegistry,
17880
18024
  recoverInstallation,
17881
18025
  removeInstallation,
17882
18026
  renderVerifiedUsage,
17883
18027
  resolveProductionRuntime,
18028
+ resolveRuntimeProbe,
17884
18029
  statusInstallation,
17885
18030
  structuredLog,
17886
18031
  upgradePackage,