@hasna/machines 0.0.18 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,6 +79,31 @@ machines topology --no-tailscale --json
79
79
  machines route --machine spark01 --json
80
80
  ```
81
81
 
82
+ Consumers that need repo paths can resolve trust-aware workspace mappings
83
+ without importing the full machines app:
84
+
85
+ ```ts
86
+ import { resolveMachineWorkspace } from "@hasna/machines/consumer";
87
+
88
+ const workspace = resolveMachineWorkspace({
89
+ machineId: "spark01",
90
+ projectId: "open-knowledge",
91
+ repoName: "open-knowledge",
92
+ });
93
+
94
+ console.log(workspace.paths.project_root.path);
95
+ console.log(workspace.paths.open_files_root.path);
96
+ ```
97
+
98
+ The resolver returns the machine workspace root, project repo root,
99
+ open-files root, current/primary flags, trust/auth status, and redacted
100
+ diagnostics. It uses explicit manifest metadata first and deterministic
101
+ workspace inference second; consumers can still pass manual overrides.
102
+
103
+ ```bash
104
+ machines workspace resolve --machine spark01 --project open-knowledge --repo open-knowledge --json
105
+ ```
106
+
82
107
  ## Compatibility SDK
83
108
 
84
109
  Open-core consumers can use `@hasna/machines` to preflight a peer before
@@ -131,6 +156,20 @@ Configure database storage with `HASNA_MACHINES_DATABASE_URL` or fallback
131
156
  `HASNA_MACHINES_STORAGE_MODE` or `MACHINES_STORAGE_MODE` with `local`,
132
157
  `hybrid`, or `remote`.
133
158
 
159
+ Machine backups are preview-only unless `--apply --yes` is passed. The backup
160
+ target can be explicit or environment-backed:
161
+
162
+ ```bash
163
+ machines backup --bucket fleet-backups --prefix machines --json
164
+ HASNA_MACHINES_S3_BUCKET=hasna-xyz-opensource-machines-prod machines backup --json
165
+ ```
166
+
167
+ `--bucket` and `--prefix` always win. Without `--bucket`, the backup command
168
+ uses `HASNA_MACHINES_S3_BUCKET` or fallback `MACHINES_S3_BUCKET`; prefix uses
169
+ `HASNA_MACHINES_S3_PREFIX`, fallback `MACHINES_S3_PREFIX`, or `machines`.
170
+ This keeps the open-source CLI local/self-hosted by default while allowing
171
+ Hasna deployments to route app-owned backups through canonical storage metadata.
172
+
134
173
  ## Applications and tooling
135
174
 
136
175
  ```bash
