sporades 0.9.38 โ†’ 0.9.40

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/CHANGES.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changes
2
2
 
3
+ ## v0.9.40 - 2026-10-23
4
+
5
+ Changes since v0.9.39.
6
+
7
+ ### ๐Ÿ“ Documentation
8
+
9
+ - Upgrade Capsule base to Node 24 LTS and normalize npm pack JSON (#288) (8f5ecc5b).
10
+
11
+ ### ๐Ÿงช Tests
12
+
13
+ - Describe SQLite inspection columns without StatementSync#columns() (#287) (9b29b3d5).
14
+
15
+ ## v0.9.39 - 2026-10-22
16
+
17
+ Changes since v0.9.38.
18
+
19
+ ### ๐Ÿš€ Features
20
+
21
+ - Add read-only sporades host db list|dump|query (#286) (c9f0d358).
22
+
23
+ ## Unreleased
24
+
25
+ ### ๐Ÿ“ฆ Packaging
26
+
27
+ - Upgrade the default Capsule Base image to `0.3.0-node24-alpine`, using Node
28
+ 24.22.1 LTS on Alpine 3.23 with a pinned upstream image digest. Upgrade the CLI
29
+ and Host helper and redeploy to replace existing containers; restarting a
30
+ release preserves its pinned image. Keep explicit image overrides unchanged.
31
+ - Normalize npm pack JSON metadata across flat and package-keyed formats in
32
+ release verification and packaged compatibility checks, preserving artifact
33
+ hashes and rejecting ambiguous output.
34
+
35
+ ### ๐Ÿš€ Features
36
+
37
+ - Add read-only `sporades host db list|dump|query` for running Hosted Capsules (#285).
38
+ It runs the Host helper over the operator's existing SSH access and the
39
+ generated Bundle's one-shot action seam, opens the Capsule SQLite file
40
+ read-only and query-only without stopping the Capsule, and refuses mutating
41
+ SQL both locally and in the runtime. `--json` output matches
42
+ `sporades db list|dump|query --json` against a Dev session. `--subname`
43
+ defaults to the project's Host binding. Requires `sporades host upgrade` and
44
+ a redeploy with this CLI; no public route is added.
45
+
46
+ ### ๐Ÿ› Bug Fixes
47
+
48
+ - Fix `sporades host db query` failing with "columns is not a function" on
49
+ Hosted Capsules. The previous Base image ran Node 22.14, whose `node:sqlite` lacks
50
+ `StatementSync#columns()` (added in 22.16). SQLite inspection queries now
51
+ describe their columns without it, including zero-row results, so Hosted,
52
+ Dev and MCP ops queries on Node 22.13โ€“22.15 return the same
53
+ `{ ok, data: { columns, rows }, error }` envelope. Requires a redeploy with
54
+ this CLI.
55
+
3
56
  ## v0.9.38 - 2026-10-22
4
57
 
5
58
  Changes since v0.9.37.
package/Dockerfile.base CHANGED
@@ -1,12 +1,12 @@
1
- FROM node:22.14-alpine
1
+ FROM node:24.22.1-alpine3.23@sha256:0468540aa58203ff9e40c2aab3907c52644ab818af6176b1c70a026137dc92a2
2
2
 
3
3
  LABEL org.opencontainers.image.title="Sporades Base Image"
4
- LABEL org.opencontainers.image.description="Thin Node 22 Base image for Sporades Container sessions and Hosted Capsules"
5
- LABEL org.opencontainers.image.version="0.2.0-node22-alpine"
4
+ LABEL org.opencontainers.image.description="Thin Node 24 LTS Base image for Sporades Container sessions and Hosted Capsules"
5
+ LABEL org.opencontainers.image.version="0.3.0-node24-alpine"
6
6
  LABEL org.opencontainers.image.source="https://github.com/sporades/sporades"
7
7
  LABEL com.sporades.base-image.name="sporades-base"
8
- LABEL com.sporades.base-image.version="0.2.0-node22-alpine"
9
- LABEL com.sporades.base-image.node="22"
8
+ LABEL com.sporades.base-image.version="0.3.0-node24-alpine"
9
+ LABEL com.sporades.base-image.node="24"
10
10
 
11
11
  RUN apk add --no-cache openssh-server fail2ban clamav clamav-daemon \
12
12
  && addgroup -g 10001 -S sporades \
@@ -256,8 +256,8 @@ var init_base_image = __esm({
256
256
  "use strict";
257
257
  SPORADES_BASE_IMAGE = {
258
258
  name: "sporades-base",
259
- image: "ghcr.io/sporades/sporades-base:0.2.0-node22-alpine",
260
- version: "0.2.0-node22-alpine",
259
+ image: "ghcr.io/sporades/sporades-base:0.3.0-node24-alpine",
260
+ version: "0.3.0-node24-alpine",
261
261
  runtimeUser: "sporades",
262
262
  runtimeUid: 10001,
263
263
  runtimeGid: 10001,
@@ -46183,7 +46183,7 @@ var mutationExecution = new MutationExecutionStorage();
46183
46183
  init_cli_support();
46184
46184
 
46185
46185
  // src/cli/cli-version.ts
46186
- var CLI_VERSION = "0.9.38";
46186
+ var CLI_VERSION = "0.9.40";
46187
46187
 
46188
46188
  // src/cli/schedule-inspection-envelope.ts
46189
46189
  var SCHEDULE_DIAGNOSTIC_FIELDS = /* @__PURE__ */ new Set([
@@ -46258,6 +46258,40 @@ function sanitizeScheduleInspectionEnvelope(envelope, invalid4) {
46258
46258
  return { ok: true, data: { capsule: { name: envelope.data.capsule.name }, schedules }, error: null };
46259
46259
  }
46260
46260
 
46261
+ // src/cli/database-inspection-envelope.ts
46262
+ var DATABASE_INSPECTION_ACTIONS = ["db.list", "db.dump", "db.query"];
46263
+ var DATABASE_INSPECTION_INPUT_BYTE_LIMIT = 24 * 1024;
46264
+ function encodeDatabaseInspectionInput(sql) {
46265
+ const encoded = Buffer.from(JSON.stringify({ sql }), "utf8").toString("base64url");
46266
+ return encoded.length <= DATABASE_INSPECTION_INPUT_BYTE_LIMIT ? encoded : null;
46267
+ }
46268
+ var DATABASE_INSPECTION_MAX_BUFFER = 128 * 1024 * 1024;
46269
+ function isDatabaseInspectionAction(action) {
46270
+ return DATABASE_INSPECTION_ACTIONS.includes(action);
46271
+ }
46272
+ function sanitizeDatabaseInspectionEnvelope(action, envelope, invalid4) {
46273
+ const data2 = envelope?.data;
46274
+ if (envelope?.ok === true && data2 && typeof data2 === "object" && !Array.isArray(data2)) {
46275
+ if (action === "db.query" && Array.isArray(data2.columns) && Array.isArray(data2.rows)) {
46276
+ return { ok: true, data: { columns: data2.columns, rows: data2.rows }, error: null };
46277
+ }
46278
+ if (action !== "db.query" && Array.isArray(data2.tables)) {
46279
+ return { ok: true, data: { tables: data2.tables }, error: null };
46280
+ }
46281
+ }
46282
+ if (envelope?.ok === false && typeof envelope.error?.message === "string") {
46283
+ return {
46284
+ ok: false,
46285
+ data: null,
46286
+ error: {
46287
+ message: envelope.error.message,
46288
+ hint: typeof envelope.error.hint === "string" ? envelope.error.hint : "Check the SQL and retry the command."
46289
+ }
46290
+ };
46291
+ }
46292
+ return invalid4();
46293
+ }
46294
+
46261
46295
  // src/cli/host-helper-archive.ts
46262
46296
  import { spawnSync } from "node:child_process";
46263
46297
  import { readFileSync } from "node:fs";
@@ -48806,6 +48840,10 @@ async function dispatchMain(request2) {
48806
48840
  runCapsuleAccessKeyAction(request2);
48807
48841
  return;
48808
48842
  }
48843
+ if (isDatabaseInspectionAction(request2.action)) {
48844
+ inspectCapsuleDatabase(request2);
48845
+ return;
48846
+ }
48809
48847
  if (request2.action === "host.stats") {
48810
48848
  await statsHost(request2);
48811
48849
  return;
@@ -48852,6 +48890,19 @@ function runCapsuleAccessKeyAction(request2) {
48852
48890
  Buffer.from(JSON.stringify(accessKeys), "utf8").toString("base64url")
48853
48891
  ]);
48854
48892
  }
48893
+ function inspectCapsuleDatabase(request2) {
48894
+ const action = request2.action;
48895
+ if (!isDatabaseInspectionAction(action)) throw helperError("Unsupported Host helper action.", "Update the Host helper or use a supported Sporades host command.");
48896
+ const exactKeys2 = (value, keys2) => Object.keys(value).length === keys2.length && Object.keys(value).every((key) => keys2.includes(key));
48897
+ const query = action === "db.query";
48898
+ const sql = request2.db?.sql;
48899
+ if (!exactKeys2(request2, query ? ["action", "host", "capsule", "db"] : ["action", "host", "capsule"]) || !request2.host || typeof request2.host !== "object" || Array.isArray(request2.host) || !exactKeys2(request2.host, ["alias", "domain", "scheme", "remoteRoot"]) || !request2.capsule || typeof request2.capsule !== "object" || Array.isArray(request2.capsule) || !exactKeys2(request2.capsule, ["subname"]) || query && (!exactKeys2(request2.db ?? {}, ["sql"]) || typeof sql !== "string" || !encodeDatabaseInspectionInput(sql))) {
48900
+ throw helperError("Invalid Hosted database inspection request.", "Upgrade the local Sporades CLI and Host helper together.");
48901
+ }
48902
+ inspectCapsuleRuntime(request2, action, "database", (envelope) => sanitizeDatabaseInspectionEnvelope(action, envelope, () => {
48903
+ throw helperError("Hosted database inspection returned an invalid response.", "Run `sporades host upgrade`, redeploy the Capsule, and retry the command.");
48904
+ }), query ? ["--sporades-action-input", encodeDatabaseInspectionInput(sql)] : []);
48905
+ }
48855
48906
  function inspectCapsuleRuntime(request2, action, label, sanitize = (envelope) => envelope, extraArgs = [], preserveBoundedDiagnostics = false) {
48856
48907
  const containerName = createHostedContainerName(request2.host.domain, request2.capsule.subname);
48857
48908
  if (!checkContainerRunning(containerName)) {
@@ -48860,7 +48911,7 @@ function inspectCapsuleRuntime(request2, action, label, sanitize = (envelope) =>
48860
48911
  throw error;
48861
48912
  }
48862
48913
  const result = runDocker(["exec", containerName, "node", "/app/server.mjs", "--sporades-action", action, ...extraArgs], {
48863
- maxBuffer: label === "Access-key" ? ACCESS_KEY_OPERATOR_PROCESS_MAX_BUFFER : void 0
48914
+ maxBuffer: label === "Access-key" ? ACCESS_KEY_OPERATOR_PROCESS_MAX_BUFFER : label === "database" ? DATABASE_INSPECTION_MAX_BUFFER : void 0
48864
48915
  });
48865
48916
  let envelope;
48866
48917
  try {
package/bin/sporades.js CHANGED
@@ -124294,7 +124294,7 @@ function createSharedDatabaseAdapterMethods(dialect) {
124294
124294
  (columnMetadata) => thenIfPromise(statement.all(), (allRows) => ({
124295
124295
  ok: true,
124296
124296
  data: {
124297
- columns: columnMetadata.map((column) => column.name),
124297
+ columns: columnMetadata?.map((column) => column.name) ?? Object.keys(allRows[0] ?? {}),
124298
124298
  rows: allRows.filter((row) => !isInternalLogIndexMetadataRow(row, sql3))
124299
124299
  },
124300
124300
  error: null
@@ -124315,6 +124315,19 @@ function createSharedDatabaseAdapterMethods(dialect) {
124315
124315
  }
124316
124316
  };
124317
124317
  }
124318
+ function describeSqliteColumns(connection, statement, sql2) {
124319
+ if (typeof statement.columns === "function") return statement.columns();
124320
+ let row;
124321
+ try {
124322
+ row = connection.prepare(
124323
+ `SELECT __sporades_columns.* FROM (SELECT 1) LEFT JOIN (${sqlWithoutTrailingTerminator(sql2)}
124324
+ ) AS __sporades_columns ON 1 LIMIT 1`
124325
+ ).get();
124326
+ } catch {
124327
+ return null;
124328
+ }
124329
+ return Object.keys(row ?? {}).map((name2) => ({ name: name2 }));
124330
+ }
124318
124331
  async function createSqliteDatabaseAdapter(databasePath, options = {}) {
124319
124332
  const { DatabaseSync } = await import("node:sqlite");
124320
124333
  const path24 = await import("node:path");
@@ -124365,7 +124378,7 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
124365
124378
  }));
124366
124379
  },
124367
124380
  columns() {
124368
- return run2(() => useConnection(() => connection.prepare(sql2).columns()));
124381
+ return run2(() => useConnection(() => describeSqliteColumns(connection, connection.prepare(sql2), sql2)));
124369
124382
  }
124370
124383
  };
124371
124384
  }
@@ -124417,7 +124430,7 @@ async function createSqliteDatabaseAdapter(databasePath, options = {}) {
124417
124430
  recordLiveQueryStatementWrite(sql2, result);
124418
124431
  return result;
124419
124432
  },
124420
- columns: () => statement.columns()
124433
+ columns: () => describeSqliteColumns(dedicated, statement, sql2)
124421
124434
  });
124422
124435
  }
124423
124436
  };
@@ -133948,8 +133961,8 @@ function errorCode2(error) {
133948
133961
  // src/base-image.ts
133949
133962
  var SPORADES_BASE_IMAGE = {
133950
133963
  name: "sporades-base",
133951
- image: "ghcr.io/sporades/sporades-base:0.2.0-node22-alpine",
133952
- version: "0.2.0-node22-alpine",
133964
+ image: "ghcr.io/sporades/sporades-base:0.3.0-node24-alpine",
133965
+ version: "0.3.0-node24-alpine",
133953
133966
  runtimeUser: "sporades",
133954
133967
  runtimeUid: 10001,
133955
133968
  runtimeGid: 10001,
@@ -149965,6 +149978,8 @@ Capsule commands:
149965
149978
  Restore a stopped Capsule; optionally --to-subname <destination>
149966
149979
  backup status Report last successful backup age for each Capsule
149967
149980
  bind <subname> Bind this project to a Hosted Capsule
149981
+ db list|dump|query <sql> [--subname <name>]
149982
+ Read a running Hosted Capsule's database over SSH
149968
149983
  register <subname> Register a Hosted Capsule
149969
149984
  canonical set <subname> <alias>|clear <subname>
149970
149985
  Select the public origin; restart a running Capsule
@@ -150849,6 +150864,18 @@ function sanitizeScheduleInspectionEnvelope(envelope, invalid4) {
150849
150864
  return { ok: true, data: { capsule: { name: envelope.data.capsule.name }, schedules }, error: null };
150850
150865
  }
150851
150866
 
150867
+ // src/cli/database-inspection-envelope.ts
150868
+ var DATABASE_INSPECTION_ACTIONS = ["db.list", "db.dump", "db.query"];
150869
+ var DATABASE_INSPECTION_INPUT_BYTE_LIMIT = 24 * 1024;
150870
+ function encodeDatabaseInspectionInput(sql2) {
150871
+ const encoded = Buffer.from(JSON.stringify({ sql: sql2 }), "utf8").toString("base64url");
150872
+ return encoded.length <= DATABASE_INSPECTION_INPUT_BYTE_LIMIT ? encoded : null;
150873
+ }
150874
+ var DATABASE_INSPECTION_MAX_BUFFER = 128 * 1024 * 1024;
150875
+ function isDatabaseInspectionAction(action) {
150876
+ return DATABASE_INSPECTION_ACTIONS.includes(action);
150877
+ }
150878
+
150852
150879
  // src/cli/server-env-check.ts
150853
150880
  import path20 from "node:path";
150854
150881
  async function readProjectServerEnv(projectDir) {
@@ -153035,7 +153062,7 @@ jobs:
153035
153062
  }
153036
153063
 
153037
153064
  // src/cli/cli-version.ts
153038
- var CLI_VERSION = "0.9.38";
153065
+ var CLI_VERSION = "0.9.40";
153039
153066
 
153040
153067
  // src/cli/sporades.ts
153041
153068
  var SUPPORTED_TEMPLATES = new Set(CLIENT_TEMPLATES);
@@ -154261,7 +154288,7 @@ function parseHostArgs(args) {
154261
154288
  force = true;
154262
154289
  break;
154263
154290
  default:
154264
- if (arg.startsWith("--")) {
154291
+ if (arg.startsWith("--") && subcommand !== "db") {
154265
154292
  throw commandError2(
154266
154293
  `Unknown flag: ${arg}`,
154267
154294
  "Use `sporades host add`, `sporades host use`, `sporades host current`, `sporades host health`, `sporades host bind`, `sporades host register`, `sporades host rotate-key`, `sporades host unregister`, `sporades host delete`, `sporades host push`, `sporades host bootstrap`, `sporades host upgrade`, `sporades host list`, `sporades host releases`, `sporades host rollback`, `sporades host reconcile`, `sporades host stats`, `sporades host logs`, or `sporades host invoke`."
@@ -154437,6 +154464,16 @@ function parseHostArgs(args) {
154437
154464
  validateCapsuleSubname(subname);
154438
154465
  return { subcommand, ...parsePolicyOperation(positional), hostAlias, subname, json: json2, projectDir: process.cwd() };
154439
154466
  }
154467
+ case "db": {
154468
+ const [operation, ...sqlParts] = positional;
154469
+ const usage = "Use `sporades host db list`, `sporades host db dump`, or `sporades host db query <sql>`, with optional `--subname <name> --host <alias>`.";
154470
+ if (!["list", "dump", "query"].includes(operation)) throw commandError2(`Unknown host db command: ${operation ?? ""}`.trim(), usage);
154471
+ if (operation !== "query" && sqlParts.length > 0) throw commandError2("Too many positional arguments.", `Use \`sporades host db ${operation} --json\`.`);
154472
+ if (operation === "query" && sqlParts.length === 0) throw commandError2("Missing SQL query.", "Use `sporades host db query <sql>`.");
154473
+ if (hostAlias) validateHostAlias(hostAlias);
154474
+ if (subname) validateCapsuleSubname(subname);
154475
+ return { subcommand, operation, ...operation === "query" ? { sql: sqlParts.join(" ") } : {}, subname, hostAlias, json: json2, projectDir: process.cwd() };
154476
+ }
154440
154477
  case "jobs":
154441
154478
  if (positional.length > 0) throw commandError2("Too many positional arguments.", "Use `sporades host jobs --host <alias> --subname <name>`.");
154442
154479
  if (!hostAlias) throw commandError2("Missing Host profile alias.", "Pass `--host <alias>`.");
@@ -156856,6 +156893,31 @@ async function manageHost(options) {
156856
156893
  writeResult(result, !result.ok);
156857
156894
  return;
156858
156895
  }
156896
+ case "db": {
156897
+ if (options.operation === "query") {
156898
+ assertReadOnlyInspectionSql(options.sql);
156899
+ if (!encodeDatabaseInspectionInput(options.sql)) {
156900
+ throw commandError2("SQL query is too large for Hosted inspection.", "Keep `sporades host db query` SQL under about 16 KiB.");
156901
+ }
156902
+ }
156903
+ const config = await readHostConfig();
156904
+ const target = await resolveHostPushTarget(config, options);
156905
+ let result = invokeRemoteHostHelper({
156906
+ alias: target.alias,
156907
+ profile: target.profile,
156908
+ action: `db.${options.operation}`,
156909
+ subname: target.subname,
156910
+ projectDir: options.projectDir,
156911
+ ...options.operation === "query" ? { db: { sql: options.sql } } : {}
156912
+ });
156913
+ if (!result.ok && /Unsupported Host helper action/i.test(result.error.message)) {
156914
+ result = { ok: false, data: null, error: { code: "HOST_HELPER_UPGRADE_REQUIRED", message: "The Host server CLI does not support database inspection.", hint: `Run \`sporades host upgrade --host ${target.alias}\`, then retry the command.` } };
156915
+ } else if (!result.ok && /Unsupported Sporades runtime action/i.test(result.error.message)) {
156916
+ result = { ok: false, data: null, error: { code: "HOSTED_BUNDLE_UPGRADE_REQUIRED", message: "The Hosted Capsule's Bundle does not support database inspection.", hint: `Redeploy it with this CLI (\`sporades host push --restart --host ${target.alias}\`), then retry the command.` } };
156917
+ }
156918
+ printDatabaseInspection(options.operation, result, options.json);
156919
+ return;
156920
+ }
156859
156921
  case "jobs": {
156860
156922
  const config = await readHostConfig();
156861
156923
  const resolved = resolveHostProfile(config, options.hostAlias);
@@ -158196,21 +158258,24 @@ function inspectedSshPort(inspected) {
158196
158258
  return Number.isInteger(port) ? { host: entry.HostIp ?? null, port } : null;
158197
158259
  }
158198
158260
  async function inspectDatabase(options) {
158199
- if (options.subcommand === "query") {
158200
- const validation = validateReadOnlyInspectionSql(options.sql);
158201
- if (!validation.ok) {
158202
- throw commandError2(validation.error.message, validation.error.hint);
158203
- }
158261
+ if (options.subcommand === "query") assertReadOnlyInspectionSql(options.sql);
158262
+ printDatabaseInspection(options.subcommand, await fetchInspectionDatabase(options), options.json);
158263
+ }
158264
+ function assertReadOnlyInspectionSql(sql2) {
158265
+ const validation = validateReadOnlyInspectionSql(sql2);
158266
+ if (!validation.ok) {
158267
+ throw commandError2(validation.error.message, validation.error.hint);
158204
158268
  }
158205
- const result = await fetchInspectionDatabase(options);
158206
- if (options.json) {
158269
+ }
158270
+ function printDatabaseInspection(subcommand, result, json2) {
158271
+ if (json2) {
158207
158272
  writeResult(result, !result.ok);
158208
158273
  return;
158209
158274
  }
158210
158275
  if (!result.ok) {
158211
158276
  throw commandError2(result.error.message, result.error.hint);
158212
158277
  }
158213
- switch (options.subcommand) {
158278
+ switch (subcommand) {
158214
158279
  case "list":
158215
158280
  for (const table of result.data.tables) {
158216
158281
  process.stdout.write(`${table}
@@ -158859,6 +158924,7 @@ function invokeRemoteHostHelper(options) {
158859
158924
  if (options.accessKeys) {
158860
158925
  request.accessKeys = options.accessKeys;
158861
158926
  }
158927
+ if (options.db) request.db = options.db;
158862
158928
  if (options.verification) {
158863
158929
  request.verification = options.verification;
158864
158930
  }
@@ -158876,6 +158942,7 @@ function invokeRemoteHostHelper(options) {
158876
158942
  encoding: "utf8",
158877
158943
  ...String(options.action).startsWith("access-keys.") ? { maxBuffer: ACCESS_KEY_OPERATOR_PROCESS_MAX_BUFFER } : {},
158878
158944
  ...String(options.action).includes(".backup.") ? { maxBuffer: 16 * 1024 * 1024 } : {},
158945
+ ...isDatabaseInspectionAction(options.action) ? { maxBuffer: DATABASE_INSPECTION_MAX_BUFFER } : {},
158879
158946
  input: `${JSON.stringify(request)}
158880
158947
  `
158881
158948
  });
@@ -1,7 +1,7 @@
1
1
  export const SPORADES_BASE_IMAGE = {
2
2
  name: "sporades-base",
3
- image: "ghcr.io/sporades/sporades-base:0.2.0-node22-alpine",
4
- version: "0.2.0-node22-alpine",
3
+ image: "ghcr.io/sporades/sporades-base:0.3.0-node24-alpine",
4
+ version: "0.3.0-node24-alpine",
5
5
  runtimeUser: "sporades",
6
6
  runtimeUid: 10001,
7
7
  runtimeGid: 10001,
@@ -25,7 +25,7 @@ Options:
25
25
  readonly monitoring: "Usage: sporades monitoring stack <init|validate|upgrade|rollback|backup|restore> [options]\n sporades monitoring sender <issue|rotate|commit|cancel|revoke|export|status|legacy-revoke> [options]\n\nGenerate or inspect the versioned trace stack from an installed Sporades package.\nInitialization creates a reviewable directory; it does not start services.\nSender operations run locally on the Monitoring server; results contain no secrets.\nRotation stages a second generation; commit retires the old one after sender verification.\n\nOptions:\n --dir <path> Target stack directory (default: current directory)\n --backup <path> Protected cold snapshot directory (backup/restore)\n --baseline <path> Trusted prior release assets for a schema-3 upgrade\n --sender <name> Named sender for lifecycle operations (optional for status)\n --host <identity> Exact inventory Host scope (issue or legacy-revoke only)\n --out <path> New mode-0600 credential handoff file (export only)\n --generation <n> Verified pending generation to activate (commit only)\n --ingest Disable shared legacy ingestion (legacy-revoke only)\n --json Write { ok, data, error } JSON output\n --help, -h Show this help\n";
26
26
  readonly telemetry: "Usage: sporades telemetry profile <add|list|show|remove> [name] [options]\n\nRegister and inspect operator-owned OTLP/HTTP Telemetry profiles. Descriptors store only\ncredential environment and private CA file references; they never store secret values.\nDev sessions use --telemetry <name> first, then sporades.json telemetry.profile, then no export.\n\nOptions for profile add:\n --endpoint <url> OTLP/HTTP base origin (HTTPS, or HTTP loopback with --loopback)\n --dashboard <url> Optional dashboard HTTPS URL\n --credential-env <KEY> Environment variable containing the ingestion bearer token\n --inventory-credential-env <KEY> Exact Host-scoped lifecycle inventory token\n --inventory-host <id> Stable inventory identity (default: first connected domain)\n --metrics-interval-ms <N> Metrics export period, 5000-300000 ms (default 15000)\n --trace-propagation-origin <origin> Approve exact fetch origin (repeatable, max 32)\n --event-loop-delay-resolution-ms <N> Delay timer precision, 10-1000 ms (default 20)\n --ca-file <path> Absolute private CA certificate path for verified TLS\n --loopback Permit a local HTTP collector for development\n --json Write { ok, data, error } JSON output\n --help, -h Show this help\n";
27
27
  readonly deploy: "Usage: sporades deploy [status|stop|restart|reconcile|remove|reset|ssh] [options]\n\nStart and manage a local Container session.\n\nCommands:\n deploy Start a local Container session\n deploy status Print Container session status\n deploy stop Stop the running Container session\n deploy restart Restart the running Container session\n deploy policy publish <file>|remove\n Publish or remove the declared admission policy\n deploy reconcile Settle an interrupted deployment-file attempt\n deploy ssh Inspect effective Container SSH access\n deploy remove Remove the Container session\n deploy reset Remove the Container session and local container state\n\nOptions:\n --port <number> Published local port when starting\n --telemetry <name> Export HTTP traces using a registered Telemetry profile\n --no-telemetry Disable export for this Container session\n --force Replace stale or conflicting container state when starting\n --json Write JSON output\n --help, -h Show this help\n";
28
- readonly host: "Usage: sporades host <command> [options]\n\nManage Host profiles and Hosted Capsules.\n\nProfile commands:\n add <alias> Add a Host profile\n use <alias> Set the default Host profile\n current Print the selected Host profile\n bootstrap Provision the remote Host server\n upgrade Copy the local Host helper to the Host server\n health [subname] Check Host server or Hosted Capsule health\n services enable|disable|uninstall|status observability [--on <alias>]\n Manage pinned GlitchTip and Umami (opt-in)\n services connect <subname> [--host <Capsule-host>] [--on <ops-host>]\n Seal configured error/analytics env; push to activate\n services backup|backups observability [--on <alias>]\n Snapshot or list service databases using Host backup policy\n services restore observability --id <snapshot> --confirm observability\n Restore disabled services; enable again after inspection\n telemetry connect|migrate|reconcile|status|check\n telemetry inventory-export|inventory-reconcile\n Manage the shared Host Telemetry relay\n telemetry exports-disable|remove-agents\n Disable exports; remove agents after inventory acknowledgement\n telemetry resources-enable|resources-disable|resources-remove\n Manage Host OS and Caddy collection independently of Capsules\n telemetry enable|disable <subname>\n Change a Hosted Capsule's Telemetry opt-out\n\nCapsule commands:\n backup configure --file <policy.json>\n Configure encrypted backups and an optional Host timer\n backup create|list|prune --subname <name>\n Manage encrypted SQLite and files snapshots\n backup restore --subname <source> --id <full-id> --confirm <destination>\n Restore a stopped Capsule; optionally --to-subname <destination>\n backup status Report last successful backup age for each Capsule\n bind <subname> Bind this project to a Hosted Capsule\n register <subname> Register a Hosted Capsule\n canonical set <subname> <alias>|clear <subname>\n Select the public origin; restart a running Capsule\n push Push and install a Hosted Capsule release\n start <subname> Start a Hosted Capsule\n stop <subname> Stop a Hosted Capsule\n restart <subname> Restart a Hosted Capsule\n policy publish <file>|remove --host <alias> --subname <name>\n Publish or remove the recorded admission policy\n reconcile <subname> Settle an interrupted deployment-file attempt\n ssh [subname] Inspect effective Hosted Capsule SSH access\n stats [subname] Print Host server or Hosted Capsule stats\n logs [source] Print Hosted Capsule logs\n releases <subname> List Hosted Capsule releases\n rollback <subname> <release-id>\n Roll back to a previous release\n rotate-key <subname>\n Rotate Hosted Capsule Sealed Server env keys\n unregister <subname>\n Unregister a Hosted Capsule\n delete <subname> Delete Hosted Capsule storage\n\nOther commands:\n list List Hosted Capsules\n github workflow write\n Write a GitHub Actions deploy workflow\n invoke <action> Invoke a low-level remote Host helper action\n\nOptions:\n --host <alias> Host profile alias\n --profile <name> Verified HTTPS Telemetry profile for connect or migrate\n --query-credential-env <name>\n Ephemeral operator user:password for check or migrate\n --server <target> SSH target for host add\n --domain <domain> Hosted domain for host add\n --canonical-domain <hostname>\n Public HTTPS link origin for register\n --alias-domain <hostname>\n Custom HTTPS domain for register (repeatable)\n --remote-root <path>\n Remote root path for host add\n --tls <mode> TLS mode: automatic or cloudflare-origin\n --subname <name> Hosted Capsule subname\n --lines <n>, -n <n> Log line count\n --allow-missing-env Push despite missing required Server env keys\n --restart Restart after host push\n --verify Verify release health after host push\n --fallback-to-previous-release\n Roll back when verified push fails\n --branch <name> GitHub workflow branch\n --file <path> GitHub workflow output path\n --dry-run Print workflow without writing it\n --force Overwrite workflow output when writing it\n --json Write JSON output\n --help, -h Show this help\n";
28
+ readonly host: "Usage: sporades host <command> [options]\n\nManage Host profiles and Hosted Capsules.\n\nProfile commands:\n add <alias> Add a Host profile\n use <alias> Set the default Host profile\n current Print the selected Host profile\n bootstrap Provision the remote Host server\n upgrade Copy the local Host helper to the Host server\n health [subname] Check Host server or Hosted Capsule health\n services enable|disable|uninstall|status observability [--on <alias>]\n Manage pinned GlitchTip and Umami (opt-in)\n services connect <subname> [--host <Capsule-host>] [--on <ops-host>]\n Seal configured error/analytics env; push to activate\n services backup|backups observability [--on <alias>]\n Snapshot or list service databases using Host backup policy\n services restore observability --id <snapshot> --confirm observability\n Restore disabled services; enable again after inspection\n telemetry connect|migrate|reconcile|status|check\n telemetry inventory-export|inventory-reconcile\n Manage the shared Host Telemetry relay\n telemetry exports-disable|remove-agents\n Disable exports; remove agents after inventory acknowledgement\n telemetry resources-enable|resources-disable|resources-remove\n Manage Host OS and Caddy collection independently of Capsules\n telemetry enable|disable <subname>\n Change a Hosted Capsule's Telemetry opt-out\n\nCapsule commands:\n backup configure --file <policy.json>\n Configure encrypted backups and an optional Host timer\n backup create|list|prune --subname <name>\n Manage encrypted SQLite and files snapshots\n backup restore --subname <source> --id <full-id> --confirm <destination>\n Restore a stopped Capsule; optionally --to-subname <destination>\n backup status Report last successful backup age for each Capsule\n bind <subname> Bind this project to a Hosted Capsule\n db list|dump|query <sql> [--subname <name>]\n Read a running Hosted Capsule's database over SSH\n register <subname> Register a Hosted Capsule\n canonical set <subname> <alias>|clear <subname>\n Select the public origin; restart a running Capsule\n push Push and install a Hosted Capsule release\n start <subname> Start a Hosted Capsule\n stop <subname> Stop a Hosted Capsule\n restart <subname> Restart a Hosted Capsule\n policy publish <file>|remove --host <alias> --subname <name>\n Publish or remove the recorded admission policy\n reconcile <subname> Settle an interrupted deployment-file attempt\n ssh [subname] Inspect effective Hosted Capsule SSH access\n stats [subname] Print Host server or Hosted Capsule stats\n logs [source] Print Hosted Capsule logs\n releases <subname> List Hosted Capsule releases\n rollback <subname> <release-id>\n Roll back to a previous release\n rotate-key <subname>\n Rotate Hosted Capsule Sealed Server env keys\n unregister <subname>\n Unregister a Hosted Capsule\n delete <subname> Delete Hosted Capsule storage\n\nOther commands:\n list List Hosted Capsules\n github workflow write\n Write a GitHub Actions deploy workflow\n invoke <action> Invoke a low-level remote Host helper action\n\nOptions:\n --host <alias> Host profile alias\n --profile <name> Verified HTTPS Telemetry profile for connect or migrate\n --query-credential-env <name>\n Ephemeral operator user:password for check or migrate\n --server <target> SSH target for host add\n --domain <domain> Hosted domain for host add\n --canonical-domain <hostname>\n Public HTTPS link origin for register\n --alias-domain <hostname>\n Custom HTTPS domain for register (repeatable)\n --remote-root <path>\n Remote root path for host add\n --tls <mode> TLS mode: automatic or cloudflare-origin\n --subname <name> Hosted Capsule subname\n --lines <n>, -n <n> Log line count\n --allow-missing-env Push despite missing required Server env keys\n --restart Restart after host push\n --verify Verify release health after host push\n --fallback-to-previous-release\n Roll back when verified push fails\n --branch <name> GitHub workflow branch\n --file <path> GitHub workflow output path\n --dry-run Print workflow without writing it\n --force Overwrite workflow output when writing it\n --json Write JSON output\n --help, -h Show this help\n";
29
29
  readonly logs: "Usage: sporades logs [tail] [options]\n\nPrint Dev session logs.\n\nCommands:\n logs Print recent Dev session logs\n logs tail Follow Dev session logs\n\nOptions:\n --port <number> Target Dev session or local Container port\n --json Write JSON output\n --help, -h Show this help\n";
30
30
  readonly db: "Usage: sporades db <command> [options]\n\nInspect the Dev session database.\n\nCommands:\n list List database tables\n dump Dump database contents\n query <sql> Run a read-only SQL query\n\nOptions:\n --port <number> Target Dev session or local Container port\n --json Write JSON output\n --help, -h Show this help\n";
31
31
  readonly default: "Usage: sporades <command> [options]\n \n Commands:\n stripe-fake Run the local Team Billing test provider\n create <name> Scaffold a new Capsule\n dev Start a local Dev session\n auth Manage local auth configuration and simulation\n access-keys Inspect and retire Access keys through a running Capsule\n security Inspect effective Capsule security policy\n doctor Run read-only Sporades diagnostics\n env Manage Sealed Server env\n monitoring Generate and validate a monitoring stack\n telemetry Register operator Telemetry profiles\n deploy Start a local Container session\n host Manage Host profiles and Hosted Capsules\n logs Print Dev session logs\n db Inspect the Dev session database\n \n Options:\n --help, -h Show help for command\n --version, -v Show CLI version\n --host <alias> Show Host server CLI version with --version\n --json Write JSON output when supported by the command\n ";
@@ -46,6 +46,6 @@ Options:
46
46
  --no-git Skip git initialization
47
47
  --json Write JSON output
48
48
  --help, -h Show this help
49
- ` | "Usage: sporades dev [status|stop|reset] [options]\n\nStart and manage a local Dev session.\n\nCommands:\n dev Start a Dev session\n dev status Print Dev session status\n dev stop Stop the running Dev session\n dev reset Stop the Dev session and remove local Dev state\n\nOptions:\n --port <number> Dev session port when starting\n --public Allow non-localhost access when starting\n --telemetry <name> Export HTTP traces using a registered Telemetry profile\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades auth <command> [options]\n\nManage local auth configuration and identity simulation.\n\nCommands:\n status Print auth provider status\n clients List connected Dev session clients\n set <provider> Configure or disable anonymous, email, Google, Microsoft, Apple, or Facebook\n as email Simulate a local email identity\n\nOptions:\n --client-id <id> OAuth client/app ID (Apple Services ID)\n --client-secret <secret> OAuth client/app secret\n --client-json <path> Read provider-specific credentials JSON\n --tenant <tenant> Microsoft tenant (default: common)\n --team-id <id> Apple Developer Team ID\n --key-id <id> Apple Sign in key ID\n --private-key <pem> Apple private key (stored only in Server env)\n --graph-version <name> Facebook Graph API version\n --disable Disable the selected provider without changing siblings\n --email <address> Simulated email identity\n --display-name <name> Simulated display name\n --picture <url> Simulated profile picture URL\n --port <number> Target Dev session port\n --client <target> Delivery target: current, all, or a client ID\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades access-keys <command> [options]\n\nInspect and retire Access keys through a running Capsule.\n\nCommands:\n list --user-id <id> List one owner's Access-key metadata\n inspect <key-id> Inspect one Access key\n revoke <key-id> Revoke one Access key\n revoke-all --user-id <id> Revoke one owner's current Access keys\n delete <key-id> Delete revoked Access-key history\n\nOptions:\n --session <name> Session: dev, container, or hosted (default: dev)\n --host <alias> Host profile alias for a Hosted Capsule\n --subname <name> Hosted Capsule subname\n --cursor <cursor> Opaque list cursor\n --limit <n> List page size from 1 through 100\n --status <status> List active, expired, or revoked keys\n --yes Explicitly approve a destructive operation\n --json Write structured JSON; does not imply consent\n --help, -h Show this help\n" | "Usage: sporades security [options]\n\nInspect effective Capsule security policy.\n\nOptions:\n --session <name> Session: dev, public-dev, container, or hosted\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades doctor [options]\n\nRun read-only Sporades diagnostics.\n\nOptions:\n --session <name> Session: dev, public-dev, container, or hosted\n --host <alias> Host profile alias for Hosted Capsule checks\n --subname <name> Hosted Capsule subname\n --strict Exit non-zero on warnings as well as failures\n --json Write structured JSON output\n --help, -h Show this help\n" | "Usage: sporades env <command> [options]\n\nManage Sealed Server env.\n\nCommands:\n set <name> Read one value from stdin and seal it\n has <name> Test whether a Server env key is defined\n init Create local Sealed Server env key material\n import Import Server env values from a file\n status Print Sealed Server env status\n export Export Sealed Server env for a Host profile\n reencrypt Re-encrypt local Sealed Server env material\n\nOptions:\n --stdin Read the value for env set from stdin\n --file <path> Input file for import or export\n --host <alias> Host profile alias\n --subname <name> Hosted Capsule subname\n --output <path> Export output path\n --sealed Treat input as an already sealed export\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades monitoring stack <init|validate|upgrade|rollback|backup|restore> [options]\n sporades monitoring sender <issue|rotate|commit|cancel|revoke|export|status|legacy-revoke> [options]\n\nGenerate or inspect the versioned trace stack from an installed Sporades package.\nInitialization creates a reviewable directory; it does not start services.\nSender operations run locally on the Monitoring server; results contain no secrets.\nRotation stages a second generation; commit retires the old one after sender verification.\n\nOptions:\n --dir <path> Target stack directory (default: current directory)\n --backup <path> Protected cold snapshot directory (backup/restore)\n --baseline <path> Trusted prior release assets for a schema-3 upgrade\n --sender <name> Named sender for lifecycle operations (optional for status)\n --host <identity> Exact inventory Host scope (issue or legacy-revoke only)\n --out <path> New mode-0600 credential handoff file (export only)\n --generation <n> Verified pending generation to activate (commit only)\n --ingest Disable shared legacy ingestion (legacy-revoke only)\n --json Write { ok, data, error } JSON output\n --help, -h Show this help\n" | "Usage: sporades telemetry profile <add|list|show|remove> [name] [options]\n\nRegister and inspect operator-owned OTLP/HTTP Telemetry profiles. Descriptors store only\ncredential environment and private CA file references; they never store secret values.\nDev sessions use --telemetry <name> first, then sporades.json telemetry.profile, then no export.\n\nOptions for profile add:\n --endpoint <url> OTLP/HTTP base origin (HTTPS, or HTTP loopback with --loopback)\n --dashboard <url> Optional dashboard HTTPS URL\n --credential-env <KEY> Environment variable containing the ingestion bearer token\n --inventory-credential-env <KEY> Exact Host-scoped lifecycle inventory token\n --inventory-host <id> Stable inventory identity (default: first connected domain)\n --metrics-interval-ms <N> Metrics export period, 5000-300000 ms (default 15000)\n --trace-propagation-origin <origin> Approve exact fetch origin (repeatable, max 32)\n --event-loop-delay-resolution-ms <N> Delay timer precision, 10-1000 ms (default 20)\n --ca-file <path> Absolute private CA certificate path for verified TLS\n --loopback Permit a local HTTP collector for development\n --json Write { ok, data, error } JSON output\n --help, -h Show this help\n" | "Usage: sporades deploy [status|stop|restart|reconcile|remove|reset|ssh] [options]\n\nStart and manage a local Container session.\n\nCommands:\n deploy Start a local Container session\n deploy status Print Container session status\n deploy stop Stop the running Container session\n deploy restart Restart the running Container session\n deploy policy publish <file>|remove\n Publish or remove the declared admission policy\n deploy reconcile Settle an interrupted deployment-file attempt\n deploy ssh Inspect effective Container SSH access\n deploy remove Remove the Container session\n deploy reset Remove the Container session and local container state\n\nOptions:\n --port <number> Published local port when starting\n --telemetry <name> Export HTTP traces using a registered Telemetry profile\n --no-telemetry Disable export for this Container session\n --force Replace stale or conflicting container state when starting\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades host <command> [options]\n\nManage Host profiles and Hosted Capsules.\n\nProfile commands:\n add <alias> Add a Host profile\n use <alias> Set the default Host profile\n current Print the selected Host profile\n bootstrap Provision the remote Host server\n upgrade Copy the local Host helper to the Host server\n health [subname] Check Host server or Hosted Capsule health\n services enable|disable|uninstall|status observability [--on <alias>]\n Manage pinned GlitchTip and Umami (opt-in)\n services connect <subname> [--host <Capsule-host>] [--on <ops-host>]\n Seal configured error/analytics env; push to activate\n services backup|backups observability [--on <alias>]\n Snapshot or list service databases using Host backup policy\n services restore observability --id <snapshot> --confirm observability\n Restore disabled services; enable again after inspection\n telemetry connect|migrate|reconcile|status|check\n telemetry inventory-export|inventory-reconcile\n Manage the shared Host Telemetry relay\n telemetry exports-disable|remove-agents\n Disable exports; remove agents after inventory acknowledgement\n telemetry resources-enable|resources-disable|resources-remove\n Manage Host OS and Caddy collection independently of Capsules\n telemetry enable|disable <subname>\n Change a Hosted Capsule's Telemetry opt-out\n\nCapsule commands:\n backup configure --file <policy.json>\n Configure encrypted backups and an optional Host timer\n backup create|list|prune --subname <name>\n Manage encrypted SQLite and files snapshots\n backup restore --subname <source> --id <full-id> --confirm <destination>\n Restore a stopped Capsule; optionally --to-subname <destination>\n backup status Report last successful backup age for each Capsule\n bind <subname> Bind this project to a Hosted Capsule\n register <subname> Register a Hosted Capsule\n canonical set <subname> <alias>|clear <subname>\n Select the public origin; restart a running Capsule\n push Push and install a Hosted Capsule release\n start <subname> Start a Hosted Capsule\n stop <subname> Stop a Hosted Capsule\n restart <subname> Restart a Hosted Capsule\n policy publish <file>|remove --host <alias> --subname <name>\n Publish or remove the recorded admission policy\n reconcile <subname> Settle an interrupted deployment-file attempt\n ssh [subname] Inspect effective Hosted Capsule SSH access\n stats [subname] Print Host server or Hosted Capsule stats\n logs [source] Print Hosted Capsule logs\n releases <subname> List Hosted Capsule releases\n rollback <subname> <release-id>\n Roll back to a previous release\n rotate-key <subname>\n Rotate Hosted Capsule Sealed Server env keys\n unregister <subname>\n Unregister a Hosted Capsule\n delete <subname> Delete Hosted Capsule storage\n\nOther commands:\n list List Hosted Capsules\n github workflow write\n Write a GitHub Actions deploy workflow\n invoke <action> Invoke a low-level remote Host helper action\n\nOptions:\n --host <alias> Host profile alias\n --profile <name> Verified HTTPS Telemetry profile for connect or migrate\n --query-credential-env <name>\n Ephemeral operator user:password for check or migrate\n --server <target> SSH target for host add\n --domain <domain> Hosted domain for host add\n --canonical-domain <hostname>\n Public HTTPS link origin for register\n --alias-domain <hostname>\n Custom HTTPS domain for register (repeatable)\n --remote-root <path>\n Remote root path for host add\n --tls <mode> TLS mode: automatic or cloudflare-origin\n --subname <name> Hosted Capsule subname\n --lines <n>, -n <n> Log line count\n --allow-missing-env Push despite missing required Server env keys\n --restart Restart after host push\n --verify Verify release health after host push\n --fallback-to-previous-release\n Roll back when verified push fails\n --branch <name> GitHub workflow branch\n --file <path> GitHub workflow output path\n --dry-run Print workflow without writing it\n --force Overwrite workflow output when writing it\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades logs [tail] [options]\n\nPrint Dev session logs.\n\nCommands:\n logs Print recent Dev session logs\n logs tail Follow Dev session logs\n\nOptions:\n --port <number> Target Dev session or local Container port\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades db <command> [options]\n\nInspect the Dev session database.\n\nCommands:\n list List database tables\n dump Dump database contents\n query <sql> Run a read-only SQL query\n\nOptions:\n --port <number> Target Dev session or local Container port\n --json Write JSON output\n --help, -h Show this help\n";
49
+ ` | "Usage: sporades dev [status|stop|reset] [options]\n\nStart and manage a local Dev session.\n\nCommands:\n dev Start a Dev session\n dev status Print Dev session status\n dev stop Stop the running Dev session\n dev reset Stop the Dev session and remove local Dev state\n\nOptions:\n --port <number> Dev session port when starting\n --public Allow non-localhost access when starting\n --telemetry <name> Export HTTP traces using a registered Telemetry profile\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades auth <command> [options]\n\nManage local auth configuration and identity simulation.\n\nCommands:\n status Print auth provider status\n clients List connected Dev session clients\n set <provider> Configure or disable anonymous, email, Google, Microsoft, Apple, or Facebook\n as email Simulate a local email identity\n\nOptions:\n --client-id <id> OAuth client/app ID (Apple Services ID)\n --client-secret <secret> OAuth client/app secret\n --client-json <path> Read provider-specific credentials JSON\n --tenant <tenant> Microsoft tenant (default: common)\n --team-id <id> Apple Developer Team ID\n --key-id <id> Apple Sign in key ID\n --private-key <pem> Apple private key (stored only in Server env)\n --graph-version <name> Facebook Graph API version\n --disable Disable the selected provider without changing siblings\n --email <address> Simulated email identity\n --display-name <name> Simulated display name\n --picture <url> Simulated profile picture URL\n --port <number> Target Dev session port\n --client <target> Delivery target: current, all, or a client ID\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades access-keys <command> [options]\n\nInspect and retire Access keys through a running Capsule.\n\nCommands:\n list --user-id <id> List one owner's Access-key metadata\n inspect <key-id> Inspect one Access key\n revoke <key-id> Revoke one Access key\n revoke-all --user-id <id> Revoke one owner's current Access keys\n delete <key-id> Delete revoked Access-key history\n\nOptions:\n --session <name> Session: dev, container, or hosted (default: dev)\n --host <alias> Host profile alias for a Hosted Capsule\n --subname <name> Hosted Capsule subname\n --cursor <cursor> Opaque list cursor\n --limit <n> List page size from 1 through 100\n --status <status> List active, expired, or revoked keys\n --yes Explicitly approve a destructive operation\n --json Write structured JSON; does not imply consent\n --help, -h Show this help\n" | "Usage: sporades security [options]\n\nInspect effective Capsule security policy.\n\nOptions:\n --session <name> Session: dev, public-dev, container, or hosted\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades doctor [options]\n\nRun read-only Sporades diagnostics.\n\nOptions:\n --session <name> Session: dev, public-dev, container, or hosted\n --host <alias> Host profile alias for Hosted Capsule checks\n --subname <name> Hosted Capsule subname\n --strict Exit non-zero on warnings as well as failures\n --json Write structured JSON output\n --help, -h Show this help\n" | "Usage: sporades env <command> [options]\n\nManage Sealed Server env.\n\nCommands:\n set <name> Read one value from stdin and seal it\n has <name> Test whether a Server env key is defined\n init Create local Sealed Server env key material\n import Import Server env values from a file\n status Print Sealed Server env status\n export Export Sealed Server env for a Host profile\n reencrypt Re-encrypt local Sealed Server env material\n\nOptions:\n --stdin Read the value for env set from stdin\n --file <path> Input file for import or export\n --host <alias> Host profile alias\n --subname <name> Hosted Capsule subname\n --output <path> Export output path\n --sealed Treat input as an already sealed export\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades monitoring stack <init|validate|upgrade|rollback|backup|restore> [options]\n sporades monitoring sender <issue|rotate|commit|cancel|revoke|export|status|legacy-revoke> [options]\n\nGenerate or inspect the versioned trace stack from an installed Sporades package.\nInitialization creates a reviewable directory; it does not start services.\nSender operations run locally on the Monitoring server; results contain no secrets.\nRotation stages a second generation; commit retires the old one after sender verification.\n\nOptions:\n --dir <path> Target stack directory (default: current directory)\n --backup <path> Protected cold snapshot directory (backup/restore)\n --baseline <path> Trusted prior release assets for a schema-3 upgrade\n --sender <name> Named sender for lifecycle operations (optional for status)\n --host <identity> Exact inventory Host scope (issue or legacy-revoke only)\n --out <path> New mode-0600 credential handoff file (export only)\n --generation <n> Verified pending generation to activate (commit only)\n --ingest Disable shared legacy ingestion (legacy-revoke only)\n --json Write { ok, data, error } JSON output\n --help, -h Show this help\n" | "Usage: sporades telemetry profile <add|list|show|remove> [name] [options]\n\nRegister and inspect operator-owned OTLP/HTTP Telemetry profiles. Descriptors store only\ncredential environment and private CA file references; they never store secret values.\nDev sessions use --telemetry <name> first, then sporades.json telemetry.profile, then no export.\n\nOptions for profile add:\n --endpoint <url> OTLP/HTTP base origin (HTTPS, or HTTP loopback with --loopback)\n --dashboard <url> Optional dashboard HTTPS URL\n --credential-env <KEY> Environment variable containing the ingestion bearer token\n --inventory-credential-env <KEY> Exact Host-scoped lifecycle inventory token\n --inventory-host <id> Stable inventory identity (default: first connected domain)\n --metrics-interval-ms <N> Metrics export period, 5000-300000 ms (default 15000)\n --trace-propagation-origin <origin> Approve exact fetch origin (repeatable, max 32)\n --event-loop-delay-resolution-ms <N> Delay timer precision, 10-1000 ms (default 20)\n --ca-file <path> Absolute private CA certificate path for verified TLS\n --loopback Permit a local HTTP collector for development\n --json Write { ok, data, error } JSON output\n --help, -h Show this help\n" | "Usage: sporades deploy [status|stop|restart|reconcile|remove|reset|ssh] [options]\n\nStart and manage a local Container session.\n\nCommands:\n deploy Start a local Container session\n deploy status Print Container session status\n deploy stop Stop the running Container session\n deploy restart Restart the running Container session\n deploy policy publish <file>|remove\n Publish or remove the declared admission policy\n deploy reconcile Settle an interrupted deployment-file attempt\n deploy ssh Inspect effective Container SSH access\n deploy remove Remove the Container session\n deploy reset Remove the Container session and local container state\n\nOptions:\n --port <number> Published local port when starting\n --telemetry <name> Export HTTP traces using a registered Telemetry profile\n --no-telemetry Disable export for this Container session\n --force Replace stale or conflicting container state when starting\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades host <command> [options]\n\nManage Host profiles and Hosted Capsules.\n\nProfile commands:\n add <alias> Add a Host profile\n use <alias> Set the default Host profile\n current Print the selected Host profile\n bootstrap Provision the remote Host server\n upgrade Copy the local Host helper to the Host server\n health [subname] Check Host server or Hosted Capsule health\n services enable|disable|uninstall|status observability [--on <alias>]\n Manage pinned GlitchTip and Umami (opt-in)\n services connect <subname> [--host <Capsule-host>] [--on <ops-host>]\n Seal configured error/analytics env; push to activate\n services backup|backups observability [--on <alias>]\n Snapshot or list service databases using Host backup policy\n services restore observability --id <snapshot> --confirm observability\n Restore disabled services; enable again after inspection\n telemetry connect|migrate|reconcile|status|check\n telemetry inventory-export|inventory-reconcile\n Manage the shared Host Telemetry relay\n telemetry exports-disable|remove-agents\n Disable exports; remove agents after inventory acknowledgement\n telemetry resources-enable|resources-disable|resources-remove\n Manage Host OS and Caddy collection independently of Capsules\n telemetry enable|disable <subname>\n Change a Hosted Capsule's Telemetry opt-out\n\nCapsule commands:\n backup configure --file <policy.json>\n Configure encrypted backups and an optional Host timer\n backup create|list|prune --subname <name>\n Manage encrypted SQLite and files snapshots\n backup restore --subname <source> --id <full-id> --confirm <destination>\n Restore a stopped Capsule; optionally --to-subname <destination>\n backup status Report last successful backup age for each Capsule\n bind <subname> Bind this project to a Hosted Capsule\n db list|dump|query <sql> [--subname <name>]\n Read a running Hosted Capsule's database over SSH\n register <subname> Register a Hosted Capsule\n canonical set <subname> <alias>|clear <subname>\n Select the public origin; restart a running Capsule\n push Push and install a Hosted Capsule release\n start <subname> Start a Hosted Capsule\n stop <subname> Stop a Hosted Capsule\n restart <subname> Restart a Hosted Capsule\n policy publish <file>|remove --host <alias> --subname <name>\n Publish or remove the recorded admission policy\n reconcile <subname> Settle an interrupted deployment-file attempt\n ssh [subname] Inspect effective Hosted Capsule SSH access\n stats [subname] Print Host server or Hosted Capsule stats\n logs [source] Print Hosted Capsule logs\n releases <subname> List Hosted Capsule releases\n rollback <subname> <release-id>\n Roll back to a previous release\n rotate-key <subname>\n Rotate Hosted Capsule Sealed Server env keys\n unregister <subname>\n Unregister a Hosted Capsule\n delete <subname> Delete Hosted Capsule storage\n\nOther commands:\n list List Hosted Capsules\n github workflow write\n Write a GitHub Actions deploy workflow\n invoke <action> Invoke a low-level remote Host helper action\n\nOptions:\n --host <alias> Host profile alias\n --profile <name> Verified HTTPS Telemetry profile for connect or migrate\n --query-credential-env <name>\n Ephemeral operator user:password for check or migrate\n --server <target> SSH target for host add\n --domain <domain> Hosted domain for host add\n --canonical-domain <hostname>\n Public HTTPS link origin for register\n --alias-domain <hostname>\n Custom HTTPS domain for register (repeatable)\n --remote-root <path>\n Remote root path for host add\n --tls <mode> TLS mode: automatic or cloudflare-origin\n --subname <name> Hosted Capsule subname\n --lines <n>, -n <n> Log line count\n --allow-missing-env Push despite missing required Server env keys\n --restart Restart after host push\n --verify Verify release health after host push\n --fallback-to-previous-release\n Roll back when verified push fails\n --branch <name> GitHub workflow branch\n --file <path> GitHub workflow output path\n --dry-run Print workflow without writing it\n --force Overwrite workflow output when writing it\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades logs [tail] [options]\n\nPrint Dev session logs.\n\nCommands:\n logs Print recent Dev session logs\n logs tail Follow Dev session logs\n\nOptions:\n --port <number> Target Dev session or local Container port\n --json Write JSON output\n --help, -h Show this help\n" | "Usage: sporades db <command> [options]\n\nInspect the Dev session database.\n\nCommands:\n list List database tables\n dump Dump database contents\n query <sql> Run a read-only SQL query\n\nOptions:\n --port <number> Target Dev session or local Container port\n --json Write JSON output\n --help, -h Show this help\n";
50
50
  export {};
51
51
  //# sourceMappingURL=cli-help.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-help.d.ts","sourceRoot":"","sources":["../../src/cli/cli-help.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+VL,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,OAAO,SAAS,EAAE,SAAS,CAAC,CAAC;AAErE,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;s6aAE7C"}
1
+ {"version":3,"file":"cli-help.d.ts","sourceRoot":"","sources":["../../src/cli/cli-help.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiWL,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,OAAO,SAAS,EAAE,SAAS,CAAC,CAAC;AAErE,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;8hbAE7C"}
@@ -246,6 +246,8 @@ Capsule commands:
246
246
  Restore a stopped Capsule; optionally --to-subname <destination>
247
247
  backup status Report last successful backup age for each Capsule
248
248
  bind <subname> Bind this project to a Hosted Capsule
249
+ db list|dump|query <sql> [--subname <name>]
250
+ Read a running Hosted Capsule's database over SSH
249
251
  register <subname> Register a Hosted Capsule
250
252
  canonical set <subname> <alias>|clear <subname>
251
253
  Select the public origin; restart a running Capsule
@@ -1 +1 @@
1
- {"version":3,"file":"cli-help.js","sourceRoot":"","sources":["../../src/cli/cli-help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnG,MAAM,aAAa,GAAG,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AACnJ,MAAM,aAAa,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACnH,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAElF,MAAM,SAAS,GAAG;IAChB,aAAa,EAAE;;;;;;;;;;;;;CAahB;IACC,MAAM,EAAE;;;;;;;;0CAQgC,aAAa;0CACb,aAAa;kCACrB,YAAY;;;;;CAK7C;IACC,GAAG,EAAE;;;;;;;;;;;;;;;;CAgBN;IACC,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BP;IACC,aAAa,EAAE;;;;;;;;;;;;;;;;;;;;;CAqBhB;IACC,QAAQ,EAAE;;;;;;;;CAQX;IACC,MAAM,EAAE;;;;;;;;;;;CAWT;IACC,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsBN;IACC,UAAU,EAAE;;;;;;;;;;;;;;;;;;;CAmBb;IACC,SAAS,EAAE;;;;;;;;;;;;;;;;;;;CAmBZ;IACC,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;CAuBT;IACC,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6FP;IACC,IAAI,EAAE;;;;;;;;;;;;CAYP;IACC,EAAE,EAAE;;;;;;;;;;;;;CAaL;IACC,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;KAuBN;CACK,CAAC;AAIX,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,SAAS,CAAC,OAAyB,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC;AACnE,CAAC"}
1
+ {"version":3,"file":"cli-help.js","sourceRoot":"","sources":["../../src/cli/cli-help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnG,MAAM,aAAa,GAAG,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AACnJ,MAAM,aAAa,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACnH,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAElF,MAAM,SAAS,GAAG;IAChB,aAAa,EAAE;;;;;;;;;;;;;CAahB;IACC,MAAM,EAAE;;;;;;;;0CAQgC,aAAa;0CACb,aAAa;kCACrB,YAAY;;;;;CAK7C;IACC,GAAG,EAAE;;;;;;;;;;;;;;;;CAgBN;IACC,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BP;IACC,aAAa,EAAE;;;;;;;;;;;;;;;;;;;;;CAqBhB;IACC,QAAQ,EAAE;;;;;;;;CAQX;IACC,MAAM,EAAE;;;;;;;;;;;CAWT;IACC,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsBN;IACC,UAAU,EAAE;;;;;;;;;;;;;;;;;;;CAmBb;IACC,SAAS,EAAE;;;;;;;;;;;;;;;;;;;CAmBZ;IACC,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;CAuBT;IACC,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+FP;IACC,IAAI,EAAE;;;;;;;;;;;;CAYP;IACC,EAAE,EAAE;;;;;;;;;;;;;CAaL;IACC,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;KAuBN;CACK,CAAC;AAIX,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,SAAS,CAAC,OAAyB,CAAC,IAAI,SAAS,CAAC,OAAO,CAAC;AACnE,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.9.38";
1
+ export declare const CLI_VERSION = "0.9.40";
2
2
  //# sourceMappingURL=cli-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Generated by `npm run build` from package.json. Do not edit by hand.
2
- export const CLI_VERSION = "0.9.38";
2
+ export const CLI_VERSION = "0.9.40";
3
3
  //# sourceMappingURL=cli-version.js.map
@@ -0,0 +1,9 @@
1
+ type LooseRecord = Record<string, any>;
2
+ export declare const DATABASE_INSPECTION_ACTIONS: readonly ["db.list", "db.dump", "db.query"];
3
+ export type DatabaseInspectionAction = (typeof DATABASE_INSPECTION_ACTIONS)[number];
4
+ export declare function encodeDatabaseInspectionInput(sql: string): string | null;
5
+ export declare const DATABASE_INSPECTION_MAX_BUFFER: number;
6
+ export declare function isDatabaseInspectionAction(action: unknown): action is DatabaseInspectionAction;
7
+ export declare function sanitizeDatabaseInspectionEnvelope(action: DatabaseInspectionAction, envelope: LooseRecord, invalid: () => never): LooseRecord;
8
+ export {};
9
+ //# sourceMappingURL=database-inspection-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-inspection-envelope.d.ts","sourceRoot":"","sources":["../../src/cli/database-inspection-envelope.ts"],"names":[],"mappings":"AAIA,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEvC,eAAO,MAAM,2BAA2B,6CAA8C,CAAC;AACvF,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAOpF,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,iBAGxD;AAID,eAAO,MAAM,8BAA8B,QAAoB,CAAC;AAEhE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,wBAAwB,CAE9F;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,wBAAwB,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,KAAK,GAAG,WAAW,CAqB7I"}
@@ -0,0 +1,38 @@
1
+ export const DATABASE_INSPECTION_ACTIONS = ["db.list", "db.dump", "db.query"];
2
+ // The Bundle refuses `--sporades-action-input` over 24 KiB of base64url, so SQL whose encoded
3
+ // input is larger could not reach the read-only gate in one piece. Measuring the encoded form, not
4
+ // the raw SQL, keeps heavily escaped SQL from passing here and failing as malformed input there.
5
+ const DATABASE_INSPECTION_INPUT_BYTE_LIMIT = 24 * 1024;
6
+ export function encodeDatabaseInspectionInput(sql) {
7
+ const encoded = Buffer.from(JSON.stringify({ sql }), "utf8").toString("base64url");
8
+ return encoded.length <= DATABASE_INSPECTION_INPUT_BYTE_LIMIT ? encoded : null;
9
+ }
10
+ // A dump answers with every inspectable row, so its frame is bounded by the Capsule's data rather
11
+ // than by Sporades. This is the ceiling on what one SSH or `docker exec` hop will buffer.
12
+ export const DATABASE_INSPECTION_MAX_BUFFER = 128 * 1024 * 1024;
13
+ export function isDatabaseInspectionAction(action) {
14
+ return DATABASE_INSPECTION_ACTIONS.includes(action);
15
+ }
16
+ export function sanitizeDatabaseInspectionEnvelope(action, envelope, invalid) {
17
+ const data = envelope?.data;
18
+ if (envelope?.ok === true && data && typeof data === "object" && !Array.isArray(data)) {
19
+ if (action === "db.query" && Array.isArray(data.columns) && Array.isArray(data.rows)) {
20
+ return { ok: true, data: { columns: data.columns, rows: data.rows }, error: null };
21
+ }
22
+ if (action !== "db.query" && Array.isArray(data.tables)) {
23
+ return { ok: true, data: { tables: data.tables }, error: null };
24
+ }
25
+ }
26
+ if (envelope?.ok === false && typeof envelope.error?.message === "string") {
27
+ return {
28
+ ok: false,
29
+ data: null,
30
+ error: {
31
+ message: envelope.error.message,
32
+ hint: typeof envelope.error.hint === "string" ? envelope.error.hint : "Check the SQL and retry the command.",
33
+ },
34
+ };
35
+ }
36
+ return invalid();
37
+ }
38
+ //# sourceMappingURL=database-inspection-envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-inspection-envelope.js","sourceRoot":"","sources":["../../src/cli/database-inspection-envelope.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAU,CAAC;AAGvF,8FAA8F;AAC9F,mGAAmG;AACnG,iGAAiG;AACjG,MAAM,oCAAoC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEvD,MAAM,UAAU,6BAA6B,CAAC,GAAW;IACvD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACnF,OAAO,OAAO,CAAC,MAAM,IAAI,oCAAoC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACjF,CAAC;AAED,kGAAkG;AAClG,0FAA0F;AAC1F,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhE,MAAM,UAAU,0BAA0B,CAAC,MAAe;IACxD,OAAQ,2BAAkD,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,MAAgC,EAAE,QAAqB,EAAE,OAAoB;IAC9H,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,CAAC;IAC5B,IAAI,QAAQ,EAAE,EAAE,KAAK,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACtF,IAAI,MAAM,KAAK,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACrF,CAAC;QACD,IAAI,MAAM,KAAK,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACxD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAClE,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,EAAE,EAAE,KAAK,KAAK,IAAI,OAAO,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC1E,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,IAAI;YACV,KAAK,EAAE;gBACL,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO;gBAC/B,IAAI,EAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,sCAAsC;aAC7G;SACF,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,EAAE,CAAC;AACnB,CAAC"}