@marcusrbrown/infra 0.27.0 → 0.27.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +86 -88
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6950,7 +6950,7 @@ import { goke } from "goke";
6950
6950
  // package.json
6951
6951
  var package_default = {
6952
6952
  name: "@marcusrbrown/infra",
6953
- version: "0.27.0",
6953
+ version: "0.27.1",
6954
6954
  description: "Infrastructure management CLI \u2014 deploy automation, health checks, and MCP bridge",
6955
6955
  keywords: [
6956
6956
  "infra",
@@ -12720,7 +12720,7 @@ async function restoreGatewayCa(opts, spawn = defaultSpawn4) {
12720
12720
  }
12721
12721
  }
12722
12722
  function registerGatewayRestore(cli) {
12723
- cli.command("gateway restore", "Restore the mitmproxy CA certificate and private key from a local tarball into the gateway Docker volume. Restarts mitmproxy and gateway services after restore and confirms byte-equal content before reporting success.").option("--input <file>", z25.string().describe("Path to the tarball produced by `gateway backup --include-ca`. Required.")).option("--include-ca", z25.boolean().default(true).describe("Restore the mitmproxy CA certificate and private key. Currently the only supported restore target.")).example("# Restore CA from a backup tarball").example("infra gateway restore --input apps/gateway/.local/mitmproxy-ca.tar").example("# Restore CA from a custom path").example("infra gateway restore --input /secure/backup/mitmproxy-ca.tar --include-ca").action(async (options) => {
12723
+ cli.command("gateway restore", "Restore the mitmproxy CA certificate and private key from a local tarball into the gateway Docker volume. Restarts mitmproxy and gateway services after restore and confirms byte-equal content before reporting success.").option("--input <file>", z25.string().describe("Path to the tarball produced by `gateway backup --include-ca`.")).required().option("--include-ca", z25.boolean().default(true).describe("Restore the mitmproxy CA certificate and private key. Currently the only supported restore target.")).example("# Restore CA from a backup tarball").example("infra gateway restore --input apps/gateway/.local/mitmproxy-ca.tar").example("# Restore CA from a custom path").example("infra gateway restore --input /secure/backup/mitmproxy-ca.tar --include-ca").action(async (options) => {
12724
12724
  const hostEnvKey = "GATEWAY_HOST";
12725
12725
  const host = process.env[hostEnvKey];
12726
12726
  if (!host) {
@@ -13577,7 +13577,7 @@ function registerMcp(cli) {
13577
13577
  import { z as z43 } from "zod";
13578
13578
 
13579
13579
  // src/status/deploy-state.ts
13580
- import { z as z30 } from "zod";
13580
+ import { z as z31 } from "zod";
13581
13581
 
13582
13582
  // src/status/github-api.ts
13583
13583
  var GITHUB_API_ORIGIN = "https://api.github.com";
@@ -13659,51 +13659,78 @@ async function githubGetLogText(fetchFn, token, path) {
13659
13659
  }
13660
13660
  }
13661
13661
 
13662
- // src/status/observation.ts
13662
+ // ../shared/status/history-schema.ts
13663
13663
  import { z as z29 } from "zod";
13664
- var STATUS_SCHEMA_VERSION = 1;
13665
- var OBSERVATION_KINDS = ["reachability", "container-health", "deploy-state", "end-to-end", "resource"];
13666
- var OBSERVATION_SOURCES = ["http", "ssh", "github-api", "monitor", "config"];
13664
+ var HISTORY_SCHEMA_VERSION = 1;
13665
+ var HISTORY_WINDOW_DAYS = 90;
13666
+ var HISTORY_WINDOW_MS = HISTORY_WINDOW_DAYS * 24 * 60 * 60000;
13667
13667
  var APP_IDS = ["keeweb", "cliproxy", "gateway", "umami", "dashboard", "vpn", "broker"];
13668
+ var OBSERVATION_KINDS = ["reachability", "container-health", "deploy-state", "end-to-end", "resource"];
13669
+ var MAX_OBSERVATIONS_PER_SAMPLE = 32;
13668
13670
  var isoTimestampSchema = z29.iso.datetime();
13669
- var observationDetailSchema = z29.strictObject({
13671
+ var sampleDetailSchema = z29.strictObject({
13670
13672
  statusCode: z29.number().int().min(100).max(599).optional(),
13671
- durationMs: z29.number().int().min(0).max(600000).optional(),
13672
13673
  contractVersion: z29.string().min(1).max(64).optional(),
13673
13674
  since: isoTimestampSchema.optional(),
13674
13675
  probeAt: isoTimestampSchema.optional(),
13675
13676
  reason: z29.string().max(32).regex(/^[a-z][a-z0-9_-]*$/).optional()
13676
13677
  });
13678
+ var resultCodeSchema = z29.enum(["p", "f", "u", "n"]);
13679
+ var observationIdSchema = z29.string().max(64).regex(/^[a-z][a-z0-9-]*$/);
13680
+ var historySampleSchema = z29.strictObject({
13681
+ t: isoTimestampSchema,
13682
+ run: z29.string().regex(/^(?:\d{1,20}|local(?:-\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z)?)$/),
13683
+ att: z29.number().int().min(1).max(1e4),
13684
+ r: z29.record(observationIdSchema, resultCodeSchema),
13685
+ d: z29.record(observationIdSchema, sampleDetailSchema).optional()
13686
+ }).refine((sample) => {
13687
+ const count = Object.keys(sample.r).length;
13688
+ return count >= 1 && count <= MAX_OBSERVATIONS_PER_SAMPLE;
13689
+ }, "r must hold 1 to 32 results").refine((sample) => Object.keys(sample.d ?? {}).every((id) => (id in sample.r)), "d may only describe ids present in r");
13690
+ var historyMetaSchema = z29.strictObject({
13691
+ schemaVersion: z29.literal(HISTORY_SCHEMA_VERSION),
13692
+ lastSampleAt: isoTimestampSchema
13693
+ });
13694
+ var historyAppFileSchema = z29.array(historySampleSchema);
13695
+
13696
+ // src/status/observation.ts
13697
+ import { z as z30 } from "zod";
13698
+ var STATUS_SCHEMA_VERSION = 1;
13699
+ var OBSERVATION_SOURCES = ["http", "ssh", "github-api", "monitor", "config"];
13700
+ var isoTimestampSchema2 = z30.iso.datetime();
13701
+ var observationDetailSchema = sampleDetailSchema.extend({
13702
+ durationMs: z30.number().int().min(0).max(600000).optional()
13703
+ });
13677
13704
  var observationBaseShape = {
13678
- id: z29.string().max(64).regex(/^[a-z][a-z0-9-]*$/),
13679
- kind: z29.enum(OBSERVATION_KINDS),
13680
- source: z29.enum(OBSERVATION_SOURCES),
13705
+ id: z30.string().max(64).regex(/^[a-z][a-z0-9-]*$/),
13706
+ kind: z30.enum(OBSERVATION_KINDS),
13707
+ source: z30.enum(OBSERVATION_SOURCES),
13681
13708
  detail: observationDetailSchema.optional()
13682
13709
  };
13683
- var observationSchema = z29.discriminatedUnion("result", [
13684
- z29.strictObject({
13710
+ var observationSchema = z30.discriminatedUnion("result", [
13711
+ z30.strictObject({
13685
13712
  ...observationBaseShape,
13686
- result: z29.enum(["pass", "fail", "unknown"]),
13687
- observedAt: isoTimestampSchema
13713
+ result: z30.enum(["pass", "fail", "unknown"]),
13714
+ observedAt: isoTimestampSchema2
13688
13715
  }),
13689
- z29.strictObject({
13716
+ z30.strictObject({
13690
13717
  ...observationBaseShape,
13691
- result: z29.literal("not_measured"),
13692
- observedAt: z29.null()
13718
+ result: z30.literal("not_measured"),
13719
+ observedAt: z30.null()
13693
13720
  })
13694
13721
  ]);
13695
- var jsonValueSchema = z29.lazy(() => z29.union([
13696
- z29.string(),
13697
- z29.number(),
13698
- z29.boolean(),
13699
- z29.null(),
13700
- z29.array(jsonValueSchema),
13701
- z29.record(z29.string(), jsonValueSchema)
13722
+ var jsonValueSchema = z30.lazy(() => z30.union([
13723
+ z30.string(),
13724
+ z30.number(),
13725
+ z30.boolean(),
13726
+ z30.null(),
13727
+ z30.array(jsonValueSchema),
13728
+ z30.record(z30.string(), jsonValueSchema)
13702
13729
  ]));
13703
- var appStatusSchema = z29.strictObject({
13704
- app: z29.enum(APP_IDS),
13705
- observations: z29.array(observationSchema),
13706
- facts: z29.record(z29.string().max(64), jsonValueSchema)
13730
+ var appStatusSchema = z30.strictObject({
13731
+ app: z30.enum(APP_IDS),
13732
+ observations: z30.array(observationSchema),
13733
+ facts: z30.record(z30.string().max(64), jsonValueSchema)
13707
13734
  }).check((ctx) => {
13708
13735
  const seen = new Set;
13709
13736
  for (const [index, observation] of ctx.value.observations.entries()) {
@@ -13718,10 +13745,10 @@ var appStatusSchema = z29.strictObject({
13718
13745
  seen.add(observation.id);
13719
13746
  }
13720
13747
  });
13721
- var statusEnvelopeSchema = z29.strictObject({
13722
- schemaVersion: z29.literal(STATUS_SCHEMA_VERSION),
13723
- generatedAt: isoTimestampSchema,
13724
- apps: z29.array(appStatusSchema)
13748
+ var statusEnvelopeSchema = z30.strictObject({
13749
+ schemaVersion: z30.literal(STATUS_SCHEMA_VERSION),
13750
+ generatedAt: isoTimestampSchema2,
13751
+ apps: z30.array(appStatusSchema)
13725
13752
  }).check((ctx) => {
13726
13753
  const seen = new Set;
13727
13754
  for (const [index, entry] of ctx.value.apps.entries()) {
@@ -13743,8 +13770,8 @@ var DEPLOY_STATE_REF = "main";
13743
13770
  var DEPLOY_STATE_CALLS_PER_APP = 2;
13744
13771
  var DEPLOY_STATE_CALL_BUDGET = APP_IDS.length * DEPLOY_STATE_CALLS_PER_APP;
13745
13772
  var SPEC = { id: DEPLOY_STATE_ID, kind: "deploy-state", source: "github-api" };
13746
- var deploymentListSchema = z30.array(z30.object({ id: z30.number().int().positive(), ref: z30.string() }));
13747
- var statusListSchema = z30.array(z30.object({ state: z30.string().min(1).max(32), created_at: z30.iso.datetime() }));
13773
+ var deploymentListSchema = z31.array(z31.object({ id: z31.number().int().positive(), ref: z31.string() }));
13774
+ var statusListSchema = z31.array(z31.object({ state: z31.string().min(1).max(32), created_at: z31.iso.datetime() }));
13748
13775
  function mapDeploymentState(state) {
13749
13776
  switch (state) {
13750
13777
  case "success":
@@ -13801,7 +13828,7 @@ async function readDeployStates(options) {
13801
13828
  }
13802
13829
 
13803
13830
  // src/status/monitor-state.ts
13804
- import { z as z31 } from "zod";
13831
+ import { z as z32 } from "zod";
13805
13832
  var MONITOR_OBSERVATION_ID = "provider-route";
13806
13833
  var MONITOR_WORKFLOW_FILE = "cliproxy-auth-monitor.yaml";
13807
13834
  var PROBE_LINE_PREFIX = "CLIProxy auth monitor probe:";
@@ -13818,16 +13845,16 @@ var PROBE_REASON_STATE = {
13818
13845
  timeout: "unknown",
13819
13846
  network: "unknown"
13820
13847
  };
13821
- var runsSchema = z31.object({
13822
- workflow_runs: z31.array(z31.object({
13823
- id: z31.number().int().positive(),
13824
- event: z31.string(),
13825
- head_branch: z31.string().nullable(),
13826
- status: z31.string()
13848
+ var runsSchema = z32.object({
13849
+ workflow_runs: z32.array(z32.object({
13850
+ id: z32.number().int().positive(),
13851
+ event: z32.string(),
13852
+ head_branch: z32.string().nullable(),
13853
+ status: z32.string()
13827
13854
  }))
13828
13855
  });
13829
- var jobsSchema = z31.object({ jobs: z31.array(z31.object({ id: z31.number().int().positive() })) });
13830
- var isoSchema = z31.iso.datetime();
13856
+ var jobsSchema = z32.object({ jobs: z32.array(z32.object({ id: z32.number().int().positive() })) });
13857
+ var isoSchema = z32.iso.datetime();
13831
13858
  var RUNNER_PREFIX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z /;
13832
13859
  var PROBE_LINE = /^CLIProxy auth monitor probe: probe=([a-z]+) reason=([a-z0-9-]+) at (\S+)$/;
13833
13860
  function isReason(value) {
@@ -13938,27 +13965,27 @@ function githubObservationsFor(app, reads, now) {
13938
13965
 
13939
13966
  // src/commands/status-history-locate.ts
13940
13967
  import { appendFile } from "fs/promises";
13941
- import { z as z32 } from "zod";
13968
+ import { z as z33 } from "zod";
13942
13969
  var HISTORY_ARTIFACT_NAME = "status-history";
13943
13970
  var SNAPSHOT_WORKFLOW_PATH = ".github/workflows/status-snapshot.yaml";
13944
13971
  var HISTORY_LOCATE_RUN_CHECK_BUDGET = 5;
13945
13972
  var LIST_PAGE_SIZE = 100;
13946
13973
  var WRITER_EVENTS = new Set(["schedule", "workflow_dispatch"]);
13947
13974
  var REPOSITORY_PATTERN = /^[\w.-]+\/[\w.-]+$/;
13948
- var artifactsSchema = z32.object({
13949
- total_count: z32.number().int().nonnegative(),
13950
- artifacts: z32.array(z32.object({
13951
- name: z32.string(),
13952
- expired: z32.boolean(),
13953
- created_at: z32.iso.datetime({ offset: true }),
13954
- workflow_run: z32.object({ id: z32.number().int().positive(), head_branch: z32.string().nullable() }).nullable()
13975
+ var artifactsSchema = z33.object({
13976
+ total_count: z33.number().int().nonnegative(),
13977
+ artifacts: z33.array(z33.object({
13978
+ name: z33.string(),
13979
+ expired: z33.boolean(),
13980
+ created_at: z33.iso.datetime({ offset: true }),
13981
+ workflow_run: z33.object({ id: z33.number().int().positive(), head_branch: z33.string().nullable() }).nullable()
13955
13982
  }))
13956
13983
  });
13957
- var runSchema = z32.object({
13958
- id: z32.number().int().positive(),
13959
- event: z32.string(),
13960
- head_branch: z32.string().nullable(),
13961
- path: z32.string()
13984
+ var runSchema = z33.object({
13985
+ id: z33.number().int().positive(),
13986
+ event: z33.string(),
13987
+ head_branch: z33.string().nullable(),
13988
+ path: z33.string()
13962
13989
  });
13963
13990
  async function locateHistory(fetchFn, token, repository) {
13964
13991
  const listed = await githubGetJson(fetchFn, token, `/repos/${repository}/actions/artifacts?name=${HISTORY_ARTIFACT_NAME}&per_page=${LIST_PAGE_SIZE}`, artifactsSchema);
@@ -14023,13 +14050,8 @@ import { z as z34 } from "zod";
14023
14050
  import { randomUUID } from "crypto";
14024
14051
  import { mkdir, readdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
14025
14052
  import { join as join8 } from "path";
14026
- import { z as z33 } from "zod";
14027
- var HISTORY_SCHEMA_VERSION = 1;
14028
- var HISTORY_WINDOW_DAYS = 90;
14029
- var HISTORY_WINDOW_MS = HISTORY_WINDOW_DAYS * 24 * 60 * 60000;
14030
14053
  var HISTORY_MAX_BYTES = 10 * 1024 * 1024;
14031
14054
  var MAX_FUTURE_SKEW_MS2 = 5 * 60000;
14032
- var MAX_OBSERVATIONS_PER_SAMPLE = 32;
14033
14055
  var MESSAGES = {
14034
14056
  "missing-history": "history not found; pass --bootstrap to create it",
14035
14057
  "history-exists": "history already exists; refusing to bootstrap over it",
@@ -14057,31 +14079,7 @@ class HistoryError extends Error {
14057
14079
  }
14058
14080
  }
14059
14081
  var RESULT_CODES = { pass: "p", fail: "f", unknown: "u", not_measured: "n" };
14060
- var resultCodeSchema = z33.enum(["p", "f", "u", "n"]);
14061
- var observationIdSchema = z33.string().max(64).regex(/^[a-z][a-z0-9-]*$/);
14062
14082
  var SAMPLE_DETAIL_KEYS = ["statusCode", "reason", "contractVersion", "since", "probeAt"];
14063
- var sampleDetailSchema = observationDetailSchema.pick({
14064
- statusCode: true,
14065
- reason: true,
14066
- contractVersion: true,
14067
- since: true,
14068
- probeAt: true
14069
- });
14070
- var historySampleSchema = z33.strictObject({
14071
- t: z33.iso.datetime(),
14072
- run: z33.string().regex(/^(?:\d{1,20}|local(?:-\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z)?)$/),
14073
- att: z33.number().int().min(1).max(1e4),
14074
- r: z33.record(observationIdSchema, resultCodeSchema),
14075
- d: z33.record(observationIdSchema, sampleDetailSchema).optional()
14076
- }).refine((sample) => {
14077
- const count = Object.keys(sample.r).length;
14078
- return count >= 1 && count <= MAX_OBSERVATIONS_PER_SAMPLE;
14079
- }, "r must hold 1 to 32 results").refine((sample) => Object.keys(sample.d ?? {}).every((id) => (id in sample.r)), "d may only describe ids present in r");
14080
- var historyMetaSchema = z33.strictObject({
14081
- schemaVersion: z33.literal(HISTORY_SCHEMA_VERSION),
14082
- lastSampleAt: z33.iso.datetime()
14083
- });
14084
- var historyAppFileSchema = z33.array(historySampleSchema);
14085
14083
  function recordByApp(build) {
14086
14084
  return {
14087
14085
  keeweb: build("keeweb"),
@@ -14374,7 +14372,7 @@ async function statusSnapshotAction(options, ctx, deps, env = process.env) {
14374
14372
  }
14375
14373
  }
14376
14374
  function registerStatusSnapshot(cli, deps) {
14377
- cli.command("status snapshot", "Take one public-safe status sample and merge it into the 90-day history directory. Runs only the public HTTPS probes, the GitHub Deployments reader and the auth monitor reader (GITHUB_TOKEN or GH_TOKEN, read-only); it uses no SSH and no deploy secrets. Stages every file as a temp file before renaming any into place; any invariant violation exits non-zero and writes nothing. Prints counts only. Exits 0 once the snapshot is written, even when observations are fail or unknown, because those are data. CLI-only: not exposed over MCP.").option("--history <dir>", z34.string().min(1).describe("History directory holding meta.json and apps/<app>.json. It must already contain a valid history unless --bootstrap is given.")).option("--bootstrap", z34.boolean().describe("Create the history and its first sample. Fails if meta.json or any app file already exists; without this flag a missing history fails.")).option("--now [iso]", z34.string().describe("UTC ISO timestamp (for example 2026-11-01T12:00:00Z) to use as the sample time and the 90-day trim reference instead of the clock. Intended for tests and replays.")).example("# Create the first history sample in an empty directory").example("infra status snapshot --history ./status-history --bootstrap").example("# Append a sample to an existing history").example("infra status snapshot --history ./status-history").action((options, ctx) => statusSnapshotAction(options, ctx, deps));
14375
+ cli.command("status snapshot", "Take one public-safe status sample and merge it into the 90-day history directory. Runs only the public HTTPS probes, the GitHub Deployments reader and the auth monitor reader (GITHUB_TOKEN or GH_TOKEN, read-only); it uses no SSH and no deploy secrets. Stages every file as a temp file before renaming any into place; any invariant violation exits non-zero and writes nothing. Prints counts only. Exits 0 once the snapshot is written, even when observations are fail or unknown, because those are data. CLI-only: not exposed over MCP.").option("--history <dir>", z34.string().min(1).describe("History directory holding meta.json and apps/<app>.json. It must already contain a valid history unless --bootstrap is given.")).required().option("--bootstrap", z34.boolean().describe("Create the history and its first sample. Fails if meta.json or any app file already exists; without this flag a missing history fails.")).option("--now [iso]", z34.string().describe("UTC ISO timestamp (for example 2026-11-01T12:00:00Z) to use as the sample time and the 90-day trim reference instead of the clock. Intended for tests and replays.")).example("# Create the first history sample in an empty directory").example("infra status snapshot --history ./status-history --bootstrap").example("# Append a sample to an existing history").example("infra status snapshot --history ./status-history").action((options, ctx) => statusSnapshotAction(options, ctx, deps));
14378
14376
  }
14379
14377
 
14380
14378
  // src/commands/umami/deploy.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcusrbrown/infra",
3
- "version": "0.27.0",
3
+ "version": "0.27.1",
4
4
  "description": "Infrastructure management CLI — deploy automation, health checks, and MCP bridge",
5
5
  "keywords": [
6
6
  "infra",