@hasna/machines 0.0.30 → 0.0.32

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/mcp/index.js CHANGED
@@ -5793,6 +5793,17 @@ function runMachineCommand(machineId, command) {
5793
5793
  exitCode: result.status ?? 1
5794
5794
  };
5795
5795
  }
5796
+ function describeMachineCommandFailure(operation, result) {
5797
+ const detail = (result.stderr || result.stdout || "").trim();
5798
+ const suffix = detail ? `: ${detail}` : "";
5799
+ return `${operation} failed on ${result.machineId} via ${result.source} (exit ${result.exitCode})${suffix}`;
5800
+ }
5801
+ function requireMachineCommandSuccess(operation, result) {
5802
+ if (result.exitCode !== 0) {
5803
+ throw new Error(describeMachineCommandFailure(operation, result));
5804
+ }
5805
+ return result;
5806
+ }
5796
5807
 
5797
5808
  // src/commands/apps.ts
5798
5809
  function getPackageName(app) {
@@ -5855,7 +5866,14 @@ function buildAppSteps(machine) {
5855
5866
  }));
5856
5867
  }
5857
5868
  function resolveMachine(machineId) {
5858
- return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
5869
+ if (!machineId)
5870
+ return detectCurrentMachineManifest();
5871
+ return getManifestMachine(machineId) || {
5872
+ id: machineId,
5873
+ platform: "linux",
5874
+ workspacePath: "",
5875
+ apps: []
5876
+ };
5859
5877
  }
