@hasna/machines 0.0.18 → 0.0.20
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 +56 -8
- package/dist/cli/index.js +307 -20
- package/dist/commands/backup.d.ts +17 -2
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/compatibility.d.ts.map +1 -1
- package/dist/consumer.d.ts +2 -2
- package/dist/consumer.d.ts.map +1 -1
- package/dist/consumer.js +252 -13
- package/dist/index.js +343 -17
- package/dist/manifests.d.ts +8 -0
- package/dist/manifests.d.ts.map +1 -1
- package/dist/mcp/index.js +311 -21
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/topology.d.ts +65 -0
- package/dist/topology.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,13 +49,20 @@ machines self-test
|
|
|
49
49
|
|
|
50
50
|
## Topology SDK
|
|
51
51
|
|
|
52
|
-
`@hasna/machines` exposes a compact
|
|
53
|
-
that need machine identity without importing CLI
|
|
54
|
-
need the stable app-to-app contract
|
|
52
|
+
`@hasna/machines` exposes a compact consumer SDK for other open-core packages
|
|
53
|
+
that need machine identity without importing CLI, MCP, agent, installer, or
|
|
54
|
+
storage-heavy internals. Consumers that only need the stable app-to-app contract
|
|
55
|
+
should import `@hasna/machines/consumer`:
|
|
55
56
|
|
|
56
57
|
```ts
|
|
57
|
-
import {
|
|
58
|
-
|
|
58
|
+
import {
|
|
59
|
+
MACHINES_CONSUMER_CONTRACT,
|
|
60
|
+
discoverMachineTopology,
|
|
61
|
+
getLocalMachineTopology,
|
|
62
|
+
resolveMachineRoute,
|
|
63
|
+
} from "@hasna/machines/consumer";
|
|
64
|
+
|
|
65
|
+
console.log(MACHINES_CONSUMER_CONTRACT.schema_version);
|
|
59
66
|
const topology = discoverMachineTopology();
|
|
60
67
|
const local = getLocalMachineTopology();
|
|
61
68
|
const route = resolveMachineRoute("spark01");
|
|
@@ -67,9 +74,11 @@ The SDK merges manifest entries, local heartbeats, SSH route hints, and
|
|
|
67
74
|
when present, and fall back to local probes or app-local machine registries when
|
|
68
75
|
it is absent.
|
|
69
76
|
|
|
70
|
-
Topology, route, and compatibility JSON include `schema_version`,
|
|
71
|
-
version metadata, and capability flags. The current consumer contract
|
|
72
|
-
`1
|
|
77
|
+
Topology, route, workspace, and compatibility JSON include `schema_version`,
|
|
78
|
+
package version metadata, and capability flags. The current consumer contract
|
|
79
|
+
version is `1`; the exported `MACHINES_CONSUMER_CONTRACT` records the stable
|
|
80
|
+
entrypoint, envelope names, and stable exports used by downstream apps such as
|
|
81
|
+
`@hasna/knowledge`.
|
|
73
82
|
|
|
74
83
|
CLI and MCP expose the same topology view:
|
|
75
84
|
|
|
@@ -79,6 +88,31 @@ machines topology --no-tailscale --json
|
|
|
79
88
|
machines route --machine spark01 --json
|
|
80
89
|
```
|
|
81
90
|
|
|
91
|
+
Consumers that need repo paths can resolve trust-aware workspace mappings
|
|
92
|
+
without importing the full machines app:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { resolveMachineWorkspace } from "@hasna/machines/consumer";
|
|
96
|
+
|
|
97
|
+
const workspace = resolveMachineWorkspace({
|
|
98
|
+
machineId: "spark01",
|
|
99
|
+
projectId: "open-knowledge",
|
|
100
|
+
repoName: "open-knowledge",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
console.log(workspace.paths.project_root.path);
|
|
104
|
+
console.log(workspace.paths.open_files_root.path);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The resolver returns the machine workspace root, project repo root,
|
|
108
|
+
open-files root, current/primary flags, trust/auth status, and redacted
|
|
109
|
+
diagnostics. It uses explicit manifest metadata first and deterministic
|
|
110
|
+
workspace inference second; consumers can still pass manual overrides.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
machines workspace resolve --machine spark01 --project open-knowledge --repo open-knowledge --json
|
|
114
|
+
```
|
|
115
|
+
|
|
82
116
|
## Compatibility SDK
|
|
83
117
|
|
|
84
118
|
Open-core consumers can use `@hasna/machines` to preflight a peer before
|
|
@@ -131,6 +165,20 @@ Configure database storage with `HASNA_MACHINES_DATABASE_URL` or fallback
|
|
|
131
165
|
`HASNA_MACHINES_STORAGE_MODE` or `MACHINES_STORAGE_MODE` with `local`,
|
|
132
166
|
`hybrid`, or `remote`.
|
|
133
167
|
|
|
168
|
+
Machine backups are preview-only unless `--apply --yes` is passed. The backup
|
|
169
|
+
target can be explicit or environment-backed:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
machines backup --bucket fleet-backups --prefix machines --json
|
|
173
|
+
HASNA_MACHINES_S3_BUCKET=hasna-xyz-opensource-machines-prod machines backup --json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`--bucket` and `--prefix` always win. Without `--bucket`, the backup command
|
|
177
|
+
uses `HASNA_MACHINES_S3_BUCKET` or fallback `MACHINES_S3_BUCKET`; prefix uses
|
|
178
|
+
`HASNA_MACHINES_S3_PREFIX`, fallback `MACHINES_S3_PREFIX`, or `machines`.
|
|
179
|
+
This keeps the open-source CLI local/self-hosted by default while allowing
|
|
180
|
+
Hasna deployments to route app-owned backups through canonical storage metadata.
|
|
181
|
+
|
|
134
182
|
## Applications and tooling
|
|
135
183
|
|
|
136
184
|
```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
|
|
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 (
|
|
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 ?
|
|
2312
|
+
return env2 ? readEnv2(env2.name) ?? null : null;
|
|
2313
2313
|
}
|
|
2314
2314
|
function getStorageMode() {
|
|
2315
|
-
const mode = normalizeStorageMode(
|
|
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
|
|
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
|
|
7427
|
+
function runBackup(bucket, prefix, options = {}) {
|
|
7383
7428
|
const plan = buildBackupPlan(bucket, prefix);
|
|
7384
7429
|
if (!options.apply)
|
|
7385
7430
|
return plan;
|
|
@@ -7583,6 +7628,16 @@ import { spawnSync } from "child_process";
|
|
|
7583
7628
|
init_paths();
|
|
7584
7629
|
var MACHINES_CONSUMER_CONTRACT_VERSION = 1;
|
|
7585
7630
|
var MACHINES_PACKAGE_NAME = "@hasna/machines";
|
|
7631
|
+
var MACHINES_CONSUMER_CAPABILITIES = {
|
|
7632
|
+
topology: true,
|
|
7633
|
+
compatibility: true,
|
|
7634
|
+
route_resolution: true,
|
|
7635
|
+
cli_json_fallback: true,
|
|
7636
|
+
workspace_path_mapping: true
|
|
7637
|
+
};
|
|
7638
|
+
function getMachinesConsumerCapabilities() {
|
|
7639
|
+
return { ...MACHINES_CONSUMER_CAPABILITIES };
|
|
7640
|
+
}
|
|
7586
7641
|
function normalizePlatform2(value = platform3()) {
|
|
7587
7642
|
const normalized = value.toLowerCase();
|
|
7588
7643
|
if (normalized === "darwin" || normalized === "macos")
|
|
@@ -7783,12 +7838,7 @@ function discoverMachineTopology(options = {}) {
|
|
|
7783
7838
|
name: MACHINES_PACKAGE_NAME,
|
|
7784
7839
|
version: getPackageVersion()
|
|
7785
7840
|
},
|
|
7786
|
-
capabilities:
|
|
7787
|
-
topology: true,
|
|
7788
|
-
compatibility: true,
|
|
7789
|
-
route_resolution: true,
|
|
7790
|
-
cli_json_fallback: true
|
|
7791
|
-
},
|
|
7841
|
+
capabilities: getMachinesConsumerCapabilities(),
|
|
7792
7842
|
generated_at: now.toISOString(),
|
|
7793
7843
|
local_machine_id: localMachineId,
|
|
7794
7844
|
local_hostname: hostname3(),
|
|
@@ -7912,6 +7962,215 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
7912
7962
|
warnings
|
|
7913
7963
|
};
|
|
7914
7964
|
}
|
|
7965
|
+
function isRecord(value) {
|
|
7966
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7967
|
+
}
|
|
7968
|
+
function metadataString(metadata, keys) {
|
|
7969
|
+
for (const key of keys) {
|
|
7970
|
+
const value = metadata[key];
|
|
7971
|
+
if (typeof value === "string" && value.trim())
|
|
7972
|
+
return value.trim();
|
|
7973
|
+
}
|
|
7974
|
+
return null;
|
|
7975
|
+
}
|
|
7976
|
+
function metadataBoolean(metadata, keys) {
|
|
7977
|
+
for (const key of keys) {
|
|
7978
|
+
const value = metadata[key];
|
|
7979
|
+
if (typeof value === "boolean")
|
|
7980
|
+
return value;
|
|
7981
|
+
}
|
|
7982
|
+
return null;
|
|
7983
|
+
}
|
|
7984
|
+
function metadataStringArray(metadata, keys) {
|
|
7985
|
+
for (const key of keys) {
|
|
7986
|
+
const value = metadata[key];
|
|
7987
|
+
if (Array.isArray(value))
|
|
7988
|
+
return value.filter((entry) => typeof entry === "string");
|
|
7989
|
+
}
|
|
7990
|
+
return [];
|
|
7991
|
+
}
|
|
7992
|
+
function readMappedPath(input) {
|
|
7993
|
+
for (const containerName of input.containers) {
|
|
7994
|
+
const container = input.metadata[containerName];
|
|
7995
|
+
if (!isRecord(container))
|
|
7996
|
+
continue;
|
|
7997
|
+
for (const key of input.keys) {
|
|
7998
|
+
const value = container[key];
|
|
7999
|
+
if (typeof value === "string" && value.trim())
|
|
8000
|
+
return value.trim();
|
|
8001
|
+
if (isRecord(value)) {
|
|
8002
|
+
const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
|
|
8003
|
+
if (path)
|
|
8004
|
+
return path;
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
}
|
|
8008
|
+
return null;
|
|
8009
|
+
}
|
|
8010
|
+
function trimTrailingSlash(value) {
|
|
8011
|
+
return value.replace(/\/+$/, "");
|
|
8012
|
+
}
|
|
8013
|
+
function joinPath(left, right) {
|
|
8014
|
+
return `${trimTrailingSlash(left)}/${right.replace(/^\/+/, "")}`;
|
|
8015
|
+
}
|
|
8016
|
+
function inferRepoRoot(workspaceRoot, repoName) {
|
|
8017
|
+
if (!workspaceRoot || !repoName)
|
|
8018
|
+
return null;
|
|
8019
|
+
const root = trimTrailingSlash(workspaceRoot);
|
|
8020
|
+
if (root.endsWith(`/${repoName}`) || root === repoName)
|
|
8021
|
+
return root;
|
|
8022
|
+
if (root.endsWith("/workspace") || root.endsWith("/Workspace")) {
|
|
8023
|
+
return joinPath(root, `hasna/opensource/${repoName}`);
|
|
8024
|
+
}
|
|
8025
|
+
return joinPath(root, repoName);
|
|
8026
|
+
}
|
|
8027
|
+
function projectPathFromMetadata(metadata, projectId, repoName) {
|
|
8028
|
+
const keys = [projectId, repoName].filter((value) => Boolean(value));
|
|
8029
|
+
return readMappedPath({
|
|
8030
|
+
metadata,
|
|
8031
|
+
containers: ["workspace_paths", "workspacePaths", "repo_paths", "repoPaths", "project_paths", "projectPaths", "projects"],
|
|
8032
|
+
keys
|
|
8033
|
+
});
|
|
8034
|
+
}
|
|
8035
|
+
function openFilesPathFromMetadata(metadata, projectId, repoName) {
|
|
8036
|
+
const direct = metadataString(metadata, ["open_files_root", "openFilesRoot", "open_files_path", "openFilesPath"]);
|
|
8037
|
+
if (direct)
|
|
8038
|
+
return direct;
|
|
8039
|
+
const keys = [projectId, repoName, "open-files", "open_files", "default"].filter((value) => Boolean(value));
|
|
8040
|
+
return readMappedPath({
|
|
8041
|
+
metadata,
|
|
8042
|
+
containers: ["open_files_roots", "openFilesRoots", "open_files_paths", "openFilesPaths"],
|
|
8043
|
+
keys
|
|
8044
|
+
});
|
|
8045
|
+
}
|
|
8046
|
+
function trustStatus(machine) {
|
|
8047
|
+
if (!machine)
|
|
8048
|
+
return "unknown";
|
|
8049
|
+
const explicit = metadataString(machine.metadata, ["trust_status", "trustStatus"]);
|
|
8050
|
+
if (explicit === "trusted" || explicit === "untrusted" || explicit === "unknown")
|
|
8051
|
+
return explicit;
|
|
8052
|
+
const trusted = metadataBoolean(machine.metadata, ["trusted", "syncTrusted", "sync_trusted"]);
|
|
8053
|
+
if (trusted === true)
|
|
8054
|
+
return "trusted";
|
|
8055
|
+
if (trusted === false)
|
|
8056
|
+
return "untrusted";
|
|
8057
|
+
if (machine.route_hints.some((hint) => hint.kind === "local"))
|
|
8058
|
+
return "trusted";
|
|
8059
|
+
if (machine.tags.includes("trusted"))
|
|
8060
|
+
return "trusted";
|
|
8061
|
+
return "unknown";
|
|
8062
|
+
}
|
|
8063
|
+
function authStatus(machine) {
|
|
8064
|
+
if (!machine)
|
|
8065
|
+
return "unknown";
|
|
8066
|
+
const explicit = metadataString(machine.metadata, ["auth_status", "authStatus"]);
|
|
8067
|
+
if (explicit === "authenticated" || explicit === "unauthenticated" || explicit === "unknown")
|
|
8068
|
+
return explicit;
|
|
8069
|
+
const authenticated = metadataBoolean(machine.metadata, ["authenticated", "sshAuthorized", "ssh_authorized"]);
|
|
8070
|
+
if (authenticated === true)
|
|
8071
|
+
return "authenticated";
|
|
8072
|
+
if (authenticated === false)
|
|
8073
|
+
return "unauthenticated";
|
|
8074
|
+
if (machine.route_hints.some((hint) => hint.kind === "local"))
|
|
8075
|
+
return "authenticated";
|
|
8076
|
+
return "unknown";
|
|
8077
|
+
}
|
|
8078
|
+
function primaryMachine(machine, projectId, primaryMachineId) {
|
|
8079
|
+
if (!machine)
|
|
8080
|
+
return false;
|
|
8081
|
+
if (primaryMachineId)
|
|
8082
|
+
return machine.machine_id === primaryMachineId;
|
|
8083
|
+
if (metadataBoolean(machine.metadata, ["primary", "primary_machine", "primaryMachine"]) === true)
|
|
8084
|
+
return true;
|
|
8085
|
+
const primaryProjects = metadataStringArray(machine.metadata, ["primary_projects", "primaryProjects"]);
|
|
8086
|
+
if (primaryProjects.includes(projectId))
|
|
8087
|
+
return true;
|
|
8088
|
+
return machine.tags.includes("primary");
|
|
8089
|
+
}
|
|
8090
|
+
function metadataKeysForDiagnostics(metadata) {
|
|
8091
|
+
return Object.keys(metadata).filter((key) => !/(secret|token|key|password|credential)/i.test(key)).sort();
|
|
8092
|
+
}
|
|
8093
|
+
function resolveMachineWorkspace(options) {
|
|
8094
|
+
const topology = options.topology ?? discoverMachineTopology(options);
|
|
8095
|
+
const warnings = [...topology.warnings];
|
|
8096
|
+
const { machine, matchedBy } = findRouteMachine(topology, options.machineId);
|
|
8097
|
+
const generatedAt = (options.now ?? new Date).toISOString();
|
|
8098
|
+
const repoName = options.repoName ?? options.projectId;
|
|
8099
|
+
const openFilesRepoName = options.openFilesRepoName ?? "open-files";
|
|
8100
|
+
if (!machine) {
|
|
8101
|
+
warnings.push(`machine_not_found:${options.machineId}`);
|
|
8102
|
+
return {
|
|
8103
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
8104
|
+
package: topology.package,
|
|
8105
|
+
ok: false,
|
|
8106
|
+
requested_machine_id: options.machineId,
|
|
8107
|
+
machine_id: null,
|
|
8108
|
+
generated_at: generatedAt,
|
|
8109
|
+
project: { project_id: options.projectId, repo_name: repoName, canonical: Boolean(options.projectId) },
|
|
8110
|
+
machine: { current: false, primary: false, trust_status: "unknown", auth_status: "unknown" },
|
|
8111
|
+
paths: {
|
|
8112
|
+
workspace_root: { path: null, source: "unresolved" },
|
|
8113
|
+
project_root: { path: null, source: "unresolved" },
|
|
8114
|
+
open_files_root: { path: null, source: "unresolved" }
|
|
8115
|
+
},
|
|
8116
|
+
evidence: {
|
|
8117
|
+
topology: true,
|
|
8118
|
+
matched_by: matchedBy,
|
|
8119
|
+
manifest_declared: null,
|
|
8120
|
+
metadata_keys: []
|
|
8121
|
+
},
|
|
8122
|
+
warnings
|
|
8123
|
+
};
|
|
8124
|
+
}
|
|
8125
|
+
const metadata = machine.metadata;
|
|
8126
|
+
const workspaceRootPath = options.workspaceRoot ?? machine.workspace_path;
|
|
8127
|
+
const workspaceRootSource = options.workspaceRoot ? "argument" : machine.workspace_path ? "manifest" : "unresolved";
|
|
8128
|
+
const metadataProjectRoot = projectPathFromMetadata(metadata, options.projectId, repoName);
|
|
8129
|
+
const inferredProjectRoot = inferRepoRoot(workspaceRootPath, repoName);
|
|
8130
|
+
const projectRootPath = options.projectRoot ?? metadataProjectRoot ?? inferredProjectRoot;
|
|
8131
|
+
const projectRootSource = options.projectRoot ? "argument" : metadataProjectRoot ? "manifest_metadata" : inferredProjectRoot ? "inferred" : "unresolved";
|
|
8132
|
+
const metadataOpenFilesRoot = openFilesPathFromMetadata(metadata, options.projectId, openFilesRepoName);
|
|
8133
|
+
const inferredOpenFilesRoot = inferRepoRoot(workspaceRootPath, openFilesRepoName);
|
|
8134
|
+
const openFilesRootPath = options.openFilesRoot ?? metadataOpenFilesRoot ?? inferredOpenFilesRoot;
|
|
8135
|
+
const openFilesRootSource = options.openFilesRoot ? "argument" : metadataOpenFilesRoot ? "manifest_metadata" : inferredOpenFilesRoot ? "inferred" : "unresolved";
|
|
8136
|
+
if (projectRootSource === "inferred")
|
|
8137
|
+
warnings.push(`project_root_inferred:${options.projectId}`);
|
|
8138
|
+
if (openFilesRootSource === "inferred")
|
|
8139
|
+
warnings.push(`open_files_root_inferred:${options.projectId}`);
|
|
8140
|
+
if (!projectRootPath)
|
|
8141
|
+
warnings.push(`project_root_unresolved:${options.projectId}`);
|
|
8142
|
+
return {
|
|
8143
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
8144
|
+
package: topology.package,
|
|
8145
|
+
ok: Boolean(projectRootPath),
|
|
8146
|
+
requested_machine_id: options.machineId,
|
|
8147
|
+
machine_id: machine.machine_id,
|
|
8148
|
+
generated_at: generatedAt,
|
|
8149
|
+
project: {
|
|
8150
|
+
project_id: options.projectId,
|
|
8151
|
+
repo_name: repoName,
|
|
8152
|
+
canonical: Boolean(options.projectId && repoName)
|
|
8153
|
+
},
|
|
8154
|
+
machine: {
|
|
8155
|
+
current: machine.machine_id === topology.local_machine_id,
|
|
8156
|
+
primary: primaryMachine(machine, options.projectId, options.primaryMachineId),
|
|
8157
|
+
trust_status: trustStatus(machine),
|
|
8158
|
+
auth_status: authStatus(machine)
|
|
8159
|
+
},
|
|
8160
|
+
paths: {
|
|
8161
|
+
workspace_root: { path: workspaceRootPath, source: workspaceRootSource },
|
|
8162
|
+
project_root: { path: projectRootPath, source: projectRootSource },
|
|
8163
|
+
open_files_root: { path: openFilesRootPath, source: openFilesRootSource }
|
|
8164
|
+
},
|
|
8165
|
+
evidence: {
|
|
8166
|
+
topology: true,
|
|
8167
|
+
matched_by: matchedBy,
|
|
8168
|
+
manifest_declared: machine.manifest_declared,
|
|
8169
|
+
metadata_keys: metadataKeysForDiagnostics(metadata)
|
|
8170
|
+
},
|
|
8171
|
+
warnings
|
|
8172
|
+
};
|
|
8173
|
+
}
|
|
7915
8174
|
|
|
7916
8175
|
// src/commands/ssh.ts
|
|
7917
8176
|
function shellQuote(value) {
|
|
@@ -9011,12 +9270,7 @@ function checkMachineCompatibility(options = {}) {
|
|
|
9011
9270
|
name: MACHINES_PACKAGE_NAME,
|
|
9012
9271
|
version: getPackageVersion()
|
|
9013
9272
|
},
|
|
9014
|
-
capabilities:
|
|
9015
|
-
topology: true,
|
|
9016
|
-
compatibility: true,
|
|
9017
|
-
route_resolution: true,
|
|
9018
|
-
cli_json_fallback: true
|
|
9019
|
-
},
|
|
9273
|
+
capabilities: getMachinesConsumerCapabilities(),
|
|
9020
9274
|
ok: summary.fail === 0,
|
|
9021
9275
|
machine_id: machineId,
|
|
9022
9276
|
source: checks[0]?.source ?? "local",
|
|
@@ -10514,6 +10768,22 @@ function renderCompatibilityResult(result) {
|
|
|
10514
10768
|
].join(`
|
|
10515
10769
|
`);
|
|
10516
10770
|
}
|
|
10771
|
+
function renderWorkspaceResolution(result) {
|
|
10772
|
+
return renderKeyValueTable([
|
|
10773
|
+
["machine", result.machine_id ?? result.requested_machine_id],
|
|
10774
|
+
["ok", String(result.ok)],
|
|
10775
|
+
["project", result.project.project_id],
|
|
10776
|
+
["repo", result.project.repo_name ?? "unknown"],
|
|
10777
|
+
["current", String(result.machine.current)],
|
|
10778
|
+
["primary", String(result.machine.primary)],
|
|
10779
|
+
["trust", result.machine.trust_status],
|
|
10780
|
+
["auth", result.machine.auth_status],
|
|
10781
|
+
["workspace root", `${result.paths.workspace_root.path ?? "unresolved"} (${result.paths.workspace_root.source})`],
|
|
10782
|
+
["project root", `${result.paths.project_root.path ?? "unresolved"} (${result.paths.project_root.source})`],
|
|
10783
|
+
["open-files root", `${result.paths.open_files_root.path ?? "unresolved"} (${result.paths.open_files_root.source})`],
|
|
10784
|
+
["warnings", result.warnings.join(", ") || "none"]
|
|
10785
|
+
]);
|
|
10786
|
+
}
|
|
10517
10787
|
function renderFleetStatus(status) {
|
|
10518
10788
|
return [
|
|
10519
10789
|
renderKeyValueTable([
|
|
@@ -10664,11 +10934,28 @@ program2.command("compatibility").description("Check remote package, command, an
|
|
|
10664
10934
|
if (!result.ok && !options.json)
|
|
10665
10935
|
process.exitCode = 1;
|
|
10666
10936
|
});
|
|
10937
|
+
var workspaceCommand = program2.command("workspace").description("Resolve sync-safe workspace paths for open-* consumers");
|
|
10938
|
+
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) => {
|
|
10939
|
+
const result = resolveMachineWorkspace({
|
|
10940
|
+
machineId: options.machine,
|
|
10941
|
+
projectId: options.project,
|
|
10942
|
+
repoName: options.repo,
|
|
10943
|
+
openFilesRepoName: options.openFilesRepo,
|
|
10944
|
+
primaryMachineId: options.primaryMachine,
|
|
10945
|
+
workspaceRoot: options.workspaceRoot,
|
|
10946
|
+
projectRoot: options.projectRoot,
|
|
10947
|
+
openFilesRoot: options.openFilesRoot,
|
|
10948
|
+
includeTailscale: options.tailscale !== false
|
|
10949
|
+
});
|
|
10950
|
+
printJsonOrText(result, renderWorkspaceResolution(result), options.json);
|
|
10951
|
+
if (!result.ok && !options.json)
|
|
10952
|
+
process.exitCode = 1;
|
|
10953
|
+
});
|
|
10667
10954
|
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
10955
|
const result = diffMachines(options.left, options.right);
|
|
10669
10956
|
console.log(JSON.stringify(result, null, 2));
|
|
10670
10957
|
});
|
|
10671
|
-
program2.command("backup").description("Create and optionally upload a machine backup archive").
|
|
10958
|
+
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
10959
|
const result = options.apply ? runBackup(options.bucket, options.prefix, { apply: true, yes: options.yes }) : buildBackupPlan(options.bucket, options.prefix);
|
|
10673
10960
|
console.log(JSON.stringify(result, null, 2));
|
|
10674
10961
|
});
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import type { SetupResult } from "../types.js";
|
|
2
|
-
export declare
|
|
3
|
-
export declare
|
|
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;
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,kCAAkC,
|
|
1
|
+
{"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,kCAAkC,EAA0D,KAAK,uBAAuB,EAAE,KAAK,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAG5L,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AACzD,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;AAEjE,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;IAC1C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA0B;IACzC,cAAc,EAAE,OAAO,kCAAkC,CAAC;IAC1D,OAAO,EAAE,uBAAuB,CAAC;IACjC,YAAY,EAAE,4BAA4B,CAAC;IAC3C,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,MAAM,0BAA0B,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,oBAAoB,CAAC;AAEtG,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACtC,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACtC,UAAU,CAAC,EAAE,0BAA0B,EAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAiQD,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,2BAAgC,GAAG,0BAA0B,CAgC/G"}
|
package/dist/consumer.d.ts
CHANGED
|
@@ -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_CAPABILITIES, MACHINES_CONSUMER_CONTRACT, MACHINES_CONSUMER_ENTRYPOINT, MACHINES_CONSUMER_CONTRACT_VERSION, MACHINES_PACKAGE_NAME, discoverMachineTopology, getMachinesConsumerCapabilities, getLocalMachineTopology, resolveMachineRoute, resolveMachineWorkspace, } from "./topology.js";
|
|
2
|
+
export type { MachineRouteConfidence, MachineRouteHint, MachineRouteKind, MachineRouteOptions, MachineRouteResolution, MachineTopology, MachineTopologyEntry, MachineTopologyOptions, MachineWorkspaceAuthStatus, MachineWorkspaceOptions, MachineWorkspacePath, MachineWorkspacePathSource, MachineWorkspaceProject, MachineWorkspaceResolution, MachineWorkspaceTrustStatus, MachinesConsumerContract, MachinesConsumerEnvelope, 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";
|
package/dist/consumer.d.ts.map
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"consumer.d.ts","sourceRoot":"","sources":["../src/consumer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,0BAA0B,EAC1B,4BAA4B,EAC5B,kCAAkC,EAClC,qBAAqB,EACrB,uBAAuB,EACvB,+BAA+B,EAC/B,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,wBAAwB,EACxB,wBAAwB,EACxB,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"}
|