@caelo-cms/shared 0.10.43 → 0.10.46

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.
@@ -17,7 +17,7 @@
17
17
  */
18
18
  const LOOKUP_FAIL_THRESHOLD = 3;
19
19
  const counters = new Map();
20
- /** Total fail-closed trips since process start — surfaced in /security/costs. */
20
+ /** Total fail-closed trips since process start — surfaced in /settings/costs. */
21
21
  let totalTrips = 0;
22
22
  export function recordCapLookupSuccess(key) {
23
23
  counters.delete(key);
@@ -14,7 +14,7 @@
14
14
  * contribution point (the `international-site` plugin knows each
15
15
  * page's locale — core does not, since epic #380), else
16
16
  * 2. the site's stored language (`site_defaults.site_language`, set
17
- * by the AI via `set_site_identity` or by the Owner at /security/seo).
17
+ * by the AI via `set_site_identity` or by the Owner at /settings/seo).
18
18
  * The stored language has no default (migration 0232, CLAUDE.md §2): NULL
19
19
  * means nobody chose one yet. Nothing here substitutes a language for it —
20
20
  * the preview renders `<html>` without `lang` and flags
@@ -15,7 +15,7 @@
15
15
  * contribution point (the `international-site` plugin knows each
16
16
  * page's locale — core does not, since epic #380), else
17
17
  * 2. the site's stored language (`site_defaults.site_language`, set
18
- * by the AI via `set_site_identity` or by the Owner at /security/seo).
18
+ * by the AI via `set_site_identity` or by the Owner at /settings/seo).
19
19
  * The stored language has no default (migration 0232, CLAUDE.md §2): NULL
20
20
  * means nobody chose one yet. Nothing here substitutes a language for it —
21
21
  * the preview renders `<html>` without `lang` and flags
package/dist/index.d.ts CHANGED
@@ -26,6 +26,7 @@ export * from "./plugin-preview.js";
26
26
  export * from "./preview-compose.js";
27
27
  export * from "./preview-scanner.js";
28
28
  export * from "./proposal-status.js";
29
+ export * from "./provisioning-command.js";
29
30
  export * from "./redirect-target.js";
30
31
  export * from "./responsive-images.js";
31
32
  export * from "./result.js";
@@ -52,5 +53,5 @@ export * from "./theme-render.js";
52
53
  export * from "./themes.js";
53
54
  export * from "./themes-errors.js";
54
55
  export * from "./url.js";
55
- export { CAELO_VERSION, CALEO_VERSION, type CaeloVersion, parseVersion } from "./version.js";
56
+ export { CAELO_VERSION, CALEO_VERSION, type CaeloVersion, isNewerVersion, parseVersion, } from "./version.js";
56
57
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,cAAc,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,UAAU,EACV,aAAa,EACb,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,cAAc,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACL,KAAK,UAAU,EACf,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,UAAU,EACV,aAAa,EACb,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,aAAa,EACb,aAAa,EACb,KAAK,YAAY,EACjB,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ export * from "./plugin-preview.js";
27
27
  export * from "./preview-compose.js";
28
28
  export * from "./preview-scanner.js";
29
29
  export * from "./proposal-status.js";
30
+ export * from "./provisioning-command.js";
30
31
  export * from "./redirect-target.js";
31
32
  export * from "./responsive-images.js";
32
33
  export * from "./result.js";
@@ -53,5 +54,5 @@ export * from "./theme-render.js";
53
54
  export * from "./themes.js";
54
55
  export * from "./themes-errors.js";
55
56
  export * from "./url.js";