5860
5878
  function parseProbeOutput(app, machine, stdout) {
5861
5879
  const lines = stdout.trim().split(`
@@ -5886,27 +5904,28 @@ function buildAppsPlan(machineId) {
5886
5904
  executed: 0
5887
5905
  };
5888
5906
  }
5889
- function getAppsStatus(machineId) {
5907
+ function getAppsStatus(machineId, runner = runMachineCommand) {
5890
5908
  const machine = resolveMachine(machineId);
5909
+ const readiness = requireMachineCommandSuccess("Apps status readiness check", runner(machine.id, "true"));
5891
5910
  const apps = (machine.apps || []).map((app) => {
5892
- const probe = runMachineCommand(machine.id, buildAppProbeCommand(machine, app));
5911
+ const probe = requireMachineCommandSuccess(`App probe ${app.name}`, runner(machine.id, buildAppProbeCommand(machine, app)));
5893
5912
  return parseProbeOutput(app, machine, probe.stdout);
5894
5913
  });
5895
5914
  return {
5896
5915
  machineId: machine.id,
5897
- source: apps.length > 0 ? runMachineCommand(machine.id, "true").source : machine.id === detectCurrentMachineManifest().id ? "local" : runMachineCommand(machine.id, "true").source,
5916
+ source: readiness.source,
5898
5917
  apps
5899
5918
  };
5900
5919
  }
5901
- function diffApps(machineId) {
5902
- const status = getAppsStatus(machineId);
5920
+ function diffApps(machineId, runner = runMachineCommand) {
5921
+ const status = getAppsStatus(machineId, runner);
5903
5922
  return {
5904
5923
  ...status,
5905
5924
  missing: status.apps.filter((app) => !app.installed).map((app) => app.name),
5906
5925
  installed: status.apps.filter((app) => app.installed).map((app) => app.name)
5907
5926
  };
5908
5927
  }
5909
- function runAppsInstall(machineId, options = {}) {
5928
+ function runAppsInstall(machineId, options = {}, runner = runMachineCommand) {
5910
5929
  const plan = buildAppsPlan(machineId);
5911
5930
  if (!options.apply)
5912
5931
  return plan;
@@ -5915,14 +5934,7 @@ function runAppsInstall(machineId, options = {}) {
5915
5934
  }
5916
5935
  let executed = 0;
5917
5936
  for (const step of plan.steps) {
5918
- const result = Bun.spawnSync(["bash", "-lc", step.command], {
5919
- stdout: "pipe",
5920
- stderr: "pipe",
5921
- env: process.env
5922
- });
5923
- if (result.exitCode !== 0) {
5924
- throw new Error(`App install failed (${step.id}): ${result.stderr.toString().trim()}`);
5925
- }
5937
+ requireMachineCommandSuccess(`App install ${step.id}`, runner(plan.machineId, step.command));
5926
5938
  executed += 1;
5927
5939
  }
5928
5940
  return {
@@ -6182,7 +6194,13 @@ function buildInstallSteps(machine, tools) {
6182
6194
  }));
6183
6195
  }
6184
6196
  function resolveMachine2(machineId) {
6185
- return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
6197
+ if (!machineId)
6198
+ return detectCurrentMachineManifest();
6199
+ return getManifestMachine(machineId) || {
6200
+ id: machineId,
6201
+ platform: "linux",
6202
+ workspacePath: ""
6203
+ };
6186
6204
  }
6187
6205
  function buildProbeCommand(tool) {
6188
6206
  const binary = getToolBinary(tool);
@@ -6209,25 +6227,28 @@ function buildClaudeInstallPlan(machineId, tools) {
6209
6227
  executed: 0
6210
6228
  };
6211
6229
  }
6212
- function getClaudeCliStatus(machineId, tools) {
6230
+ function getClaudeCliStatus(machineId, tools, runner = runMachineCommand) {
6213
6231
  const machine = resolveMachine2(machineId);
6214
6232
  const normalizedTools = normalizeTools(tools);
6215
- const route = runMachineCommand(machine.id, "true").source;
6233
+ const route = requireMachineCommandSuccess("AI CLI status readiness check", runner(machine.id, "true")).source;
6216
6234
  return {
6217
6235
  machineId: machine.id,
6218
6236
  source: route,
6219
- tools: normalizedTools.map((tool) => parseProbe(tool, runMachineCommand(machine.id, buildProbeCommand(tool)).stdout))
6237
+ tools: normalizedTools.map((tool) => {
6238
+ const result = requireMachineCommandSuccess(`AI CLI probe ${tool}`, runner(machine.id, buildProbeCommand(tool)));
6239
+ return parseProbe(tool, result.stdout);
6240
+ })
6220
6241
  };
6221
6242
  }
6222
- function diffClaudeCli(machineId, tools) {
6223
- const status = getClaudeCliStatus(machineId, tools);
6243
+ function diffClaudeCli(machineId, tools, runner = runMachineCommand) {
6244
+ const status = getClaudeCliStatus(machineId, tools, runner);
6224
6245
  return {
6225
6246
  ...status,
6226
6247
  missing: status.tools.filter((tool) => !tool.installed).map((tool) => tool.tool),
6227
6248
  installed: status.tools.filter((tool) => tool.installed).map((tool) => tool.tool)
6228
6249
  };
6229
6250
  }
6230
- function runClaudeInstall(machineId, tools, options = {}) {
6251
+ function runClaudeInstall(machineId, tools, options = {}, runner = runMachineCommand) {
6231
6252
  const plan = buildClaudeInstallPlan(machineId, tools);
6232
6253
  if (!options.apply)
6233
6254
  return plan;
@@ -6236,14 +6257,7 @@ function runClaudeInstall(machineId, tools, options = {}) {
6236
6257
  }
6237
6258
  let executed = 0;
6238
6259
  for (const step of plan.steps) {
6239
- const result = Bun.spawnSync(["bash", "-lc", step.command], {
6240
- stdout: "pipe",
6241
- stderr: "pipe",
6242
- env: process.env
6243
- });
6244
- if (result.exitCode !== 0) {
6245
- throw new Error(`AI CLI install failed (${step.id}): ${result.stderr.toString().trim()}`);
6246
- }
6260
+ requireMachineCommandSuccess(`AI CLI install ${step.id}`, runner(plan.machineId, step.command));
6247
6261
  executed += 1;
6248
6262
  }
6249
6263
  return {
@@ -6287,7 +6301,10 @@ function buildInstallSteps2(machine) {
6287
6301
  ];
6288
6302
  }
6289
6303
  function buildTailscaleInstallPlan(machineId) {
6290
- const machine = (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
6304
+ const machine = machineId ? getManifestMachine(machineId) : detectCurrentMachineManifest();
6305
+ if (!machine) {
6306
+ throw new Error(`Machine not found in manifest: ${machineId}`);
6307
+ }
6291
6308
  return {
6292
6309
  machineId: machine.id,
6293
6310
  mode: "plan",
@@ -6295,7 +6312,7 @@ function buildTailscaleInstallPlan(machineId) {
6295
6312
  executed: 0
6296
6313
  };
6297
6314
  }
6298
- function runTailscaleInstall(machineId, options = {}) {
6315
+ function runTailscaleInstall(machineId, options = {}, runner = runMachineCommand) {
6299
6316
  const plan = buildTailscaleInstallPlan(machineId);
6300
6317
  if (!options.apply)
6301
6318
  return plan;
@@ -6304,14 +6321,7 @@ function runTailscaleInstall(machineId, options = {}) {
6304
6321
  }
6305
6322
  let executed = 0;
6306
6323
  for (const step of plan.steps) {
6307
- const result = Bun.spawnSync(["bash", "-lc", step.command], {
6308
- stdout: "pipe",
6309
- stderr: "pipe",
6310
- env: process.env
6311
- });
6312
- if (result.exitCode !== 0) {
6313
- throw new Error(`Tailscale install failed (${step.id}): ${result.stderr.toString().trim()}`);
6314
- }
6324
+ requireMachineCommandSuccess(`Tailscale install ${step.id}`, runner(plan.machineId, step.command));
6315
6325
  executed += 1;
6316
6326
  }
6317
6327
  return {
@@ -6967,6 +6977,9 @@ function buildSetupPlan(machineId) {
6967
6977
  const manifest = readManifest();
6968
6978
  const currentMachineId = getLocalMachineId();
6969
6979
  const selected = machineId ? manifest.machines.find((machine) => machine.id === machineId) : manifest.machines.find((machine) => machine.id === currentMachineId);
6980
+ if (machineId && !selected) {
6981
+ throw new Error(`Machine not found in manifest: ${machineId}`);
6982
+ }
6970
6983
  const target = selected || {
6971
6984
  id: currentMachineId,
6972
6985
  platform: "linux",
@@ -6980,7 +6993,7 @@ function buildSetupPlan(machineId) {
6980
6993
  executed: 0
6981
6994
  };
6982
6995
  }
6983
- function runSetup(machineId, options = {}) {
6996
+ function runSetup(machineId, options = {}, runner = runMachineCommand) {
6984
6997
  const plan = buildSetupPlan(machineId);
6985
6998
  if (!options.apply) {
6986
6999
  return plan;
@@ -6990,18 +7003,17 @@ function runSetup(machineId, options = {}) {
6990
7003
  }
6991
7004
  let executed = 0;
6992
7005
  for (const step of plan.steps) {
6993
- const result = Bun.spawnSync(["bash", "-lc", step.command], {
6994
- stdout: "pipe",
6995
- stderr: "pipe",
6996
- env: process.env
6997
- });
7006
+ const result = runner(plan.machineId, step.command);
6998
7007
  if (result.exitCode !== 0) {
6999
7008
  recordSetupRun(plan.machineId, "failed", {
7000
7009
  executed,
7001
7010
  failedStep: step,
7002
- stderr: result.stderr.toString()
7011
+ stderr: result.stderr,
7012
+ stdout: result.stdout,
7013
+ exitCode: result.exitCode,
7014
+ source: result.source
7003
7015
  });
7004
- throw new Error(`Setup step failed (${step.id}): ${result.stderr.toString().trim()}`);
7016
+ throw new Error(describeMachineCommandFailure(`Setup step ${step.id}`, result));
7005
7017
  }
7006
7018
  executed += 1;
7007
7019
  }
@@ -7022,16 +7034,17 @@ function quote4(value) {
7022
7034
  return `'${value.replace(/'/g, `'\\''`)}'`;
