@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/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} ${JSON.stringify(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 shellQuote(value) {
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 ${shellQuote(machineId)} ${shellQuote(command)}` };
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 shellQuote2(value) {
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 = shellQuote2(spec.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=${shellQuote2(spec.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 = "machines") {
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 = "machines", options = {}) {
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 shellQuote3(value) {
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 = shellQuote3(getPackageName(app));
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 shellQuote4(value) {
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 ${shellQuote4(message)} | mail -s ${shellQuote4(subject)} ${shellQuote4(channel.target)}`;
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
  };
@@ -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;
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAaxB,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"}
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"}