package/dist/cli/index.js CHANGED
@@ -2286,7 +2286,7 @@ class PgAdapterAsync {
2286
2286
  var init_remote_storage = () => {};
2287
2287
 
2288
2288
  // src/storage-sync.ts
2289
- function readEnv(name) {
2289
+ function readEnv2(name) {
2290
2290
  const value = process.env[name]?.trim();
2291
2291
  return value || undefined;
2292
2292
  }
@@ -2298,7 +2298,7 @@ function normalizeStorageMode(value) {
2298
2298
  }
2299
2299
  function getStorageDatabaseEnvName() {
2300
2300
  for (const name of STORAGE_DATABASE_ENV) {
2301
- if (readEnv(name))
2301
+ if (readEnv2(name))
2302
2302
  return name;
2303
2303
  }
2304
2304
  return null;
@@ -2309,10 +2309,10 @@ function getStorageDatabaseEnv() {
2309
2309
  }
2310
2310
  function getStorageDatabaseUrl() {
2311
2311
  const env2 = getStorageDatabaseEnv();
2312
- return env2 ? readEnv(env2.name) ?? null : null;
2312
+ return env2 ? readEnv2(env2.name) ?? null : null;
2313
2313
  }
2314
2314
  function getStorageMode() {
2315
- const mode = normalizeStorageMode(readEnv(MACHINES_STORAGE_MODE_ENV)) ?? normalizeStorageMode(readEnv(MACHINES_STORAGE_MODE_FALLBACK_ENV));
2315
+ const mode = normalizeStorageMode(readEnv2(MACHINES_STORAGE_MODE_ENV)) ?? normalizeStorageMode(readEnv2(MACHINES_STORAGE_MODE_FALLBACK_ENV));
2316
2316
  if (mode)
2317
2317
  return mode;
2318
2318
  return getStorageDatabaseUrl() ? "hybrid" : "local";
@@ -7113,6 +7113,7 @@ var machineSchema = exports_external.object({
7113
7113
  workspacePath: exports_external.string(),
7114
7114
  bunPath: exports_external.string().optional(),
7115
7115
  tags: exports_external.array(exports_external.string()).optional(),
7116
+ metadata: exports_external.record(exports_external.unknown()).optional(),
7116
7117
  packages: exports_external.array(packageSchema).optional(),
7117
7118
  apps: exports_external.array(appSchema).optional(),
7118
7119
  files: exports_external.array(fileSchema).optional()
@@ -7344,9 +7345,52 @@ function runSetup(machineId, options = {}) {
7344
7345
  // src/commands/backup.ts
7345
7346
  import { homedir as homedir2 } from "os";
7346
7347
  import { join as join3 } from "path";
7348
+ var MACHINES_BACKUP_BUCKET_ENV = "HASNA_MACHINES_S3_BUCKET";
7349
+ var MACHINES_BACKUP_BUCKET_FALLBACK_ENV = "MACHINES_S3_BUCKET";
7350
+ var MACHINES_BACKUP_PREFIX_ENV = "HASNA_MACHINES_S3_PREFIX";
7351
+ var MACHINES_BACKUP_PREFIX_FALLBACK_ENV = "MACHINES_S3_PREFIX";
7352
+ var DEFAULT_BACKUP_PREFIX = "machines";
7347
7353
  function quote2(value) {
7348
7354
  return `'${value.replace(/'/g, `'\\''`)}'`;
7349
7355
  }
7356
+ function readEnv(name) {
7357
+ const value = process.env[name]?.trim();
7358
+ return value || undefined;
7359
+ }
7360
+ function readBackupBucketEnv() {
7361
+ const primary = readEnv(MACHINES_BACKUP_BUCKET_ENV);
7362
+ if (primary)
7363
+ return { bucket: primary, bucketSource: MACHINES_BACKUP_BUCKET_ENV };
7364
+ const fallback = readEnv(MACHINES_BACKUP_BUCKET_FALLBACK_ENV);
7365
+ if (fallback)
7366
+ return { bucket: fallback, bucketSource: MACHINES_BACKUP_BUCKET_FALLBACK_ENV };
7367
+ return null;
7368
+ }
7369
+ function readBackupPrefixEnv() {
7370
+ const primary = readEnv(MACHINES_BACKUP_PREFIX_ENV);
7371
+ if (primary)
7372
+ return { prefix: primary, prefixSource: MACHINES_BACKUP_PREFIX_ENV };
7373
+ const fallback = readEnv(MACHINES_BACKUP_PREFIX_FALLBACK_ENV);
7374
+ if (fallback)
7375
+ return { prefix: fallback, prefixSource: MACHINES_BACKUP_PREFIX_FALLBACK_ENV };
7376
+ return null;
7377
+ }
7378
+ function resolveBackupTarget(options = {}) {
7379
+ const explicitBucket = options.bucket?.trim();
7380
+ const envBucket = explicitBucket ? null : readBackupBucketEnv();
7381
+ const bucket = explicitBucket || envBucket?.bucket;
7382
+ if (!bucket) {
7383
+ throw new Error(`Missing S3 backup bucket. Pass --bucket or set ${MACHINES_BACKUP_BUCKET_ENV} or ${MACHINES_BACKUP_BUCKET_FALLBACK_ENV}.`);
7384
+ }
7385
+ const explicitPrefix = options.prefix?.trim();
7386
+ const envPrefix = explicitPrefix ? null : readBackupPrefixEnv();
7387
+ return {
7388
+ bucket,
7389
+ prefix: explicitPrefix || envPrefix?.prefix || DEFAULT_BACKUP_PREFIX,
7390
+ bucketSource: explicitBucket ? "argument" : envBucket.bucketSource,
7391
+ prefixSource: explicitPrefix ? "argument" : envPrefix?.prefixSource || "default"
7392
+ };
7393
+ }
7350
7394
  function defaultBackupSources() {
7351
7395
  const home = homedir2();
7352
7396
  return [
@@ -7355,7 +7399,8 @@ function defaultBackupSources() {
7355
7399
  join3(home, ".secrets")
7356
7400
  ];
7357
7401
  }
7358
- function buildBackupPlan(bucket, prefix = "machines") {
7402
+ function buildBackupPlan(bucket, prefix) {
7403
+ const target = resolveBackupTarget({ bucket, prefix });
7359
7404
  const archivePath = join3(homedir2(), ".hasna", "machines", "backup.tgz");
7360
7405
  const sources = defaultBackupSources();
7361
7406
  const steps = [
@@ -7368,7 +7413,7 @@ function buildBackupPlan(bucket, prefix = "machines") {
7368
7413
  {
7369
7414
  id: "backup-upload",
7370
7415
  title: "Upload archive to S3",
7371
- command: `aws s3 cp ${quote2(archivePath)} s3://${bucket}/${prefix}/$(hostname)-backup.tgz`,
7416
+ command: `aws s3 cp ${quote2(archivePath)} s3://${target.bucket}/${target.prefix}/$(hostname)-backup.tgz`,
7372
7417
  manager: "custom"
7373
7418
  }
7374
7419
  ];
@@ -7379,7 +7424,7 @@ function buildBackupPlan(bucket, prefix = "machines") {
7379
7424
  executed: 0
7380
7425
  };
7381
7426
  }
7382
- function runBackup(bucket, prefix = "machines", options = {}) {
7427
+ function runBackup(bucket, prefix, options = {}) {
7383
7428
  const plan = buildBackupPlan(bucket, prefix);
7384
7429
  if (!options.apply)
7385
7430
  return plan;
@@ -7787,7 +7832,8 @@ function discoverMachineTopology(options = {}) {
7787
7832
  topology: true,
7788
7833
  compatibility: true,
7789
7834
  route_resolution: true,
7790
- cli_json_fallback: true
7835
+ cli_json_fallback: true,
7836
+ workspace_path_mapping: true
7791
7837
  },
7792
7838
  generated_at: now.toISOString(),
7793
7839
  local_machine_id: localMachineId,
@@ -7912,6 +7958,215 @@ function resolveMachineRoute(machineId, options = {}) {
7912
7958
  warnings
7913
7959
  };
7914
7960
  }
7961
+ function isRecord(value) {
7962
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7963
+ }
7964
+ function metadataString(metadata, keys) {
7965
+ for (const key of keys) {
7966
+ const value = metadata[key];
7967
+ if (typeof value === "string" && value.trim())
7968
+ return value.trim();
7969
+ }
7970
+ return null;
7971
+ }
7972
+ function metadataBoolean(metadata, keys) {
7973
+ for (const key of keys) {
7974
+ const value = metadata[key];
7975
+ if (typeof value === "boolean")
7976
+ return value;
7977
+ }
7978
+ return null;
7979
+ }
7980
+ function metadataStringArray(metadata, keys) {
7981
+ for (const key of keys) {
7982
+ const value = metadata[key];
7983
+ if (Array.isArray(value))
7984
+ return value.filter((entry) => typeof entry === "string");
7985
+ }
7986
+ return [];
7987
+ }
7988
+ function readMappedPath(input) {
7989
+ for (const containerName of input.containers) {
7990
+ const container = input.metadata[containerName];
7991
+ if (!isRecord(container))
7992
+ continue;
7993
+ for (const key of input.keys) {
7994
+ const value = container[key];
7995
+ if (typeof value === "string" && value.trim())
7996
+ return value.trim();
7997
+ if (isRecord(value)) {
7998
+ const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
7999
+ if (path)
8000
+ return path;
8001
+ }
8002
+ }
8003
+ }
8004
+ return null;
8005
+ }
8006
+ function trimTrailingSlash(value) {
8007
+ return value.replace(/\/+$/, "");
8008
+ }
8009
+ function joinPath(left, right) {
8010
+ return `${trimTrailingSlash(left)}/${right.replace(/^\/+/, "")}`;
8011
+ }
8012
+ function inferRepoRoot(workspaceRoot, repoName) {
8013
+ if (!workspaceRoot || !repoName)
8014
+ return null;
8015
+ const root = trimTrailingSlash(workspaceRoot);
8016
+ if (root.endsWith(`/${repoName}`) || root === repoName)
8017
+ return root;
8018
+ if (root.endsWith("/workspace") || root.endsWith("/Workspace")) {
8019
+ return joinPath(root, `hasna/opensource/${repoName}`);
8020
+ }
8021
+ return joinPath(root, repoName);
8022
+ }
8023
+ function projectPathFromMetadata(metadata, projectId, repoName) {
8024
+ const keys = [projectId, repoName].filter((value) => Boolean(value));
8025
+ return readMappedPath({
8026
+ metadata,
8027
+ containers: ["workspace_paths", "workspacePaths", "repo_paths", "repoPaths", "project_paths", "projectPaths", "projects"],
8028
+ keys
8029
+ });
8030
+ }
8031
+ function openFilesPathFromMetadata(metadata, projectId, repoName) {
8032
+ const direct = metadataString(metadata, ["open_files_root", "openFilesRoot", "open_files_path", "openFilesPath"]);
8033
+ if (direct)
8034
+ return direct;
8035
+ const keys = [projectId, repoName, "open-files", "open_files", "default"].filter((value) => Boolean(value));
8036
+ return readMappedPath({
8037
+ metadata,
8038
+ containers: ["open_files_roots", "openFilesRoots", "open_files_paths", "openFilesPaths"],
8039
+ keys
8040
+ });
8041
+ }
8042
+ function trustStatus(machine) {
8043
+ if (!machine)
8044
+ return "unknown";
8045
+ const explicit = metadataString(machine.metadata, ["trust_status", "trustStatus"]);
8046
+ if (explicit === "trusted" || explicit === "untrusted" || explicit === "unknown")
8047
+ return explicit;
8048
+ const trusted = metadataBoolean(machine.metadata, ["trusted", "syncTrusted", "sync_trusted"]);
8049
+ if (trusted === true)
8050
+ return "trusted";
8051
+ if (trusted === false)
8052
+ return "untrusted";
8053
+ if (machine.route_hints.some((hint) => hint.kind === "local"))
8054
+ return "trusted";
8055
+ if (machine.tags.includes("trusted"))
8056
+ return "trusted";
8057
+ return "unknown";
8058
+ }
8059
+ function authStatus(machine) {
8060
+ if (!machine)
8061
+ return "unknown";
8062
+ const explicit = metadataString(machine.metadata, ["auth_status", "authStatus"]);
8063
+ if (explicit === "authenticated" || explicit === "unauthenticated" || explicit === "unknown")
8064
+ return explicit;
8065
+ const authenticated = metadataBoolean(machine.metadata, ["authenticated", "sshAuthorized", "ssh_authorized"]);
8066
+ if (authenticated === true)
8067
+ return "authenticated";
8068
+ if (authenticated === false)
8069
+ return "unauthenticated";
8070
+ if (machine.route_hints.some((hint) => hint.kind === "local"))
8071
+ return "authenticated";
8072
+ return "unknown";
8073
+ }
8074
+ function primaryMachine(machine, projectId, primaryMachineId) {
8075
+ if (!machine)
8076
+ return false;
8077
+ if (primaryMachineId)
8078
+ return machine.machine_id === primaryMachineId;
8079
+ if (metadataBoolean(machine.metadata, ["primary", "primary_machine", "primaryMachine"]) === true)
8080
+ return true;
8081
+ const primaryProjects = metadataStringArray(machine.metadata, ["primary_projects", "primaryProjects"]);
8082
+ if (primaryProjects.includes(projectId))
8083
+ return true;
8084
+ return machine.tags.includes("primary");
8085
+ }
8086
+ function metadataKeysForDiagnostics(metadata) {
8087
+ return Object.keys(metadata).filter((key) => !/(secret|token|key|password|credential)/i.test(key)).sort();
8088
+ }
8089
+ function resolveMachineWorkspace(options) {
8090
+ const topology = options.topology ?? discoverMachineTopology(options);
8091
+ const warnings = [...topology.warnings];
8092
+ const { machine, matchedBy } = findRouteMachine(topology, options.machineId);
8093
+ const generatedAt = (options.now ?? new Date).toISOString();
8094
+ const repoName = options.repoName ?? options.projectId;
8095
+ const openFilesRepoName = options.openFilesRepoName ?? "open-files";
8096
+ if (!machine) {
8097
+ warnings.push(`machine_not_found:${options.machineId}`);
8098
+ return {
8099
+ schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
8100
+ package: topology.package,
8101
+ ok: false,
8102
+ requested_machine_id: options.machineId,
8103
+ machine_id: null,
8104
+ generated_at: generatedAt,
8105
+ project: { project_id: options.projectId, repo_name: repoName, canonical: Boolean(options.projectId) },
8106
+ machine: { current: false, primary: false, trust_status: "unknown", auth_status: "unknown" },
8107
+ paths: {
8108
+ workspace_root: { path: null, source: "unresolved" },
8109
+ project_root: { path: null, source: "unresolved" },
8110
+ open_files_root: { path: null, source: "unresolved" }
8111
+ },
8112
+ evidence: {
8113
+ topology: true,
8114
+ matched_by: matchedBy,
8115
+ manifest_declared: null,
8116
+ metadata_keys: []
8117
+ },
8118
+ warnings
8119
+ };
8120
+ }
8121
+ const metadata = machine.metadata;
8122
+ const workspaceRootPath = options.workspaceRoot ?? machine.workspace_path;
8123
+ const workspaceRootSource = options.workspaceRoot ? "argument" : machine.workspace_path ? "manifest" : "unresolved";
8124
+ const metadataProjectRoot = projectPathFromMetadata(metadata, options.projectId, repoName);
8125
+ const inferredProjectRoot = inferRepoRoot(workspaceRootPath, repoName);
8126
+ const projectRootPath = options.projectRoot ?? metadataProjectRoot ?? inferredProjectRoot;
8127
+ const projectRootSource = options.projectRoot ? "argument" : metadataProjectRoot ? "manifest_metadata" : inferredProjectRoot ? "inferred" : "unresolved";
8128
+ const metadataOpenFilesRoot = openFilesPathFromMetadata(metadata, options.projectId, openFilesRepoName);
8129
+ const inferredOpenFilesRoot = inferRepoRoot(workspaceRootPath, openFilesRepoName);
8130
+ const openFilesRootPath = options.openFilesRoot ?? metadataOpenFilesRoot ?? inferredOpenFilesRoot;
8131
+ const openFilesRootSource = options.openFilesRoot ? "argument" : metadataOpenFilesRoot ? "manifest_metadata" : inferredOpenFilesRoot ? "inferred" : "unresolved";
8132
+ if (projectRootSource === "inferred")
8133
+ warnings.push(`project_root_inferred:${options.projectId}`);
8134
+ if (openFilesRootSource === "inferred")
8135
+ warnings.push(`open_files_root_inferred:${options.projectId}`);
8136
+ if (!projectRootPath)
8137
+ warnings.push(`project_root_unresolved:${options.projectId}`);
8138
+ return {
8139
+ schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
8140
+ package: topology.package,
8141
+ ok: Boolean(projectRootPath),
8142
+ requested_machine_id: options.machineId,
8143
+ machine_id: machine.machine_id,
8144
+ generated_at: generatedAt,
8145
+ project: {
8146
+ project_id: options.projectId,
8147
+ repo_name: repoName,
8148
+ canonical: Boolean(options.projectId && repoName)
8149
+ },
8150
+ machine: {
8151
+ current: machine.machine_id === topology.local_machine_id,
8152
+ primary: primaryMachine(machine, options.projectId, options.primaryMachineId),
8153
+ trust_status: trustStatus(machine),
8154
+ auth_status: authStatus(machine)
8155
+ },
8156
+ paths: {
8157
+ workspace_root: { path: workspaceRootPath, source: workspaceRootSource },
8158
+ project_root: { path: projectRootPath, source: projectRootSource },
8159
+ open_files_root: { path: openFilesRootPath, source: openFilesRootSource }
8160
+ },
8161
+ evidence: {
8162
+ topology: true,
8163
+ matched_by: matchedBy,
8164
+ manifest_declared: machine.manifest_declared,
8165
+ metadata_keys: metadataKeysForDiagnostics(metadata)
8166
+ },
8167
+ warnings
8168
+ };
8169
+ }
7915
8170
 
7916
8171
  // src/commands/ssh.ts
7917
8172
  function shellQuote(value) {
@@ -10514,6 +10769,22 @@ function renderCompatibilityResult(result) {
10514
10769
  ].join(`
10515
10770
  `);
10516
10771
  }
10772
+ function renderWorkspaceResolution(result) {
10773
+ return renderKeyValueTable([
10774
+ ["machine", result.machine_id ?? result.requested_machine_id],
10775
+ ["ok", String(result.ok)],
10776
+ ["project", result.project.project_id],
10777
+ ["repo", result.project.repo_name ?? "unknown"],
10778
+ ["current", String(result.machine.current)],
10779
+ ["primary", String(result.machine.primary)],
10780
+ ["trust", result.machine.trust_status],
10781
+ ["auth", result.machine.auth_status],
10782
+ ["workspace root", `${result.paths.workspace_root.path ?? "unresolved"} (${result.paths.workspace_root.source})`],
10783
+ ["project root", `${result.paths.project_root.path ?? "unresolved"} (${result.paths.project_root.source})`],
10784
+ ["open-files root", `${result.paths.open_files_root.path ?? "unresolved"} (${result.paths.open_files_root.source})`],
10785
+ ["warnings", result.warnings.join(", ") || "none"]
10786
+ ]);
10787
+ }
10517
10788
  function renderFleetStatus(status) {
10518
10789
  return [
10519
10790
  renderKeyValueTable([
@@ -10664,11 +10935,28 @@ program2.command("compatibility").description("Check remote package, command, an
10664
10935
  if (!result.ok && !options.json)
10665
10936
  process.exitCode = 1;
10666
10937
  });
10938
+ var workspaceCommand = program2.command("workspace").description("Resolve sync-safe workspace paths for open-* consumers");
10939
+ workspaceCommand.command("resolve").description("Resolve repo and open-files roots for a machine/project").requiredOption("--machine <id>", "Machine identifier").requiredOption("--project <id>", "Canonical project id").option("--repo <name>", "Repository name; defaults to project id").option("--open-files-repo <name>", "Open-files repository name", "open-files").option("--primary-machine <id>", "Primary machine id for the project").option("--workspace-root <path>", "Override the machine workspace root").option("--project-root <path>", "Override the resolved project root").option("--open-files-root <path>", "Override the resolved open-files root").option("--no-tailscale", "Skip tailscale status probing").option("-j, --json", "Print JSON output", false).action((options) => {
10940
+ const result = resolveMachineWorkspace({
10941
+ machineId: options.machine,
10942
+ projectId: options.project,
10943
+ repoName: options.repo,
10944
+ openFilesRepoName: options.openFilesRepo,
10945
+ primaryMachineId: options.primaryMachine,
10946
+ workspaceRoot: options.workspaceRoot,
10947
+ projectRoot: options.projectRoot,
10948
+ openFilesRoot: options.openFilesRoot,
10949
+ includeTailscale: options.tailscale !== false
10950
+ });
10951
+ printJsonOrText(result, renderWorkspaceResolution(result), options.json);
10952
+ if (!result.ok && !options.json)
10953
+ process.exitCode = 1;
10954
+ });
10667
10955
  program2.command("diff").description("Show manifest differences between two machines").requiredOption("--left <id>", "Left machine identifier").option("--right <id>", "Right machine identifier (defaults to current machine)").option("-j, --json", "Print JSON output", false).action((options) => {
10668
10956
  const result = diffMachines(options.left, options.right);
10669
10957
  console.log(JSON.stringify(result, null, 2));
10670
10958
  });
10671
- program2.command("backup").description("Create and optionally upload a machine backup archive").requiredOption("--bucket <name>", "S3 bucket name").option("--prefix <prefix>", "S3 key prefix", "machines").option("--apply", "Execute backup commands instead of previewing the plan", false).option("--yes", "Confirm execution when using --apply", false).option("-j, --json", "Print JSON output", false).action((options) => {
10959
+ program2.command("backup").description("Create and optionally upload a machine backup archive").option("--bucket <name>", "S3 bucket name; defaults to HASNA_MACHINES_S3_BUCKET or MACHINES_S3_BUCKET").option("--prefix <prefix>", "S3 key prefix; defaults to HASNA_MACHINES_S3_PREFIX, MACHINES_S3_PREFIX, or machines").option("--apply", "Execute backup commands instead of previewing the plan", false).option("--yes", "Confirm execution when using --apply", false).option("-j, --json", "Print JSON output", false).action((options) => {
10672
10960
  const result = options.apply ? runBackup(options.bucket, options.prefix, { apply: true, yes: options.yes }) : buildBackupPlan(options.bucket, options.prefix);
10673
10961
  console.log(JSON.stringify(result, null, 2));
10674
10962
  });
@@ -1,6 +1,21 @@
1
1
  import type { SetupResult } from "../types.js";
2
- export declare function buildBackupPlan(bucket: string, prefix?: string): SetupResult;
3
- export declare function runBackup(bucket: string, prefix?: string, options?: {
2
+ export declare const MACHINES_BACKUP_BUCKET_ENV = "HASNA_MACHINES_S3_BUCKET";
3
+ export declare const MACHINES_BACKUP_BUCKET_FALLBACK_ENV = "MACHINES_S3_BUCKET";
4
+ export declare const MACHINES_BACKUP_PREFIX_ENV = "HASNA_MACHINES_S3_PREFIX";
5
+ export declare const MACHINES_BACKUP_PREFIX_FALLBACK_ENV = "MACHINES_S3_PREFIX";
6
+ export declare const DEFAULT_BACKUP_PREFIX = "machines";
7
+ export interface BackupTarget {
8
+ bucket: string;
9
+ prefix: string;
10
+ bucketSource: "argument" | typeof MACHINES_BACKUP_BUCKET_ENV | typeof MACHINES_BACKUP_BUCKET_FALLBACK_ENV;
11
+ prefixSource: "argument" | typeof MACHINES_BACKUP_PREFIX_ENV | typeof MACHINES_BACKUP_PREFIX_FALLBACK_ENV | "default";
12
+ }
13
+ export declare function resolveBackupTarget(options?: {
14
+ bucket?: string;
15
+ prefix?: string;
16
+ }): BackupTarget;
17
+ export declare function buildBackupPlan(bucket?: string, prefix?: string): SetupResult;
18
+ export declare function runBackup(bucket?: string, prefix?: string, options?: {
4
19
  apply?: boolean;
5
20
  yes?: boolean;
6
21
  }): SetupResult;
@@ -1 +1 @@
1
- {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../src/commands/backup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAe1D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,SAAa,GAAG,WAAW,CAwBhF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,SAAa,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,WAAW,CA0B5H"}
1
+ {"version":3,"file":"backup.d.ts","sourceRoot":"","sources":["../../src/commands/backup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAE1D,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AACrE,eAAO,MAAM,mCAAmC,uBAAuB,CAAC;AACxE,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AACrE,eAAO,MAAM,mCAAmC,uBAAuB,CAAC;AACxE,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,UAAU,GAAG,OAAO,0BAA0B,GAAG,OAAO,mCAAmC,CAAC;IAC1G,YAAY,EAAE,UAAU,GAAG,OAAO,0BAA0B,GAAG,OAAO,mCAAmC,GAAG,SAAS,CAAC;CACvH;AA2BD,wBAAgB,mBAAmB,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,YAAY,CAoBpG;AAWD,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,CAyB7E;AAED,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,WAAW,CA0BzH"}
@@ -1,5 +1,5 @@
1
- export { MACHINES_CONSUMER_CONTRACT_VERSION, MACHINES_PACKAGE_NAME, discoverMachineTopology, getLocalMachineTopology, resolveMachineRoute, } from "./topology.js";
2
- export type { MachineRouteConfidence, MachineRouteHint, MachineRouteKind, MachineRouteOptions, MachineRouteResolution, MachineTopology, MachineTopologyEntry, MachineTopologyOptions, MachinesConsumerCapabilities, MachinesContractPackage, TopologyCommandResult, TopologyCommandRunner, } from "./topology.js";
1
+ export { MACHINES_CONSUMER_CONTRACT_VERSION, MACHINES_PACKAGE_NAME, discoverMachineTopology, getLocalMachineTopology, resolveMachineRoute, resolveMachineWorkspace, } from "./topology.js";
2
+ export type { MachineRouteConfidence, MachineRouteHint, MachineRouteKind, MachineRouteOptions, MachineRouteResolution, MachineTopology, MachineTopologyEntry, MachineTopologyOptions, MachineWorkspaceAuthStatus, MachineWorkspaceOptions, MachineWorkspacePath, MachineWorkspacePathSource, MachineWorkspaceProject, MachineWorkspaceResolution, MachineWorkspaceTrustStatus, MachinesConsumerCapabilities, MachinesContractPackage, TopologyCommandResult, TopologyCommandRunner, } from "./topology.js";
3
3
  export { checkMachineCompatibility, } from "./compatibility.js";
4
4
  export type { CompatibilityCheck, CompatibilityCommandRunner, CompatibilityCommandSpec, CompatibilityPackageSpec, CompatibilitySource, CompatibilityStatus, CompatibilityWorkspaceSpec, MachineCompatibilityOptions, MachineCompatibilityReport, } from "./compatibility.js";
5
5
  export { resolveMachineCommand, runMachineCommand, } from "./remote.js";
@@ -1 +1 @@
1
- {"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../src/consumer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,4BAA4B,EAC5B,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,GAClB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../src/consumer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,EACvB,oBAAoB,EACpB,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,EAC5B,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,yBAAyB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,GAClB,MAAM,cAAc,CAAC"}