7023
7035
  }
7024
7036
  function packageCheckCommand(machine, packageName, manager = machine.platform === "macos" ? "brew" : "apt") {
7037
+ const quotedPackageName = quote4(packageName);
7025
7038
  if (manager === "bun") {
7026
- return `bun pm ls -g --all | grep -F ${quote4(packageName)}`;
7039
+ return `if bun pm ls -g --all 2>/dev/null | grep -F ${quotedPackageName} >/dev/null 2>&1; then printf 'installed=1\\n'; else printf 'installed=0\\n'; fi`;
7027
7040
  }
7028
7041
  if (manager === "brew") {
7029
- return `brew list --versions ${quote4(packageName)}`;
7042
+ return `if brew list --versions ${quotedPackageName} >/dev/null 2>&1; then printf 'installed=1\\n'; else printf 'installed=0\\n'; fi`;
7030
7043
  }
7031
7044
  if (manager === "apt") {
7032
- return `dpkg -s ${quote4(packageName)} >/dev/null 2>&1`;
7045
+ return `if dpkg -s ${quotedPackageName} >/dev/null 2>&1; then printf 'installed=1\\n'; else printf 'installed=0\\n'; fi`;
7033
7046
  }
7034
- return `command -v ${quote4(packageName)} >/dev/null 2>&1`;
7047
+ return `if command -v ${quotedPackageName} >/dev/null 2>&1; then printf 'installed=1\\n'; else printf 'installed=0\\n'; fi`;
7035
7048
  }
