@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 +39 -0
- package/dist/cli/index.js +297 -9
- package/dist/commands/backup.d.ts +17 -2
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/consumer.d.ts +2 -2
- package/dist/consumer.d.ts.map +1 -1
- package/dist/consumer.js +213 -1
- package/dist/index.js +305 -6
- package/dist/manifests.d.ts +8 -0
- package/dist/manifests.d.ts.map +1 -1
- package/dist/mcp/index.js +301 -10
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/topology.d.ts +52 -0
- package/dist/topology.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/consumer.js
CHANGED
|
@@ -4212,6 +4212,7 @@ var machineSchema = exports_external.object({
|
|
|
4212
4212
|
workspacePath: exports_external.string(),
|
|
4213
4213
|
bunPath: exports_external.string().optional(),
|
|
4214
4214
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
4215
|
+
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
4215
4216
|
packages: exports_external.array(packageSchema).optional(),
|
|
4216
4217
|
apps: exports_external.array(appSchema).optional(),
|
|
4217
4218
|
files: exports_external.array(fileSchema).optional()
|
|
@@ -4514,7 +4515,8 @@ function discoverMachineTopology(options = {}) {
|
|
|
4514
4515
|
topology: true,
|
|
4515
4516
|
compatibility: true,
|
|
4516
4517
|
route_resolution: true,
|
|
4517
|
-
cli_json_fallback: true
|
|
4518
|
+
cli_json_fallback: true,
|
|
4519
|
+
workspace_path_mapping: true
|
|
4518
4520
|
},
|
|
4519
4521
|
generated_at: now.toISOString(),
|
|
4520
4522
|
local_machine_id: localMachineId,
|
|
@@ -4639,6 +4641,215 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
4639
4641
|
warnings
|
|
4640
4642
|
};
|
|
4641
4643
|
}
|
|
4644
|
+
function isRecord(value) {
|
|
4645
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4646
|
+
}
|
|
4647
|
+
function metadataString(metadata, keys) {
|
|
4648
|
+
for (const key of keys) {
|
|
4649
|
+
const value = metadata[key];
|
|
4650
|
+
if (typeof value === "string" && value.trim())
|
|
4651
|
+
return value.trim();
|
|
4652
|
+
}
|
|
4653
|
+
return null;
|
|
4654
|
+
}
|
|
4655
|
+
function metadataBoolean(metadata, keys) {
|
|
4656
|
+
for (const key of keys) {
|
|
4657
|
+
const value = metadata[key];
|
|
4658
|
+
if (typeof value === "boolean")
|
|
4659
|
+
return value;
|
|
4660
|
+
}
|
|
4661
|
+
return null;
|
|
4662
|
+
}
|
|
4663
|
+
function metadataStringArray(metadata, keys) {
|
|
4664
|
+
for (const key of keys) {
|
|
4665
|
+
const value = metadata[key];
|
|
4666
|
+
if (Array.isArray(value))
|
|
4667
|
+
return value.filter((entry) => typeof entry === "string");
|
|
4668
|
+
}
|
|
4669
|
+
return [];
|
|
4670
|
+
}
|
|
4671
|
+
function readMappedPath(input) {
|
|
4672
|
+
for (const containerName of input.containers) {
|
|
4673
|
+
const container = input.metadata[containerName];
|
|
4674
|
+
if (!isRecord(container))
|
|
4675
|
+
continue;
|
|
4676
|
+
for (const key of input.keys) {
|
|
4677
|
+
const value = container[key];
|
|
4678
|
+
if (typeof value === "string" && value.trim())
|
|
4679
|
+
return value.trim();
|
|
4680
|
+
if (isRecord(value)) {
|
|
4681
|
+
const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
|
|
4682
|
+
if (path)
|
|
4683
|
+
return path;
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
return null;
|
|
4688
|
+
}
|
|
4689
|
+
function trimTrailingSlash(value) {
|
|
4690
|
+
return value.replace(/\/+$/, "");
|
|
4691
|
+
}
|
|
4692
|
+
function joinPath(left, right) {
|
|
4693
|
+
return `${trimTrailingSlash(left)}/${right.replace(/^\/+/, "")}`;
|
|
4694
|
+
}
|
|
4695
|
+
function inferRepoRoot(workspaceRoot, repoName) {
|
|
4696
|
+
if (!workspaceRoot || !repoName)
|
|
4697
|
+
return null;
|
|
4698
|
+
const root = trimTrailingSlash(workspaceRoot);
|
|
4699
|
+
if (root.endsWith(`/${repoName}`) || root === repoName)
|
|
4700
|
+
return root;
|
|
4701
|
+
if (root.endsWith("/workspace") || root.endsWith("/Workspace")) {
|
|
4702
|
+
return joinPath(root, `hasna/opensource/${repoName}`);
|
|
4703
|
+
}
|
|
4704
|
+
return joinPath(root, repoName);
|
|
4705
|
+
}
|
|
4706
|
+
function projectPathFromMetadata(metadata, projectId, repoName) {
|
|
4707
|
+
const keys = [projectId, repoName].filter((value) => Boolean(value));
|
|
4708
|
+
return readMappedPath({
|
|
4709
|
+
metadata,
|
|
4710
|
+
containers: ["workspace_paths", "workspacePaths", "repo_paths", "repoPaths", "project_paths", "projectPaths", "projects"],
|
|
4711
|
+
keys
|
|
4712
|
+
});
|
|
4713
|
+
}
|
|
4714
|
+
function openFilesPathFromMetadata(metadata, projectId, repoName) {
|
|
4715
|
+
const direct = metadataString(metadata, ["open_files_root", "openFilesRoot", "open_files_path", "openFilesPath"]);
|
|
4716
|
+
if (direct)
|
|
4717
|
+
return direct;
|
|
4718
|
+
const keys = [projectId, repoName, "open-files", "open_files", "default"].filter((value) => Boolean(value));
|
|
4719
|
+
return readMappedPath({
|
|
4720
|
+
metadata,
|
|
4721
|
+
containers: ["open_files_roots", "openFilesRoots", "open_files_paths", "openFilesPaths"],
|
|
4722
|
+
keys
|
|
4723
|
+
});
|
|
4724
|
+
}
|
|
4725
|
+
function trustStatus(machine) {
|
|
4726
|
+
if (!machine)
|
|
4727
|
+
return "unknown";
|
|
4728
|
+
const explicit = metadataString(machine.metadata, ["trust_status", "trustStatus"]);
|
|
4729
|
+
if (explicit === "trusted" || explicit === "untrusted" || explicit === "unknown")
|
|
4730
|
+
return explicit;
|
|
4731
|
+
const trusted = metadataBoolean(machine.metadata, ["trusted", "syncTrusted", "sync_trusted"]);
|
|
4732
|
+
if (trusted === true)
|
|
4733
|
+
return "trusted";
|
|
4734
|
+
if (trusted === false)
|
|
4735
|
+
return "untrusted";
|
|
4736
|
+
if (machine.route_hints.some((hint) => hint.kind === "local"))
|
|
4737
|
+
return "trusted";
|
|
4738
|
+
if (machine.tags.includes("trusted"))
|
|
4739
|
+
return "trusted";
|
|
4740
|
+
return "unknown";
|
|
4741
|
+
}
|
|
4742
|
+
function authStatus(machine) {
|
|
4743
|
+
if (!machine)
|
|
4744
|
+
return "unknown";
|
|
4745
|
+
const explicit = metadataString(machine.metadata, ["auth_status", "authStatus"]);
|
|
4746
|
+
if (explicit === "authenticated" || explicit === "unauthenticated" || explicit === "unknown")
|
|
4747
|
+
return explicit;
|
|
4748
|
+
const authenticated = metadataBoolean(machine.metadata, ["authenticated", "sshAuthorized", "ssh_authorized"]);
|
|
4749
|
+
if (authenticated === true)
|
|
4750
|
+
return "authenticated";
|
|
4751
|
+
if (authenticated === false)
|
|
4752
|
+
return "unauthenticated";
|
|
4753
|
+
if (machine.route_hints.some((hint) => hint.kind === "local"))
|
|
4754
|
+
return "authenticated";
|
|
4755
|
+
return "unknown";
|
|
4756
|
+
}
|
|
4757
|
+
function primaryMachine(machine, projectId, primaryMachineId) {
|
|
4758
|
+
if (!machine)
|
|
4759
|
+
return false;
|
|
4760
|
+
if (primaryMachineId)
|
|
4761
|
+
return machine.machine_id === primaryMachineId;
|
|
4762
|
+
if (metadataBoolean(machine.metadata, ["primary", "primary_machine", "primaryMachine"]) === true)
|
|
4763
|
+
return true;
|
|
4764
|
+
const primaryProjects = metadataStringArray(machine.metadata, ["primary_projects", "primaryProjects"]);
|
|
4765
|
+
if (primaryProjects.includes(projectId))
|
|
4766
|
+
return true;
|
|
4767
|
+
return machine.tags.includes("primary");
|
|
4768
|
+
}
|
|
4769
|
+
function metadataKeysForDiagnostics(metadata) {
|
|
4770
|
+
return Object.keys(metadata).filter((key) => !/(secret|token|key|password|credential)/i.test(key)).sort();
|
|
4771
|
+
}
|
|
4772
|
+
function resolveMachineWorkspace(options) {
|
|
4773
|
+
const topology = options.topology ?? discoverMachineTopology(options);
|
|
4774
|
+
const warnings = [...topology.warnings];
|
|
4775
|
+
const { machine, matchedBy } = findRouteMachine(topology, options.machineId);
|
|
4776
|
+
const generatedAt = (options.now ?? new Date).toISOString();
|
|
4777
|
+
const repoName = options.repoName ?? options.projectId;
|
|
4778
|
+
const openFilesRepoName = options.openFilesRepoName ?? "open-files";
|
|
4779
|
+
if (!machine) {
|
|
4780
|
+
warnings.push(`machine_not_found:${options.machineId}`);
|
|
4781
|
+
return {
|
|
4782
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
4783
|
+
package: topology.package,
|
|
4784
|
+
ok: false,
|
|
4785
|
+
requested_machine_id: options.machineId,
|
|
4786
|
+
machine_id: null,
|
|
4787
|
+
generated_at: generatedAt,
|
|
4788
|
+
project: { project_id: options.projectId, repo_name: repoName, canonical: Boolean(options.projectId) },
|
|
4789
|
+
machine: { current: false, primary: false, trust_status: "unknown", auth_status: "unknown" },
|
|
4790
|
+
paths: {
|
|
4791
|
+
workspace_root: { path: null, source: "unresolved" },
|
|
4792
|
+
project_root: { path: null, source: "unresolved" },
|
|
4793
|
+
open_files_root: { path: null, source: "unresolved" }
|
|
4794
|
+
},
|
|
4795
|
+
evidence: {
|
|
4796
|
+
topology: true,
|
|
4797
|
+
matched_by: matchedBy,
|
|
4798
|
+
manifest_declared: null,
|
|
4799
|
+
metadata_keys: []
|
|
4800
|
+
},
|
|
4801
|
+
warnings
|
|
4802
|
+
};
|
|
4803
|
+
}
|
|
4804
|
+
const metadata = machine.metadata;
|
|
4805
|
+
const workspaceRootPath = options.workspaceRoot ?? machine.workspace_path;
|
|
4806
|
+
const workspaceRootSource = options.workspaceRoot ? "argument" : machine.workspace_path ? "manifest" : "unresolved";
|
|
4807
|
+
const metadataProjectRoot = projectPathFromMetadata(metadata, options.projectId, repoName);
|
|
4808
|
+
const inferredProjectRoot = inferRepoRoot(workspaceRootPath, repoName);
|
|
4809
|
+
const projectRootPath = options.projectRoot ?? metadataProjectRoot ?? inferredProjectRoot;
|
|
4810
|
+
const projectRootSource = options.projectRoot ? "argument" : metadataProjectRoot ? "manifest_metadata" : inferredProjectRoot ? "inferred" : "unresolved";
|
|
4811
|
+
const metadataOpenFilesRoot = openFilesPathFromMetadata(metadata, options.projectId, openFilesRepoName);
|
|
4812
|
+
const inferredOpenFilesRoot = inferRepoRoot(workspaceRootPath, openFilesRepoName);
|
|
4813
|
+
const openFilesRootPath = options.openFilesRoot ?? metadataOpenFilesRoot ?? inferredOpenFilesRoot;
|
|
4814
|
+
const openFilesRootSource = options.openFilesRoot ? "argument" : metadataOpenFilesRoot ? "manifest_metadata" : inferredOpenFilesRoot ? "inferred" : "unresolved";
|
|
4815
|
+
if (projectRootSource === "inferred")
|
|
4816
|
+
warnings.push(`project_root_inferred:${options.projectId}`);
|
|
4817
|
+
if (openFilesRootSource === "inferred")
|
|
4818
|
+
warnings.push(`open_files_root_inferred:${options.projectId}`);
|
|
4819
|
+
if (!projectRootPath)
|
|
4820
|
+
warnings.push(`project_root_unresolved:${options.projectId}`);
|
|
4821
|
+
return {
|
|
4822
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
4823
|
+
package: topology.package,
|
|
4824
|
+
ok: Boolean(projectRootPath),
|
|
4825
|
+
requested_machine_id: options.machineId,
|
|
4826
|
+
machine_id: machine.machine_id,
|
|
4827
|
+
generated_at: generatedAt,
|
|
4828
|
+
project: {
|
|
4829
|
+
project_id: options.projectId,
|
|
4830
|
+
repo_name: repoName,
|
|
4831
|
+
canonical: Boolean(options.projectId && repoName)
|
|
4832
|
+
},
|
|
4833
|
+
machine: {
|
|
4834
|
+
current: machine.machine_id === topology.local_machine_id,
|
|
4835
|
+
primary: primaryMachine(machine, options.projectId, options.primaryMachineId),
|
|
4836
|
+
trust_status: trustStatus(machine),
|
|
4837
|
+
auth_status: authStatus(machine)
|
|
4838
|
+
},
|
|
4839
|
+
paths: {
|
|
4840
|
+
workspace_root: { path: workspaceRootPath, source: workspaceRootSource },
|
|
4841
|
+
project_root: { path: projectRootPath, source: projectRootSource },
|
|
4842
|
+
open_files_root: { path: openFilesRootPath, source: openFilesRootSource }
|
|
4843
|
+
},
|
|
4844
|
+
evidence: {
|
|
4845
|
+
topology: true,
|
|
4846
|
+
matched_by: matchedBy,
|
|
4847
|
+
manifest_declared: machine.manifest_declared,
|
|
4848
|
+
metadata_keys: metadataKeysForDiagnostics(metadata)
|
|
4849
|
+
},
|
|
4850
|
+
warnings
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4642
4853
|
function getLocalMachineTopology(options = {}) {
|
|
4643
4854
|
const topology = discoverMachineTopology(options);
|
|
4644
4855
|
return topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? {
|
|
@@ -4974,6 +5185,7 @@ function checkMachineCompatibility(options = {}) {
|
|
|
4974
5185
|
export {
|
|
4975
5186
|
runMachineCommand,
|
|
4976
5187
|
resolveSshTarget,
|
|
5188
|
+
resolveMachineWorkspace,
|
|
4977
5189
|
resolveMachineRoute,
|
|
4978
5190
|
resolveMachineCommand,
|
|
4979
5191
|
getPackageVersion,
|
package/dist/index.js
CHANGED
|
@@ -11008,6 +11008,7 @@ var machineSchema = exports_external.object({
|
|
|
11008
11008
|
workspacePath: exports_external.string(),
|
|
11009
11009
|
bunPath: exports_external.string().optional(),
|
|
11010
11010
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
11011
|
+
metadata: exports_external.record(exports_external.unknown()).optional(),
|
|
11011
11012
|
packages: exports_external.array(packageSchema).optional(),
|
|
11012
11013
|
apps: exports_external.array(appSchema).optional(),
|
|
11013
11014
|
files: exports_external.array(fileSchema).optional()
|
|
@@ -11314,7 +11315,8 @@ function discoverMachineTopology(options = {}) {
|
|
|
11314
11315
|
topology: true,
|
|
11315
11316
|
compatibility: true,
|
|
11316
11317
|
route_resolution: true,
|
|
11317
|
-
cli_json_fallback: true
|
|
11318
|
+
cli_json_fallback: true,
|
|
11319
|
+
workspace_path_mapping: true
|
|
11318
11320
|
},
|
|
11319
11321
|
generated_at: now.toISOString(),
|
|
11320
11322
|
local_machine_id: localMachineId,
|
|
@@ -11439,6 +11441,215 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
11439
11441
|
warnings
|
|
11440
11442
|
};
|
|
11441
11443
|
}
|
|
11444
|
+
function isRecord(value) {
|
|
11445
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11446
|
+
}
|
|
11447
|
+
function metadataString(metadata, keys) {
|
|
11448
|
+
for (const key of keys) {
|
|
11449
|
+
const value = metadata[key];
|
|
11450
|
+
if (typeof value === "string" && value.trim())
|
|
11451
|
+
return value.trim();
|
|
11452
|
+
}
|
|
11453
|
+
return null;
|
|
11454
|
+
}
|
|
11455
|
+
function metadataBoolean(metadata, keys) {
|
|
11456
|
+
for (const key of keys) {
|
|
11457
|
+
const value = metadata[key];
|
|
11458
|
+
if (typeof value === "boolean")
|
|
11459
|
+
return value;
|
|
11460
|
+
}
|
|
11461
|
+
return null;
|
|
11462
|
+
}
|
|
11463
|
+
function metadataStringArray(metadata, keys) {
|
|
11464
|
+
for (const key of keys) {
|
|
11465
|
+
const value = metadata[key];
|
|
11466
|
+
if (Array.isArray(value))
|
|
11467
|
+
return value.filter((entry) => typeof entry === "string");
|
|
11468
|
+
}
|
|
11469
|
+
return [];
|
|
11470
|
+
}
|
|
11471
|
+
function readMappedPath(input) {
|
|
11472
|
+
for (const containerName of input.containers) {
|
|
11473
|
+
const container = input.metadata[containerName];
|
|
11474
|
+
if (!isRecord(container))
|
|
11475
|
+
continue;
|
|
11476
|
+
for (const key of input.keys) {
|
|
11477
|
+
const value = container[key];
|
|
11478
|
+
if (typeof value === "string" && value.trim())
|
|
11479
|
+
return value.trim();
|
|
11480
|
+
if (isRecord(value)) {
|
|
11481
|
+
const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
|
|
11482
|
+
if (path)
|
|
11483
|
+
return path;
|
|
11484
|
+
}
|
|
11485
|
+
}
|
|
11486
|
+
}
|
|
11487
|
+
return null;
|
|
11488
|
+
}
|
|
11489
|
+
function trimTrailingSlash(value) {
|
|
11490
|
+
return value.replace(/\/+$/, "");
|
|
11491
|
+
}
|
|
11492
|
+
function joinPath(left, right) {
|
|
11493
|
+
return `${trimTrailingSlash(left)}/${right.replace(/^\/+/, "")}`;
|
|
11494
|
+
}
|
|
11495
|
+
function inferRepoRoot(workspaceRoot, repoName) {
|
|
11496
|
+
if (!workspaceRoot || !repoName)
|
|
11497
|
+
return null;
|
|
11498
|
+
const root = trimTrailingSlash(workspaceRoot);
|
|
11499
|
+
if (root.endsWith(`/${repoName}`) || root === repoName)
|
|
11500
|
+
return root;
|
|
11501
|
+
if (root.endsWith("/workspace") || root.endsWith("/Workspace")) {
|
|
11502
|
+
return joinPath(root, `hasna/opensource/${repoName}`);
|
|
11503
|
+
}
|
|
11504
|
+
return joinPath(root, repoName);
|
|
11505
|
+
}
|
|
11506
|
+
function projectPathFromMetadata(metadata, projectId, repoName) {
|
|
11507
|
+
const keys = [projectId, repoName].filter((value) => Boolean(value));
|
|
11508
|
+
return readMappedPath({
|
|
11509
|
+
metadata,
|
|
11510
|
+
containers: ["workspace_paths", "workspacePaths", "repo_paths", "repoPaths", "project_paths", "projectPaths", "projects"],
|
|
11511
|
+
keys
|
|
11512
|
+
});
|
|
11513
|
+
}
|
|
11514
|
+
function openFilesPathFromMetadata(metadata, projectId, repoName) {
|
|
11515
|
+
const direct = metadataString(metadata, ["open_files_root", "openFilesRoot", "open_files_path", "openFilesPath"]);
|
|
11516
|
+
if (direct)
|
|
11517
|
+
return direct;
|
|
11518
|
+
const keys = [projectId, repoName, "open-files", "open_files", "default"].filter((value) => Boolean(value));
|
|
11519
|
+
return readMappedPath({
|
|
11520
|
+
metadata,
|
|
11521
|
+
containers: ["open_files_roots", "openFilesRoots", "open_files_paths", "openFilesPaths"],
|
|
11522
|
+
keys
|
|
11523
|
+
});
|
|
11524
|
+
}
|
|
11525
|
+
function trustStatus(machine) {
|
|
11526
|
+
if (!machine)
|
|
11527
|
+
return "unknown";
|
|
11528
|
+
const explicit = metadataString(machine.metadata, ["trust_status", "trustStatus"]);
|
|
11529
|
+
if (explicit === "trusted" || explicit === "untrusted" || explicit === "unknown")
|
|
11530
|
+
return explicit;
|
|
11531
|
+
const trusted = metadataBoolean(machine.metadata, ["trusted", "syncTrusted", "sync_trusted"]);
|
|
11532
|
+
if (trusted === true)
|
|
11533
|
+
return "trusted";
|
|
11534
|
+
if (trusted === false)
|
|
11535
|
+
return "untrusted";
|
|
11536
|
+
if (machine.route_hints.some((hint) => hint.kind === "local"))
|
|
11537
|
+
return "trusted";
|
|
11538
|
+
if (machine.tags.includes("trusted"))
|
|
11539
|
+
return "trusted";
|
|
11540
|
+
return "unknown";
|
|
11541
|
+
}
|
|
11542
|
+
function authStatus(machine) {
|
|
11543
|
+
if (!machine)
|
|
11544
|
+
return "unknown";
|
|
11545
|
+
const explicit = metadataString(machine.metadata, ["auth_status", "authStatus"]);
|
|
11546
|
+
if (explicit === "authenticated" || explicit === "unauthenticated" || explicit === "unknown")
|
|
11547
|
+
return explicit;
|
|
11548
|
+
const authenticated = metadataBoolean(machine.metadata, ["authenticated", "sshAuthorized", "ssh_authorized"]);
|
|
11549
|
+
if (authenticated === true)
|
|
11550
|
+
return "authenticated";
|
|
11551
|
+
if (authenticated === false)
|
|
11552
|
+
return "unauthenticated";
|
|
11553
|
+
if (machine.route_hints.some((hint) => hint.kind === "local"))
|
|
11554
|
+
return "authenticated";
|
|
11555
|
+
return "unknown";
|
|
11556
|
+
}
|
|
11557
|
+
function primaryMachine(machine, projectId, primaryMachineId) {
|
|
11558
|
+
if (!machine)
|
|
11559
|
+
return false;
|
|
11560
|
+
if (primaryMachineId)
|
|
11561
|
+
return machine.machine_id === primaryMachineId;
|
|
11562
|
+
if (metadataBoolean(machine.metadata, ["primary", "primary_machine", "primaryMachine"]) === true)
|
|
11563
|
+
return true;
|
|
11564
|
+
const primaryProjects = metadataStringArray(machine.metadata, ["primary_projects", "primaryProjects"]);
|
|
11565
|
+
if (primaryProjects.includes(projectId))
|
|
11566
|
+
return true;
|
|
11567
|
+
return machine.tags.includes("primary");
|
|
11568
|
+
}
|
|
11569
|
+
function metadataKeysForDiagnostics(metadata) {
|
|
11570
|
+
return Object.keys(metadata).filter((key) => !/(secret|token|key|password|credential)/i.test(key)).sort();
|
|
11571
|
+
}
|
|
11572
|
+
function resolveMachineWorkspace(options) {
|
|
11573
|
+
const topology = options.topology ?? discoverMachineTopology(options);
|
|
11574
|
+
const warnings = [...topology.warnings];
|
|
11575
|
+
const { machine, matchedBy } = findRouteMachine(topology, options.machineId);
|
|
11576
|
+
const generatedAt = (options.now ?? new Date).toISOString();
|
|
11577
|
+
const repoName = options.repoName ?? options.projectId;
|
|
11578
|
+
const openFilesRepoName = options.openFilesRepoName ?? "open-files";
|
|
11579
|
+
if (!machine) {
|
|
11580
|
+
warnings.push(`machine_not_found:${options.machineId}`);
|
|
11581
|
+
return {
|
|
11582
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
11583
|
+
package: topology.package,
|
|
11584
|
+
ok: false,
|
|
11585
|
+
requested_machine_id: options.machineId,
|
|
11586
|
+
machine_id: null,
|
|
11587
|
+
generated_at: generatedAt,
|
|
11588
|
+
project: { project_id: options.projectId, repo_name: repoName, canonical: Boolean(options.projectId) },
|
|
11589
|
+
machine: { current: false, primary: false, trust_status: "unknown", auth_status: "unknown" },
|
|
11590
|
+
paths: {
|
|
11591
|
+
workspace_root: { path: null, source: "unresolved" },
|
|
11592
|
+
project_root: { path: null, source: "unresolved" },
|
|
11593
|
+
open_files_root: { path: null, source: "unresolved" }
|
|
11594
|
+
},
|
|
11595
|
+
evidence: {
|
|
11596
|
+
topology: true,
|
|
11597
|
+
matched_by: matchedBy,
|
|
11598
|
+
manifest_declared: null,
|
|
11599
|
+
metadata_keys: []
|
|
11600
|
+
},
|
|
11601
|
+
warnings
|
|
11602
|
+
};
|
|
11603
|
+
}
|
|
11604
|
+
const metadata = machine.metadata;
|
|
11605
|
+
const workspaceRootPath = options.workspaceRoot ?? machine.workspace_path;
|
|
11606
|
+
const workspaceRootSource = options.workspaceRoot ? "argument" : machine.workspace_path ? "manifest" : "unresolved";
|
|
11607
|
+
const metadataProjectRoot = projectPathFromMetadata(metadata, options.projectId, repoName);
|
|
11608
|
+
const inferredProjectRoot = inferRepoRoot(workspaceRootPath, repoName);
|
|
11609
|
+
const projectRootPath = options.projectRoot ?? metadataProjectRoot ?? inferredProjectRoot;
|
|
11610
|
+
const projectRootSource = options.projectRoot ? "argument" : metadataProjectRoot ? "manifest_metadata" : inferredProjectRoot ? "inferred" : "unresolved";
|
|
11611
|
+
const metadataOpenFilesRoot = openFilesPathFromMetadata(metadata, options.projectId, openFilesRepoName);
|
|
11612
|
+
const inferredOpenFilesRoot = inferRepoRoot(workspaceRootPath, openFilesRepoName);
|
|
11613
|
+
const openFilesRootPath = options.openFilesRoot ?? metadataOpenFilesRoot ?? inferredOpenFilesRoot;
|
|
11614
|
+
const openFilesRootSource = options.openFilesRoot ? "argument" : metadataOpenFilesRoot ? "manifest_metadata" : inferredOpenFilesRoot ? "inferred" : "unresolved";
|
|
11615
|
+
if (projectRootSource === "inferred")
|
|
11616
|
+
warnings.push(`project_root_inferred:${options.projectId}`);
|
|
11617
|
+
if (openFilesRootSource === "inferred")
|
|
11618
|
+
warnings.push(`open_files_root_inferred:${options.projectId}`);
|
|
11619
|
+
if (!projectRootPath)
|
|
11620
|
+
warnings.push(`project_root_unresolved:${options.projectId}`);
|
|
11621
|
+
return {
|
|
11622
|
+
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
11623
|
+
package: topology.package,
|
|
11624
|
+
ok: Boolean(projectRootPath),
|
|
11625
|
+
requested_machine_id: options.machineId,
|
|
11626
|
+
machine_id: machine.machine_id,
|
|
11627
|
+
generated_at: generatedAt,
|
|
11628
|
+
project: {
|
|
11629
|
+
project_id: options.projectId,
|
|
11630
|
+
repo_name: repoName,
|
|
11631
|
+
canonical: Boolean(options.projectId && repoName)
|
|
11632
|
+
},
|
|
11633
|
+
machine: {
|
|
11634
|
+
current: machine.machine_id === topology.local_machine_id,
|
|
11635
|
+
primary: primaryMachine(machine, options.projectId, options.primaryMachineId),
|
|
11636
|
+
trust_status: trustStatus(machine),
|
|
11637
|
+
auth_status: authStatus(machine)
|
|
11638
|
+
},
|
|
11639
|
+
paths: {
|
|
11640
|
+
workspace_root: { path: workspaceRootPath, source: workspaceRootSource },
|
|
11641
|
+
project_root: { path: projectRootPath, source: projectRootSource },
|
|
11642
|
+
open_files_root: { path: openFilesRootPath, source: openFilesRootSource }
|
|
11643
|
+
},
|
|
11644
|
+
evidence: {
|
|
11645
|
+
topology: true,
|
|
11646
|
+
matched_by: matchedBy,
|
|
11647
|
+
manifest_declared: machine.manifest_declared,
|
|
11648
|
+
metadata_keys: metadataKeysForDiagnostics(metadata)
|
|
11649
|
+
},
|
|
11650
|
+
warnings
|
|
11651
|
+
};
|
|
11652
|
+
}
|
|
11442
11653
|
function getLocalMachineTopology(options = {}) {
|
|
11443
11654
|
const topology = discoverMachineTopology(options);
|
|
11444
11655
|
return topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? {
|
|
@@ -11796,9 +12007,52 @@ function getAgentStatus(machineId = getLocalMachineId()) {
|
|
|
11796
12007
|
// src/commands/backup.ts
|
|
11797
12008
|
import { homedir as homedir2 } from "os";
|
|
11798
12009
|
import { join as join3 } from "path";
|
|
12010
|
+
var MACHINES_BACKUP_BUCKET_ENV = "HASNA_MACHINES_S3_BUCKET";
|
|
12011
|
+
var MACHINES_BACKUP_BUCKET_FALLBACK_ENV = "MACHINES_S3_BUCKET";
|
|
12012
|
+
var MACHINES_BACKUP_PREFIX_ENV = "HASNA_MACHINES_S3_PREFIX";
|
|
12013
|
+
var MACHINES_BACKUP_PREFIX_FALLBACK_ENV = "MACHINES_S3_PREFIX";
|
|
12014
|
+
var DEFAULT_BACKUP_PREFIX = "machines";
|
|
11799
12015
|
function quote(value) {
|
|
11800
12016
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11801
12017
|
}
|
|
12018
|
+
function readEnv2(name) {
|
|
12019
|
+
const value = process.env[name]?.trim();
|
|
12020
|
+
return value || undefined;
|
|
12021
|
+
}
|
|
12022
|
+
function readBackupBucketEnv() {
|
|
12023
|
+
const primary = readEnv2(MACHINES_BACKUP_BUCKET_ENV);
|
|
12024
|
+
if (primary)
|
|
12025
|
+
return { bucket: primary, bucketSource: MACHINES_BACKUP_BUCKET_ENV };
|
|
12026
|
+
const fallback = readEnv2(MACHINES_BACKUP_BUCKET_FALLBACK_ENV);
|
|
12027
|
+
if (fallback)
|
|
12028
|
+
return { bucket: fallback, bucketSource: MACHINES_BACKUP_BUCKET_FALLBACK_ENV };
|
|
12029
|
+
return null;
|
|
12030
|
+
}
|
|
12031
|
+
function readBackupPrefixEnv() {
|
|
12032
|
+
const primary = readEnv2(MACHINES_BACKUP_PREFIX_ENV);
|
|
12033
|
+
if (primary)
|
|
12034
|
+
return { prefix: primary, prefixSource: MACHINES_BACKUP_PREFIX_ENV };
|
|
12035
|
+
const fallback = readEnv2(MACHINES_BACKUP_PREFIX_FALLBACK_ENV);
|
|
12036
|
+
if (fallback)
|
|
12037
|
+
return { prefix: fallback, prefixSource: MACHINES_BACKUP_PREFIX_FALLBACK_ENV };
|
|
12038
|
+
return null;
|
|
12039
|
+
}
|
|
12040
|
+
function resolveBackupTarget(options = {}) {
|
|
12041
|
+
const explicitBucket = options.bucket?.trim();
|
|
12042
|
+
const envBucket = explicitBucket ? null : readBackupBucketEnv();
|
|
12043
|
+
const bucket = explicitBucket || envBucket?.bucket;
|
|
12044
|
+
if (!bucket) {
|
|
12045
|
+
throw new Error(`Missing S3 backup bucket. Pass --bucket or set ${MACHINES_BACKUP_BUCKET_ENV} or ${MACHINES_BACKUP_BUCKET_FALLBACK_ENV}.`);
|
|
12046
|
+
}
|
|
12047
|
+
const explicitPrefix = options.prefix?.trim();
|
|
12048
|
+
const envPrefix = explicitPrefix ? null : readBackupPrefixEnv();
|
|
12049
|
+
return {
|
|
12050
|
+
bucket,
|
|
12051
|
+
prefix: explicitPrefix || envPrefix?.prefix || DEFAULT_BACKUP_PREFIX,
|
|
12052
|
+
bucketSource: explicitBucket ? "argument" : envBucket.bucketSource,
|
|
12053
|
+
prefixSource: explicitPrefix ? "argument" : envPrefix?.prefixSource || "default"
|
|
12054
|
+
};
|
|
12055
|
+
}
|
|
11802
12056
|
function defaultBackupSources() {
|
|
11803
12057
|
const home = homedir2();
|
|
11804
12058
|
return [
|
|
@@ -11807,7 +12061,8 @@ function defaultBackupSources() {
|
|
|
11807
12061
|
join3(home, ".secrets")
|
|
11808
12062
|
];
|
|
11809
12063
|
}
|
|
11810
|
-
function buildBackupPlan(bucket, prefix
|
|
12064
|
+
function buildBackupPlan(bucket, prefix) {
|
|
12065
|
+
const target = resolveBackupTarget({ bucket, prefix });
|
|
11811
12066
|
const archivePath = join3(homedir2(), ".hasna", "machines", "backup.tgz");
|
|
11812
12067
|
const sources = defaultBackupSources();
|
|
11813
12068
|
const steps = [
|
|
@@ -11820,7 +12075,7 @@ function buildBackupPlan(bucket, prefix = "machines") {
|
|
|
11820
12075
|
{
|
|
11821
12076
|
id: "backup-upload",
|
|
11822
12077
|
title: "Upload archive to S3",
|
|
11823
|
-
command: `aws s3 cp ${quote(archivePath)} s3://${bucket}/${prefix}/$(hostname)-backup.tgz`,
|
|
12078
|
+
command: `aws s3 cp ${quote(archivePath)} s3://${target.bucket}/${target.prefix}/$(hostname)-backup.tgz`,
|
|
11824
12079
|
manager: "custom"
|
|
11825
12080
|
}
|
|
11826
12081
|
];
|
|
@@ -11831,7 +12086,7 @@ function buildBackupPlan(bucket, prefix = "machines") {
|
|
|
11831
12086
|
executed: 0
|
|
11832
12087
|
};
|
|
11833
12088
|
}
|
|
11834
|
-
function runBackup(bucket, prefix
|
|
12089
|
+
function runBackup(bucket, prefix, options = {}) {
|
|
11835
12090
|
const plan = buildBackupPlan(bucket, prefix);
|
|
11836
12091
|
if (!options.apply)
|
|
11837
12092
|
return plan;
|
|
@@ -22228,6 +22483,7 @@ var MACHINE_MCP_TOOL_NAMES = [
|
|
|
22228
22483
|
"machines_install_claude_preview",
|
|
22229
22484
|
"machines_install_claude_apply",
|
|
22230
22485
|
"machines_route_resolve",
|
|
22486
|
+
"machines_workspace_resolve",
|
|
22231
22487
|
"machines_ssh_resolve",
|
|
22232
22488
|
"machines_ports",
|
|
22233
22489
|
"machines_backup_preview",
|
|
@@ -22341,14 +22597,50 @@ function createMcpServer(version2) {
|
|
|
22341
22597
|
server.tool("machines_route_resolve", "Resolve the best route for a machine using manifest, heartbeat, SSH, LAN, and Tailscale topology.", { machine_id: exports_external.string().describe("Machine identifier"), include_tailscale: exports_external.boolean().optional().describe("Whether to probe tailscale status --json") }, async ({ machine_id, include_tailscale }) => ({
|
|
22342
22598
|
content: [{ type: "text", text: JSON.stringify(resolveMachineRoute(machine_id, { includeTailscale: include_tailscale !== false }), null, 2) }]
|
|
22343
22599
|
}));
|
|
22600
|
+
server.tool("machines_workspace_resolve", "Resolve sync-safe repo and open-files roots for an open-* consumer.", {
|
|
22601
|
+
machine_id: exports_external.string().describe("Machine identifier"),
|
|
22602
|
+
project_id: exports_external.string().describe("Canonical project id"),
|
|
22603
|
+
repo_name: exports_external.string().optional().describe("Repository name; defaults to project id"),
|
|
22604
|
+
open_files_repo_name: exports_external.string().optional().describe("Open-files repository name"),
|
|
22605
|
+
primary_machine_id: exports_external.string().optional().describe("Primary machine id for this project"),
|
|
22606
|
+
workspace_root: exports_external.string().optional().describe("Override the machine workspace root"),
|
|
22607
|
+
project_root: exports_external.string().optional().describe("Override the resolved project root"),
|
|
22608
|
+
open_files_root: exports_external.string().optional().describe("Override the resolved open-files root"),
|
|
22609
|
+
include_tailscale: exports_external.boolean().optional().describe("Whether to probe tailscale status --json")
|
|
22610
|
+
}, async ({
|
|
22611
|
+
machine_id,
|
|
22612
|
+
project_id,
|
|
22613
|
+
repo_name,
|
|
22614
|
+
open_files_repo_name,
|
|
22615
|
+
primary_machine_id,
|
|
22616
|
+
workspace_root,
|
|
22617
|
+
project_root,
|
|
22618
|
+
open_files_root,
|
|
22619
|
+
include_tailscale
|
|
22620
|
+
}) => ({
|
|
22621
|
+
content: [{
|
|
22622
|
+
type: "text",
|
|
22623
|
+
text: JSON.stringify(resolveMachineWorkspace({
|
|
22624
|
+
machineId: machine_id,
|
|
22625
|
+
projectId: project_id,
|
|
22626
|
+
repoName: repo_name,
|
|
22627
|
+
openFilesRepoName: open_files_repo_name,
|
|
22628
|
+
primaryMachineId: primary_machine_id,
|
|
22629
|
+
workspaceRoot: workspace_root,
|
|
22630
|
+
projectRoot: project_root,
|
|
22631
|
+
openFilesRoot: open_files_root,
|
|
22632
|
+
includeTailscale: include_tailscale !== false
|
|
22633
|
+
}), null, 2)
|
|
22634
|
+
}]
|
|
22635
|
+
}));
|
|
22344
22636
|
server.tool("machines_ssh_resolve", "Resolve the best SSH route for a machine.", { machine_id: exports_external.string().describe("Machine identifier"), remote_command: exports_external.string().optional().describe("Optional remote command") }, async ({ machine_id, remote_command }) => ({
|
|
22345
22637
|
content: [{ type: "text", text: JSON.stringify({ resolved: resolveMachineRoute(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
|
|
22346
22638
|
}));
|
|
22347
22639
|
server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
22348
22640
|
content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
|
|
22349
22641
|
}));
|
|
22350
|
-
server.tool("machines_backup_preview", "Preview backup steps for the current machine.", { bucket: exports_external.string().describe("S3 bucket name"), prefix: exports_external.string().optional().describe("S3 key prefix") }, async ({ bucket, prefix }) => ({ content: [{ type: "text", text: JSON.stringify(buildBackupPlan(bucket, prefix), null, 2) }] }));
|
|
22351
|
-
server.tool("machines_backup_apply", "Execute backup steps for the current machine.", { bucket: exports_external.string().describe("S3 bucket name"), prefix: exports_external.string().optional().describe("S3 key prefix"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ bucket, prefix, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runBackup(bucket, prefix, { apply: true, yes }), null, 2) }] }));
|
|
22642
|
+
server.tool("machines_backup_preview", "Preview backup steps for the current machine.", { bucket: exports_external.string().optional().describe("S3 bucket name; defaults to HASNA_MACHINES_S3_BUCKET or MACHINES_S3_BUCKET"), prefix: exports_external.string().optional().describe("S3 key prefix; defaults to HASNA_MACHINES_S3_PREFIX, MACHINES_S3_PREFIX, or machines") }, async ({ bucket, prefix }) => ({ content: [{ type: "text", text: JSON.stringify(buildBackupPlan(bucket, prefix), null, 2) }] }));
|
|
22643
|
+
server.tool("machines_backup_apply", "Execute backup steps for the current machine.", { bucket: exports_external.string().optional().describe("S3 bucket name; defaults to HASNA_MACHINES_S3_BUCKET or MACHINES_S3_BUCKET"), prefix: exports_external.string().optional().describe("S3 key prefix; defaults to HASNA_MACHINES_S3_PREFIX, MACHINES_S3_PREFIX, or machines"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ bucket, prefix, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runBackup(bucket, prefix, { apply: true, yes }), null, 2) }] }));
|
|
22352
22644
|
server.tool("machines_cert_preview", "Preview mkcert steps for one or more domains.", { domains: exports_external.array(exports_external.string()).describe("Domains to issue certificates for") }, async ({ domains }) => ({ content: [{ type: "text", text: JSON.stringify(buildCertPlan(domains), null, 2) }] }));
|
|
22353
22645
|
server.tool("machines_cert_apply", "Execute mkcert steps for one or more domains.", { domains: exports_external.array(exports_external.string()).describe("Domains to issue certificates for"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ domains, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runCertPlan(domains, { apply: true, yes }), null, 2) }] }));
|
|
22354
22646
|
server.tool("machines_dns_add", "Add or replace a local domain mapping.", { domain: exports_external.string().describe("Domain name"), port: exports_external.number().describe("Target port"), target_host: exports_external.string().optional().describe("Target host") }, async ({ domain, port, target_host }) => ({ content: [{ type: "text", text: JSON.stringify(addDomainMapping(domain, port, target_host), null, 2) }] }));
|
|
@@ -22407,7 +22699,9 @@ export {
|
|
|
22407
22699
|
runAppsInstall,
|
|
22408
22700
|
resolveTables,
|
|
22409
22701
|
resolveSshTarget,
|
|
22702
|
+
resolveMachineWorkspace,
|
|
22410
22703
|
resolveMachineRoute,
|
|
22704
|
+
resolveBackupTarget,
|
|
22411
22705
|
renderDomainMapping,
|
|
22412
22706
|
renderDashboardHtml,
|
|
22413
22707
|
removeNotificationChannel,
|
|
@@ -22493,5 +22787,10 @@ export {
|
|
|
22493
22787
|
MACHINES_STORAGE_ENV,
|
|
22494
22788
|
MACHINES_PACKAGE_NAME,
|
|
22495
22789
|
MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
22790
|
+
MACHINES_BACKUP_PREFIX_FALLBACK_ENV,
|
|
22791
|
+
MACHINES_BACKUP_PREFIX_ENV,
|
|
22792
|
+
MACHINES_BACKUP_BUCKET_FALLBACK_ENV,
|
|
22793
|
+
MACHINES_BACKUP_BUCKET_ENV,
|
|
22794
|
+
DEFAULT_BACKUP_PREFIX,
|
|
22496
22795
|
CROSSREFS_KEY
|
|
22497
22796
|
};
|