@siteoshq/cli 1.6.0 → 1.7.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/dist/cli.js CHANGED
@@ -484,7 +484,8 @@ var siteOSServiceAudienceSchema = z.enum([
484
484
  "siteos-forms",
485
485
  "siteos-pulse",
486
486
  "siteos-search",
487
- "siteos-seo"
487
+ "siteos-seo",
488
+ "siteos-analytics"
488
489
  ]);
489
490
  var siteOSServiceScopeSchema = z.string().min(1).max(160).regex(/^[a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*){2,}$/);
490
491
  var scopeNamespaceByAudience = {
@@ -495,7 +496,8 @@ var scopeNamespaceByAudience = {
495
496
  "siteos-forms": "forms",
496
497
  "siteos-pulse": "pulse",
497
498
  "siteos-search": "search",
498
- "siteos-seo": "seo"
499
+ "siteos-seo": "seo",
500
+ "siteos-analytics": "analytics"
499
501
  };
500
502
  function matchingServiceScopes(value, context) {
501
503
  const prefix = `${scopeNamespaceByAudience[value.audience]}:`;
@@ -1445,7 +1447,8 @@ var PROJECT_SERVICES = [
1445
1447
  "forms",
1446
1448
  "search",
1447
1449
  "trace",
1448
- "seo"
1450
+ "seo",
1451
+ "analytics"
1449
1452
  ];
1450
1453
  var ProjectSchema = z4.object({
1451
1454
  id: z4.string().min(1),
@@ -1499,7 +1502,7 @@ var OverviewSchema = z4.object({
1499
1502
  });
1500
1503
  function createProjectApi(input) {
1501
1504
  const origin = resolveSiteOSAuthBaseUrl(input.env);
1502
- async function request(path31, schema, body, method) {
1505
+ async function request(path32, schema, body, method) {
1503
1506
  const scope = body === void 0 ? "projects:workspace:read" : "projects:workspace:write";
1504
1507
  const grant = await input.grants.acquire({
1505
1508
  audience: "siteos-projects",
@@ -1513,7 +1516,7 @@ function createProjectApi(input) {
1513
1516
  message: "SiteOS API access is unavailable."
1514
1517
  });
1515
1518
  const response = await input.fetchImpl(
1516
- `${origin}/api/projects/v1/projects${path31}`,
1519
+ `${origin}/api/projects/v1/projects${path32}`,
1517
1520
  {
1518
1521
  method: method ?? (body === void 0 ? "GET" : "POST"),
1519
1522
  headers: {
@@ -1709,14 +1712,14 @@ async function commonServiceContext(options, service, environmentSlug) {
1709
1712
  const binding = attachment.environments.find(
1710
1713
  (item) => item.environmentId === context.environment.id
1711
1714
  );
1712
- if (["pulse", "cookie", "seo"].includes(service) && !binding)
1715
+ if (["pulse", "cookie", "seo", "analytics"].includes(service) && !binding)
1713
1716
  throw new SiteOSAuthApiError({
1714
1717
  code: "PROJECT_ENVIRONMENT_NOT_CONFIGURED",
1715
1718
  message: `Set up ${service} in ${context.environment.name} with \`siteos project connect ${service}\`.`
1716
1719
  });
1717
1720
  return {
1718
1721
  ...context,
1719
- resourceId: ["pulse", "cookie", "seo"].includes(service) ? binding.resourceId : attachment.resourceId,
1722
+ resourceId: ["pulse", "cookie", "seo", "analytics"].includes(service) ? binding.resourceId : attachment.resourceId,
1720
1723
  environmentBinding: binding
1721
1724
  };
1722
1725
  }
@@ -9229,13 +9232,13 @@ Usage:
9229
9232
  siteos project use <id-or-slug> [--json]
9230
9233
  siteos project update [--name <name>] [--slug <slug>] [--url <production-url>] [--json]
9231
9234
  siteos project status [--json]
9232
- siteos project connect <pulse|cookie|forms|search|trace|seo> [--resource <id>] [--json]
9235
+ siteos project connect <pulse|cookie|forms|search|trace|seo|analytics> [--resource <id>] [--json]
9233
9236
  siteos project environment list [--json]
9234
9237
  siteos project environment create --name <name> --slug <slug> [--url <url>] [--json]
9235
9238
  siteos project environment use <slug> [--json]
9236
9239
  siteos project environment update <slug> [--name <name>] [--url <url>] [--json]
9237
- siteos project environment resources <pulse|cookie|forms|search|trace|seo> [--json]
9238
- siteos project environment connect <pulse|cookie|forms|search|trace|seo> --environment <slug> [--resource <id>] [--json]
9240
+ siteos project environment resources <pulse|cookie|forms|search|trace|seo|analytics> [--json]
9241
+ siteos project environment connect <pulse|cookie|forms|search|trace|seo|analytics> --environment <slug> [--resource <id>] [--json]
9239
9242
 
9240
9243
  Select a Project once per repository. Service commands use its configured resources.
9241
9244
  Connect creates a draft workspace, or explicitly attaches an existing resource.
@@ -9333,7 +9336,7 @@ Run \`siteos project status\` to inspect its services.`;
9333
9336
  const service = parsed.positionals[0];
9334
9337
  if (!PROJECT_SERVICES.includes(service))
9335
9338
  throw new Error(
9336
- "Choose pulse, cookie, forms, search, trace or seo. Organization connections use `siteos integrations`. "
9339
+ "Choose pulse, cookie, forms, search, trace, seo or analytics. Organization connections use `siteos integrations`. "
9337
9340
  );
9338
9341
  overview = await api.connect(
9339
9342
  overview.project.id,
@@ -9389,7 +9392,9 @@ async function runEnvironmentCommand3(options) {
9389
9392
  throw new Error("Environment create requires --name and --slug.");
9390
9393
  const service = positionals[0];
9391
9394
  if ((action === "connect" || action === "resources") && !PROJECT_SERVICES.includes(service))
9392
- throw new Error("Choose pulse, cookie, forms, search, trace or seo.");
9395
+ throw new Error(
9396
+ "Choose pulse, cookie, forms, search, trace, seo or analytics."
9397
+ );
9393
9398
  if (action === "connect" && !values.environment)
9394
9399
  throw new Error("Choose a Project environment with --environment <slug>.");
9395
9400
  const context = await readCommonProject(
@@ -9836,19 +9841,376 @@ async function runServiceCommand(service, options) {
9836
9841
  }
9837
9842
  }
9838
9843
 
9844
+ // src/services/analytics-command.ts
9845
+ import { readFile as readFile17, stat as stat2 } from "fs/promises";
9846
+ import path29 from "path";
9847
+ import { parseArgs as parseArgs4 } from "util";
9848
+ import { z as z16 } from "zod";
9849
+ var ANALYTICS_HELP = `Set up website Analytics in the selected Project environment.
9850
+
9851
+ Usage:
9852
+ siteos analytics status [--environment <slug>] [--json]
9853
+ siteos analytics installation [--environment <slug>] [--json]
9854
+ siteos analytics report [--days <1|7|28>] [--event <name>] [--country <ISO|unknown>] [--campaign <id>] [--environment <slug>] [--json]
9855
+ siteos analytics realtime [--country <ISO|unknown>] [--campaign <id>] [--environment <slug>] [--json]
9856
+ siteos analytics settings show [--environment <slug>] [--json]
9857
+ siteos analytics settings set --revision <number> [--enabled <true|false>] [--cookie-events <true|false>] [--minimal-realtime <true|false>] [--environment <slug>] [--json]
9858
+ siteos analytics events list [--environment <slug>] [--json]
9859
+ siteos analytics events create --file <event.json> [--environment <slug>] [--json]
9860
+ siteos analytics events snippet <name> [--environment <slug>] [--json]
9861
+ siteos analytics campaigns list [--environment <slug>] [--json]
9862
+ siteos analytics campaigns create --file <campaign.json> [--environment <slug>] [--json]
9863
+ siteos analytics goals list [--environment <slug>] [--json]
9864
+ siteos analytics goals create --file <goal.json> [--environment <slug>] [--json]
9865
+ siteos analytics goals archive <id> [--environment <slug>] [--json]
9866
+ siteos analytics funnels list [--environment <slug>] [--json]
9867
+ siteos analytics funnels create --file <funnel.json> [--environment <slug>] [--json]
9868
+ siteos analytics funnels archive <id> [--environment <slug>] [--json]
9869
+ siteos analytics monitoring prepare [--environment <slug>] [--json]
9870
+
9871
+ Connect first: siteos project connect analytics. Cookie and Trace are optional.
9872
+ Use project environment use <slug> to select an environment; no resource fallback is allowed.
9873
+ Analytics starts independently. Consent control is configured and published in Cookie.
9874
+ An external CMP can gate its loader explicitly; privacy opt-outs still apply.
9875
+ Settings require an owner/admin, the current revision and at least one change.
9876
+ Catalog files are bounded JSON using the documented event/campaign/goal/funnel contracts.
9877
+ Monitoring preparation creates a Trace draft only; publication remains a separate operation.
9878
+ Analytics is Unlimited during early access. No synthetic events are sent by these commands.`;
9879
+ var EventInput = z16.object({
9880
+ name: z16.string().regex(/^[a-z][a-z0-9_]{0,63}$/u).refine((v) => v !== "pageview" && !v.startsWith("cookie_")),
9881
+ label: z16.string().trim().min(1).max(80),
9882
+ properties: z16.record(
9883
+ z16.string().regex(/^[a-z][a-z0-9_]{0,39}$/u),
9884
+ z16.array(
9885
+ z16.string().min(1).max(80).regex(/^[a-zA-Z0-9 _./:-]+$/u)
9886
+ ).min(1).max(20)
9887
+ ).refine((v) => Object.keys(v).length <= 8)
9888
+ }).strict();
9889
+ var Resource = z16.object({
9890
+ id: z16.string(),
9891
+ organizationId: z16.string(),
9892
+ name: z16.string(),
9893
+ slug: z16.string(),
9894
+ origin: z16.string().url(),
9895
+ publicKey: z16.string(),
9896
+ revision: z16.number().int().positive(),
9897
+ enabled: z16.boolean(),
9898
+ cookieEvents: z16.boolean(),
9899
+ minimalRealtime: z16.boolean()
9900
+ });
9901
+ var readKeys = [
9902
+ "days",
9903
+ "from",
9904
+ "to",
9905
+ "totals",
9906
+ "trend",
9907
+ "pages",
9908
+ "sources",
9909
+ "countries",
9910
+ "countryDetectionConfigured",
9911
+ "countryFilter",
9912
+ "campaignFilter",
9913
+ "eventCounts",
9914
+ "recent",
9915
+ "lastReceivedAt",
9916
+ "campaigns",
9917
+ "goals",
9918
+ "funnels",
9919
+ "convertedVisits"
9920
+ ];
9921
+ var liveKeys = [
9922
+ "at",
9923
+ "from",
9924
+ "activeSince",
9925
+ "collectionEnabled",
9926
+ "countryFilter",
9927
+ "campaignFilter",
9928
+ "activeVisitors",
9929
+ "visitors",
9930
+ "pageviews",
9931
+ "events",
9932
+ "trend",
9933
+ "pages",
9934
+ "sources",
9935
+ "countries",
9936
+ "eventCounts",
9937
+ "minimal"
9938
+ ];
9939
+ var pick = (value, keys) => Object.fromEntries(
9940
+ keys.filter((key) => Object.hasOwn(value, key)).map((key) => [key, value[key]])
9941
+ );
9942
+ async function runAnalyticsCommand(options) {
9943
+ if (!options.args.length || options.args.some((v) => ["--help", "-h"].includes(v)))
9944
+ return { exitCode: 0, stdout: ANALYTICS_HELP };
9945
+ const json = options.args.includes("--json");
9946
+ try {
9947
+ const { positionals, values } = parseArgs4({
9948
+ args: options.args,
9949
+ strict: true,
9950
+ allowPositionals: true,
9951
+ options: {
9952
+ json: { type: "boolean" },
9953
+ environment: { type: "string" },
9954
+ days: { type: "string" },
9955
+ event: { type: "string" },
9956
+ country: { type: "string" },
9957
+ campaign: { type: "string" },
9958
+ revision: { type: "string" },
9959
+ enabled: { type: "string" },
9960
+ "cookie-events": { type: "string" },
9961
+ "minimal-realtime": { type: "string" },
9962
+ file: { type: "string" }
9963
+ }
9964
+ });
9965
+ const route = positionals.slice(0, 2).join(" "), action = positionals[0];
9966
+ const operations = {
9967
+ status: { args: 1, flags: [] },
9968
+ installation: { args: 1, flags: [] },
9969
+ report: { args: 1, flags: ["days", "event", "country", "campaign"] },
9970
+ realtime: { args: 1, flags: ["country", "campaign"] },
9971
+ "settings show": { args: 2, flags: [] },
9972
+ "settings set": {
9973
+ args: 2,
9974
+ flags: ["revision", "enabled", "cookie-events", "minimal-realtime"],
9975
+ scope: "analytics:workspace:write"
9976
+ },
9977
+ "events list": { args: 2, flags: [] },
9978
+ "events snippet": { args: 3, flags: [] },
9979
+ "events create": {
9980
+ args: 2,
9981
+ flags: ["file"],
9982
+ scope: "analytics:events:write"
9983
+ },
9984
+ "campaigns list": { args: 2, flags: [] },
9985
+ "campaigns create": {
9986
+ args: 2,
9987
+ flags: ["file"],
9988
+ scope: "analytics:reports:write"
9989
+ },
9990
+ "goals list": { args: 2, flags: [] },
9991
+ "goals create": {
9992
+ args: 2,
9993
+ flags: ["file"],
9994
+ scope: "analytics:reports:write"
9995
+ },
9996
+ "goals archive": { args: 3, flags: [], scope: "analytics:reports:write" },
9997
+ "funnels list": { args: 2, flags: [] },
9998
+ "funnels create": {
9999
+ args: 2,
10000
+ flags: ["file"],
10001
+ scope: "analytics:reports:write"
10002
+ },
10003
+ "funnels archive": {
10004
+ args: 3,
10005
+ flags: [],
10006
+ scope: "analytics:reports:write"
10007
+ },
10008
+ "monitoring prepare": {
10009
+ args: 2,
10010
+ flags: [],
10011
+ scope: "analytics:monitoring:write"
10012
+ }
10013
+ };
10014
+ const operation = operations[route];
10015
+ if (!operation || positionals.length !== operation.args || Object.keys(values).some(
10016
+ (key) => !["json", "environment", ...operation.flags].includes(key)
10017
+ ))
10018
+ throw new Error(
10019
+ "Invalid Analytics operation or flags. Run `siteos analytics --help`."
10020
+ );
10021
+ if (values.days && !["1", "7", "28"].includes(values.days))
10022
+ throw new Error("Use --days 1, 7 or 28.");
10023
+ if (values.country && !/^(?:[A-Z]{2}|unknown)$/u.test(values.country))
10024
+ throw new Error("Use an uppercase ISO country code or unknown.");
10025
+ if (values.campaign) z16.string().uuid().parse(values.campaign);
10026
+ const setting = route === "settings set", creating = positionals[1] === "create", archive = positionals[1] === "archive";
10027
+ let body;
10028
+ if (setting) {
10029
+ if (!/^[1-9]\d{0,8}$/u.test(values.revision ?? ""))
10030
+ throw new Error(
10031
+ "Read settings first and supply the current positive --revision."
10032
+ );
10033
+ body = { revision: Number(values.revision) };
10034
+ for (const [flag, key] of [
10035
+ ["enabled", "enabled"],
10036
+ ["cookie-events", "cookieEvents"],
10037
+ ["minimal-realtime", "minimalRealtime"]
10038
+ ]) {
10039
+ if (values[flag] === void 0) continue;
10040
+ if (!["true", "false"].includes(values[flag]))
10041
+ throw new Error(`Use --${flag} true|false.`);
10042
+ body[key] = values[flag] === "true";
10043
+ }
10044
+ if (Object.keys(body).length < 2)
10045
+ throw new Error("Provide at least one setting to change.");
10046
+ } else if (creating) {
10047
+ if (!values.file)
10048
+ throw new Error("Creation requires --file with a JSON definition.");
10049
+ const filename = path29.resolve(options.cwd ?? process.cwd(), values.file);
10050
+ if ((await stat2(filename)).size > 16384)
10051
+ throw new Error("The definition must not exceed 16 KiB.");
10052
+ const text = await readFile17(filename, "utf8");
10053
+ if (Buffer.byteLength(text) > 16384)
10054
+ throw new Error("The definition must not exceed 16 KiB.");
10055
+ let definition;
10056
+ try {
10057
+ definition = JSON.parse(text);
10058
+ } catch {
10059
+ throw new Error("The definition must contain valid JSON.");
10060
+ }
10061
+ body = z16.record(z16.unknown()).parse(definition);
10062
+ if (action === "events") body = EventInput.parse(body);
10063
+ } else if (archive) body = { id: z16.string().uuid().parse(positionals[2]) };
10064
+ const context = await commonServiceContext(
10065
+ options,
10066
+ "analytics",
10067
+ values.environment
10068
+ );
10069
+ if (!context)
10070
+ throw new Error(
10071
+ "Select a SiteOS Project with `siteos project use` first."
10072
+ );
10073
+ const runtime = commonProjectRuntime(options), scope = operation.scope ?? "analytics:workspace:read";
10074
+ const grant = await runtime.grants.acquire({
10075
+ audience: "siteos-analytics",
10076
+ scopes: [scope]
10077
+ });
10078
+ if (grant.grant.audience !== "siteos-analytics" || grant.grant.scopes.length !== 1 || grant.grant.scopes[0] !== scope || grant.grant.organizationId !== context.overview.project.organizationId)
10079
+ throw new SiteOSAuthApiError({
10080
+ code: "AUTH_INVALID_RESPONSE",
10081
+ message: "The Analytics grant does not match this Project and operation."
10082
+ });
10083
+ const query = new URLSearchParams();
10084
+ for (const key of ["days", "event", "country", "campaign"])
10085
+ if (values[key]) query.set(key, values[key]);
10086
+ const suffix = action === "realtime" ? `/realtime?${query}` : setting ? "/settings" : creating || archive ? `/${action}` : route === "monitoring prepare" ? "/monitoring" : `?${query}`;
10087
+ if (!options.fetchImpl)
10088
+ throw new Error("SiteOS API access is unavailable.");
10089
+ const response = await options.fetchImpl(
10090
+ `${runtime.api.origin}/api/analytics/v1/resources/${encodeURIComponent(context.resourceId)}${suffix}`,
10091
+ {
10092
+ method: setting ? "PATCH" : archive ? "DELETE" : operation.scope ? "POST" : "GET",
10093
+ headers: {
10094
+ Accept: "application/json",
10095
+ Authorization: `Bearer ${grant.accessToken}`,
10096
+ ...body ? { "Content-Type": "application/json" } : {}
10097
+ },
10098
+ ...body ? { body: JSON.stringify(body) } : {},
10099
+ signal: AbortSignal.timeout(3e4)
10100
+ }
10101
+ );
10102
+ const value = await response.json();
10103
+ if (!response.ok) {
10104
+ const error = z16.object({
10105
+ error: z16.object({ code: z16.string(), message: z16.string().max(500) })
10106
+ }).safeParse(value);
10107
+ throw new SiteOSAuthApiError({
10108
+ code: error.success ? error.data.error.code : "ANALYTICS_REQUEST_FAILED",
10109
+ message: error.success ? error.data.error.message : "The Analytics request failed.",
10110
+ status: response.status
10111
+ });
10112
+ }
10113
+ const record = z16.object({
10114
+ contractVersion: z16.literal(1),
10115
+ resourceId: z16.literal(context.resourceId)
10116
+ }).passthrough().parse(value);
10117
+ let output;
10118
+ if (setting) {
10119
+ const saved = Resource.parse(record);
10120
+ if (saved.id !== context.resourceId || saved.organizationId !== context.overview.project.organizationId || saved.revision !== Number(values.revision) + 1 || Object.entries(body).some(
10121
+ ([key, val]) => key !== "revision" && saved[key] !== val
10122
+ ))
10123
+ throw new Error("The saved settings do not match this change.");
10124
+ output = saved;
10125
+ } else if (creating) {
10126
+ if (action === "events") {
10127
+ output = EventInput.parse(
10128
+ pick(record, ["name", "label", "properties"])
10129
+ );
10130
+ if (JSON.stringify(output) !== JSON.stringify(body))
10131
+ throw new Error("The saved event does not match this definition.");
10132
+ } else
10133
+ output = {
10134
+ id: z16.string().uuid().parse(record.id),
10135
+ ...pick(
10136
+ record,
10137
+ action === "campaigns" ? ["label", "source", "medium", "campaign"] : action === "goals" ? ["label", "match"] : ["label", "steps"]
10138
+ )
10139
+ };
10140
+ } else if (archive)
10141
+ output = { archived: z16.literal(true).parse(record.archived) };
10142
+ else if (route === "monitoring prepare")
10143
+ output = { state: z16.literal("prepared").parse(record.state) };
10144
+ else if (action === "realtime") output = pick(record, liveKeys);
10145
+ else {
10146
+ const resource = Resource.parse(record.resource);
10147
+ if (resource.id !== context.resourceId || resource.organizationId !== context.overview.project.organizationId)
10148
+ throw new Error(
10149
+ "The Analytics response belongs to a different resource."
10150
+ );
10151
+ if (action === "status")
10152
+ output = {
10153
+ resource,
10154
+ lastReceivedAt: record.lastReceivedAt,
10155
+ consentSource: z16.object({
10156
+ consentSource: z16.enum([
10157
+ "cookie",
10158
+ "external",
10159
+ "waiting_for_cookie",
10160
+ "not_required"
10161
+ ])
10162
+ }).parse(record.configuration).consentSource
10163
+ };
10164
+ else if (route === "settings show") output = resource;
10165
+ else if (action === "installation")
10166
+ output = {
10167
+ resource,
10168
+ installation: z16.string().parse(record.installation),
10169
+ configuration: record.configuration
10170
+ };
10171
+ else if (action === "events") {
10172
+ const definitions = z16.array(EventInput).parse(record.definitions);
10173
+ if (route === "events snippet") {
10174
+ const definition = definitions.find((d) => d.name === positionals[2]);
10175
+ if (!definition)
10176
+ throw new Error(
10177
+ "Create the event definition before requesting its snippet."
10178
+ );
10179
+ output = {
10180
+ name: definition.name,
10181
+ javascript: `window.SiteOSAnalytics?.track(${JSON.stringify(definition.name)}, ${JSON.stringify(Object.fromEntries(Object.entries(definition.properties).map(([key, values2]) => [key, values2[0]])))});`,
10182
+ guidance: "Trigger only after the business action succeeds; substitute declared categorical values."
10183
+ };
10184
+ } else output = { definitions, eventCounts: record.eventCounts };
10185
+ } else
10186
+ output = action === "report" ? pick(record, readKeys) : { [action]: z16.array(z16.unknown()).parse(record[action]) };
10187
+ }
10188
+ return { exitCode: 0, stdout: JSON.stringify(output, null, 2) };
10189
+ } catch (cause) {
10190
+ const error = cause instanceof SiteOSAuthApiError ? { code: cause.code, message: cause.message } : {
10191
+ code: "ANALYTICS_COMMAND_FAILED",
10192
+ message: cause instanceof z16.ZodError ? "The Analytics input or response does not match the supported contract." : cause instanceof Error ? cause.message : "The Analytics command failed."
10193
+ };
10194
+ return {
10195
+ exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
10196
+ ...json ? { stdout: JSON.stringify({ error }) } : { stderr: error.message }
10197
+ };
10198
+ }
10199
+ }
10200
+
9839
10201
  // src/services/seo-command.ts
9840
10202
  import { writeFile as writeFile9 } from "fs/promises";
9841
- import path30 from "path";
9842
- import { parseArgs as parseArgs5 } from "util";
9843
- import { z as z17 } from "zod";
10203
+ import path31 from "path";
10204
+ import { parseArgs as parseArgs6 } from "util";
10205
+ import { z as z18 } from "zod";
9844
10206
 
9845
10207
  // src/services/seo-performance-command.ts
9846
10208
  import { randomUUID as randomUUID6 } from "crypto";
9847
10209
  import { writeFile as writeFile8 } from "fs/promises";
9848
- import path29 from "path";
10210
+ import path30 from "path";
9849
10211
  import { setTimeout as setTimeout2 } from "timers/promises";
9850
- import { parseArgs as parseArgs4 } from "util";
9851
- import { z as z16 } from "zod";
10212
+ import { parseArgs as parseArgs5 } from "util";
10213
+ import { z as z17 } from "zod";
9852
10214
  var PERFORMANCE_HELP = `
9853
10215
  siteos seo performance run --audit <id> --url <url> [--url <url>...] [--device <mobile|desktop>] [--idempotency-key <key>] [--environment <slug>] [--json]
9854
10216
  siteos seo performance list [--device <mobile|desktop>] [--environment <slug>] [--json]
@@ -9865,7 +10227,7 @@ Reuse the returned idempotency key after an uncertain run response. Wait default
9865
10227
  async function runSeoPerformanceCommand(options) {
9866
10228
  let idempotencyKey;
9867
10229
  try {
9868
- const { values, positionals } = parseArgs4({
10230
+ const { values, positionals } = parseArgs5({
9869
10231
  args: options.args.slice(1),
9870
10232
  strict: true,
9871
10233
  allowPositionals: true,
@@ -9936,12 +10298,12 @@ async function runSeoPerformanceCommand(options) {
9936
10298
  const runtime = commonProjectRuntime(options);
9937
10299
  const writing = ["run", "cancel"].includes(action);
9938
10300
  const scope = writing ? "seo:audits:write" : "seo:workspace:read";
9939
- const batchSchema = z16.object({
9940
- id: z16.string(),
9941
- resourceId: z16.literal(context.resourceId),
9942
- organizationId: z16.literal(context.overview.project.organizationId),
9943
- sourceAuditId: z16.string(),
9944
- state: z16.enum([
10301
+ const batchSchema = z17.object({
10302
+ id: z17.string(),
10303
+ resourceId: z17.literal(context.resourceId),
10304
+ organizationId: z17.literal(context.overview.project.organizationId),
10305
+ sourceAuditId: z17.string(),
10306
+ state: z17.enum([
9945
10307
  "queued",
9946
10308
  "running",
9947
10309
  "completed",
@@ -9949,8 +10311,8 @@ async function runSeoPerformanceCommand(options) {
9949
10311
  "failed",
9950
10312
  "cancelled"
9951
10313
  ]),
9952
- device: z16.enum(["mobile", "desktop"]),
9953
- urls: z16.array(z16.string()).min(1).max(10)
10314
+ device: z17.enum(["mobile", "desktop"]),
10315
+ urls: z17.array(z17.string()).min(1).max(10)
9954
10316
  }).passthrough();
9955
10317
  const query = new URLSearchParams();
9956
10318
  if (values.device) query.set("device", values.device);
@@ -9998,17 +10360,17 @@ async function runSeoPerformanceCommand(options) {
9998
10360
  );
9999
10361
  const text = await response.text();
10000
10362
  if (values.format === "json") {
10001
- const parsed = z16.object({
10002
- contractVersion: z16.literal(1),
10363
+ const parsed = z17.object({
10364
+ contractVersion: z17.literal(1),
10003
10365
  batch: batchSchema,
10004
- pages: z16.array(z16.unknown())
10366
+ pages: z17.array(z17.unknown())
10005
10367
  }).parse(JSON.parse(text));
10006
10368
  if (parsed.batch.id !== id)
10007
10369
  throw new Error(
10008
10370
  "The export response does not match the selected check."
10009
10371
  );
10010
10372
  }
10011
- const output = path29.resolve(
10373
+ const output = path30.resolve(
10012
10374
  options.cwd ?? process.cwd(),
10013
10375
  values.output
10014
10376
  );
@@ -10024,8 +10386,8 @@ async function runSeoPerformanceCommand(options) {
10024
10386
  }
10025
10387
  const data = await response.json();
10026
10388
  if (!response.ok) {
10027
- const error = z16.object({
10028
- error: z16.object({ code: z16.string(), message: z16.string().max(500) })
10389
+ const error = z17.object({
10390
+ error: z17.object({ code: z17.string(), message: z17.string().max(500) })
10029
10391
  }).safeParse(data);
10030
10392
  throw new SiteOSAuthApiError({
10031
10393
  code: error.success ? error.data.error.code : "SEO_REQUEST_FAILED",
@@ -10033,8 +10395,8 @@ async function runSeoPerformanceCommand(options) {
10033
10395
  status: response.status
10034
10396
  });
10035
10397
  }
10036
- const record = z16.object({ contractVersion: z16.literal(1) }).passthrough().parse(data);
10037
- if (action === "cancel") z16.literal(true).parse(record.cancelled);
10398
+ const record = z17.object({ contractVersion: z17.literal(1) }).passthrough().parse(data);
10399
+ if (action === "cancel") z17.literal(true).parse(record.cancelled);
10038
10400
  else if (action === "run") {
10039
10401
  const accepted = batchSchema.parse(record.batch);
10040
10402
  const normalize = (url) => {
@@ -10047,8 +10409,8 @@ async function runSeoPerformanceCommand(options) {
10047
10409
  "The queued check does not match the requested source, device and URLs."
10048
10410
  );
10049
10411
  } else {
10050
- z16.literal(context.resourceId).parse(record.resourceId);
10051
- z16.array(batchSchema).parse(record.batches);
10412
+ z17.literal(context.resourceId).parse(record.resourceId);
10413
+ z17.array(batchSchema).parse(record.batches);
10052
10414
  const selected = batchSchema.nullable().parse(record.batch);
10053
10415
  if (id && selected?.id !== id)
10054
10416
  throw new Error(
@@ -10075,7 +10437,7 @@ async function runSeoPerformanceCommand(options) {
10075
10437
  } catch (cause) {
10076
10438
  const error = {
10077
10439
  code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
10078
- message: cause instanceof z16.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The performance command failed."
10440
+ message: cause instanceof z17.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The performance command failed."
10079
10441
  };
10080
10442
  return {
10081
10443
  exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
@@ -10128,7 +10490,7 @@ async function runSeoCommand(options) {
10128
10490
  return runSeoPerformanceCommand(options);
10129
10491
  const json = options.args.includes("--json");
10130
10492
  try {
10131
- const { positionals, values } = parseArgs5({
10493
+ const { positionals, values } = parseArgs6({
10132
10494
  args: options.args,
10133
10495
  strict: true,
10134
10496
  allowPositionals: true,
@@ -10341,17 +10703,17 @@ async function runSeoCommand(options) {
10341
10703
  );
10342
10704
  const text = await response.text();
10343
10705
  if (values.format === "json")
10344
- z17.object({
10345
- contractVersion: z17.literal(1),
10346
- audit: z17.object({
10347
- id: z17.literal(values.audit),
10348
- resourceId: z17.literal(context.resourceId)
10706
+ z18.object({
10707
+ contractVersion: z18.literal(1),
10708
+ audit: z18.object({
10709
+ id: z18.literal(values.audit),
10710
+ resourceId: z18.literal(context.resourceId)
10349
10711
  }),
10350
- kind: z17.literal(values.kind),
10351
- totalRows: z17.literal(rows),
10352
- rows: z17.array(z17.unknown()).length(rows)
10712
+ kind: z18.literal(values.kind),
10713
+ totalRows: z18.literal(rows),
10714
+ rows: z18.array(z18.unknown()).length(rows)
10353
10715
  }).parse(JSON.parse(text));
10354
- const output = path30.resolve(options.cwd ?? process.cwd(), values.output);
10716
+ const output = path31.resolve(options.cwd ?? process.cwd(), values.output);
10355
10717
  await writeFile9(output, text, { flag: "wx", mode: 384 });
10356
10718
  return {
10357
10719
  exitCode: 0,
@@ -10370,8 +10732,8 @@ async function runSeoCommand(options) {
10370
10732
  }
10371
10733
  const data = await response.json();
10372
10734
  if (!response.ok) {
10373
- const result = z17.object({
10374
- error: z17.object({ code: z17.string(), message: z17.string().max(500) })
10735
+ const result = z18.object({
10736
+ error: z18.object({ code: z18.string(), message: z18.string().max(500) })
10375
10737
  }).safeParse(data);
10376
10738
  throw new SiteOSAuthApiError({
10377
10739
  code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
@@ -10379,34 +10741,34 @@ async function runSeoCommand(options) {
10379
10741
  status: response.status
10380
10742
  });
10381
10743
  }
10382
- const record = z17.object({ contractVersion: z17.literal(1) }).passthrough().parse(data);
10744
+ const record = z18.object({ contractVersion: z18.literal(1) }).passthrough().parse(data);
10383
10745
  if (automation) {
10384
- z17.literal(context.resourceId).parse(record.resourceId);
10385
- const schedule = z17.object({
10386
- enabled: z17.boolean(),
10387
- weekday: z17.number().int().min(1).max(7),
10388
- time: z17.string(),
10389
- timeZone: z17.string(),
10390
- revision: z17.number().int().min(0),
10391
- nextRunAt: z17.string().nullable()
10746
+ z18.literal(context.resourceId).parse(record.resourceId);
10747
+ const schedule = z18.object({
10748
+ enabled: z18.boolean(),
10749
+ weekday: z18.number().int().min(1).max(7),
10750
+ time: z18.string(),
10751
+ timeZone: z18.string(),
10752
+ revision: z18.number().int().min(0),
10753
+ nextRunAt: z18.string().nullable()
10392
10754
  });
10393
- const notificationRoute = z17.object({
10394
- enabled: z17.boolean(),
10395
- minimumSeverity: z17.enum(["error", "warning"]),
10396
- includeFailures: z17.boolean(),
10397
- revision: z17.number().int().min(0),
10398
- destinationId: z17.string().nullable()
10755
+ const notificationRoute = z18.object({
10756
+ enabled: z18.boolean(),
10757
+ minimumSeverity: z18.enum(["error", "warning"]),
10758
+ includeFailures: z18.boolean(),
10759
+ revision: z18.number().int().min(0),
10760
+ destinationId: z18.string().nullable()
10399
10761
  });
10400
10762
  if (retryNotification) {
10401
- z17.literal(true).parse(record.retryQueued);
10402
- z17.literal(positionals[2]).parse(record.notificationId);
10763
+ z18.literal(true).parse(record.retryQueued);
10764
+ z18.literal(positionals[2]).parse(record.notificationId);
10403
10765
  } else if (route === "notifications destinations")
10404
- z17.object({
10405
- candidates: z17.array(
10406
- z17.object({
10407
- candidateId: z17.string(),
10408
- label: z17.string(),
10409
- availability: z17.literal("available")
10766
+ z18.object({
10767
+ candidates: z18.array(
10768
+ z18.object({
10769
+ candidateId: z18.string(),
10770
+ label: z18.string(),
10771
+ availability: z18.literal("available")
10410
10772
  })
10411
10773
  )
10412
10774
  }).parse(record);
@@ -10419,46 +10781,46 @@ async function runSeoCommand(options) {
10419
10781
  notificationRoute.parse(record.route);
10420
10782
  }
10421
10783
  } else if (!writing) {
10422
- const validated = z17.object({
10423
- resource: z17.object({
10424
- id: z17.literal(context.resourceId),
10425
- organizationId: z17.literal(context.overview.project.organizationId)
10784
+ const validated = z18.object({
10785
+ resource: z18.object({
10786
+ id: z18.literal(context.resourceId),
10787
+ organizationId: z18.literal(context.overview.project.organizationId)
10426
10788
  }),
10427
- audits: z17.array(z17.object({ id: z17.string() }).passthrough()),
10428
- audit: z17.object({
10429
- id: z17.string(),
10430
- resourceId: z17.literal(context.resourceId)
10789
+ audits: z18.array(z18.object({ id: z18.string() }).passthrough()),
10790
+ audit: z18.object({
10791
+ id: z18.string(),
10792
+ resourceId: z18.literal(context.resourceId)
10431
10793
  }).passthrough().nullable(),
10432
- pages: z17.array(z17.unknown()),
10433
- issues: z17.array(z17.unknown()),
10434
- changes: z17.array(z17.unknown()),
10435
- totalChanges: z17.number(),
10436
- dispositions: z17.array(z17.unknown())
10794
+ pages: z18.array(z18.unknown()),
10795
+ issues: z18.array(z18.unknown()),
10796
+ changes: z18.array(z18.unknown()),
10797
+ totalChanges: z18.number(),
10798
+ dispositions: z18.array(z18.unknown())
10437
10799
  }).passthrough().parse(record);
10438
10800
  const selected = query.get("audit");
10439
10801
  if (selected && validated.audit?.id !== selected)
10440
10802
  throw new Error("The SEO response does not match the requested audit.");
10441
10803
  } else if (record.audit)
10442
- z17.object({
10443
- id: z17.string(),
10444
- resourceId: z17.literal(context.resourceId),
10445
- organizationId: z17.literal(context.overview.project.organizationId),
10446
- state: z17.literal("queued")
10804
+ z18.object({
10805
+ id: z18.string(),
10806
+ resourceId: z18.literal(context.resourceId),
10807
+ organizationId: z18.literal(context.overview.project.organizationId),
10808
+ state: z18.literal("queued")
10447
10809
  }).parse(record.audit);
10448
- else if (route === "audit cancel") z17.literal(true).parse(record.cancelled);
10810
+ else if (route === "audit cancel") z18.literal(true).parse(record.cancelled);
10449
10811
  else if (action === "issue")
10450
- z17.object({
10451
- url: z17.literal(values.url),
10452
- ruleId: z17.literal(values.rule),
10453
- ignored: z17.literal(positionals[1] === "ignore"),
10454
- revision: z17.literal(Number(values.revision) + 1)
10812
+ z18.object({
10813
+ url: z18.literal(values.url),
10814
+ ruleId: z18.literal(values.rule),
10815
+ ignored: z18.literal(positionals[1] === "ignore"),
10816
+ revision: z18.literal(Number(values.revision) + 1)
10455
10817
  }).parse(record.disposition);
10456
10818
  else throw new Error("The SEO service returned an invalid response.");
10457
10819
  return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
10458
10820
  } catch (cause) {
10459
10821
  const error = {
10460
10822
  code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
10461
- message: cause instanceof z17.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
10823
+ message: cause instanceof z18.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
10462
10824
  };
10463
10825
  return {
10464
10826
  exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
@@ -10500,6 +10862,12 @@ if (!command || command === "--help" || command === "-h") {
10500
10862
  }
10501
10863
  function rootCommandRegistry() {
10502
10864
  return createCommandRegistry({
10865
+ analytics: (args2) => runAnalyticsCommand({
10866
+ args: args2,
10867
+ cwd: process.cwd(),
10868
+ env: process.env,
10869
+ fetchImpl: globalThis.fetch
10870
+ }),
10503
10871
  seo: (args2) => runSeoCommand({
10504
10872
  args: args2,
10505
10873
  cwd: process.cwd(),
@@ -10613,6 +10981,7 @@ Usage:
10613
10981
  siteos project --help
10614
10982
  siteos cookie --help
10615
10983
  siteos trace --help
10984
+ siteos analytics --help
10616
10985
  siteos seo --help
10617
10986
  siteos integrations --help
10618
10987
  siteos pulse --help
@@ -10625,6 +10994,7 @@ Commands:
10625
10994
  project Select one Project and configure its services and environments.
10626
10995
  cookie Configure, publish, and inspect the Project\u2019s cookie banner.
10627
10996
  trace Configure analytics observation and inspect evidence.
10997
+ analytics Configure website Analytics, events and realtime reports.
10628
10998
  seo Audit HTML, inspect changes and verify fixes.
10629
10999
  integrations Manage Organization connections and destinations.
10630
11000
  pulse Manage monitoring checks, tests, and deployments.