@hasna/machines 0.0.17 → 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 +310 -19
- package/dist/commands/backup.d.ts +17 -2
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/ssh.d.ts.map +1 -1
- package/dist/consumer.d.ts +2 -2
- package/dist/consumer.d.ts.map +1 -1
- package/dist/consumer.js +222 -7
- package/dist/index.js +318 -16
- package/dist/manifests.d.ts +8 -0
- package/dist/manifests.d.ts.map +1 -1
- package/dist/mcp/index.js +314 -20
- 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/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) ?? {
|
|
@@ -11463,6 +11674,9 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
11463
11674
|
import { hostname as hostname4 } from "os";
|
|
11464
11675
|
|
|
11465
11676
|
// src/commands/ssh.ts
|
|
11677
|
+
function shellQuote(value) {
|
|
11678
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
11679
|
+
}
|
|
11466
11680
|
function resolveSshTarget(machineId, options = {}) {
|
|
11467
11681
|
const resolved = resolveMachineRoute(machineId, options);
|
|
11468
11682
|
if (!resolved.ok || !resolved.target) {
|
|
@@ -11481,11 +11695,11 @@ function resolveSshTarget(machineId, options = {}) {
|
|
|
11481
11695
|
}
|
|
11482
11696
|
function buildSshCommand(machineId, remoteCommand, options = {}) {
|
|
11483
11697
|
const resolved = resolveSshTarget(machineId, options);
|
|
11484
|
-
return remoteCommand ? `ssh ${resolved.target} ${
|
|
11698
|
+
return remoteCommand ? `ssh ${resolved.target} ${shellQuote(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
11485
11699
|
}
|
|
11486
11700
|
|
|
11487
11701
|
// src/remote.ts
|
|
11488
|
-
function
|
|
11702
|
+
function shellQuote2(value) {
|
|
11489
11703
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
11490
11704
|
}
|
|
11491
11705
|
function machineIsLocal(machineId, localMachineId) {
|
|
@@ -11503,7 +11717,7 @@ function resolveMachineCommand(machineId, command, localMachineId = getLocalMach
|
|
|
11503
11717
|
} catch (error) {
|
|
11504
11718
|
const message = String(error.message ?? error);
|
|
11505
11719
|
if (message.includes("Machine route not found") || message.includes("Machine not found in manifest")) {
|
|
11506
|
-
return { source: "ssh", shellCommand: `ssh ${
|
|
11720
|
+
return { source: "ssh", shellCommand: `ssh ${shellQuote2(machineId)} ${shellQuote2(command)}` };
|
|
11507
11721
|
}
|
|
11508
11722
|
throw error;
|
|
11509
11723
|
}
|
|
@@ -11531,7 +11745,7 @@ var DEFAULT_COMMANDS = [
|
|
|
11531
11745
|
function defaultPackages() {
|
|
11532
11746
|
return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
|
|
11533
11747
|
}
|
|
11534
|
-
function
|
|
11748
|
+
function shellQuote3(value) {
|
|
11535
11749
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
11536
11750
|
}
|
|
11537
11751
|
function commandId(value) {
|
|
@@ -11582,7 +11796,7 @@ function defaultRunner2(machineId, command) {
|
|
|
11582
11796
|
return runMachineCommand(machineId, command);
|
|
11583
11797
|
}
|
|
11584
11798
|
function inspectCommand(machineId, spec, runner) {
|
|
11585
|
-
const command =
|
|
11799
|
+
const command = shellQuote3(spec.command);
|
|
11586
11800
|
const versionArgs = spec.versionArgs ?? "--version";
|
|
11587
11801
|
const script = [
|
|
11588
11802
|
`cmd=${command}`,
|
|
@@ -11611,7 +11825,7 @@ function fieldCommand(field) {
|
|
|
11611
11825
|
}
|
|
11612
11826
|
function inspectWorkspace(machineId, spec, runner) {
|
|
11613
11827
|
const script = [
|
|
11614
|
-
`path=${
|
|
11828
|
+
`path=${shellQuote3(spec.path)}`,
|
|
11615
11829
|
'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
|
|
11616
11830
|
'pkg="$path/package.json"',
|
|
11617
11831
|
'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
|
|
@@ -11793,9 +12007,52 @@ function getAgentStatus(machineId = getLocalMachineId()) {
|
|
|
11793
12007
|
// src/commands/backup.ts
|
|
11794
12008
|
import { homedir as homedir2 } from "os";
|
|
11795
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";
|
|
11796
12015
|
function quote(value) {
|
|
11797
12016
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11798
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
|
+
}
|
|
11799
12056
|
function defaultBackupSources() {
|
|
11800
12057
|
const home = homedir2();
|
|
11801
12058
|
return [
|
|
@@ -11804,7 +12061,8 @@ function defaultBackupSources() {
|
|
|
11804
12061
|
join3(home, ".secrets")
|
|
11805
12062
|
];
|
|
11806
12063
|
}
|
|
11807
|
-
function buildBackupPlan(bucket, prefix
|
|
12064
|
+
function buildBackupPlan(bucket, prefix) {
|
|
12065
|
+
const target = resolveBackupTarget({ bucket, prefix });
|
|
11808
12066
|
const archivePath = join3(homedir2(), ".hasna", "machines", "backup.tgz");
|
|
11809
12067
|
const sources = defaultBackupSources();
|
|
11810
12068
|
const steps = [
|
|
@@ -11817,7 +12075,7 @@ function buildBackupPlan(bucket, prefix = "machines") {
|
|
|
11817
12075
|
{
|
|
11818
12076
|
id: "backup-upload",
|
|
11819
12077
|
title: "Upload archive to S3",
|
|
11820
|
-
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`,
|
|
11821
12079
|
manager: "custom"
|
|
11822
12080
|
}
|
|
11823
12081
|
];
|
|
@@ -11828,7 +12086,7 @@ function buildBackupPlan(bucket, prefix = "machines") {
|
|
|
11828
12086
|
executed: 0
|
|
11829
12087
|
};
|
|
11830
12088
|
}
|
|
11831
|
-
function runBackup(bucket, prefix
|
|
12089
|
+
function runBackup(bucket, prefix, options = {}) {
|
|
11832
12090
|
const plan = buildBackupPlan(bucket, prefix);
|
|
11833
12091
|
if (!options.apply)
|
|
11834
12092
|
return plan;
|
|
@@ -11867,7 +12125,7 @@ function getAppManager(machine, app) {
|
|
|
11867
12125
|
return "winget";
|
|
11868
12126
|
return "apt";
|
|
11869
12127
|
}
|
|
11870
|
-
function
|
|
12128
|
+
function shellQuote4(value) {
|
|
11871
12129
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
11872
12130
|
}
|
|
11873
12131
|
function buildAppCommand(machine, app) {
|
|
@@ -11888,7 +12146,7 @@ function buildAppCommand(machine, app) {
|
|
|
11888
12146
|
return `sudo apt-get install -y ${packageName}`;
|
|
11889
12147
|
}
|
|
11890
12148
|
function buildAppProbeCommand(machine, app) {
|
|
11891
|
-
const packageName =
|
|
12149
|
+
const packageName = shellQuote4(getPackageName(app));
|
|
11892
12150
|
const manager = getAppManager(machine, app);
|
|
11893
12151
|
if (manager === "custom") {
|
|
11894
12152
|
return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
|
|
@@ -12424,7 +12682,7 @@ var notificationConfigSchema = exports_external.object({
|
|
|
12424
12682
|
function sortChannels(channels) {
|
|
12425
12683
|
return [...channels].sort((left, right) => left.id.localeCompare(right.id));
|
|
12426
12684
|
}
|
|
12427
|
-
function
|
|
12685
|
+
function shellQuote5(value) {
|
|
12428
12686
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
12429
12687
|
}
|
|
12430
12688
|
function hasCommand2(binary) {
|
|
@@ -12471,7 +12729,7 @@ ${message}
|
|
|
12471
12729
|
};
|
|
12472
12730
|
}
|
|
12473
12731
|
if (hasCommand2("mail")) {
|
|
12474
|
-
const command = `printf %s ${
|
|
12732
|
+
const command = `printf %s ${shellQuote5(message)} | mail -s ${shellQuote5(subject)} ${shellQuote5(channel.target)}`;
|
|
12475
12733
|
const result = Bun.spawnSync(["bash", "-lc", command], {
|
|
12476
12734
|
stdout: "pipe",
|
|
12477
12735
|
stderr: "pipe",
|
|
@@ -22225,6 +22483,7 @@ var MACHINE_MCP_TOOL_NAMES = [
|
|
|
22225
22483
|
"machines_install_claude_preview",
|
|
22226
22484
|
"machines_install_claude_apply",
|
|
22227
22485
|
"machines_route_resolve",
|
|
22486
|
+
"machines_workspace_resolve",
|
|
22228
22487
|
"machines_ssh_resolve",
|
|
22229
22488
|
"machines_ports",
|
|
22230
22489
|
"machines_backup_preview",
|
|
@@ -22338,14 +22597,50 @@ function createMcpServer(version2) {
|
|
|
22338
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 }) => ({
|
|
22339
22598
|
content: [{ type: "text", text: JSON.stringify(resolveMachineRoute(machine_id, { includeTailscale: include_tailscale !== false }), null, 2) }]
|
|
22340
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
|
+
}));
|
|
22341
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 }) => ({
|
|
22342
22637
|
content: [{ type: "text", text: JSON.stringify({ resolved: resolveMachineRoute(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
|
|
22343
22638
|
}));
|
|
22344
22639
|
server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
22345
22640
|
content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
|
|
22346
22641
|
}));
|
|
22347
|
-
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) }] }));
|
|
22348
|
-
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) }] }));
|
|
22349
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) }] }));
|
|
22350
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) }] }));
|
|
22351
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) }] }));
|
|
@@ -22404,7 +22699,9 @@ export {
|
|
|
22404
22699
|
runAppsInstall,
|
|
22405
22700
|
resolveTables,
|
|
22406
22701
|
resolveSshTarget,
|
|
22702
|
+
resolveMachineWorkspace,
|
|
22407
22703
|
resolveMachineRoute,
|
|
22704
|
+
resolveBackupTarget,
|
|
22408
22705
|
renderDomainMapping,
|
|
22409
22706
|
renderDashboardHtml,
|
|
22410
22707
|
removeNotificationChannel,
|
|
@@ -22490,5 +22787,10 @@ export {
|
|
|
22490
22787
|
MACHINES_STORAGE_ENV,
|
|
22491
22788
|
MACHINES_PACKAGE_NAME,
|
|
22492
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,
|
|
22493
22795
|
CROSSREFS_KEY
|
|
22494
22796
|
};
|
package/dist/manifests.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const machineSchema: z.ZodObject<{
|
|
|
10
10
|
workspacePath: z.ZodString;
|
|
11
11
|
bunPath: z.ZodOptional<z.ZodString>;
|
|
12
12
|
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
13
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
13
14
|
packages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
14
15
|
name: z.ZodString;
|
|
15
16
|
manager: z.ZodOptional<z.ZodEnum<["bun", "brew", "apt", "custom"]>>;
|
|
@@ -59,6 +60,7 @@ export declare const machineSchema: z.ZodObject<{
|
|
|
59
60
|
connection?: "local" | "ssh" | "tailscale" | undefined;
|
|
60
61
|
bunPath?: string | undefined;
|
|
61
62
|
tags?: string[] | undefined;
|
|
63
|
+
metadata?: Record<string, unknown> | undefined;
|
|
62
64
|
packages?: {
|
|
63
65
|
name: string;
|
|
64
66
|
manager?: "bun" | "brew" | "apt" | "custom" | undefined;
|
|
@@ -84,6 +86,7 @@ export declare const machineSchema: z.ZodObject<{
|
|
|
84
86
|
connection?: "local" | "ssh" | "tailscale" | undefined;
|
|
85
87
|
bunPath?: string | undefined;
|
|
86
88
|
tags?: string[] | undefined;
|
|
89
|
+
metadata?: Record<string, unknown> | undefined;
|
|
87
90
|
packages?: {
|
|
88
91
|
name: string;
|
|
89
92
|
manager?: "bun" | "brew" | "apt" | "custom" | undefined;
|
|
@@ -113,6 +116,7 @@ export declare const fleetSchema: z.ZodObject<{
|
|
|
113
116
|
workspacePath: z.ZodString;
|
|
114
117
|
bunPath: z.ZodOptional<z.ZodString>;
|
|
115
118
|
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
119
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
116
120
|
packages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
117
121
|
name: z.ZodString;
|
|
118
122
|
manager: z.ZodOptional<z.ZodEnum<["bun", "brew", "apt", "custom"]>>;
|
|
@@ -162,6 +166,7 @@ export declare const fleetSchema: z.ZodObject<{
|
|
|
162
166
|
connection?: "local" | "ssh" | "tailscale" | undefined;
|
|
163
167
|
bunPath?: string | undefined;
|
|
164
168
|
tags?: string[] | undefined;
|
|
169
|
+
metadata?: Record<string, unknown> | undefined;
|
|
165
170
|
packages?: {
|
|
166
171
|
name: string;
|
|
167
172
|
manager?: "bun" | "brew" | "apt" | "custom" | undefined;
|
|
@@ -187,6 +192,7 @@ export declare const fleetSchema: z.ZodObject<{
|
|
|
187
192
|
connection?: "local" | "ssh" | "tailscale" | undefined;
|
|
188
193
|
bunPath?: string | undefined;
|
|
189
194
|
tags?: string[] | undefined;
|
|
195
|
+
metadata?: Record<string, unknown> | undefined;
|
|
190
196
|
packages?: {
|
|
191
197
|
name: string;
|
|
192
198
|
manager?: "bun" | "brew" | "apt" | "custom" | undefined;
|
|
@@ -214,6 +220,7 @@ export declare const fleetSchema: z.ZodObject<{
|
|
|
214
220
|
connection?: "local" | "ssh" | "tailscale" | undefined;
|
|
215
221
|
bunPath?: string | undefined;
|
|
216
222
|
tags?: string[] | undefined;
|
|
223
|
+
metadata?: Record<string, unknown> | undefined;
|
|
217
224
|
packages?: {
|
|
218
225
|
name: string;
|
|
219
226
|
manager?: "bun" | "brew" | "apt" | "custom" | undefined;
|
|
@@ -243,6 +250,7 @@ export declare const fleetSchema: z.ZodObject<{
|
|
|
243
250
|
connection?: "local" | "ssh" | "tailscale" | undefined;
|
|
244
251
|
bunPath?: string | undefined;
|
|
245
252
|
tags?: string[] | undefined;
|
|
253
|
+
metadata?: Record<string, unknown> | undefined;
|
|
246
254
|
packages?: {
|
|
247
255
|
name: string;
|
|
248
256
|
manager?: "bun" | "brew" | "apt" | "custom" | undefined;
|
package/dist/manifests.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../src/manifests.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAoBjE,eAAO,MAAM,aAAa
|
|
1
|
+
{"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../src/manifests.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAoBjE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcxB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAItB,CAAC;AAoBH,wBAAgB,kBAAkB,IAAI,aAAa,CAMlD;AAED,wBAAgB,YAAY,CAAC,IAAI,SAAoB,GAAG,aAAa,CAMpE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,SAAoB,GAAG,aAAa,CAExE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,SAAoB,GAAG,MAAM,CAUvF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,SAAoB,GAAG,eAAe,GAAG,IAAI,CAEtG;AAED,wBAAgB,4BAA4B,IAAI,eAAe,CAiB9D"}
|