7036
7049
  function packageInstallCommand(machine, packageName, manager = machine.platform === "macos" ? "brew" : "apt") {
7037
7050
  if (manager === "bun") {
@@ -7045,15 +7058,15 @@ function packageInstallCommand(machine, packageName, manager = machine.platform
7045
7058
  }
7046
7059
  return packageName;
7047
7060
  }
7048
- function detectPackageActions(machine) {
7061
+ function detectPackageActions(machine, runner) {
7049
7062
  return (machine.packages || []).map((pkg, index) => {
7050
7063
  const manager = pkg.manager || (machine.platform === "macos" ? "brew" : "apt");
7051
- const check2 = Bun.spawnSync(["bash", "-lc", packageCheckCommand(machine, pkg.name, manager)], {
7052
- stdout: "ignore",
7053
- stderr: "ignore",
7054
- env: process.env
7055
- });
7056
- const installed = check2.exitCode === 0;
7064
+ const check2 = runner(machine.id, packageCheckCommand(machine, pkg.name, manager));
7065
+ if (check2.exitCode !== 0) {
7066
+ throw new Error(describeMachineCommandFailure(`Sync package probe ${pkg.name}`, check2));
7067
+ }
7068
+ const installed = check2.stdout.split(`
7069
+ `).some((line) => line.trim() === "installed=1");
7057
7070
  return {
7058
7071
  id: `package-${index + 1}`,
7059
7072
  title: `${installed ? "Package present" : "Install package"} ${pkg.name}`,
@@ -7064,6 +7077,9 @@ function detectPackageActions(machine) {
7064
7077
  });
7065
7078
  }
7066
7079
  function detectFileActions(machine) {
7080
+ if ((machine.files || []).length > 0 && resolveMachineCommand(machine.id, "true").source !== "local") {
7081
+ throw new Error(`Remote file sync planning is not supported for ${machine.id}; refusing to inspect or apply local paths as remote state.`);
7082
+ }
7067
7083
  return (machine.files || []).map((file, index) => {
7068
7084
  const sourceExists = existsSync8(file.source);
7069
7085
  const targetExists = existsSync8(file.target);
@@ -7087,17 +7103,20 @@ function detectFileActions(machine) {
7087
7103
  };
7088
7104
  });
7089
7105
  }
7090
- function buildSyncPlan(machineId) {
7106
+ function buildSyncPlan(machineId, runner = runMachineCommand) {
7091
7107
  const manifest = readManifest();
7092
7108
  const currentMachineId = getLocalMachineId();
7093
7109
  const selected = machineId ? manifest.machines.find((machine) => machine.id === machineId) : manifest.machines.find((machine) => machine.id === currentMachineId);
7110
+ if (machineId && !selected) {
7111
+ throw new Error(`Machine not found in manifest: ${machineId}`);
7112
+ }
7094
7113
  const target = selected || {
7095
7114
  id: currentMachineId,
7096
7115
  platform: "linux",
7097
7116
  workspacePath: `${homedir6()}/workspace`
7098
7117
  };
7099
7118
  const actions = [
7100
- ...detectPackageActions(target),
7119
+ ...detectPackageActions(target, runner),
7101
7120
  ...detectFileActions(target)
7102
7121
  ];
7103
7122
  return {
@@ -7127,8 +7146,8 @@ function applyFileAction(command) {
7127
7146
  symlinkSync(sourcePath, targetPath);
7128
7147
  }
7129
7148
  }
7130
- function runSync(machineId, options = {}) {
7131
- const plan = buildSyncPlan(machineId);
7149
+ function runSync(machineId, options = {}, runner = runMachineCommand) {
7150
+ const plan = buildSyncPlan(machineId, runner);
7132
7151
  if (!options.apply) {
7133
7152
  return plan;
7134
7153
  }
@@ -7142,18 +7161,17 @@ function runSync(machineId, options = {}) {
7142
7161
  if (action.kind === "file") {
7143
7162
  applyFileAction(action.command);
7144
7163
  } else {
7145
- const result = Bun.spawnSync(["bash", "-lc", action.command], {
7146
- stdout: "pipe",
7147
- stderr: "pipe",
7148
- env: process.env
7149
- });
7164
+ const result = runner(plan.machineId, action.command);
7150
7165
  if (result.exitCode !== 0) {
7151
7166
  recordSyncRun(plan.machineId, "failed", {
7152
7167
  executed,
7153
7168
  failedAction: action,
7154
- stderr: result.stderr.toString()
7169
+ stderr: result.stderr,
7170
+ stdout: result.stdout,
7171
+ exitCode: result.exitCode,
7172
+ source: result.source
7155
7173
  });
7156
- throw new Error(`Sync action failed (${action.id}): ${result.stderr.toString().trim()}`);
7174
+ throw new Error(describeMachineCommandFailure(`Sync action ${action.id}`, result));
7157
7175
  }
7158
7176
  }
7159
7177
  executed += 1;
package/dist/remote.d.ts CHANGED
@@ -5,9 +5,12 @@ export interface MachineCommandResult {
5
5
  stderr: string;
6
6
  exitCode: number;
7
7
  }
8
+ export type MachineCommandRunner = (machineId: string, command: string) => MachineCommandResult;
8
9
  export declare function resolveMachineCommand(machineId: string, command: string, localMachineId?: string): {
9
10
  source: MachineCommandResult["source"];
10
11
  shellCommand: string;
11
12
  };
12
13
  export declare function runMachineCommand(machineId: string, command: string): MachineCommandResult;
14
+ export declare function describeMachineCommandFailure(operation: string, result: MachineCommandResult): string;
15
+ export declare function requireMachineCommandSuccess(operation: string, result: MachineCommandResult): MachineCommandResult;
13
16
  //# sourceMappingURL=remote.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAC;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAaD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,SAAsB,GAAG;IAAE,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAiBhL;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAc1F"}
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAC;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,oBAAoB,CAAC;AAahG,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,SAAsB,GAAG;IAAE,MAAM,EAAE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAiBhL;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAc1F;AAED,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAIrG;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,oBAAoB,CAKlH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/machines",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "Machine fleet management CLI + MCP for developers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "repository": {
59
59
  "type": "git",
60
- "url": "https://github.com/hasna/machines.git"
60
+ "url": "git+https://github.com/hasna/machines.git"
61
61
  },
62
62
  "homepage": "https://github.com/hasna/machines",
63
63
  "bugs": {