orionfold-relay 0.20.0 → 0.21.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 CHANGED
@@ -143,7 +143,10 @@ relay license remove <license-id> # forget a license
143
143
  pack. Paid packs are new content, not repossessed features.
144
144
 
145
145
  The full terms in plain language — seats, transfer, what expiry does and doesn't do —
146
- are in [docs/trust/license-terms.md](docs/trust/license-terms.md).
146
+ are in [docs/trust/license-terms.md](docs/trust/license-terms.md). The gating
147
+ philosophy behind the boundary — and the never-regress promise that binds us to it —
148
+ is published at [orionfold.com/promise](https://orionfold.com/promise/). Browse and
149
+ buy premium packs at [orionfold.com/relay](https://orionfold.com/relay/).
147
150
 
148
151
  ---
149
152
 
package/dist/cli.js CHANGED
@@ -1186,7 +1186,7 @@ var CURRENT_PLUGIN_API_VERSION, CAPABILITY_VALUES, ORIGIN_VALUES, PrimitivesBund
1186
1186
  var init_types = __esm({
1187
1187
  "src/lib/plugins/sdk/types.ts"() {
1188
1188
  "use strict";
1189
- CURRENT_PLUGIN_API_VERSION = "0.20";
1189
+ CURRENT_PLUGIN_API_VERSION = "0.21";
1190
1190
  CAPABILITY_VALUES = ["fs", "net", "child_process", "env"];
1191
1191
  ORIGIN_VALUES = ["ainative-internal", "third-party"];
1192
1192
  PrimitivesBundleManifestSchema = z.object({
@@ -1232,16 +1232,16 @@ function computeHash(content) {
1232
1232
  function safePreview(content) {
1233
1233
  return content.slice(0, MAX_PREVIEW_CHARS).trim();
1234
1234
  }
1235
- function safeStat(path21) {
1235
+ function safeStat(path23) {
1236
1236
  try {
1237
- return statSync2(path21);
1237
+ return statSync2(path23);
1238
1238
  } catch {
1239
1239
  return null;
1240
1240
  }
1241
1241
  }
1242
- function safeReadFile(path21) {
1242
+ function safeReadFile(path23) {
1243
1243
  try {
1244
- return readFileSync4(path21, "utf-8");
1244
+ return readFileSync4(path23, "utf-8");
1245
1245
  } catch {
1246
1246
  return null;
1247
1247
  }
@@ -11951,9 +11951,9 @@ function buildPermissionSummary(toolName, input) {
11951
11951
  }
11952
11952
  }
11953
11953
  if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "read" || toolName === "write" || toolName === "edit") {
11954
- const path21 = input.file_path ?? input.path;
11955
- if (typeof path21 === "string" && path21.trim().length > 0) {
11956
- return truncate(path21.trim());
11954
+ const path23 = input.file_path ?? input.path;
11955
+ if (typeof path23 === "string" && path23.trim().length > 0) {
11956
+ return truncate(path23.trim());
11957
11957
  }
11958
11958
  }
11959
11959
  if (toolName?.startsWith("mcp__")) {
@@ -11987,9 +11987,9 @@ function getPermissionDetailEntries(toolName, input) {
11987
11987
  }
11988
11988
  }
11989
11989
  if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "read" || toolName === "write" || toolName === "edit") {
11990
- const path21 = input.file_path ?? input.path;
11991
- if (typeof path21 === "string") {
11992
- return [{ label: "Path", value: path21 }];
11990
+ const path23 = input.file_path ?? input.path;
11991
+ if (typeof path23 === "string") {
11992
+ return [{ label: "Path", value: path23 }];
11993
11993
  }
11994
11994
  }
11995
11995
  return Object.entries(input).slice(0, 6).map(([key, value]) => ({
@@ -12891,7 +12891,7 @@ var init_registry6 = __esm({
12891
12891
  init_registry5();
12892
12892
  init_installer();
12893
12893
  init_schedule_spec();
12894
- SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.19"]);
12894
+ SUPPORTED_API_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PLUGIN_API_VERSION, "0.20"]);
12895
12895
  pluginCache = null;
12896
12896
  lastLoadedPluginIds = /* @__PURE__ */ new Set();
12897
12897
  PluginTableSchema = z16.object({
@@ -12934,8 +12934,8 @@ function pluginTools(_ctx) {
12934
12934
  Promise.resolve().then(() => (init_ainative_paths(), ainative_paths_exports)),
12935
12935
  Promise.resolve().then(() => (init_types(), types_exports))
12936
12936
  ]);
12937
- const fs20 = await import("fs");
12938
- const path21 = await import("path");
12937
+ const fs22 = await import("fs");
12938
+ const path23 = await import("path");
12939
12939
  const yaml13 = await import("js-yaml");
12940
12940
  const kind5 = listPlugins2();
12941
12941
  const registrations = await listPluginMcpRegistrations2();
@@ -12952,13 +12952,13 @@ function pluginTools(_ctx) {
12952
12952
  let manifestHash;
12953
12953
  let capabilityAcceptStatus = "pending";
12954
12954
  try {
12955
- const pluginYamlPath = path21.join(
12955
+ const pluginYamlPath = path23.join(
12956
12956
  pluginsDir,
12957
12957
  pluginId,
12958
12958
  "plugin.yaml"
12959
12959
  );
12960
- if (fs20.existsSync(pluginYamlPath)) {
12961
- const content = fs20.readFileSync(pluginYamlPath, "utf-8");
12960
+ if (fs22.existsSync(pluginYamlPath)) {
12961
+ const content = fs22.readFileSync(pluginYamlPath, "utf-8");
12962
12962
  const rawManifest = yaml13.load(content);
12963
12963
  if (rawManifest !== null && typeof rawManifest === "object" && !Array.isArray(rawManifest)) {
12964
12964
  const record = rawManifest;
@@ -12971,7 +12971,7 @@ function pluginTools(_ctx) {
12971
12971
  try {
12972
12972
  manifestHash = deriveManifestHash2(content);
12973
12973
  const parsed = PluginManifestSchema2.safeParse(rawManifest);
12974
- const pluginRootDir = path21.join(pluginsDir, pluginId);
12974
+ const pluginRootDir = path23.join(pluginsDir, pluginId);
12975
12975
  const check = isCapabilityAccepted2(
12976
12976
  pluginId,
12977
12977
  manifestHash,
@@ -19387,13 +19387,13 @@ var init_codex_app_server_client = __esm({
19387
19387
  await syncPluginMcpToCodex2();
19388
19388
  } catch (err2) {
19389
19389
  try {
19390
- const fs20 = await import("fs");
19391
- const path21 = await import("path");
19390
+ const fs22 = await import("fs");
19391
+ const path23 = await import("path");
19392
19392
  const { getAinativeLogsDir: getAinativeLogsDir2 } = await Promise.resolve().then(() => (init_ainative_paths(), ainative_paths_exports));
19393
19393
  const dir = getAinativeLogsDir2();
19394
- fs20.mkdirSync(dir, { recursive: true });
19395
- fs20.appendFileSync(
19396
- path21.join(dir, "plugins.log"),
19394
+ fs22.mkdirSync(dir, { recursive: true });
19395
+ fs22.appendFileSync(
19396
+ path23.join(dir, "plugins.log"),
19397
19397
  `${(/* @__PURE__ */ new Date()).toISOString()} codex-sync-failed: ${err2 instanceof Error ? err2.message : String(err2)}
19398
19398
  `
19399
19399
  );
@@ -22985,14 +22985,14 @@ function resolvePostAction(action, row, itemVariable) {
22985
22985
  function substituteRowPath(template, row, itemVariable) {
22986
22986
  const escaped = itemVariable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22987
22987
  const pattern = new RegExp(`\\{\\{\\s*${escaped}\\.([\\w.]+)\\s*\\}\\}`, "g");
22988
- return template.replace(pattern, (_match, path21) => {
22989
- const value = readPath(row, path21);
22988
+ return template.replace(pattern, (_match, path23) => {
22989
+ const value = readPath(row, path23);
22990
22990
  if (value === void 0 || value === null) return "";
22991
22991
  return String(value);
22992
22992
  });
22993
22993
  }
22994
- function readPath(obj, path21) {
22995
- const parts = path21.split(".");
22994
+ function readPath(obj, path23) {
22995
+ const parts = path23.split(".");
22996
22996
  let current = obj;
22997
22997
  for (const part of parts) {
22998
22998
  if (current === null || current === void 0) return void 0;
@@ -23179,14 +23179,14 @@ ${resolvedTemplate}`);
23179
23179
  return parts.join("");
23180
23180
  }
23181
23181
  function resolveRowTemplate(template, context) {
23182
- return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, path21) => {
23183
- const value = readContextPath(context, path21.trim());
23182
+ return template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_match, path23) => {
23183
+ const value = readContextPath(context, path23.trim());
23184
23184
  if (value === void 0 || value === null) return "";
23185
23185
  return typeof value === "string" ? value : JSON.stringify(value);
23186
23186
  });
23187
23187
  }
23188
- function readContextPath(value, path21) {
23189
- const parts = path21.split(".");
23188
+ function readContextPath(value, path23) {
23189
+ const parts = path23.split(".");
23190
23190
  let current = value;
23191
23191
  for (const part of parts) {
23192
23192
  if (current === null || current === void 0) return void 0;
@@ -25611,25 +25611,76 @@ var init_app_schedule_id = __esm({
25611
25611
  }
25612
25612
  });
25613
25613
 
25614
+ // src/lib/packs/install-state.ts
25615
+ var install_state_exports = {};
25616
+ __export(install_state_exports, {
25617
+ InstallStateSchema: () => InstallStateSchema,
25618
+ hashFileSha256: () => hashFileSha256,
25619
+ installStatePath: () => installStatePath,
25620
+ readInstallState: () => readInstallState,
25621
+ writeInstallState: () => writeInstallState
25622
+ });
25623
+ import fs19 from "fs";
25624
+ import path20 from "path";
25625
+ import { createHash as createHash5 } from "crypto";
25626
+ import { z as z30 } from "zod";
25627
+ function installStatePath(appsDir, appId) {
25628
+ return path20.join(appsDir, appId, "install-state.json");
25629
+ }
25630
+ function readInstallState(appsDir, appId) {
25631
+ try {
25632
+ const raw = fs19.readFileSync(installStatePath(appsDir, appId), "utf-8");
25633
+ const parsed = InstallStateSchema.safeParse(JSON.parse(raw));
25634
+ return parsed.success ? parsed.data : null;
25635
+ } catch {
25636
+ return null;
25637
+ }
25638
+ }
25639
+ function writeInstallState(appsDir, appId, state) {
25640
+ const dir = path20.join(appsDir, appId);
25641
+ fs19.mkdirSync(dir, { recursive: true });
25642
+ const tmp = path20.join(dir, `.install-state.${process.pid}.tmp`);
25643
+ fs19.writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
25644
+ fs19.renameSync(tmp, installStatePath(appsDir, appId));
25645
+ }
25646
+ function hashFileSha256(absPath) {
25647
+ return createHash5("sha256").update(fs19.readFileSync(absPath)).digest("hex");
25648
+ }
25649
+ var InstallStateSchema;
25650
+ var init_install_state = __esm({
25651
+ "src/lib/packs/install-state.ts"() {
25652
+ "use strict";
25653
+ InstallStateSchema = z30.object({
25654
+ packVersion: z30.string().min(1),
25655
+ installedAt: z30.string().min(1),
25656
+ /** Dropped-artifact relPath (pack space, e.g. "profiles/x--y/SKILL.md") → sha256 hex of the DEST bytes. */
25657
+ files: z30.record(z30.string(), z30.string())
25658
+ }).strict();
25659
+ }
25660
+ });
25661
+
25614
25662
  // src/lib/packs/install.ts
25615
25663
  var install_exports = {};
25616
25664
  __export(install_exports, {
25617
- installPack: () => installPack
25665
+ acquirePack: () => acquirePack,
25666
+ artifactDestPath: () => artifactDestPath,
25667
+ installPack: () => installPack,
25668
+ relayCoreVersion: () => relayCoreVersion
25618
25669
  });
25619
- import fs19 from "fs";
25620
- import path20 from "path";
25670
+ import fs20 from "fs";
25671
+ import path21 from "path";
25621
25672
  import os4 from "os";
25622
25673
  import { execFileSync as execFileSync3 } from "child_process";
25623
25674
  import yaml12 from "js-yaml";
25624
25675
  import semver from "semver";
25625
25676
  function relayCoreVersion() {
25626
- if (semver.valid("0.20.0")) {
25627
- return "0.20.0";
25677
+ if (semver.valid("0.21.0")) {
25678
+ return "0.21.0";
25628
25679
  }
25629
25680
  try {
25630
25681
  const root = getAppRoot(import.meta.dirname, 3);
25631
25682
  const pkg2 = JSON.parse(
25632
- fs19.readFileSync(path20.join(root, "package.json"), "utf-8")
25683
+ fs20.readFileSync(path21.join(root, "package.json"), "utf-8")
25633
25684
  );
25634
25685
  if (pkg2.version && semver.valid(pkg2.version)) return pkg2.version;
25635
25686
  } catch {
@@ -25783,11 +25834,21 @@ async function installPack(source, options = {}) {
25783
25834
  droppedManifest.entitlement = pack.meta.entitlement;
25784
25835
  }
25785
25836
  writeManifest(appsDir, pack.meta.id, droppedManifest);
25786
- const { profilesDropped, blueprintsDropped } = dropArtifacts(
25837
+ const { profilesDropped, blueprintsDropped, dropped } = dropArtifacts(
25787
25838
  resolved.files,
25788
25839
  profilesDir,
25789
25840
  blueprintsDir
25790
25841
  );
25842
+ const { writeInstallState: writeInstallState2, hashFileSha256: hashFileSha2562 } = await Promise.resolve().then(() => (init_install_state(), install_state_exports));
25843
+ const stateFiles = {};
25844
+ for (const artifact of dropped) {
25845
+ stateFiles[artifact.relPath] = hashFileSha2562(artifact.destPath);
25846
+ }
25847
+ writeInstallState2(appsDir, pack.meta.id, {
25848
+ packVersion: pack.meta.version,
25849
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
25850
+ files: stateFiles
25851
+ });
25791
25852
  if (blueprintsDropped > 0) {
25792
25853
  const { reloadBlueprints: reloadBlueprints2 } = await Promise.resolve().then(() => (init_registry3(), registry_exports3));
25793
25854
  reloadBlueprints2();
@@ -25812,25 +25873,25 @@ function isGitUrl(source) {
25812
25873
  }
25813
25874
  function acquirePack(source) {
25814
25875
  if (!isGitUrl(source)) {
25815
- const resolved = path20.resolve(source);
25816
- if (!fs19.existsSync(resolved)) {
25876
+ const resolved = path21.resolve(source);
25877
+ if (!fs20.existsSync(resolved)) {
25817
25878
  throw new PackValidationError(`Pack path does not exist: ${resolved}`);
25818
25879
  }
25819
25880
  return { dir: resolved, cleanup: () => {
25820
25881
  } };
25821
25882
  }
25822
- const tmp = fs19.mkdtempSync(path20.join(os4.tmpdir(), "ainative-pack-clone-"));
25883
+ const tmp = fs20.mkdtempSync(path21.join(os4.tmpdir(), "ainative-pack-clone-"));
25823
25884
  try {
25824
25885
  execFileSync3("git", ["clone", "--depth", "1", source, tmp], {
25825
25886
  stdio: "pipe"
25826
25887
  });
25827
25888
  } catch (err2) {
25828
- fs19.rmSync(tmp, { recursive: true, force: true });
25889
+ fs20.rmSync(tmp, { recursive: true, force: true });
25829
25890
  throw new PackValidationError(`git clone failed for ${source}`, err2);
25830
25891
  }
25831
25892
  return {
25832
25893
  dir: tmp,
25833
- cleanup: () => fs19.rmSync(tmp, { recursive: true, force: true })
25894
+ cleanup: () => fs20.rmSync(tmp, { recursive: true, force: true })
25834
25895
  };
25835
25896
  }
25836
25897
  function findResolved(files, relPath) {
@@ -25839,7 +25900,7 @@ function findResolved(files, relPath) {
25839
25900
  function readCustomerSeed(resolved) {
25840
25901
  const file = findResolved(resolved.files, "seed/customers.yaml");
25841
25902
  if (!file) return [];
25842
- const parsed = yaml12.load(fs19.readFileSync(file.absPath, "utf-8"));
25903
+ const parsed = yaml12.load(fs20.readFileSync(file.absPath, "utf-8"));
25843
25904
  if (!Array.isArray(parsed)) {
25844
25905
  throw new PackValidationError(
25845
25906
  "seed/customers.yaml must be a YAML list of { slug, name, ... }"
@@ -25851,7 +25912,7 @@ function readTableSeed(resolved, logicalId) {
25851
25912
  for (const ext of ["json", "yaml", "yml"]) {
25852
25913
  const file = findResolved(resolved.files, `seed/tables/${logicalId}.${ext}`);
25853
25914
  if (!file) continue;
25854
- const text2 = fs19.readFileSync(file.absPath, "utf-8");
25915
+ const text2 = fs20.readFileSync(file.absPath, "utf-8");
25855
25916
  const parsed = ext === "json" ? JSON.parse(text2) : yaml12.load(text2);
25856
25917
  if (!Array.isArray(parsed)) {
25857
25918
  throw new PackValidationError(
@@ -25909,30 +25970,36 @@ function rewriteViewRefs(view, maps) {
25909
25970
  return view;
25910
25971
  }
25911
25972
  function writeManifest(appsDir, appId, manifest) {
25912
- fs19.mkdirSync(path20.join(appsDir, appId), { recursive: true });
25973
+ fs20.mkdirSync(path21.join(appsDir, appId), { recursive: true });
25913
25974
  writeAppManifest(appId, manifest, appsDir);
25914
25975
  }
25976
+ function artifactDestPath(relPath, profilesDir, blueprintsDir) {
25977
+ if (relPath.startsWith("profiles/")) {
25978
+ return path21.join(profilesDir, relPath.slice("profiles/".length));
25979
+ }
25980
+ if (relPath.startsWith("blueprints/") && relPath.endsWith(".yaml")) {
25981
+ return path21.join(blueprintsDir, relPath.slice("blueprints/".length));
25982
+ }
25983
+ return null;
25984
+ }
25915
25985
  function dropArtifacts(files, profilesDir, blueprintsDir) {
25916
25986
  const profileDirs = /* @__PURE__ */ new Set();
25917
25987
  let blueprintsDropped = 0;
25988
+ const dropped = [];
25918
25989
  for (const file of files) {
25990
+ const dest = artifactDestPath(file.relPath, profilesDir, blueprintsDir);
25991
+ if (!dest) continue;
25992
+ fs20.mkdirSync(path21.dirname(dest), { recursive: true });
25993
+ fs20.copyFileSync(file.absPath, dest);
25994
+ dropped.push({ relPath: file.relPath, destPath: dest });
25919
25995
  if (file.relPath.startsWith("profiles/")) {
25920
- const dest = path20.join(profilesDir, file.relPath.slice("profiles/".length));
25921
- fs19.mkdirSync(path20.dirname(dest), { recursive: true });
25922
- fs19.copyFileSync(file.absPath, dest);
25923
25996
  const top = file.relPath.split("/")[1];
25924
25997
  if (top) profileDirs.add(top);
25925
- } else if (file.relPath.startsWith("blueprints/") && file.relPath.endsWith(".yaml")) {
25926
- const dest = path20.join(
25927
- blueprintsDir,
25928
- file.relPath.slice("blueprints/".length)
25929
- );
25930
- fs19.mkdirSync(path20.dirname(dest), { recursive: true });
25931
- fs19.copyFileSync(file.absPath, dest);
25998
+ } else {
25932
25999
  blueprintsDropped += 1;
25933
26000
  }
25934
26001
  }
25935
- return { profilesDropped: profileDirs.size, blueprintsDropped };
26002
+ return { profilesDropped: profileDirs.size, blueprintsDropped, dropped };
25936
26003
  }
25937
26004
  function titleCase3(slug) {
25938
26005
  return slug.split(/[-_]/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
@@ -25947,6 +26014,145 @@ var init_install = __esm({
25947
26014
  }
25948
26015
  });
25949
26016
 
26017
+ // src/lib/packs/update.ts
26018
+ var update_exports = {};
26019
+ __export(update_exports, {
26020
+ PackNotInstalledError: () => PackNotInstalledError,
26021
+ packUpdateAvailability: () => packUpdateAvailability,
26022
+ updatePack: () => updatePack
26023
+ });
26024
+ import fs21 from "fs";
26025
+ import path22 from "path";
26026
+ import semver2 from "semver";
26027
+ function packUpdateAvailability(appId, opts2 = {}) {
26028
+ const appsDir = opts2.appsDir ?? getAinativeAppsDir();
26029
+ const installedVersion = readInstallState(appsDir, appId)?.packVersion ?? null;
26030
+ const template = findPackTemplate(appId, { templatesDir: opts2.templatesDir });
26031
+ const availableVersion = template?.meta?.version ?? null;
26032
+ const updateAvailable = availableVersion !== null && semver2.valid(availableVersion) !== null && (installedVersion === null || semver2.valid(installedVersion) === null || semver2.compare(availableVersion, installedVersion) > 0);
26033
+ return { installedVersion, availableVersion, updateAvailable };
26034
+ }
26035
+ async function updatePack(id, options = {}) {
26036
+ const appsDir = options.appsDir ?? getAinativeAppsDir();
26037
+ const profilesDir = options.profilesDir ?? getAinativeProfilesDir();
26038
+ const blueprintsDir = options.blueprintsDir ?? getAinativeBlueprintsDir();
26039
+ const installed = getApp(id, appsDir);
26040
+ if (!installed) {
26041
+ throw new PackNotInstalledError(
26042
+ `Pack "${id}" is not installed. Install it first with: relay pack add ${id}`
26043
+ );
26044
+ }
26045
+ const state = readInstallState(appsDir, id);
26046
+ const previousVersion = state?.packVersion ?? null;
26047
+ const { resolvePackSource: resolvePackSource2 } = await Promise.resolve().then(() => (init_catalog(), catalog_exports));
26048
+ const resolvedSource = resolvePackSource2(options.source ?? id, {
26049
+ templatesDir: options.templatesDir
26050
+ });
26051
+ const { dir: packDir, cleanup } = acquirePack(resolvedSource);
26052
+ try {
26053
+ const pack = parsePack(packDir);
26054
+ if (pack.meta.id !== id) {
26055
+ throw new PackValidationError(
26056
+ `Update source is pack "${pack.meta.id}", not "${id}" \u2014 refusing to cross-update.`
26057
+ );
26058
+ }
26059
+ const coreVersion = options.coreVersion ?? relayCoreVersion();
26060
+ if (pack.meta.relayCore && !semver2.satisfies(coreVersion, pack.meta.relayCore)) {
26061
+ throw new PackValidationError(
26062
+ `Pack ${pack.meta.id}@${pack.meta.version} requires relay-core ${pack.meta.relayCore}, but this install is ${coreVersion}.`
26063
+ );
26064
+ }
26065
+ const newVersion = pack.meta.version;
26066
+ if (previousVersion !== null && semver2.valid(previousVersion) && semver2.valid(newVersion) && semver2.compare(newVersion, previousVersion) <= 0) {
26067
+ return {
26068
+ packId: id,
26069
+ previousVersion,
26070
+ newVersion,
26071
+ upToDate: true,
26072
+ backedUp: []
26073
+ };
26074
+ }
26075
+ if (pack.meta.entitlement) {
26076
+ const { assertEntitled: assertEntitled2, PackLicenseError: PackLicenseError2 } = await Promise.resolve().then(() => (init_gate(), gate_exports));
26077
+ try {
26078
+ if (options.licenseUrl) {
26079
+ const { loadLicense: loadLicense2 } = await Promise.resolve().then(() => (init_load(), load_exports));
26080
+ const license = await loadLicense2(options.licenseUrl);
26081
+ assertEntitled2(pack.meta.entitlement, license);
26082
+ } else {
26083
+ const { findEntitledLicense: findEntitledLicense2 } = await Promise.resolve().then(() => (init_store(), store_exports));
26084
+ if (!findEntitledLicense2(pack.meta.entitlement)) {
26085
+ assertEntitled2(pack.meta.entitlement, void 0);
26086
+ }
26087
+ }
26088
+ } catch (err2) {
26089
+ if (err2 instanceof PackLicenseError2) {
26090
+ const renew = pack.meta.purchaseUrl ? `renew at ${pack.meta.purchaseUrl}` : `redeem one with: relay license add <path-or-url to your .license.json>`;
26091
+ throw new PackLicenseError2(
26092
+ `Your installed ${id} keeps working \u2014 nothing is locked. Updating to v${newVersion} needs an active license: ${renew}. (${err2.message})`,
26093
+ err2.reason
26094
+ );
26095
+ }
26096
+ throw err2;
26097
+ }
26098
+ }
26099
+ const resolved = resolvePackLayer(pack);
26100
+ const backupRoot = path22.join(
26101
+ appsDir,
26102
+ id,
26103
+ "backup",
26104
+ previousVersion ?? "unknown"
26105
+ );
26106
+ const backedUp = [];
26107
+ for (const file of resolved.files) {
26108
+ const dest = artifactDestPath(file.relPath, profilesDir, blueprintsDir);
26109
+ if (!dest || !fs21.existsSync(dest)) continue;
26110
+ const recorded = state?.files[file.relPath];
26111
+ if (recorded !== void 0 && hashFileSha256(dest) === recorded) continue;
26112
+ const backupPath = path22.join(backupRoot, file.relPath);
26113
+ fs21.mkdirSync(path22.dirname(backupPath), { recursive: true });
26114
+ fs21.copyFileSync(dest, backupPath);
26115
+ backedUp.push(file.relPath);
26116
+ }
26117
+ const install = await installPack(packDir, {
26118
+ appsDir,
26119
+ profilesDir,
26120
+ blueprintsDir,
26121
+ coreVersion: options.coreVersion,
26122
+ licenseUrl: options.licenseUrl,
26123
+ templatesDir: options.templatesDir
26124
+ });
26125
+ return {
26126
+ packId: id,
26127
+ previousVersion,
26128
+ newVersion,
26129
+ upToDate: false,
26130
+ backedUp,
26131
+ install
26132
+ };
26133
+ } finally {
26134
+ cleanup();
26135
+ }
26136
+ }
26137
+ var PackNotInstalledError;
26138
+ var init_update = __esm({
26139
+ "src/lib/packs/update.ts"() {
26140
+ "use strict";
26141
+ init_ainative_paths();
26142
+ init_registry();
26143
+ init_install();
26144
+ init_format();
26145
+ init_catalog();
26146
+ init_install_state();
26147
+ PackNotInstalledError = class extends Error {
26148
+ constructor(message) {
26149
+ super(message);
26150
+ this.name = "PackNotInstalledError";
26151
+ }
26152
+ };
26153
+ }
26154
+ });
26155
+
25950
26156
  // src/lib/packs/cli.ts
25951
26157
  var cli_exports = {};
25952
26158
  __export(cli_exports, {
@@ -25980,7 +26186,7 @@ async function runPackCommand(argv, io) {
25980
26186
  case "remove":
25981
26187
  return runRemove(arg, io);
25982
26188
  case "update":
25983
- return runUpdate(arg, io);
26189
+ return runUpdate(arg, rest[1], licenseUrl, io);
25984
26190
  default:
25985
26191
  io.error(`Unknown pack action: ${action ?? "(none)"}`);
25986
26192
  io.error(USAGE);
@@ -26020,9 +26226,15 @@ async function runList(io) {
26020
26226
  io.log("No packs installed.");
26021
26227
  return 0;
26022
26228
  }
26229
+ const { packUpdateAvailability: packUpdateAvailability2 } = await Promise.resolve().then(() => (init_update(), update_exports));
26023
26230
  for (const app of apps) {
26024
26231
  const premium = app.entitlement ? " [premium]" : "";
26025
- io.log(`${app.id} ${app.name} ${app.primitivesSummary}${premium}`);
26232
+ const avail = packUpdateAvailability2(app.id, { appsDir });
26233
+ const version = avail.installedVersion ? ` installed v${avail.installedVersion}` : "";
26234
+ const update = avail.updateAvailable && avail.availableVersion ? ` [update available \u2192 v${avail.availableVersion}]` : "";
26235
+ io.log(
26236
+ `${app.id} ${app.name} ${app.primitivesSummary}${version}${update}${premium}`
26237
+ );
26026
26238
  }
26027
26239
  return 0;
26028
26240
  } catch (err2) {
@@ -26062,11 +26274,47 @@ async function runRemove(id, io) {
26062
26274
  return 1;
26063
26275
  }
26064
26276
  }
26065
- async function runUpdate(id, io) {
26066
- io.log(
26067
- `pack update${id ? ` ${id}` : ""}: managed-base updates land in a future release. v1 is editable-seed \u2014 edit the installed pack in place.`
26068
- );
26069
- return 0;
26277
+ async function runUpdate(id, source, licenseUrl, io) {
26278
+ if (!id) {
26279
+ io.error(
26280
+ "Missing pack id. Usage: relay pack update <id> [source] [--license-url=<path|url>]"
26281
+ );
26282
+ return 1;
26283
+ }
26284
+ try {
26285
+ const { updatePack: updatePack2 } = await Promise.resolve().then(() => (init_update(), update_exports));
26286
+ const report = await updatePack2(id, {
26287
+ appsDir: io.appsDir,
26288
+ profilesDir: io.profilesDir,
26289
+ blueprintsDir: io.blueprintsDir,
26290
+ source,
26291
+ licenseUrl
26292
+ });
26293
+ if (report.upToDate) {
26294
+ io.log(
26295
+ `${report.packId} is already up to date (v${report.newVersion}).`
26296
+ );
26297
+ return 0;
26298
+ }
26299
+ const install = report.install;
26300
+ io.log(
26301
+ `Updated ${report.packId} v${report.previousVersion ?? "unknown"} \u2192 v${report.newVersion}: ${install.tablesCreated} table(s) added (${install.rowsSeeded} row(s)), ${install.profilesDropped} profile(s), ${install.blueprintsDropped} blueprint(s), ${install.schedulesRegistered} schedule(s).`
26302
+ );
26303
+ if (report.backedUp.length > 0) {
26304
+ io.log(
26305
+ `Backed up ${report.backedUp.length} user-modified file(s) to apps/${report.packId}/backup/${report.previousVersion ?? "unknown"}/ before overwriting:`
26306
+ );
26307
+ for (const relPath of report.backedUp) {
26308
+ io.log(` ${relPath}`);
26309
+ }
26310
+ }
26311
+ return 0;
26312
+ } catch (err2) {
26313
+ io.error(
26314
+ `Failed to update pack: ${err2 instanceof Error ? err2.message : String(err2)}`
26315
+ );
26316
+ return 1;
26317
+ }
26070
26318
  }
26071
26319
  var USAGE;
26072
26320
  var init_cli = __esm({
@@ -26078,7 +26326,7 @@ var init_cli = __esm({
26078
26326
  " add <name|path|git-url> [--license-url=<path|url>] install a pack",
26079
26327
  " list list installed packs",
26080
26328
  " remove <id> uninstall a pack",
26081
- " update <id> (v1 stub \u2014 editable-seed; edit in place)"
26329
+ " update <id> [source] [--license-url=<path|url>] update to a newer version"
26082
26330
  ].join("\n");
26083
26331
  }
26084
26332
  });
@@ -26548,10 +26796,10 @@ import { existsSync as existsSync4, renameSync, cpSync, rmSync as rmSync2, readF
26548
26796
  import { join as join6 } from "path";
26549
26797
  import { homedir as homedir2 } from "os";
26550
26798
  import Database from "better-sqlite3";
26551
- function hasSqliteHeader(path21) {
26799
+ function hasSqliteHeader(path23) {
26552
26800
  const SQLITE_MAGIC = "SQLite format 3\0";
26553
26801
  try {
26554
- const header = readFileSync3(path21, { encoding: null });
26802
+ const header = readFileSync3(path23, { encoding: null });
26555
26803
  return header.length >= 16 && header.subarray(0, 16).toString("binary") === SQLITE_MAGIC;
26556
26804
  } catch {
26557
26805
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orionfold-relay",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Orionfold Relay — a local-first, multi-agent orchestration runtime and builder scaffold for AI-native work.",
5
5
  "keywords": [
6
6
  "ai",