@hasna/machines 0.0.20 → 0.0.22

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
@@ -11117,7 +11117,8 @@ var MACHINES_CONSUMER_CAPABILITIES = {
11117
11117
  compatibility: true,
11118
11118
  route_resolution: true,
11119
11119
  cli_json_fallback: true,
11120
- workspace_path_mapping: true
11120
+ workspace_path_mapping: true,
11121
+ workspace_diagnostics: true
11121
11122
  };
11122
11123
  var MACHINES_CONSUMER_CONTRACT = {
11123
11124
  schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
@@ -11531,6 +11532,170 @@ function inferRepoRoot(workspaceRoot, repoName) {
11531
11532
  }
11532
11533
  return joinPath(root, repoName);
11533
11534
  }
11535
+ function shellQuote(value) {
11536
+ return `'${value.replace(/'/g, "'\\''")}'`;
11537
+ }
11538
+ function shellCommand(command) {
11539
+ return command.map(shellQuote).join(" ");
11540
+ }
11541
+ function canCheckPathForMachine(machine, localMachineId) {
11542
+ if (!machine)
11543
+ return false;
11544
+ if (machine.machine_id === localMachineId)
11545
+ return true;
11546
+ return machine.route_hints.some((hint) => hint.kind === "local");
11547
+ }
11548
+ function checkedPathExists(path, check) {
11549
+ if (!path || !check)
11550
+ return null;
11551
+ return existsSync4(path);
11552
+ }
11553
+ function repairHint(input) {
11554
+ const command = [
11555
+ "machines",
11556
+ "workspace",
11557
+ "repair",
11558
+ "--machine",
11559
+ input.machineId,
11560
+ "--project",
11561
+ input.projectId
11562
+ ];
11563
+ if (input.repoName)
11564
+ command.push("--repo", input.repoName);
11565
+ if (input.openFilesRepoName)
11566
+ command.push("--open-files-repo", input.openFilesRepoName);
11567
+ command.push("--json");
11568
+ const applyCommand = [...command.slice(0, -1), "--apply", "--json"];
11569
+ return {
11570
+ id: `repair:${input.machineId}:${input.projectId}`,
11571
+ reason: input.reason,
11572
+ command,
11573
+ shell_command: shellCommand(command),
11574
+ apply_command: applyCommand,
11575
+ apply_shell_command: shellCommand(applyCommand)
11576
+ };
11577
+ }
11578
+ function pathDiagnostic(input) {
11579
+ if (!input.path.path) {
11580
+ return {
11581
+ id: input.id,
11582
+ status: "missing",
11583
+ severity: input.required ? "fail" : "warn",
11584
+ message: `${input.label} is unresolved.`,
11585
+ path: null,
11586
+ source: input.path.source,
11587
+ path_exists: null
11588
+ };
11589
+ }
11590
+ if (input.pathExists === false) {
11591
+ return {
11592
+ id: input.id,
11593
+ status: "stale",
11594
+ severity: "fail",
11595
+ message: `${input.label} points to a path that does not exist on this machine.`,
11596
+ path: input.path.path,
11597
+ source: input.path.source,
11598
+ path_exists: false
11599
+ };
11600
+ }
11601
+ if (input.path.source === "inferred") {
11602
+ return {
11603
+ id: input.id,
11604
+ status: "inferred",
11605
+ severity: "warn",
11606
+ message: `${input.label} was inferred from the workspace root; write an explicit manifest mapping for repeatable downstream sync.`,
11607
+ path: input.path.path,
11608
+ source: input.path.source,
11609
+ path_exists: input.pathExists
11610
+ };
11611
+ }
11612
+ return {
11613
+ id: input.id,
11614
+ status: "ok",
11615
+ severity: "ok",
11616
+ message: `${input.label} is explicit enough for downstream sync.`,
11617
+ path: input.path.path,
11618
+ source: input.path.source,
11619
+ path_exists: input.pathExists
11620
+ };
11621
+ }
11622
+ function workspaceDiagnostics(input) {
11623
+ const checkPaths = canCheckPathForMachine(input.machine, input.localMachineId);
11624
+ const diagnostics = [];
11625
+ if (!input.machine) {
11626
+ diagnostics.push({
11627
+ id: "manifest",
11628
+ status: "missing_manifest",
11629
+ severity: "fail",
11630
+ message: "Machine is not present in topology or manifest.",
11631
+ path: null,
11632
+ source: "manifest",
11633
+ path_exists: null
11634
+ });
11635
+ } else if (!input.resolution.evidence.manifest_declared) {
11636
+ diagnostics.push({
11637
+ id: "manifest",
11638
+ status: "missing_manifest",
11639
+ severity: "warn",
11640
+ message: "Machine came from live topology but is not declared in the manifest.",
11641
+ path: null,
11642
+ source: "manifest",
11643
+ path_exists: null
11644
+ });
11645
+ }
11646
+ diagnostics.push(pathDiagnostic({
11647
+ id: "workspace_root",
11648
+ label: "Workspace root",
11649
+ path: input.resolution.paths.workspace_root,
11650
+ pathExists: checkedPathExists(input.resolution.paths.workspace_root.path, checkPaths),
11651
+ required: false
11652
+ }));
11653
+ diagnostics.push(pathDiagnostic({
11654
+ id: "project_root",
11655
+ label: "Project root",
11656
+ path: input.resolution.paths.project_root,
11657
+ pathExists: checkedPathExists(input.resolution.paths.project_root.path, checkPaths),
11658
+ required: true
11659
+ }));
11660
+ diagnostics.push(pathDiagnostic({
11661
+ id: "open_files_root",
11662
+ label: "Open-files root",
11663
+ path: input.resolution.paths.open_files_root,
11664
+ pathExists: checkedPathExists(input.resolution.paths.open_files_root.path, checkPaths),
11665
+ required: false
11666
+ }));
11667
+ if (input.resolution.machine.trust_status !== "trusted") {
11668
+ diagnostics.push({
11669
+ id: "trust",
11670
+ status: "untrusted",
11671
+ severity: "warn",
11672
+ message: `Machine trust status is ${input.resolution.machine.trust_status}; manifest repair apply requires trust or --allow-untrusted.`,
11673
+ path: null,
11674
+ source: "trust",
11675
+ path_exists: null
11676
+ });
11677
+ }
11678
+ if (input.resolution.machine.auth_status !== "authenticated") {
11679
+ diagnostics.push({
11680
+ id: "auth",
11681
+ status: "unknown_auth",
11682
+ severity: "warn",
11683
+ message: `Machine auth status is ${input.resolution.machine.auth_status}; remote sync may still fail if SSH is unavailable.`,
11684
+ path: null,
11685
+ source: "auth",
11686
+ path_exists: null
11687
+ });
11688
+ }
11689
+ const needsRepair = diagnostics.some((entry) => (entry.id === "project_root" || entry.id === "open_files_root") && (entry.status === "missing" || entry.status === "inferred" || entry.status === "stale"));
11690
+ const repairHints = needsRepair ? [repairHint({
11691
+ machineId: input.resolution.machine_id ?? input.resolution.requested_machine_id,
11692
+ projectId: input.resolution.project.project_id,
11693
+ repoName: input.resolution.project.repo_name,
11694
+ openFilesRepoName: input.openFilesRepoName,
11695
+ reason: "Write explicit workspace_paths and open_files_roots manifest metadata."
11696
+ })] : [];
11697
+ return { diagnostics, repairHints };
11698
+ }
11534
11699
  function projectPathFromMetadata(metadata, projectId, repoName) {
11535
11700
  const keys = [projectId, repoName].filter((value) => Boolean(value));
11536
11701
  return readMappedPath({
@@ -11606,7 +11771,7 @@ function resolveMachineWorkspace(options) {
11606
11771
  const openFilesRepoName = options.openFilesRepoName ?? "open-files";
11607
11772
  if (!machine) {
11608
11773
  warnings.push(`machine_not_found:${options.machineId}`);
11609
- return {
11774
+ const resolution2 = {
11610
11775
  schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
11611
11776
  package: topology.package,
11612
11777
  ok: false,
@@ -11620,6 +11785,8 @@ function resolveMachineWorkspace(options) {
11620
11785
  project_root: { path: null, source: "unresolved" },
11621
11786
  open_files_root: { path: null, source: "unresolved" }
11622
11787
  },
11788
+ diagnostics: [],
11789
+ repair_hints: [],
11623
11790
  evidence: {
11624
11791
  topology: true,
11625
11792
  matched_by: matchedBy,
@@ -11628,6 +11795,17 @@ function resolveMachineWorkspace(options) {
11628
11795
  },
11629
11796
  warnings
11630
11797
  };
11798
+ const diagnostics2 = workspaceDiagnostics({
11799
+ machine,
11800
+ localMachineId: topology.local_machine_id,
11801
+ resolution: resolution2,
11802
+ openFilesRepoName
11803
+ });
11804
+ return {
11805
+ ...resolution2,
11806
+ diagnostics: diagnostics2.diagnostics,
11807
+ repair_hints: diagnostics2.repairHints
11808
+ };
11631
11809
  }
11632
11810
  const metadata = machine.metadata;
11633
11811
  const workspaceRootPath = options.workspaceRoot ?? machine.workspace_path;
@@ -11646,7 +11824,7 @@ function resolveMachineWorkspace(options) {
11646
11824
  warnings.push(`open_files_root_inferred:${options.projectId}`);
11647
11825
  if (!projectRootPath)
11648
11826
  warnings.push(`project_root_unresolved:${options.projectId}`);
11649
- return {
11827
+ const resolution = {
11650
11828
  schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
11651
11829
  package: topology.package,
11652
11830
  ok: Boolean(projectRootPath),
@@ -11669,6 +11847,8 @@ function resolveMachineWorkspace(options) {
11669
11847
  project_root: { path: projectRootPath, source: projectRootSource },
11670
11848
  open_files_root: { path: openFilesRootPath, source: openFilesRootSource }
11671
11849
  },
11850
+ diagnostics: [],
11851
+ repair_hints: [],
11672
11852
  evidence: {
11673
11853
  topology: true,
11674
11854
  matched_by: matchedBy,
@@ -11677,6 +11857,17 @@ function resolveMachineWorkspace(options) {
11677
11857
  },
11678
11858
  warnings
11679
11859
  };
11860
+ const diagnostics = workspaceDiagnostics({
11861
+ machine,
11862
+ localMachineId: topology.local_machine_id,
11863
+ resolution,
11864
+ openFilesRepoName
11865
+ });
11866
+ return {
11867
+ ...resolution,
11868
+ diagnostics: diagnostics.diagnostics,
11869
+ repair_hints: diagnostics.repairHints
11870
+ };
11680
11871
  }
11681
11872
  function getLocalMachineTopology(options = {}) {
11682
11873
  const topology = discoverMachineTopology(options);
@@ -11702,7 +11893,7 @@ import { spawnSync as spawnSync2 } from "child_process";
11702
11893
  import { hostname as hostname4 } from "os";
11703
11894
 
11704
11895
  // src/commands/ssh.ts
11705
- function shellQuote(value) {
11896
+ function shellQuote2(value) {
11706
11897
  return `'${value.replace(/'/g, "'\\''")}'`;
11707
11898
  }
11708
11899
  function resolveSshTarget(machineId, options = {}) {
@@ -11723,11 +11914,11 @@ function resolveSshTarget(machineId, options = {}) {
11723
11914
  }
11724
11915
  function buildSshCommand(machineId, remoteCommand, options = {}) {
11725
11916
  const resolved = resolveSshTarget(machineId, options);
11726
- return remoteCommand ? `ssh ${resolved.target} ${shellQuote(remoteCommand)}` : `ssh ${resolved.target}`;
11917
+ return remoteCommand ? `ssh ${resolved.target} ${shellQuote2(remoteCommand)}` : `ssh ${resolved.target}`;
11727
11918
  }
11728
11919
 
11729
11920
  // src/remote.ts
11730
- function shellQuote2(value) {
11921
+ function shellQuote3(value) {
11731
11922
  return `'${value.replace(/'/g, "'\\''")}'`;
11732
11923
  }
11733
11924
  function machineIsLocal(machineId, localMachineId) {
@@ -11745,7 +11936,7 @@ function resolveMachineCommand(machineId, command, localMachineId = getLocalMach
11745
11936
  } catch (error) {
11746
11937
  const message = String(error.message ?? error);
11747
11938
  if (message.includes("Machine route not found") || message.includes("Machine not found in manifest")) {
11748
- return { source: "ssh", shellCommand: `ssh ${shellQuote2(machineId)} ${shellQuote2(command)}` };
11939
+ return { source: "ssh", shellCommand: `ssh ${shellQuote3(machineId)} ${shellQuote3(command)}` };
11749
11940
  }
11750
11941
  throw error;
11751
11942
  }
@@ -11773,7 +11964,7 @@ var DEFAULT_COMMANDS = [
11773
11964
  function defaultPackages() {
11774
11965
  return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
11775
11966
  }
11776
- function shellQuote3(value) {
11967
+ function shellQuote4(value) {
11777
11968
  return `'${value.replace(/'/g, "'\\''")}'`;
11778
11969
  }
11779
11970
  function commandId(value) {
@@ -11824,7 +12015,7 @@ function defaultRunner2(machineId, command) {
11824
12015
  return runMachineCommand(machineId, command);
11825
12016
  }
11826
12017
  function inspectCommand(machineId, spec, runner) {
11827
- const command = shellQuote3(spec.command);
12018
+ const command = shellQuote4(spec.command);
11828
12019
  const versionArgs = spec.versionArgs ?? "--version";
11829
12020
  const script = [
11830
12021
  `cmd=${command}`,
@@ -11853,7 +12044,7 @@ function fieldCommand(field) {
11853
12044
  }
11854
12045
  function inspectWorkspace(machineId, spec, runner) {
11855
12046
  const script = [
11856
- `path=${shellQuote3(spec.path)}`,
12047
+ `path=${shellQuote4(spec.path)}`,
11857
12048
  'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
11858
12049
  'pkg="$path/package.json"',
11859
12050
  'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
@@ -12148,7 +12339,7 @@ function getAppManager(machine, app) {
12148
12339
  return "winget";
12149
12340
  return "apt";
12150
12341
  }
12151
- function shellQuote4(value) {
12342
+ function shellQuote5(value) {
12152
12343
  return `'${value.replace(/'/g, `'\\''`)}'`;
12153
12344
  }
12154
12345
  function buildAppCommand(machine, app) {
@@ -12169,7 +12360,7 @@ function buildAppCommand(machine, app) {
12169
12360
  return `sudo apt-get install -y ${packageName}`;
12170
12361
  }
12171
12362
  function buildAppProbeCommand(machine, app) {
12172
- const packageName = shellQuote4(getPackageName(app));
12363
+ const packageName = shellQuote5(getPackageName(app));
12173
12364
  const manager = getAppManager(machine, app);
12174
12365
  if (manager === "custom") {
12175
12366
  return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
@@ -12705,7 +12896,7 @@ var notificationConfigSchema = exports_external.object({
12705
12896
  function sortChannels(channels) {
12706
12897
  return [...channels].sort((left, right) => left.id.localeCompare(right.id));
12707
12898
  }
12708
- function shellQuote5(value) {
12899
+ function shellQuote6(value) {
12709
12900
  return `'${value.replace(/'/g, `'\\''`)}'`;
12710
12901
  }
12711
12902
  function hasCommand2(binary) {
@@ -12752,7 +12943,7 @@ ${message}
12752
12943
  };
12753
12944
  }
12754
12945
  if (hasCommand2("mail")) {
12755
- const command = `printf %s ${shellQuote5(message)} | mail -s ${shellQuote5(subject)} ${shellQuote5(channel.target)}`;
12946
+ const command = `printf %s ${shellQuote6(message)} | mail -s ${shellQuote6(subject)} ${shellQuote6(channel.target)}`;
12756
12947
  const result = Bun.spawnSync(["bash", "-lc", command], {
12757
12948
  stdout: "pipe",
12758
12949
  stderr: "pipe",
@@ -13568,6 +13759,154 @@ function runSync(machineId, options = {}) {
13568
13759
  recordSyncRun(plan.machineId, "completed", summary);
13569
13760
  return summary;
13570
13761
  }
13762
+ // src/commands/workspace.ts
13763
+ function isRecord2(value) {
13764
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13765
+ }
13766
+ function cloneMetadata(metadata) {
13767
+ return isRecord2(metadata) ? { ...metadata } : {};
13768
+ }
13769
+ function mappedPath(metadata, field, key) {
13770
+ const container = metadata[field];
13771
+ if (!isRecord2(container))
13772
+ return null;
13773
+ const value = container[key];
13774
+ if (typeof value === "string" && value.trim())
13775
+ return value.trim();
13776
+ if (isRecord2(value)) {
13777
+ const nested = value["path"] ?? value["root"] ?? value["workspacePath"] ?? value["workspace_path"];
13778
+ if (typeof nested === "string" && nested.trim())
13779
+ return nested.trim();
13780
+ }
13781
+ return null;
13782
+ }
13783
+ function writeMappedPath(metadata, field, key, path) {
13784
+ const existing = metadata[field];
13785
+ const container = isRecord2(existing) ? { ...existing } : {};
13786
+ container[key] = path;
13787
+ metadata[field] = container;
13788
+ }
13789
+ function buildPatch(input) {
13790
+ const previous = mappedPath(input.metadata, input.field, input.key);
13791
+ if (!input.path) {
13792
+ return {
13793
+ field: input.field,
13794
+ key: input.key,
13795
+ path: null,
13796
+ previous_path: previous,
13797
+ status: "unresolved"
13798
+ };
13799
+ }
13800
+ const status = previous === input.path ? "unchanged" : input.apply ? "written" : "would_write";
13801
+ return {
13802
+ field: input.field,
13803
+ key: input.key,
13804
+ path: input.path,
13805
+ previous_path: previous,
13806
+ status
13807
+ };
13808
+ }
13809
+ function upsertMachineMetadata(manifest, machineId, metadata) {
13810
+ return {
13811
+ ...manifest,
13812
+ machines: manifest.machines.map((machine) => machine.id === machineId ? { ...machine, metadata } : machine)
13813
+ };
13814
+ }
13815
+ function repairWorkspaceManifestMappings(options) {
13816
+ const projectId = options.projectId;
13817
+ const repoName = options.repoName ?? projectId;
13818
+ const openFilesRepoName = options.openFilesRepoName ?? "open-files";
13819
+ const apply = options.apply === true;
13820
+ const resolution = resolveMachineWorkspace({
13821
+ machineId: options.machineId,
13822
+ projectId,
13823
+ repoName,
13824
+ openFilesRepoName,
13825
+ projectRoot: options.projectRoot,
13826
+ openFilesRoot: options.openFilesRoot,
13827
+ workspaceRoot: options.workspaceRoot,
13828
+ includeTailscale: options.includeTailscale,
13829
+ now: options.now
13830
+ });
13831
+ const warnings = [...resolution.warnings];
13832
+ const manifest = readManifest();
13833
+ const manifestMachineId = resolution.machine_id ?? options.machineId;
13834
+ const machine = manifest.machines.find((entry) => entry.id === manifestMachineId) ?? null;
13835
+ const trusted = resolution.machine.trust_status === "trusted" || options.allowUntrusted === true;
13836
+ if (!machine) {
13837
+ warnings.push(`manifest_machine_missing:${manifestMachineId}`);
13838
+ return {
13839
+ ok: false,
13840
+ applied: false,
13841
+ manifest_path: getManifestPath(),
13842
+ machine_id: resolution.machine_id,
13843
+ project_id: projectId,
13844
+ repo_name: repoName,
13845
+ open_files_repo_name: openFilesRepoName,
13846
+ trusted,
13847
+ resolution,
13848
+ patches: [],
13849
+ warnings
13850
+ };
13851
+ }
13852
+ const metadata = cloneMetadata(machine.metadata);
13853
+ const patches = [
13854
+ buildPatch({
13855
+ metadata,
13856
+ field: "workspace_paths",
13857
+ key: projectId,
13858
+ path: options.projectRoot ?? resolution.paths.project_root.path,
13859
+ apply
13860
+ }),
13861
+ buildPatch({
13862
+ metadata,
13863
+ field: "open_files_roots",
13864
+ key: projectId,
13865
+ path: options.openFilesRoot ?? resolution.paths.open_files_root.path,
13866
+ apply
13867
+ })
13868
+ ];
13869
+ const hasUnresolved = patches.some((patch) => patch.status === "unresolved");
13870
+ const hasWrites = patches.some((patch) => patch.status === "would_write" || patch.status === "written");
13871
+ if (apply && hasWrites && !trusted) {
13872
+ warnings.push(`manifest_repair_requires_trusted_machine:${manifestMachineId}`);
13873
+ return {
13874
+ ok: false,
13875
+ applied: false,
13876
+ manifest_path: getManifestPath(),
13877
+ machine_id: manifestMachineId,
13878
+ project_id: projectId,
13879
+ repo_name: repoName,
13880
+ open_files_repo_name: openFilesRepoName,
13881
+ trusted,
13882
+ resolution,
13883
+ patches: patches.map((patch) => patch.status === "written" ? { ...patch, status: "would_write" } : patch),
13884
+ warnings
13885
+ };
13886
+ }
13887
+ let applied = false;
13888
+ if (apply && !hasUnresolved && hasWrites) {
13889
+ for (const patch of patches) {
13890
+ if (patch.path && patch.status === "written")
13891
+ writeMappedPath(metadata, patch.field, patch.key, patch.path);
13892
+ }
13893
+ writeManifest(upsertMachineMetadata(manifest, manifestMachineId, metadata));
13894
+ applied = true;
13895
+ }
13896
+ return {
13897
+ ok: resolution.ok && !hasUnresolved && (!apply || applied || !hasWrites),
13898
+ applied,
13899
+ manifest_path: getManifestPath(),
13900
+ machine_id: manifestMachineId,
13901
+ project_id: projectId,
13902
+ repo_name: repoName,
13903
+ open_files_repo_name: openFilesRepoName,
13904
+ trusted,
13905
+ resolution,
13906
+ patches,
13907
+ warnings
13908
+ };
13909
+ }
13571
13910
  // node_modules/zod/v4/core/core.js
13572
13911
  var NEVER2 = Object.freeze({
13573
13912
  status: "aborted"
@@ -22725,6 +23064,7 @@ export {
22725
23064
  resolveMachineWorkspace,
22726
23065
  resolveMachineRoute,
22727
23066
  resolveBackupTarget,
23067
+ repairWorkspaceManifestMappings,
22728
23068
  renderDomainMapping,
22729
23069
  renderDashboardHtml,
22730
23070
  removeNotificationChannel,