56
- export { CAELO_VERSION, CALEO_VERSION, parseVersion } from "./version.js";
57
+ export { CAELO_VERSION, CALEO_VERSION, isNewerVersion, parseVersion, } from "./version.js";
57
58
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAKL,UAAU,EACV,aAAa,EACb,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,aAAa,EAAqB,YAAY,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AAExC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAKL,UAAU,EACV,aAAa,EACb,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC;AACtD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,aAAa,EACb,aAAa,EAEb,cAAc,EACd,YAAY,GACb,MAAM,cAAc,CAAC"}
@@ -453,7 +453,7 @@ export function composePageWithLayout(input) {
453
453
  // caller, not silently emit a body-less page.
454
454
  if (!listSlotNames(input.layoutHtml).includes("content")) {
455
455
  const slug = input.layoutSlug ?? "(unknown)";
456
- throw new ComposeError("layout-missing-content", `layout "${slug}" is missing the required \`<caelo-slot name="content">\` slot — fix via /security/layouts`, input.layoutSlug);
456
+ throw new ComposeError("layout-missing-content", `layout "${slug}" is missing the required \`<caelo-slot name="content">\` slot — fix via /settings/layouts`, input.layoutSlug);
457
457
  }
458
458
  // CSS / JS aggregation order: layout (ground) → template (overrides
459
459
  // layout) → modules (highest specificity). The array's source order
@@ -0,0 +1,40 @@
1
+ /** npm name of the provisioning CLI. */
2
+ export declare const PROVISIONING_PACKAGE = "@caelo-cms/provisioning";
3
+ /**
4
+ * `bunx @caelo-cms/provisioning@<version> <args>`.
5
+ *
6
+ * @param version an exact release (`0.10.44`, a leading `v` is dropped);
7
+ * throws on anything that is not a version, so a tag like `latest` can
8
+ * never slip into a printed command.
9
+ * @param args the CLI arguments, already split.
10
+ * @example provisioningCommand("0.10.44", ["status"])
11
+ * // → "bunx @caelo-cms/provisioning@0.10.44 status"
12
+ */
13
+ export declare function provisioningCommand(version: string, args?: readonly string[]): string;
14
+ /**
15
+ * The upgrade command for one install, pinned twice: the CLI runs as the
16
+ * target release (its upgrade logic matches the release it installs) and
17
+ * `--version` makes it roll exactly that release, even if a newer one ships
18
+ * before the operator runs it (without the flag, `upgrade` rolls the
19
+ * registry's `latest` tag).
20
+ *
21
+ * @param args.version the release to install.
22
+ * @param args.install the install's domain (or install id); `upgrade`
23
+ * matches either against `~/.caelo-<id>/install.json`.
24
+ * @example provisioningUpgradeCommand({ version: "0.10.44", install: "example.com" })
25
+ * // → "bunx @caelo-cms/provisioning@0.10.44 upgrade --version 0.10.44 --install example.com"
26
+ */
27
+ export declare function provisioningUpgradeCommand(args: {
28
+ version: string;
29
+ install: string;
30
+ }): string;
31
+ /**
32
+ * The `--install` value for the install whose public site is `siteUrl`
33
+ * (the admin's `CAELO_SITE_URL`, which the stacks set to
34
+ * `https://<domain>` — the same domain `install.json` records).
35
+ *
36
+ * @returns the lower-cased hostname, or null when `siteUrl` is not a URL
37
+ * or its host is not a plain domain (an IP literal with a port, say).
38
+ */
39
+ export declare function installHintFromSiteUrl(siteUrl: string): string | null;
40
+ //# sourceMappingURL=provisioning-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provisioning-command.d.ts","sourceRoot":"","sources":["../src/provisioning-command.ts"],"names":[],"mappings":"AAiBA,wCAAwC;AACxC,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAS9D;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAGzF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAM7F;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE"}
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ /**
3
+ * The `bunx @caelo-cms/provisioning …` commands Caelo prints — in the CLI's
4
+ * own next-step hints, `status`, and the admin's Settings → Updates page
5
+ * (issue #696 Phase 0).
6
+ *
7
+ * Every command names an exact package version. Without one, `bunx` reuses
8
+ * whatever copy of the CLI it cached first and never looks again, so an
9
+ * operator who installed months ago would upgrade with months-old upgrade
10
+ * logic (the same trap #552 fixed for the MCP shim). The version is always
11
+ * one the caller knows for certain: the running CLI's own, or the release
12
+ * the release check reported — never "whatever is newest".
13
+ */
14
+ import { parseVersion } from "./version.js";
15
+ /** npm name of the provisioning CLI. */
16
+ export const PROVISIONING_PACKAGE = "@caelo-cms/provisioning";
17
+ /**
18
+ * A hostname the `--install` flag can carry into a copy-pasted shell
19
+ * command: letters, digits, dots and hyphens only, so nothing in it can be
20
+ * read by the shell.
21
+ */
22
+ const SAFE_INSTALL_HINT = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*$/;
23
+ /**
24
+ * `bunx @caelo-cms/provisioning@<version> <args>`.
25
+ *
26
+ * @param version an exact release (`0.10.44`, a leading `v` is dropped);
27
+ * throws on anything that is not a version, so a tag like `latest` can
28
+ * never slip into a printed command.
29
+ * @param args the CLI arguments, already split.
30
+ * @example provisioningCommand("0.10.44", ["status"])
31
+ * // → "bunx @caelo-cms/provisioning@0.10.44 status"
32
+ */
33
+ export function provisioningCommand(version, args = []) {
34
+ const exact = parseVersion(version.replace(/^v/, "")).raw;
35
+ return [`bunx ${PROVISIONING_PACKAGE}@${exact}`, ...args].join(" ");
36
+ }
37
+ /**
38
+ * The upgrade command for one install, pinned twice: the CLI runs as the
39
+ * target release (its upgrade logic matches the release it installs) and
40
+ * `--version` makes it roll exactly that release, even if a newer one ships
41
+ * before the operator runs it (without the flag, `upgrade` rolls the
42
+ * registry's `latest` tag).
43
+ *
44
+ * @param args.version the release to install.
45
+ * @param args.install the install's domain (or install id); `upgrade`
46
+ * matches either against `~/.caelo-<id>/install.json`.
47
+ * @example provisioningUpgradeCommand({ version: "0.10.44", install: "example.com" })
48
+ * // → "bunx @caelo-cms/provisioning@0.10.44 upgrade --version 0.10.44 --install example.com"
49
+ */
50
+ export function provisioningUpgradeCommand(args) {
51
+ const version = parseVersion(args.version.replace(/^v/, "")).raw;
52
+ if (!SAFE_INSTALL_HINT.test(args.install)) {
53
+ throw new Error(`not a domain or install id: ${JSON.stringify(args.install)}`);
54
+ }
55
+ return provisioningCommand(version, ["upgrade", "--version", version, "--install", args.install]);
56
+ }
57
+ /**
58
+ * The `--install` value for the install whose public site is `siteUrl`
59
+ * (the admin's `CAELO_SITE_URL`, which the stacks set to
60
+ * `https://<domain>` — the same domain `install.json` records).
61
+ *
62
+ * @returns the lower-cased hostname, or null when `siteUrl` is not a URL
63
+ * or its host is not a plain domain (an IP literal with a port, say).
64
+ */
65
+ export function installHintFromSiteUrl(siteUrl) {
66
+ let host;
67
+ try {
68
+ host = new URL(siteUrl).hostname.toLowerCase();
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ return SAFE_INSTALL_HINT.test(host) ? host : null;
74
+ }
75
+ //# sourceMappingURL=provisioning-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provisioning-command.js","sourceRoot":"","sources":["../src/provisioning-command.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,wCAAwC;AACxC,MAAM,CAAC,MAAM,oBAAoB,GAAG,yBAAyB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,yEAAyE,CAAC;AAEpG;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,OAA0B,EAAE;IAC/E,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,OAAO,CAAC,QAAQ,oBAAoB,IAAI,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAA0C;IACnF,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IACjE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,mBAAmB,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AACpG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe;IACpD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC"}
@@ -49,7 +49,7 @@ export declare const EXPECTED_RETURN_SHAPES: readonly ExpectedReturnShape[];
49
49
  * The tier VOCABULARY is deliberately abstract — tool results and
50
50
  * editor-facing text never name the underlying model (CLAUDE.md §2:
51
51
  * provider brand never surfaces in the editor chat UI); concrete model
52
- * ids appear only on Owner surfaces (security panel, cost dashboard).
52
+ * ids appear only on Owner surfaces (Settings panel, cost dashboard).
53
53
  */
54
54
  export declare const subagentModelTier: z.ZodEnum<{
55
55
  small: "small";
package/dist/subagents.js CHANGED
@@ -44,7 +44,7 @@ export const EXPECTED_RETURN_SHAPES = expectedReturnShape.options;
44
44
  * The tier VOCABULARY is deliberately abstract — tool results and
45
45
  * editor-facing text never name the underlying model (CLAUDE.md §2:
46
46
  * provider brand never surfaces in the editor chat UI); concrete model
47
- * ids appear only on Owner surfaces (security panel, cost dashboard).
47
+ * ids appear only on Owner surfaces (Settings panel, cost dashboard).
48
48
  */
49
49
  export const subagentModelTier = z.enum(["inherit", "mid", "small"]);
50
50
  /** Single source of truth for the tier enum (same #251 drift-guard pattern
package/dist/version.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * 5. Pushes — GitHub Actions takes over for the publish step.
10
10
  *
11
11
  * Read by:
12
- * - admin's /security index page (header badge + about-modal),
12
+ * - admin's /settings index page (header badge + about-modal),
13
13
  * - cms-provision CLI's `version` subcommand,
14
14
  * - User-Agent header on plugin-host's outbound HTTP calls,
15
15
  * - Tier-1 manifest signing (the version is hashed into the
@@ -19,7 +19,7 @@
19
19
  * Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
20
20
  * follow standard SemVer.
21
21
  */
22
- export declare const CAELO_VERSION = "0.10.43";
22
+ export declare const CAELO_VERSION = "0.10.46";
23
23
  /**
24
24
  * Deprecated alias for back-compat — early P17 work spelled this
25
25
  * `CALEO_VERSION` (typo of "Caelo"). New code should import
@@ -31,7 +31,7 @@ export declare const CAELO_VERSION = "0.10.43";
31
31
  *
32
32
  * @deprecated use CAELO_VERSION
33
33
  */
34
- export declare const CALEO_VERSION = "0.10.43";
34
+ export declare const CALEO_VERSION = "0.10.46";
35
35
  /**
36
36
  * Parsed shape — major/minor/patch + optional pre-release tag.
37
37
  * Stable interface for callers that need to feature-gate (rare —
@@ -46,4 +46,15 @@ export interface CaeloVersion {
46
46
  readonly raw: string;
47
47
  }
48
48
  export declare function parseVersion(raw?: string): CaeloVersion;
49
+ /**
50
+ * True when release `latest` is newer than `current` (both SemVer
51
+ * `X.Y.Z[-pre]`; a leading `v` is ignored). A pre-release sorts before the
52
+ * same X.Y.Z stable; two pre-releases of one X.Y.Z compare as strings.
53
+ * Mirrors the ordering in scripts/release.ts so the "update available"
54
+ * notice agrees with the release pipeline. A string that is not a version
55
+ * is never newer.
56
+ *
57
+ * @example isNewerVersion("0.10.44", "0.10.43") // → true
58
+ */
59
+ export declare function isNewerVersion(latest: string, current: string): boolean;
49
60
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,eAAO,MAAM,aAAa,YAAY,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,YAAgB,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,YAAY,CAYtE"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,eAAO,MAAM,aAAa,YAAY,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,YAAgB,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAsB,GAAG,YAAY,CAYtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAkBvE"}
package/dist/version.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * 5. Pushes — GitHub Actions takes over for the publish step.
11
11
  *
12
12
  * Read by:
13
- * - admin's /security index page (header badge + about-modal),
13
+ * - admin's /settings index page (header badge + about-modal),
14
14
  * - cms-provision CLI's `version` subcommand,
15
15
  * - User-Agent header on plugin-host's outbound HTTP calls,
16
16
  * - Tier-1 manifest signing (the version is hashed into the
@@ -20,7 +20,7 @@
20
20
  * Format: SemVer 2.0.0. Pre-1.0 minor bumps are breaking; post-1.0
21
21
  * follow standard SemVer.
22
22
  */
23
- export const CAELO_VERSION = "0.10.43";
23
+ export const CAELO_VERSION = "0.10.46";
24
24
  /**
25
25
  * Deprecated alias for back-compat — early P17 work spelled this
26
26
  * `CALEO_VERSION` (typo of "Caelo"). New code should import
@@ -46,4 +46,39 @@ export function parseVersion(raw = CAELO_VERSION) {
46
46
  raw,
47
47
  };
48
48
  }
49
+ /**
50
+ * True when release `latest` is newer than `current` (both SemVer
51
+ * `X.Y.Z[-pre]`; a leading `v` is ignored). A pre-release sorts before the
52
+ * same X.Y.Z stable; two pre-releases of one X.Y.Z compare as strings.
53
+ * Mirrors the ordering in scripts/release.ts so the "update available"
54
+ * notice agrees with the release pipeline. A string that is not a version
55
+ * is never newer.
56
+ *
57
+ * @example isNewerVersion("0.10.44", "0.10.43") // → true
58
+ */
59
+ export function isNewerVersion(latest, current) {
60
+ const parse = (v) => {
61
+ try {
62
+ return parseVersion(v.replace(/^v/, ""));
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ };
68
+ const a = parse(latest);
69
+ const b = parse(current);
70
+ if (!a || !b)
71
+ return false;
72
+ for (const k of ["major", "minor", "patch"]) {
73
+ if (a[k] !== b[k])
74
+ return a[k] > b[k];
75
+ }
76
+ if (a.preRelease && !b.preRelease)
77
+ return false;
78
+ if (!a.preRelease && b.preRelease)
79
+ return true;
80
+ if (a.preRelease && b.preRelease)
81
+ return a.preRelease > b.preRelease;
82
+ return false;
83
+ }
49
84
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAgB3C,MAAM,UAAU,YAAY,CAAC,MAAc,aAAa;IACtD,MAAM,CAAC,GAAG,iCAAiC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;QACvC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI;QACxB,GAAG;KACJ,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,mCAAmC;AAEnC;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAC;AAEvC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;AAgB3C,MAAM,UAAU,YAAY,CAAC,MAAc,aAAa;IACtD,MAAM,CAAC,GAAG,iCAAiC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;QACvC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI;QACxB,GAAG;KACJ,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,OAAe;IAC5D,MAAM,KAAK,GAAG,CAAC,CAAS,EAAuB,EAAE;QAC/C,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IACF,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IACzB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAU,EAAE,CAAC;QACrD,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAChD,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;IACrE,OAAO,KAAK,CAAC;AACf,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caelo-cms/shared",
3
- "version": "0.10.43",
3
+ "version": "0.10.46",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
6
  "description": "Shared Zod schemas + types + the two-pass HTML composer used by every Caelo CMS workspace package. Stable enough for plugin authors and embedders to depend on directly.",
@@ -20,7 +20,7 @@
20
20
  const LOOKUP_FAIL_THRESHOLD = 3;
21
21
  const counters = new Map<string, number>();
22
22
 
23
- /** Total fail-closed trips since process start — surfaced in /security/costs. */
23
+ /** Total fail-closed trips since process start — surfaced in /settings/costs. */
24
24
  let totalTrips = 0;
25
25
 
26
26
  export function recordCapLookupSuccess(key: string): void {
@@ -16,7 +16,7 @@
16
16
  * contribution point (the `international-site` plugin knows each
17
17
  * page's locale — core does not, since epic #380), else
18
18
  * 2. the site's stored language (`site_defaults.site_language`, set
19
- * by the AI via `set_site_identity` or by the Owner at /security/seo).
19
+ * by the AI via `set_site_identity` or by the Owner at /settings/seo).
20
20
  * The stored language has no default (migration 0232, CLAUDE.md §2): NULL
21
21
  * means nobody chose one yet. Nothing here substitutes a language for it —
22
22
  * the preview renders `<html>` without `lang` and flags
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export * from "./plugin-preview.js";
39
39
  export * from "./preview-compose.js";
40
40
  export * from "./preview-scanner.js";
41
41
  export * from "./proposal-status.js";
42
+ export * from "./provisioning-command.js";
42
43
  export * from "./redirect-target.js";
43
44
  export * from "./responsive-images.js";
44
45
  export * from "./result.js";
@@ -65,4 +66,10 @@ export * from "./theme-render.js";
65
66
  export * from "./themes.js";
66
67
  export * from "./themes-errors.js";
67
68
  export * from "./url.js";
68
- export { CAELO_VERSION, CALEO_VERSION, type CaeloVersion, parseVersion } from "./version.js";
69
+ export {
70
+ CAELO_VERSION,
71
+ CALEO_VERSION,
72
+ type CaeloVersion,
73
+ isNewerVersion,
74
+ parseVersion,
75
+ } from "./version.js";
@@ -733,7 +733,7 @@ export function composePageWithLayout(input: ComposeWithLayoutInput): ComposeOut
733
733
  const slug = input.layoutSlug ?? "(unknown)";
734
734
  throw new ComposeError(
735
735
  "layout-missing-content",
736
- `layout "${slug}" is missing the required \`<caelo-slot name="content">\` slot — fix via /security/layouts`,
736
+ `layout "${slug}" is missing the required \`<caelo-slot name="content">\` slot — fix via /settings/layouts`,
737
737
  input.layoutSlug,
738
738
  );
739
739
  }
@@ -0,0 +1,78 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ import { describe, expect, it } from "bun:test";
4
+ import {
5
+ installHintFromSiteUrl,
6
+ provisioningCommand,
7
+ provisioningUpgradeCommand,
8
+ } from "./provisioning-command.js";
9
+ import { isNewerVersion } from "./version.js";
10
+
11
+ describe("provisioningCommand", () => {
12
+ it("pins the package to an exact version", () => {
13
+ expect(provisioningCommand("0.10.44", ["status"])).toBe(
14
+ "bunx @caelo-cms/provisioning@0.10.44 status",
15
+ );
16
+ expect(provisioningCommand("v0.10.44")).toBe("bunx @caelo-cms/provisioning@0.10.44");
17
+ });
18
+
19
+ it("refuses a tag or anything else that is not a version", () => {
20
+ expect(() => provisioningCommand("latest")).toThrow(/invalid Caelo version/);
21
+ expect(() => provisioningCommand("0.10")).toThrow(/invalid Caelo version/);
22
+ });
23
+ });
24
+
25
+ describe("provisioningUpgradeCommand", () => {
26
+ it("runs the target release's CLI, rolls exactly that release, on exactly this install", () => {
27
+ expect(provisioningUpgradeCommand({ version: "0.10.44", install: "example.com" })).toBe(
28
+ "bunx @caelo-cms/provisioning@0.10.44 upgrade --version 0.10.44 --install example.com",
29
+ );
30
+ });
31
+
32
+ it("accepts a v-prefixed tag and a pre-release", () => {
33
+ expect(provisioningUpgradeCommand({ version: "v0.11.0-rc.1", install: "gcp-acme" })).toBe(
34
+ "bunx @caelo-cms/provisioning@0.11.0-rc.1 upgrade --version 0.11.0-rc.1 --install gcp-acme",
35
+ );
36
+ });
37
+
38
+ it("refuses an install hint the shell could interpret", () => {
39
+ for (const install of ["example.com; rm -rf ~", "$(id)", "a b", "", "Example.com"]) {
40
+ expect(() => provisioningUpgradeCommand({ version: "0.10.44", install })).toThrow(
41
+ /not a domain/,
42
+ );
43
+ }
44
+ });
45
+ });
46
+
47
+ describe("installHintFromSiteUrl", () => {
48
+ it("names the install by its site's domain", () => {
49
+ expect(installHintFromSiteUrl("https://Example.com")).toBe("example.com");
50
+ expect(installHintFromSiteUrl("https://www.example.co.uk/")).toBe("www.example.co.uk");
51
+ });
52
+
53
+ it("returns null for something that is not a URL", () => {
54
+ expect(installHintFromSiteUrl("example.com")).toBeNull();
55
+ expect(installHintFromSiteUrl("")).toBeNull();
56
+ });
57
+ });
58
+
59
+ describe("isNewerVersion", () => {
60
+ it("orders by major, minor, patch", () => {
61
+ expect(isNewerVersion("0.10.44", "0.10.43")).toBe(true);
62
+ expect(isNewerVersion("0.11.0", "0.10.99")).toBe(true);
63
+ expect(isNewerVersion("1.0.0", "0.99.99")).toBe(true);
64
+ expect(isNewerVersion("0.10.43", "0.10.43")).toBe(false);
65
+ expect(isNewerVersion("0.10.42", "0.10.43")).toBe(false);
66
+ });
67
+
68
+ it("sorts a pre-release before its stable", () => {
69
+ expect(isNewerVersion("0.11.0-rc.1", "0.11.0")).toBe(false);
70
+ expect(isNewerVersion("0.11.0", "0.11.0-rc.1")).toBe(true);
71
+ expect(isNewerVersion("0.11.0-rc.2", "0.11.0-rc.1")).toBe(true);
72
+ });
73
+
74
+ it("ignores a leading v and treats a non-version as never newer", () => {
75
+ expect(isNewerVersion("v0.10.44", "0.10.43")).toBe(true);
76
+ expect(isNewerVersion("nightly", "0.10.43")).toBe(false);
77
+ });
78
+ });
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+
3
+ /**
4
+ * The `bunx @caelo-cms/provisioning …` commands Caelo prints — in the CLI's
5
+ * own next-step hints, `status`, and the admin's Settings → Updates page
6
+ * (issue #696 Phase 0).
7
+ *
8
+ * Every command names an exact package version. Without one, `bunx` reuses
9
+ * whatever copy of the CLI it cached first and never looks again, so an
10
+ * operator who installed months ago would upgrade with months-old upgrade
11
+ * logic (the same trap #552 fixed for the MCP shim). The version is always
12
+ * one the caller knows for certain: the running CLI's own, or the release
13
+ * the release check reported — never "whatever is newest".
14
+ */
15
+
16
+ import { parseVersion } from "./version.js";
17
+
18
+ /** npm name of the provisioning CLI. */
19
+ export const PROVISIONING_PACKAGE = "@caelo-cms/provisioning";
20
+
21
+ /**
22
+ * A hostname the `--install` flag can carry into a copy-pasted shell
23
+ * command: letters, digits, dots and hyphens only, so nothing in it can be
24
+ * read by the shell.
25
+ */
26
+ const SAFE_INSTALL_HINT = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*$/;
27
+
28
+ /**
29
+ * `bunx @caelo-cms/provisioning@<version> <args>`.
30
+ *
31
+ * @param version an exact release (`0.10.44`, a leading `v` is dropped);
32
+ * throws on anything that is not a version, so a tag like `latest` can
33
+ * never slip into a printed command.
34
+ * @param args the CLI arguments, already split.
35
+ * @example provisioningCommand("0.10.44", ["status"])
36
+ * // → "bunx @caelo-cms/provisioning@0.10.44 status"
37
+ */
38
+ export function provisioningCommand(version: string, args: readonly string[] = []): string {
39
+ const exact = parseVersion(version.replace(/^v/, "")).raw;
40
+ return [`bunx ${PROVISIONING_PACKAGE}@${exact}`, ...args].join(" ");
41
+ }
42
+
43
+ /**
44
+ * The upgrade command for one install, pinned twice: the CLI runs as the
45
+ * target release (its upgrade logic matches the release it installs) and
46
+ * `--version` makes it roll exactly that release, even if a newer one ships
47
+ * before the operator runs it (without the flag, `upgrade` rolls the
48
+ * registry's `latest` tag).
49
+ *
50
+ * @param args.version the release to install.
51
+ * @param args.install the install's domain (or install id); `upgrade`
52
+ * matches either against `~/.caelo-<id>/install.json`.
53
+ * @example provisioningUpgradeCommand({ version: "0.10.44", install: "example.com" })
54
+ * // → "bunx @caelo-cms/provisioning@0.10.44 upgrade --version 0.10.44 --install example.com"
55
+ */
56
+ export function provisioningUpgradeCommand(args: { version: string; install: string }): string {
57
+ const version = parseVersion(args.version.replace(/^v/, "")).raw;
58
+ if (!SAFE_INSTALL_HINT.test(args.install)) {
59
+ throw new Error(`not a domain or install id: ${JSON.stringify(args.install)}`);
60
+ }
61
+ return provisioningCommand(version, ["upgrade", "--version", version, "--install", args.install]);
62
+ }
63
+
64
+ /**
65
+ * The `--install` value for the install whose public site is `siteUrl`
66
+ * (the admin's `CAELO_SITE_URL`, which the stacks set to
67
+ * `https://<domain>` — the same domain `install.json` records).
68
+ *
69
+ * @returns the lower-cased hostname, or null when `siteUrl` is not a URL
70
+ * or its host is not a plain domain (an IP literal with a port, say).
71
+ */
72
+ export function installHintFromSiteUrl(siteUrl: string): string | null {
73
+ let host: string;
74
+ try {
75
+ host = new URL(siteUrl).hostname.toLowerCase();
76
+ } catch {
77
+ return null;
78
+ }
79
+ return SAFE_INSTALL_HINT.test(host) ? host : null;
80
+ }
package/src/subagents.ts CHANGED
@@ -50,7 +50,7 @@ export const EXPECTED_RETURN_SHAPES: readonly ExpectedReturnShape[] = expectedRe
50
50
  * The tier VOCABULARY is deliberately abstract — tool results and
51
51
  * editor-facing text never name the underlying model (CLAUDE.md §2:
52
52
  * provider brand never surfaces in the editor chat UI); concrete model
53
- * ids appear only on Owner surfaces (security panel, cost dashboard).
53
+ * ids appear only on Owner surfaces (Settings panel, cost dashboard).
54
54
  */
55
55
  export const subagentModelTier = z.enum(["inherit", "mid", "small"]);
56
56
  export type SubagentModelTier = z.infer<typeof subagentModelTier>;
package/src/version.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * 5. Pushes — GitHub Actions takes over for the publish step.
12
12
  *
13
13
  * Read by:
14
- * - admin's /security index page (header badge + about-modal),
14
+ * - admin's /settings index page (header badge + about-modal),
15
15
  * - cms-provision CLI's `version` subcommand,
16
16
  * - User-Agent header on plugin-host's outbound HTTP calls,
17
17
  * - Tier-1 manifest signing (the version is hashed into the
@@ -22,7 +22,7 @@
22
22
  * follow standard SemVer.
23
23
  */
24
24
 
25
- export const CAELO_VERSION = "0.10.43";
25
+ export const CAELO_VERSION = "0.10.46";
26
26
 
27
27
  /**
28
28
  * Deprecated alias for back-compat — early P17 work spelled this
@@ -64,3 +64,33 @@ export function parseVersion(raw: string = CAELO_VERSION): CaeloVersion {
64
64
  raw,
65
65
  };
66
66
  }
67
+
68
+ /**
69
+ * True when release `latest` is newer than `current` (both SemVer
70
+ * `X.Y.Z[-pre]`; a leading `v` is ignored). A pre-release sorts before the
71
+ * same X.Y.Z stable; two pre-releases of one X.Y.Z compare as strings.
72
+ * Mirrors the ordering in scripts/release.ts so the "update available"
73
+ * notice agrees with the release pipeline. A string that is not a version
74
+ * is never newer.
75
+ *
76
+ * @example isNewerVersion("0.10.44", "0.10.43") // → true
77
+ */
78
+ export function isNewerVersion(latest: string, current: string): boolean {
79
+ const parse = (v: string): CaeloVersion | null => {
80
+ try {
81
+ return parseVersion(v.replace(/^v/, ""));
82
+ } catch {
83
+ return null;
84
+ }
85
+ };
86
+ const a = parse(latest);
87
+ const b = parse(current);
88
+ if (!a || !b) return false;
89
+ for (const k of ["major", "minor", "patch"] as const) {
90
+ if (a[k] !== b[k]) return a[k] > b[k];
91
+ }
92
+ if (a.preRelease && !b.preRelease) return false;
93
+ if (!a.preRelease && b.preRelease) return true;
94
+ if (a.preRelease && b.preRelease) return a.preRelease > b.preRelease;
95
+ return false;
96
+ }