@hasna/machines 0.0.20 → 0.0.21

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 CHANGED
@@ -113,6 +113,15 @@ workspace inference second; consumers can still pass manual overrides.
113
113
  machines workspace resolve --machine spark01 --project open-knowledge --repo open-knowledge --json
114
114
  ```
115
115
 
116
+ If a resolver result reports inferred project or open-files roots, repair the
117
+ manifest metadata explicitly. The command previews changes by default and only
118
+ writes when `--apply` is passed:
119
+
120
+ ```bash
121
+ machines workspace repair --machine spark01 --project open-knowledge --repo open-knowledge --json
122
+ machines workspace repair --machine spark01 --project open-knowledge --repo open-knowledge --apply --json
123
+ ```
124
+
116
125
  ## Compatibility SDK
117
126
 
118
127
  Open-core consumers can use `@hasna/machines` to preflight a peer before
package/dist/cli/index.js CHANGED
@@ -9038,6 +9038,156 @@ function getStatus() {
9038
9038
  };
9039
9039
  }
9040
9040
 
9041
+ // src/commands/workspace.ts
9042
+ init_paths();
9043
+ function isRecord2(value) {
9044
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9045
+ }
9046
+ function cloneMetadata(metadata) {
9047
+ return isRecord2(metadata) ? { ...metadata } : {};
9048
+ }
9049
+ function mappedPath(metadata, field, key) {
9050
+ const container = metadata[field];
9051
+ if (!isRecord2(container))
9052
+ return null;
9053
+ const value = container[key];
9054
+ if (typeof value === "string" && value.trim())
9055
+ return value.trim();
9056
+ if (isRecord2(value)) {
9057
+ const nested = value["path"] ?? value["root"] ?? value["workspacePath"] ?? value["workspace_path"];
9058
+ if (typeof nested === "string" && nested.trim())
9059
+ return nested.trim();
9060
+ }
9061
+ return null;
9062
+ }
9063
+ function writeMappedPath(metadata, field, key, path) {
9064
+ const existing = metadata[field];
9065
+ const container = isRecord2(existing) ? { ...existing } : {};
9066
+ container[key] = path;
9067
+ metadata[field] = container;
9068
+ }
9069
+ function buildPatch(input) {
9070
+ const previous = mappedPath(input.metadata, input.field, input.key);
9071
+ if (!input.path) {
9072
+ return {
9073
+ field: input.field,
9074
+ key: input.key,
9075
+ path: null,
9076
+ previous_path: previous,
9077
+ status: "unresolved"
9078
+ };
9079
+ }
9080
+ const status = previous === input.path ? "unchanged" : input.apply ? "written" : "would_write";
9081
+ return {
9082
+ field: input.field,
9083
+ key: input.key,
9084
+ path: input.path,
9085
+ previous_path: previous,
9086
+ status
9087
+ };
9088
+ }
9089
+ function upsertMachineMetadata(manifest, machineId, metadata) {
9090
+ return {
9091
+ ...manifest,
9092
+ machines: manifest.machines.map((machine) => machine.id === machineId ? { ...machine, metadata } : machine)
9093
+ };
9094
+ }
9095
+ function repairWorkspaceManifestMappings(options) {
9096
+ const projectId = options.projectId;
9097
+ const repoName = options.repoName ?? projectId;
9098
+ const openFilesRepoName = options.openFilesRepoName ?? "open-files";
9099
+ const apply = options.apply === true;
9100
+ const resolution = resolveMachineWorkspace({
9101
+ machineId: options.machineId,
9102
+ projectId,
9103
+ repoName,
9104
+ openFilesRepoName,
9105
+ projectRoot: options.projectRoot,
9106
+ openFilesRoot: options.openFilesRoot,
9107
+ workspaceRoot: options.workspaceRoot,
9108
+ includeTailscale: options.includeTailscale,
9109
+ now: options.now
9110
+ });
9111
+ const warnings = [...resolution.warnings];
9112
+ const manifest = readManifest();
9113
+ const manifestMachineId = resolution.machine_id ?? options.machineId;
9114
+ const machine = manifest.machines.find((entry) => entry.id === manifestMachineId) ?? null;
9115
+ const trusted = resolution.machine.trust_status === "trusted" || options.allowUntrusted === true;
9116
+ if (!machine) {
9117
+ warnings.push(`manifest_machine_missing:${manifestMachineId}`);
9118
+ return {
9119
+ ok: false,
9120
+ applied: false,
9121
+ manifest_path: getManifestPath(),
9122
+ machine_id: resolution.machine_id,
9123
+ project_id: projectId,
9124
+ repo_name: repoName,
9125
+ open_files_repo_name: openFilesRepoName,
9126
+ trusted,
9127
+ resolution,
9128
+ patches: [],
9129
+ warnings
9130
+ };
9131
+ }
9132
+ const metadata = cloneMetadata(machine.metadata);
9133
+ const patches = [
9134
+ buildPatch({
9135
+ metadata,
9136
+ field: "workspace_paths",
9137
+ key: projectId,
9138
+ path: options.projectRoot ?? resolution.paths.project_root.path,
9139
+ apply
9140
+ }),
9141
+ buildPatch({
9142
+ metadata,
9143
+ field: "open_files_roots",
9144
+ key: projectId,
9145
+ path: options.openFilesRoot ?? resolution.paths.open_files_root.path,
9146
+ apply
9147
+ })
9148
+ ];
9149
+ const hasUnresolved = patches.some((patch) => patch.status === "unresolved");
9150
+ const hasWrites = patches.some((patch) => patch.status === "would_write" || patch.status === "written");
9151
+ if (apply && hasWrites && !trusted) {
9152
+ warnings.push(`manifest_repair_requires_trusted_machine:${manifestMachineId}`);
9153
+ return {
9154
+ ok: false,
9155
+ applied: false,
9156
+ manifest_path: getManifestPath(),
9157
+ machine_id: manifestMachineId,
9158
+ project_id: projectId,
9159
+ repo_name: repoName,
9160
+ open_files_repo_name: openFilesRepoName,
9161
+ trusted,
9162
+ resolution,
9163
+ patches: patches.map((patch) => patch.status === "written" ? { ...patch, status: "would_write" } : patch),
9164
+ warnings
9165
+ };
9166
+ }
9167
+ let applied = false;
9168
+ if (apply && !hasUnresolved && hasWrites) {
9169
+ for (const patch of patches) {
9170
+ if (patch.path && patch.status === "written")
9171
+ writeMappedPath(metadata, patch.field, patch.key, patch.path);
9172
+ }
9173
+ writeManifest(upsertMachineMetadata(manifest, manifestMachineId, metadata));
9174
+ applied = true;
9175
+ }
9176
+ return {
9177
+ ok: resolution.ok && !hasUnresolved && (!apply || applied || !hasWrites),
9178
+ applied,
9179
+ manifest_path: getManifestPath(),
9180
+ machine_id: manifestMachineId,
9181
+ project_id: projectId,
9182
+ repo_name: repoName,
9183
+ open_files_repo_name: openFilesRepoName,
9184
+ trusted,
9185
+ resolution,
9186
+ patches,
9187
+ warnings
9188
+ };
9189
+ }
9190
+
9041
9191
  // src/compatibility.ts
9042
9192
  init_db();
9043
9193
  var DEFAULT_COMMANDS = [
@@ -10784,6 +10934,25 @@ function renderWorkspaceResolution(result) {
10784
10934
  ["warnings", result.warnings.join(", ") || "none"]
10785
10935
  ]);
10786
10936
  }
10937
+ function renderWorkspaceRepairResult(result) {
10938
+ const patchLines = result.patches.map((patch) => {
10939
+ const path = patch.path ?? "unresolved";
10940
+ const previous = patch.previous_path ? ` previous=${patch.previous_path}` : "";
10941
+ return `${patch.field}.${patch.key}: ${patch.status} ${path}${previous}`;
10942
+ });
10943
+ return [
10944
+ renderKeyValueTable([
10945
+ ["machine", result.machine_id ?? "unresolved"],
10946
+ ["project", result.project_id],
10947
+ ["trusted", String(result.trusted)],
10948
+ ["applied", String(result.applied)],
10949
+ ["manifest", result.manifest_path],
10950
+ ["warnings", result.warnings.join(", ") || "none"]
10951
+ ]),
10952
+ renderList("patches", patchLines)
10953
+ ].join(`
10954
+ `);
10955
+ }
10787
10956
  function renderFleetStatus(status) {
10788
10957
  return [
10789
10958
  renderKeyValueTable([
@@ -10951,6 +11120,23 @@ workspaceCommand.command("resolve").description("Resolve repo and open-files roo
10951
11120
  if (!result.ok && !options.json)
10952
11121
  process.exitCode = 1;
10953
11122
  });
11123
+ workspaceCommand.command("repair").description("Preview or write explicit manifest path mappings for inferred workspace roots").requiredOption("--machine <id>", "Machine identifier").requiredOption("--project <id>", "Canonical project id").option("--repo <name>", "Repository name; defaults to project id").option("--open-files-repo <name>", "Open-files repository name", "open-files").option("--workspace-root <path>", "Override the machine workspace root for resolution").option("--project-root <path>", "Explicit project root to write").option("--open-files-root <path>", "Explicit open-files root to write").option("--apply", "Write the mappings into the manifest", false).option("--allow-untrusted", "Allow writing mappings for machines not marked trusted", false).option("--no-tailscale", "Skip tailscale status probing").option("-j, --json", "Print JSON output", false).action((options) => {
11124
+ const result = repairWorkspaceManifestMappings({
11125
+ machineId: options.machine,
11126
+ projectId: options.project,
11127
+ repoName: options.repo,
11128
+ openFilesRepoName: options.openFilesRepo,
11129
+ workspaceRoot: options.workspaceRoot,
11130
+ projectRoot: options.projectRoot,
11131
+ openFilesRoot: options.openFilesRoot,
11132
+ apply: options.apply,
11133
+ allowUntrusted: options.allowUntrusted,
11134
+ includeTailscale: options.tailscale !== false
11135
+ });
11136
+ printJsonOrText(result, renderWorkspaceRepairResult(result), options.json);
11137
+ if (!result.ok && !options.json)
11138
+ process.exitCode = 1;
11139
+ });
10954
11140
  program2.command("diff").description("Show manifest differences between two machines").requiredOption("--left <id>", "Left machine identifier").option("--right <id>", "Right machine identifier (defaults to current machine)").option("-j, --json", "Print JSON output", false).action((options) => {
10955
11141
  const result = diffMachines(options.left, options.right);
10956
11142
  console.log(JSON.stringify(result, null, 2));
@@ -0,0 +1,37 @@
1
+ import { type MachineWorkspaceResolution } from "../topology.js";
2
+ export interface WorkspaceManifestRepairOptions {
3
+ machineId: string;
4
+ projectId: string;
5
+ repoName?: string;
6
+ openFilesRepoName?: string;
7
+ projectRoot?: string;
8
+ openFilesRoot?: string;
9
+ workspaceRoot?: string;
10
+ includeTailscale?: boolean;
11
+ apply?: boolean;
12
+ allowUntrusted?: boolean;
13
+ now?: Date;
14
+ }
15
+ export type WorkspaceManifestRepairStatus = "unchanged" | "would_write" | "written" | "unresolved";
16
+ export interface WorkspaceManifestRepairPatch {
17
+ field: "workspace_paths" | "open_files_roots";
18
+ key: string;
19
+ path: string | null;
20
+ previous_path: string | null;
21
+ status: WorkspaceManifestRepairStatus;
22
+ }
23
+ export interface WorkspaceManifestRepairResult {
24
+ ok: boolean;
25
+ applied: boolean;
26
+ manifest_path: string;
27
+ machine_id: string | null;
28
+ project_id: string;
29
+ repo_name: string;
30
+ open_files_repo_name: string;
31
+ trusted: boolean;
32
+ resolution: MachineWorkspaceResolution;
33
+ patches: WorkspaceManifestRepairPatch[];
34
+ warnings: string[];
35
+ }
36
+ export declare function repairWorkspaceManifestMappings(options: WorkspaceManifestRepairOptions): WorkspaceManifestRepairResult;
37
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/commands/workspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAG1F,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,6BAA6B,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;AAEnG,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,iBAAiB,GAAG,kBAAkB,CAAC;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,6BAA6B,CAAC;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,0BAA0B,CAAC;IACvC,OAAO,EAAE,4BAA4B,EAAE,CAAC;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAqED,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,8BAA8B,GAAG,6BAA6B,CAkGtH"}
package/dist/index.d.ts CHANGED
@@ -23,6 +23,7 @@ export * from "./commands/setup.js";
23
23
  export * from "./commands/ssh.js";
24
24
  export * from "./commands/sync.js";
25
25
  export * from "./commands/status.js";
26
+ export * from "./commands/workspace.js";
26
27
  export * from "./mcp/server.js";
27
28
  export { MACHINES_STORAGE_ENV, MACHINES_STORAGE_FALLBACK_ENV, MACHINES_STORAGE_MODE_ENV, MACHINES_STORAGE_MODE_FALLBACK_ENV, MACHINES_STORAGE_TABLES, STORAGE_DATABASE_ENV, STORAGE_MODE_ENV, STORAGE_TABLES, getStorageDatabaseEnv, getStorageDatabaseEnvName, getStorageDatabaseUrl, getStorageMode, getStoragePg, getStorageStatus, getSyncMetaAll, parseStorageTables, resolveTables, runStorageMigrations, storagePull, storagePush, storageSync, } from "./storage.js";
28
29
  export type { StorageEnv, StorageMode, StorageStatus, SyncMeta, SyncResult as StorageSyncResult } from "./storage.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,oBAAoB,EACpB,6BAA6B,EAC7B,yBAAyB,EACzB,kCAAkC,EAClC,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtH,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,oBAAoB,EACpB,6BAA6B,EAC7B,yBAAyB,EACzB,kCAAkC,EAClC,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,WAAW,EACX,WAAW,EACX,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtH,cAAc,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -13568,6 +13568,154 @@ function runSync(machineId, options = {}) {
13568
13568
  recordSyncRun(plan.machineId, "completed", summary);
13569
13569
  return summary;
13570
13570
  }
13571
+ // src/commands/workspace.ts
13572
+ function isRecord2(value) {
13573
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13574
+ }
13575
+ function cloneMetadata(metadata) {
13576
+ return isRecord2(metadata) ? { ...metadata } : {};
13577
+ }
13578
+ function mappedPath(metadata, field, key) {
13579
+ const container = metadata[field];
13580
+ if (!isRecord2(container))
13581
+ return null;
13582
+ const value = container[key];
13583
+ if (typeof value === "string" && value.trim())
13584
+ return value.trim();
13585
+ if (isRecord2(value)) {
13586
+ const nested = value["path"] ?? value["root"] ?? value["workspacePath"] ?? value["workspace_path"];
13587
+ if (typeof nested === "string" && nested.trim())
13588
+ return nested.trim();
13589
+ }
13590
+ return null;
13591
+ }
13592
+ function writeMappedPath(metadata, field, key, path) {
13593
+ const existing = metadata[field];
13594
+ const container = isRecord2(existing) ? { ...existing } : {};
13595
+ container[key] = path;
13596
+ metadata[field] = container;
13597
+ }
13598
+ function buildPatch(input) {
13599
+ const previous = mappedPath(input.metadata, input.field, input.key);
13600
+ if (!input.path) {
13601
+ return {
13602
+ field: input.field,
13603
+ key: input.key,
13604
+ path: null,
13605
+ previous_path: previous,
13606
+ status: "unresolved"
13607
+ };
13608
+ }
13609
+ const status = previous === input.path ? "unchanged" : input.apply ? "written" : "would_write";
13610
+ return {
13611
+ field: input.field,
13612
+ key: input.key,
13613
+ path: input.path,
13614
+ previous_path: previous,
13615
+ status
13616
+ };
13617
+ }
13618
+ function upsertMachineMetadata(manifest, machineId, metadata) {
13619
+ return {
13620
+ ...manifest,
13621
+ machines: manifest.machines.map((machine) => machine.id === machineId ? { ...machine, metadata } : machine)
13622
+ };
13623
+ }
13624
+ function repairWorkspaceManifestMappings(options) {
13625
+ const projectId = options.projectId;
13626
+ const repoName = options.repoName ?? projectId;
13627
+ const openFilesRepoName = options.openFilesRepoName ?? "open-files";
13628
+ const apply = options.apply === true;
13629
+ const resolution = resolveMachineWorkspace({
13630
+ machineId: options.machineId,
13631
+ projectId,
13632
+ repoName,
13633
+ openFilesRepoName,
13634
+ projectRoot: options.projectRoot,
13635
+ openFilesRoot: options.openFilesRoot,
13636
+ workspaceRoot: options.workspaceRoot,
13637
+ includeTailscale: options.includeTailscale,
13638
+ now: options.now
13639
+ });
13640
+ const warnings = [...resolution.warnings];
13641
+ const manifest = readManifest();
13642
+ const manifestMachineId = resolution.machine_id ?? options.machineId;
13643
+ const machine = manifest.machines.find((entry) => entry.id === manifestMachineId) ?? null;
13644
+ const trusted = resolution.machine.trust_status === "trusted" || options.allowUntrusted === true;
13645
+ if (!machine) {
13646
+ warnings.push(`manifest_machine_missing:${manifestMachineId}`);
13647
+ return {
13648
+ ok: false,
13649
+ applied: false,
13650
+ manifest_path: getManifestPath(),
13651
+ machine_id: resolution.machine_id,
13652
+ project_id: projectId,
13653
+ repo_name: repoName,
13654
+ open_files_repo_name: openFilesRepoName,
13655
+ trusted,
13656
+ resolution,
13657
+ patches: [],
13658
+ warnings
13659
+ };
13660
+ }
13661
+ const metadata = cloneMetadata(machine.metadata);
13662
+ const patches = [
13663
+ buildPatch({
13664
+ metadata,
13665
+ field: "workspace_paths",
13666
+ key: projectId,
13667
+ path: options.projectRoot ?? resolution.paths.project_root.path,
13668
+ apply
13669
+ }),
13670
+ buildPatch({
13671
+ metadata,
13672
+ field: "open_files_roots",
13673
+ key: projectId,
13674
+ path: options.openFilesRoot ?? resolution.paths.open_files_root.path,
13675
+ apply
13676
+ })
13677
+ ];
13678
+ const hasUnresolved = patches.some((patch) => patch.status === "unresolved");
13679
+ const hasWrites = patches.some((patch) => patch.status === "would_write" || patch.status === "written");
13680
+ if (apply && hasWrites && !trusted) {
13681
+ warnings.push(`manifest_repair_requires_trusted_machine:${manifestMachineId}`);
13682
+ return {
13683
+ ok: false,
13684
+ applied: false,
13685
+ manifest_path: getManifestPath(),
13686
+ machine_id: manifestMachineId,
13687
+ project_id: projectId,
13688
+ repo_name: repoName,
13689
+ open_files_repo_name: openFilesRepoName,
13690
+ trusted,
13691
+ resolution,
13692
+ patches: patches.map((patch) => patch.status === "written" ? { ...patch, status: "would_write" } : patch),
13693
+ warnings
13694
+ };
13695
+ }
13696
+ let applied = false;
13697
+ if (apply && !hasUnresolved && hasWrites) {
13698
+ for (const patch of patches) {
13699
+ if (patch.path && patch.status === "written")
13700
+ writeMappedPath(metadata, patch.field, patch.key, patch.path);
13701
+ }
13702
+ writeManifest(upsertMachineMetadata(manifest, manifestMachineId, metadata));
13703
+ applied = true;
13704
+ }
13705
+ return {
13706
+ ok: resolution.ok && !hasUnresolved && (!apply || applied || !hasWrites),
13707
+ applied,
13708
+ manifest_path: getManifestPath(),
13709
+ machine_id: manifestMachineId,
13710
+ project_id: projectId,
13711
+ repo_name: repoName,
13712
+ open_files_repo_name: openFilesRepoName,
13713
+ trusted,
13714
+ resolution,
13715
+ patches,
13716
+ warnings
13717
+ };
13718
+ }
13571
13719
  // node_modules/zod/v4/core/core.js
13572
13720
  var NEVER2 = Object.freeze({
13573
13721
  status: "aborted"
@@ -22725,6 +22873,7 @@ export {
22725
22873
  resolveMachineWorkspace,
22726
22874
  resolveMachineRoute,
22727
22875
  resolveBackupTarget,
22876
+ repairWorkspaceManifestMappings,
22728
22877
  renderDomainMapping,
22729
22878
  renderDashboardHtml,
22730
22879
  removeNotificationChannel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/machines",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Machine fleet management CLI + MCP for developers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",