@hasna/machines 0.0.24 → 0.0.25

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
@@ -118,6 +118,32 @@ machines topology --no-tailscale --json
118
118
  machines route --machine spark01 --json
119
119
  ```
120
120
 
121
+ ## Screen sharing
122
+
123
+ Open Screen Sharing (VNC) to any machine using its best live route — no stale
124
+ IP bookmarks. The route resolver picks the current LAN address or Tailscale name
125
+ automatically, so it keeps working even when DHCP rotates a machine's IP.
126
+
127
+ ```bash
128
+ machines screen machine005 # open Screen Sharing.app → vnc://<user>@<live-route>
129
+ machines screen machine005 --print # print the vnc:// URL instead of opening
130
+ machines screen machine005 --json # full resolution detail (route, confidence, user)
131
+ machines screen --all # open every reachable machine
132
+ machines screen --all --print # list resolved vnc:// URLs for the whole fleet
133
+ ```
134
+
135
+ Enable Remote Management / Screen Sharing on a fresh macOS machine over SSH
136
+ (kickstart + SRP + legacy VNC password so user-password auth works from
137
+ Screen Sharing.app and Apple Remote Desktop):
138
+
139
+ ```bash
140
+ machines screen-enable --machine machine005 --user jo --vnc-password steaua17
141
+ machines screen-enable --machine machine005 --user jo --print # show the SSH command, don't run it
142
+ ```
143
+
144
+ `--vnc-password` is truncated to 8 characters by the legacy VNC protocol. The
145
+ user comes from the manifest (`metadata.user`) when present, or `--user`.
146
+
121
147
  Consumers that need repo paths can resolve trust-aware workspace mappings
122
148
  without importing the full machines app:
123
149
 
@@ -979,7 +979,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
979
979
  this._exitCallback = (err) => {
980
980
  if (err.code !== "commander.executeSubCommandAsync") {
981
981
  throw err;
982
- } else {}
982
+ }
983
983
  };
984
984
  }
985
985
  return this;
package/dist/cli/index.js CHANGED
@@ -993,7 +993,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
993
993
  this._exitCallback = (err) => {
994
994
  if (err.code !== "commander.executeSubCommandAsync") {
995
995
  throw err;
996
- } else {}
996
+ }
997
997
  };
998
998
  }
999
999
  return this;
@@ -2601,6 +2601,9 @@ var {
2601
2601
  Help
2602
2602
  } = import__.default;
2603
2603
 
2604
+ // src/cli/index.ts
2605
+ import { execFileSync } from "child_process";
2606
+
2604
2607
  // node_modules/chalk/source/vendor/ansi-styles/index.js
2605
2608
  var ANSI_BACKGROUND_OFFSET = 10;
2606
2609
  var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
@@ -7165,6 +7168,7 @@ function writeManifest(manifest, path = getManifestPath()) {
7165
7168
  generatedAt: new Date().toISOString(),
7166
7169
  machines: normalizeMachines(manifest.machines)
7167
7170
  };
7171
+ fleetSchema.parse(payload);
7168
7172
  writeFileSync(path, `${JSON.stringify(payload, null, 2)}
7169
7173
  `, "utf8");
7170
7174
  return path;
@@ -7200,9 +7204,10 @@ function manifestList() {
7200
7204
  return readManifest();
7201
7205
  }
7202
7206
  function manifestAdd(machine) {
7207
+ const validatedMachine = machineSchema.parse(machine);
7203
7208
  const manifest = readManifest();
7204
- const nextMachines = manifest.machines.filter((entry) => entry.id !== machine.id);
7205
- nextMachines.push(machine);
7209
+ const nextMachines = manifest.machines.filter((entry) => entry.id !== validatedMachine.id);
7210
+ nextMachines.push(validatedMachine);
7206
7211
  const nextManifest = { ...manifest, machines: nextMachines };
7207
7212
  writeManifest(nextManifest);
7208
7213
  return nextManifest;
@@ -7227,6 +7232,7 @@ function manifestValidate() {
7227
7232
  }
7228
7233
 
7229
7234
  // src/commands/setup.ts
7235
+ import { homedir as homedir2 } from "os";
7230
7236
  init_db();
7231
7237
  function quote(value) {
7232
7238
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -7275,11 +7281,11 @@ function buildPackageSteps(machine) {
7275
7281
  const manager = pkg.manager || (machine.platform === "macos" ? "brew" : "apt");
7276
7282
  let command = pkg.name;
7277
7283
  if (manager === "bun") {
7278
- command = `bun install -g ${pkg.name}`;
7284
+ command = `bun install -g ${quote(pkg.name)}`;
7279
7285
  } else if (manager === "brew") {
7280
- command = `brew install ${pkg.name}`;
7286
+ command = `brew install ${quote(pkg.name)}`;
7281
7287
  } else if (manager === "apt") {
7282
- command = `sudo apt-get install -y ${pkg.name}`;
7288
+ command = `sudo apt-get install -y ${quote(pkg.name)}`;
7283
7289
  }
7284
7290
  return {
7285
7291
  id: `package-${index + 1}`,
@@ -7297,7 +7303,7 @@ function buildSetupPlan(machineId) {
7297
7303
  const target = selected || {
7298
7304
  id: currentMachineId,
7299
7305
  platform: "linux",
7300
- workspacePath: "~/workspace"
7306
+ workspacePath: `${homedir2()}/workspace`
7301
7307
  };
7302
7308
  const steps = [...buildBaseSteps(target), ...buildPackageSteps(target)];
7303
7309
  return {
@@ -7343,7 +7349,7 @@ function runSetup(machineId, options = {}) {
7343
7349
  }
7344
7350
 
7345
7351
  // src/commands/backup.ts
7346
- import { homedir as homedir2 } from "os";
7352
+ import { homedir as homedir3, hostname as hostname3 } from "os";
7347
7353
  import { join as join3 } from "path";
7348
7354
  var MACHINES_BACKUP_BUCKET_ENV = "HASNA_MACHINES_S3_BUCKET";
7349
7355
  var MACHINES_BACKUP_BUCKET_FALLBACK_ENV = "MACHINES_S3_BUCKET";
@@ -7392,7 +7398,7 @@ function resolveBackupTarget(options = {}) {
7392
7398
  };
7393
7399
  }
7394
7400
  function defaultBackupSources() {
7395
- const home = homedir2();
7401
+ const home = homedir3();
7396
7402
  return [
7397
7403
  join3(home, ".hasna"),
7398
7404
  join3(home, ".ssh"),
@@ -7401,7 +7407,7 @@ function defaultBackupSources() {
7401
7407
  }
7402
7408
  function buildBackupPlan(bucket, prefix) {
7403
7409
  const target = resolveBackupTarget({ bucket, prefix });
7404
- const archivePath = join3(homedir2(), ".hasna", "machines", "backup.tgz");
7410
+ const archivePath = join3(homedir3(), ".hasna", "machines", "backup.tgz");
7405
7411
  const sources = defaultBackupSources();
7406
7412
  const steps = [
7407
7413
  {
@@ -7413,7 +7419,7 @@ function buildBackupPlan(bucket, prefix) {
7413
7419
  {
7414
7420
  id: "backup-upload",
7415
7421
  title: "Upload archive to S3",
7416
- command: `aws s3 cp ${quote2(archivePath)} s3://${target.bucket}/${target.prefix}/$(hostname)-backup.tgz`,
7422
+ command: `aws s3 cp ${quote2(archivePath)} ${quote2(`s3://${target.bucket}/${target.prefix}/${hostname3()}-backup.tgz`)}`,
7417
7423
  manager: "custom"
7418
7424
  }
7419
7425
  ];
@@ -7452,13 +7458,13 @@ function runBackup(bucket, prefix, options = {}) {
7452
7458
  }
7453
7459
 
7454
7460
  // src/commands/cert.ts
7455
- import { homedir as homedir3, platform as platform2 } from "os";
7461
+ import { homedir as homedir4, platform as platform2 } from "os";
7456
7462
  import { join as join4 } from "path";
7457
7463
  function quote3(value) {
7458
7464
  return `'${value.replace(/'/g, `'\\''`)}'`;
7459
7465
  }
7460
7466
  function certDir() {
7461
- return join4(homedir3(), ".hasna", "machines", "certs");
7467
+ return join4(homedir4(), ".hasna", "machines", "certs");
7462
7468
  }
7463
7469
  function buildCertPlan(domains) {
7464
7470
  if (domains.length === 0) {
@@ -7618,12 +7624,12 @@ function diffMachines(leftMachineId, rightMachineId) {
7618
7624
  // src/remote.ts
7619
7625
  init_db();
7620
7626
  import { spawnSync as spawnSync2 } from "child_process";
7621
- import { hostname as hostname4 } from "os";
7627
+ import { hostname as hostname5 } from "os";
7622
7628
 
7623
7629
  // src/topology.ts
7624
7630
  init_db();
7625
7631
  import { existsSync as existsSync5 } from "fs";
7626
- import { arch as arch2, hostname as hostname3, platform as platform3, userInfo as userInfo2 } from "os";
7632
+ import { arch as arch2, hostname as hostname4, platform as platform3, userInfo as userInfo2 } from "os";
7627
7633
  import { spawnSync } from "child_process";
7628
7634
  init_paths();
7629
7635
  var MACHINES_CONSUMER_CONTRACT_VERSION = 1;
@@ -7847,7 +7853,7 @@ function discoverMachineTopology(options = {}) {
7847
7853
  capabilities: getMachinesConsumerCapabilities(),
7848
7854
  generated_at: now.toISOString(),
7849
7855
  local_machine_id: localMachineId,
7850
- local_hostname: hostname3(),
7856
+ local_hostname: hostname4(),
7851
7857
  current_platform: normalizePlatform2(),
7852
7858
  manifest_path_known: existsSync5(getManifestPath()),
7853
7859
  machines,
@@ -7872,7 +7878,7 @@ function routeTargetMatches(machine, requested) {
7872
7878
  }
7873
7879
  function findRouteMachine(topology, requestedMachineId) {
7874
7880
  const requested = normalizeMachineAlias(requestedMachineId);
7875
- if (requested === "local" || requested === "localhost" || requested === normalizeMachineAlias(hostname3()) || requested === normalizeMachineAlias(topology.local_machine_id)) {
7881
+ if (requested === "local" || requested === "localhost" || requested === normalizeMachineAlias(hostname4()) || requested === normalizeMachineAlias(topology.local_machine_id)) {
7876
7882
  return {
7877
7883
  machine: topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? null,
7878
7884
  matchedBy: "local_alias"
@@ -8497,7 +8503,7 @@ function shellQuote3(value) {
8497
8503
  return `'${value.replace(/'/g, "'\\''")}'`;
8498
8504
  }
8499
8505
  function machineIsLocal(machineId, localMachineId) {
8500
- return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname4();
8506
+ return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname5();
8501
8507
  }
8502
8508
  function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
8503
8509
  if (machineIsLocal(machineId, localMachineId)) {
@@ -8549,20 +8555,21 @@ function shellQuote4(value) {
8549
8555
  }
8550
8556
  function buildAppCommand(machine, app) {
8551
8557
  const packageName = getPackageName(app);
8558
+ const quotedPackageName = shellQuote4(packageName);
8552
8559
  const manager = getAppManager(machine, app);
8553
8560
  if (manager === "custom") {
8554
8561
  return packageName;
8555
8562
  }
8556
8563
  if (machine.platform === "macos") {
8557
8564
  if (manager === "cask") {
8558
- return `brew install --cask ${packageName}`;
8565
+ return `brew install --cask ${quotedPackageName}`;
8559
8566
  }
8560
- return `brew install ${packageName}`;
8567
+ return `brew install ${quotedPackageName}`;
8561
8568
  }
8562
8569
  if (machine.platform === "windows") {
8563
- return `winget install ${packageName}`;
8570
+ return `winget install ${quotedPackageName}`;
8564
8571
  }
8565
- return `sudo apt-get install -y ${packageName}`;
8572
+ return `sudo apt-get install -y ${quotedPackageName}`;
8566
8573
  }
8567
8574
  function buildAppProbeCommand(machine, app) {
8568
8575
  const packageName = shellQuote4(getPackageName(app));
@@ -9145,8 +9152,55 @@ function listPorts(machineId) {
9145
9152
  };
9146
9153
  }
9147
9154
 
9155
+ // src/commands/screen.ts
9156
+ function shellQuote6(value) {
9157
+ return `'${value.replace(/'/g, "'\\''")}'`;
9158
+ }
9159
+ function splitTarget(target) {
9160
+ const at = target.indexOf("@");
9161
+ if (at === -1)
9162
+ return [null, target];
9163
+ return [target.slice(0, at), target.slice(at + 1)];
9164
+ }
9165
+ function resolveScreenTarget(machineId, options = {}) {
9166
+ const resolved = resolveMachineRoute(machineId, options);
9167
+ if (!resolved.ok || !resolved.target) {
9168
+ throw new Error(`Machine route not found: ${machineId}`);
9169
+ }
9170
+ if (resolved.route === "unknown") {
9171
+ throw new Error(`Machine route is not reachable for screen sharing: ${machineId}`);
9172
+ }
9173
+ let [user, host] = splitTarget(resolved.target);
9174
+ if (!user) {
9175
+ const topology = options.topology ?? discoverMachineTopology(options);
9176
+ const entry = topology.machines.find((m) => m.machine_id === (resolved.machine_id ?? machineId));
9177
+ user = entry?.user ?? null;
9178
+ }
9179
+ const url = user ? `vnc://${user}@${host}` : `vnc://${host}`;
9180
+ return {
9181
+ machineId: resolved.machine_id ?? machineId,
9182
+ user,
9183
+ host,
9184
+ url,
9185
+ route: resolved.route,
9186
+ confidence: resolved.confidence,
9187
+ warnings: resolved.warnings
9188
+ };
9189
+ }
9190
+ function buildScreenEnableRemoteCommand(user, vncPassword) {
9191
+ const kickstart = "/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart";
9192
+ const lines = [
9193
+ `dseditgroup -o edit -a ${shellQuote6(user)} -t user com.apple.access_screensharing 2>/dev/null || true`,
9194
+ "defaults write /Library/Preferences/com.apple.RemoteManagement AllowSRPForNetworkNodes -bool true",
9195
+ `${kickstart} -configure -clientopts -setvnclegacy -vnclegacy yes -setvncpw -vncpw ${shellQuote6(vncPassword)}`,
9196
+ `${kickstart} -activate -configure -access -on -users ${shellQuote6(user)} -privs -all -restart -agent -menu`
9197
+ ];
9198
+ return lines.join(" && ");
9199
+ }
9200
+
9148
9201
  // src/commands/sync.ts
9149
9202
  import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
9203
+ import { homedir as homedir5 } from "os";
9150
9204
  init_paths();
9151
9205
  init_db();
9152
9206
  function quote4(value) {
@@ -9166,13 +9220,13 @@ function packageCheckCommand(machine, packageName, manager = machine.platform ==
9166
9220
  }
9167
9221
  function packageInstallCommand(machine, packageName, manager = machine.platform === "macos" ? "brew" : "apt") {
9168
9222
  if (manager === "bun") {
9169
- return `bun install -g ${packageName}`;
9223
+ return `bun install -g ${quote4(packageName)}`;
9170
9224
  }
9171
9225
  if (manager === "brew") {
9172
- return `brew install ${packageName}`;
9226
+ return `brew install ${quote4(packageName)}`;
9173
9227
  }
9174
9228
  if (manager === "apt") {
9175
- return `sudo apt-get install -y ${packageName}`;
9229
+ return `sudo apt-get install -y ${quote4(packageName)}`;
9176
9230
  }
9177
9231
  return packageName;
9178
9232
  }
@@ -9225,7 +9279,7 @@ function buildSyncPlan(machineId) {
9225
9279
  const target = selected || {
9226
9280
  id: currentMachineId,
9227
9281
  platform: "linux",
9228
- workspacePath: "~/workspace"
9282
+ workspacePath: `${homedir5()}/workspace`
9229
9283
  };
9230
9284
  const actions = [
9231
9285
  ...detectPackageActions(target),
@@ -9492,7 +9546,7 @@ var DEFAULT_COMMANDS = [
9492
9546
  function defaultPackages() {
9493
9547
  return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
9494
9548
  }
9495
- function shellQuote6(value) {
9549
+ function shellQuote7(value) {
9496
9550
  return `'${value.replace(/'/g, "'\\''")}'`;
9497
9551
  }
9498
9552
  function commandId(value) {
@@ -9543,7 +9597,7 @@ function defaultRunner2(machineId, command) {
9543
9597
  return runMachineCommand(machineId, command);
9544
9598
  }
9545
9599
  function inspectCommand(machineId, spec, runner) {
9546
- const command = shellQuote6(spec.command);
9600
+ const command = shellQuote7(spec.command);
9547
9601
  const versionArgs = spec.versionArgs ?? "--version";
9548
9602
  const script = [
9549
9603
  `cmd=${command}`,
@@ -9572,7 +9626,7 @@ function fieldCommand(field) {
9572
9626
  }
9573
9627
  function inspectWorkspace(machineId, spec, runner) {
9574
9628
  const script = [
9575
- `path=${shellQuote6(spec.path)}`,
9629
+ `path=${shellQuote7(spec.path)}`,
9576
9630
  'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
9577
9631
  'pkg="$path/package.json"',
9578
9632
  'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
@@ -11318,6 +11372,11 @@ manifestCommand.command("add").description("Add or replace a machine in the flee
11318
11372
  console.log(JSON.stringify(manifestAdd(machine2), null, 2));
11319
11373
  return;
11320
11374
  }
11375
+ for (const key of ["id", "platform", "workspacePath"]) {
11376
+ if (typeof options[key] !== "string" || !options[key].trim()) {
11377
+ throw new Error(`manifest add requires --${key === "workspacePath" ? "workspace-path" : key} unless --from-stdin is used`);
11378
+ }
11379
+ }
11321
11380
  const packages = Array.isArray(options["package"]) ? options["package"].map((name) => ({ name: String(name) })) : undefined;
11322
11381
  const files = Array.isArray(options["file"]) ? options["file"].map((value) => {
11323
11382
  const [source, target, mode] = String(value).split(":");
@@ -11623,6 +11682,68 @@ program2.command("ssh").description("Choose the best SSH route for a machine").r
11623
11682
  }
11624
11683
  console.log(buildSshCommand(options.machine, options.cmd));
11625
11684
  });
11685
+ program2.command("screen").description("Open Screen Sharing (VNC) to a machine using its best live route").argument("[machine]", "Machine identifier").option("--machine <id>", "Machine identifier (alternative to positional arg)").option("--all", "Open every reachable machine", false).option("--print", "Print the vnc:// URL instead of opening it", false).option("-j, --json", "Print JSON output", false).action((machineArg, options) => {
11686
+ if (options.all) {
11687
+ const topology = discoverMachineTopology();
11688
+ const results = topology.machines.map((m) => {
11689
+ try {
11690
+ const resolved2 = resolveScreenTarget(m.machine_id, { topology });
11691
+ return { machine: m.machine_id, ok: true, url: resolved2.url, route: resolved2.route };
11692
+ } catch (error) {
11693
+ return { machine: m.machine_id, ok: false, error: error instanceof Error ? error.message : String(error) };
11694
+ }
11695
+ });
11696
+ if (options.json) {
11697
+ console.log(JSON.stringify(results, null, 2));
11698
+ return;
11699
+ }
11700
+ for (const r of results) {
11701
+ if (r.ok && r.url) {
11702
+ if (!options.print)
11703
+ execFileSync("open", [r.url], { stdio: "ignore" });
11704
+ console.log(`${r.ok ? "\u2713" : "\u2717"} ${r.machine.padEnd(14)} ${r.url ?? r.error}`);
11705
+ } else {
11706
+ console.log(`\u2717 ${r.machine.padEnd(14)} ${r.error}`);
11707
+ }
11708
+ }
11709
+ return;
11710
+ }
11711
+ const machineId = machineArg ?? options.machine;
11712
+ if (!machineId) {
11713
+ console.error("Provide a machine: machines screen <id> (or --all)");
11714
+ process.exitCode = 1;
11715
+ return;
11716
+ }
11717
+ const resolved = resolveScreenTarget(machineId);
11718
+ if (options.json) {
11719
+ console.log(JSON.stringify(resolved, null, 2));
11720
+ return;
11721
+ }
11722
+ if (options.print) {
11723
+ console.log(resolved.url);
11724
+ return;
11725
+ }
11726
+ execFileSync("open", [resolved.url], { stdio: "ignore" });
11727
+ console.log(`Opening Screen Sharing \u2192 ${resolved.url} (route: ${resolved.route})`);
11728
+ });
11729
+ program2.command("screen-enable").description("Enable Remote Management / Screen Sharing on a macOS machine over SSH").requiredOption("--machine <id>", "Machine identifier").option("--user <user>", "macOS user to grant screen-sharing (overrides manifest)").option("--vnc-password <pw>", "Legacy VNC password (<=8 chars honored)", "").option("--print", "Print the remote command instead of running it", false).action((options) => {
11730
+ const screen = resolveScreenTarget(options.machine);
11731
+ const user = options.user ?? screen.user;
11732
+ if (!user) {
11733
+ console.error(`No SSH user known for ${options.machine}; pass --user <name> or set metadata.user in the manifest.`);
11734
+ process.exitCode = 1;
11735
+ return;
11736
+ }
11737
+ const vncPw = (options.vncPassword || "").slice(0, 8);
11738
+ const remoteCmd = buildScreenEnableRemoteCommand(user, vncPw);
11739
+ const sshCmd = buildSshCommand(options.machine, `sudo -p '' bash -c ${JSON.stringify(remoteCmd)}`);
11740
+ if (options.print) {
11741
+ console.log(sshCmd);
11742
+ return;
11743
+ }
11744
+ console.log(`Run this to enable Screen Sharing on ${options.machine} (will prompt for sudo on the target):`);
11745
+ console.log(` ${sshCmd}`);
11746
+ });
11626
11747
  program2.command("ports").description("List listening ports on a machine").option("--machine <id>", "Machine identifier").option("-j, --json", "Print JSON output", false).action((options) => {
11627
11748
  const result = listPorts(options.machine);
11628
11749
  console.log(JSON.stringify(result, null, 2));
@@ -11787,4 +11908,10 @@ healCommand.command("uninstall").description("Remove the systemd watchdog servic
11787
11908
  }
11788
11909
  console.log(renderList("uninstall", uninstallHealService()));
11789
11910
  });
11790
- await program2.parseAsync(process.argv);
11911
+ try {
11912
+ await program2.parseAsync(process.argv);
11913
+ } catch (error) {
11914
+ const message = error instanceof Error ? error.message : String(error);
11915
+ console.error(source_default.red(message));
11916
+ process.exit(1);
11917
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAGhB,eAAe,EACf,WAAW,EAEZ,MAAM,aAAa,CAAC;AA8FrB,wBAAgB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,EAAE,CAAA;CAAE,CAM3F;AAED,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAQ7D;AAED,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAWlE;AAED,wBAAgB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,CAO3D;AAED,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,WAAW,CA0BhH"}
1
+ {"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAGhB,eAAe,EACf,WAAW,EAEZ,MAAM,aAAa,CAAC;AA+FrB,wBAAgB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,EAAE,CAAA;CAAE,CAM3F;AAED,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAQ7D;AAED,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAWlE;AAED,wBAAgB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,CAO3D;AAED,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,WAAW,CA0BhH"}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/commands/manifest.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAElE,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,YAAY,IAAI,aAAa,CAE5C;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,aAAa,CAOnE;AAED,wBAAgB,+BAA+B,IAAI,aAAa,CAE/D;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAErE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAQ/D;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/commands/manifest.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAElE,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,YAAY,IAAI,aAAa,CAE5C;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,aAAa,CAQnE;AAED,wBAAgB,+BAA+B,IAAI,aAAa,CAE/D;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAErE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAQ/D;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAEhD"}
@@ -0,0 +1,35 @@
1
+ import { type MachineRouteOptions, type MachineRouteKind, type MachineRouteConfidence } from "../topology.js";
2
+ export interface ResolvedScreenTarget {
3
+ machineId: string;
4
+ user: string | null;
5
+ host: string;
6
+ url: string;
7
+ route: MachineRouteKind;
8
+ confidence: MachineRouteConfidence;
9
+ warnings: string[];
10
+ }
11
+ /**
12
+ * Resolve the best screen-sharing (VNC) target for a machine.
13
+ * Prefers the live LAN route over Tailscale (lower latency for screen sharing),
14
+ * and always produces a `vnc://user@host` URL when a user is known.
15
+ */
16
+ export declare function resolveScreenTarget(machineId: string, options?: MachineRouteOptions): ResolvedScreenTarget;
17
+ /**
18
+ * Build the macOS command that opens Screen Sharing to a machine.
19
+ * `open vnc://user@host` launches Screen Sharing.app pointed at the resolved route.
20
+ */
21
+ export declare function buildScreenCommand(machineId: string, options?: MachineRouteOptions): string;
22
+ /**
23
+ * Build the remote command that ENABLES Remote Management / Screen Sharing on a
24
+ * macOS target via `kickstart`, plus the SRP + legacy-VNC password tweaks that
25
+ * make user-password auth work reliably from Screen Sharing.app and ARD.
26
+ *
27
+ * `vncPassword` is truncated to 8 chars by the VNC protocol; callers should pass
28
+ * an <=8 char value (or accept that only the first 8 chars are honored by legacy
29
+ * VNC clients).
30
+ *
31
+ * Returns the shell command to run AS ROOT on the target (caller pipes the sudo
32
+ * password or runs under an already-root context).
33
+ */
34
+ export declare function buildScreenEnableRemoteCommand(user: string, vncPassword: string): string;
35
+ //# sourceMappingURL=screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/commands/screen.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE5J,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,gBAAgB,CAAC;IACxB,UAAU,EAAE,sBAAsB,CAAC;IACnC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,oBAAoB,CA+B9G;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAG/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAcxF"}
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,WAAW,EAAa,MAAM,aAAa,CAAC;AAsE3E,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAqB9D;AAED,wBAAgB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,WAAW,CAoC1G"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAmB,WAAW,EAAa,MAAM,aAAa,CAAC;AAsE3E,wBAAgB,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,WAAW,CAqB9D;AAED,wBAAgB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,WAAW,CAoC1G"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAA+B,UAAU,EAAE,MAAM,aAAa,CAAC;AAiF3E,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAwB5D;AAwBD,wBAAgB,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,UAAU,CAyCxG"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA+B,UAAU,EAAE,MAAM,aAAa,CAAC;AAiF3E,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAwB5D;AAwBD,wBAAgB,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,UAAU,CAyCxG"}
package/dist/consumer.js CHANGED
@@ -4264,6 +4264,7 @@ function writeManifest(manifest, path = getManifestPath()) {
4264
4264
  generatedAt: new Date().toISOString(),
4265
4265
  machines: normalizeMachines(manifest.machines)
4266
4266
  };
4267
+ fleetSchema.parse(payload);
4267
4268
  writeFileSync(path, `${JSON.stringify(payload, null, 2)}
4268
4269
  `, "utf8");
4269
4270
  return path;
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from "./commands/self-test.js";
21
21
  export * from "./commands/serve.js";
22
22
  export * from "./commands/setup.js";
23
23
  export * from "./commands/ssh.js";
24
+ export * from "./commands/screen.js";
24
25
  export * from "./commands/sync.js";
25
26
  export * from "./commands/status.js";
26
27
  export * from "./commands/workspace.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,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"}
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,sBAAsB,CAAC;AACrC,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
@@ -11060,6 +11060,7 @@ function writeManifest(manifest, path = getManifestPath()) {
11060
11060
  generatedAt: new Date().toISOString(),
11061
11061
  machines: normalizeMachines(manifest.machines)
11062
11062
  };
11063
+ fleetSchema.parse(payload);
11063
11064
  writeFileSync(path, `${JSON.stringify(payload, null, 2)}
11064
11065
  `, "utf8");
11065
11066
  return path;
@@ -12465,7 +12466,7 @@ function getAgentStatus(machineId = getLocalMachineId()) {
12465
12466
  }));
12466
12467
  }
12467
12468
  // src/commands/backup.ts
12468
- import { homedir as homedir2 } from "os";
12469
+ import { homedir as homedir2, hostname as hostname5 } from "os";
12469
12470
  import { join as join3 } from "path";
12470
12471
  var MACHINES_BACKUP_BUCKET_ENV = "HASNA_MACHINES_S3_BUCKET";
12471
12472
  var MACHINES_BACKUP_BUCKET_FALLBACK_ENV = "MACHINES_S3_BUCKET";
@@ -12535,7 +12536,7 @@ function buildBackupPlan(bucket, prefix) {
12535
12536
  {
12536
12537
  id: "backup-upload",
12537
12538
  title: "Upload archive to S3",
12538
- command: `aws s3 cp ${quote(archivePath)} s3://${target.bucket}/${target.prefix}/$(hostname)-backup.tgz`,
12539
+ command: `aws s3 cp ${quote(archivePath)} ${quote(`s3://${target.bucket}/${target.prefix}/${hostname5()}-backup.tgz`)}`,
12539
12540
  manager: "custom"
12540
12541
  }
12541
12542
  ];
@@ -12590,20 +12591,21 @@ function shellQuote5(value) {
12590
12591
  }
12591
12592
  function buildAppCommand(machine, app) {
12592
12593
  const packageName = getPackageName(app);
12594
+ const quotedPackageName = shellQuote5(packageName);
12593
12595
  const manager = getAppManager(machine, app);
12594
12596
  if (manager === "custom") {
12595
12597
  return packageName;
12596
12598
  }
12597
12599
  if (machine.platform === "macos") {
12598
12600
  if (manager === "cask") {
12599
- return `brew install --cask ${packageName}`;
12601
+ return `brew install --cask ${quotedPackageName}`;
12600
12602
  }
12601
- return `brew install ${packageName}`;
12603
+ return `brew install ${quotedPackageName}`;
12602
12604
  }
12603
12605
  if (machine.platform === "windows") {
12604
- return `winget install ${packageName}`;
12606
+ return `winget install ${quotedPackageName}`;
12605
12607
  }
12606
- return `sudo apt-get install -y ${packageName}`;
12608
+ return `sudo apt-get install -y ${quotedPackageName}`;
12607
12609
  }
12608
12610
  function buildAppProbeCommand(machine, app) {
12609
12611
  const packageName = shellQuote5(getPackageName(app));
@@ -12889,9 +12891,10 @@ function manifestList() {
12889
12891
  return readManifest();
12890
12892
  }
12891
12893
  function manifestAdd(machine) {
12894
+ const validatedMachine = machineSchema.parse(machine);
12892
12895
  const manifest = readManifest();
12893
- const nextMachines = manifest.machines.filter((entry) => entry.id !== machine.id);
12894
- nextMachines.push(machine);
12896
+ const nextMachines = manifest.machines.filter((entry) => entry.id !== validatedMachine.id);
12897
+ nextMachines.push(validatedMachine);
12895
12898
  const nextManifest = { ...manifest, machines: nextMachines };
12896
12899
  writeManifest(nextManifest);
12897
12900
  return nextManifest;
@@ -13741,6 +13744,7 @@ function runSelfTest() {
13741
13744
  };
13742
13745
  }
13743
13746
  // src/commands/setup.ts
13747
+ import { homedir as homedir4 } from "os";
13744
13748
  function quote3(value) {
13745
13749
  return `'${value.replace(/'/g, `'\\''`)}'`;
13746
13750
  }
@@ -13788,11 +13792,11 @@ function buildPackageSteps(machine) {
13788
13792
  const manager = pkg.manager || (machine.platform === "macos" ? "brew" : "apt");
13789
13793
  let command = pkg.name;
13790
13794
  if (manager === "bun") {
13791
- command = `bun install -g ${pkg.name}`;
13795
+ command = `bun install -g ${quote3(pkg.name)}`;
13792
13796
  } else if (manager === "brew") {
13793
- command = `brew install ${pkg.name}`;
13797
+ command = `brew install ${quote3(pkg.name)}`;
13794
13798
  } else if (manager === "apt") {
13795
- command = `sudo apt-get install -y ${pkg.name}`;
13799
+ command = `sudo apt-get install -y ${quote3(pkg.name)}`;
13796
13800
  }
13797
13801
  return {
13798
13802
  id: `package-${index + 1}`,
@@ -13810,7 +13814,7 @@ function buildSetupPlan(machineId) {
13810
13814
  const target = selected || {
13811
13815
  id: currentMachineId,
13812
13816
  platform: "linux",
13813
- workspacePath: "~/workspace"
13817
+ workspacePath: `${homedir4()}/workspace`
13814
13818
  };
13815
13819
  const steps = [...buildBaseSteps(target), ...buildPackageSteps(target)];
13816
13820
  return {
@@ -13854,8 +13858,58 @@ function runSetup(machineId, options = {}) {
13854
13858
  recordSetupRun(plan.machineId, "completed", summary);
13855
13859
  return summary;
13856
13860
  }
13861
+ // src/commands/screen.ts
13862
+ function shellQuote7(value) {
13863
+ return `'${value.replace(/'/g, "'\\''")}'`;
13864
+ }
13865
+ function splitTarget(target) {
13866
+ const at = target.indexOf("@");
13867
+ if (at === -1)
13868
+ return [null, target];
13869
+ return [target.slice(0, at), target.slice(at + 1)];
13870
+ }
13871
+ function resolveScreenTarget(machineId, options = {}) {
13872
+ const resolved = resolveMachineRoute(machineId, options);
13873
+ if (!resolved.ok || !resolved.target) {
13874
+ throw new Error(`Machine route not found: ${machineId}`);
13875
+ }
13876
+ if (resolved.route === "unknown") {
13877
+ throw new Error(`Machine route is not reachable for screen sharing: ${machineId}`);
13878
+ }
13879
+ let [user, host] = splitTarget(resolved.target);
13880
+ if (!user) {
13881
+ const topology = options.topology ?? discoverMachineTopology(options);
13882
+ const entry = topology.machines.find((m) => m.machine_id === (resolved.machine_id ?? machineId));
13883
+ user = entry?.user ?? null;
13884
+ }
13885
+ const url = user ? `vnc://${user}@${host}` : `vnc://${host}`;
13886
+ return {
13887
+ machineId: resolved.machine_id ?? machineId,
13888
+ user,
13889
+ host,
13890
+ url,
13891
+ route: resolved.route,
13892
+ confidence: resolved.confidence,
13893
+ warnings: resolved.warnings
13894
+ };
13895
+ }
13896
+ function buildScreenCommand(machineId, options = {}) {
13897
+ const resolved = resolveScreenTarget(machineId, options);
13898
+ return `open ${resolved.url}`;
13899
+ }
13900
+ function buildScreenEnableRemoteCommand(user, vncPassword) {
13901
+ const kickstart = "/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart";
13902
+ const lines = [
13903
+ `dseditgroup -o edit -a ${shellQuote7(user)} -t user com.apple.access_screensharing 2>/dev/null || true`,
13904
+ "defaults write /Library/Preferences/com.apple.RemoteManagement AllowSRPForNetworkNodes -bool true",
13905
+ `${kickstart} -configure -clientopts -setvnclegacy -vnclegacy yes -setvncpw -vncpw ${shellQuote7(vncPassword)}`,
13906
+ `${kickstart} -activate -configure -access -on -users ${shellQuote7(user)} -privs -all -restart -agent -menu`
13907
+ ];
13908
+ return lines.join(" && ");
13909
+ }
13857
13910
  // src/commands/sync.ts
13858
13911
  import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
13912
+ import { homedir as homedir5 } from "os";
13859
13913
  function quote4(value) {
13860
13914
  return `'${value.replace(/'/g, `'\\''`)}'`;
13861
13915
  }
@@ -13873,13 +13927,13 @@ function packageCheckCommand(machine, packageName, manager = machine.platform ==
13873
13927
  }
13874
13928
  function packageInstallCommand(machine, packageName, manager = machine.platform === "macos" ? "brew" : "apt") {
13875
13929
  if (manager === "bun") {
13876
- return `bun install -g ${packageName}`;
13930
+ return `bun install -g ${quote4(packageName)}`;
13877
13931
  }
13878
13932
  if (manager === "brew") {
13879
- return `brew install ${packageName}`;
13933
+ return `brew install ${quote4(packageName)}`;
13880
13934
  }
13881
13935
  if (manager === "apt") {
13882
- return `sudo apt-get install -y ${packageName}`;
13936
+ return `sudo apt-get install -y ${quote4(packageName)}`;
13883
13937
  }
13884
13938
  return packageName;
13885
13939
  }
@@ -13932,7 +13986,7 @@ function buildSyncPlan(machineId) {
13932
13986
  const target = selected || {
13933
13987
  id: currentMachineId,
13934
13988
  platform: "linux",
13935
- workspacePath: "~/workspace"
13989
+ workspacePath: `${homedir5()}/workspace`
13936
13990
  };
13937
13991
  const actions = [
13938
13992
  ...detectPackageActions(target),
@@ -14877,7 +14931,7 @@ var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]
14877
14931
  var cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
14878
14932
  var base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
14879
14933
  var base64url = /^[A-Za-z0-9_-]*$/;
14880
- var hostname5 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
14934
+ var hostname6 = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
14881
14935
  var e164 = /^\+(?:[0-9]){6,14}[0-9]$/;
14882
14936
  var dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
14883
14937
  var date = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
@@ -15489,7 +15543,7 @@ var $ZodURL = /* @__PURE__ */ $constructor("$ZodURL", (inst, def) => {
15489
15543
  code: "invalid_format",
15490
15544
  format: "url",
15491
15545
  note: "Invalid hostname",
15492
- pattern: hostname5.source,
15546
+ pattern: hostname6.source,
15493
15547
  input: payload.value,
15494
15548
  inst,
15495
15549
  continue: !def.abort
@@ -17478,7 +17532,7 @@ class JSONSchemaGenerator {
17478
17532
  if (val === undefined) {
17479
17533
  if (this.unrepresentable === "throw") {
17480
17534
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
17481
- } else {}
17535
+ }
17482
17536
  } else if (typeof val === "bigint") {
17483
17537
  if (this.unrepresentable === "throw") {
17484
17538
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -23307,6 +23361,7 @@ export {
23307
23361
  runAppsInstall,
23308
23362
  resolveTables,
23309
23363
  resolveSshTarget,
23364
+ resolveScreenTarget,
23310
23365
  resolveMachineWorkspace,
23311
23366
  resolveMachineRoute,
23312
23367
  resolveBackupTarget,
@@ -23380,6 +23435,8 @@ export {
23380
23435
  buildSshCommand,
23381
23436
  buildSetupPlan,
23382
23437
  buildServer,
23438
+ buildScreenEnableRemoteCommand,
23439
+ buildScreenCommand,
23383
23440
  buildClaudeInstallPlan,
23384
23441
  buildCertPlan,
23385
23442
  buildBackupPlan,
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
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,CAWvF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,SAAoB,GAAG,eAAe,GAAG,IAAI,CAEtG;AAED,wBAAgB,4BAA4B,IAAI,eAAe,CAiB9D"}
package/dist/mcp/index.js CHANGED
@@ -4017,7 +4017,7 @@ var coerce = {
4017
4017
  };
4018
4018
  var NEVER = INVALID;
4019
4019
  // src/commands/backup.ts
4020
- import { homedir } from "os";
4020
+ import { homedir, hostname } from "os";
4021
4021
  import { join as join2 } from "path";
4022
4022
  var MACHINES_BACKUP_BUCKET_ENV = "HASNA_MACHINES_S3_BUCKET";
4023
4023
  var MACHINES_BACKUP_BUCKET_FALLBACK_ENV = "MACHINES_S3_BUCKET";
@@ -4087,7 +4087,7 @@ function buildBackupPlan(bucket, prefix) {
4087
4087
  {
4088
4088
  id: "backup-upload",
4089
4089
  title: "Upload archive to S3",
4090
- command: `aws s3 cp ${quote(archivePath)} s3://${target.bucket}/${target.prefix}/$(hostname)-backup.tgz`,
4090
+ command: `aws s3 cp ${quote(archivePath)} ${quote(`s3://${target.bucket}/${target.prefix}/${hostname()}-backup.tgz`)}`,
4091
4091
  manager: "custom"
4092
4092
  }
4093
4093
  ];
@@ -4127,7 +4127,7 @@ function runBackup(bucket, prefix, options = {}) {
4127
4127
 
4128
4128
  // src/manifests.ts
4129
4129
  import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
4130
- import { arch, homedir as homedir2, hostname, platform, userInfo } from "os";
4130
+ import { arch, homedir as homedir2, hostname as hostname2, platform, userInfo } from "os";
4131
4131
  import { dirname as dirname3 } from "path";
4132
4132
 
4133
4133
  // src/paths.ts
@@ -4235,6 +4235,7 @@ function writeManifest(manifest, path = getManifestPath()) {
4235
4235
  generatedAt: new Date().toISOString(),
4236
4236
  machines: normalizeMachines(manifest.machines)
4237
4237
  };
4238
+ fleetSchema.parse(payload);
4238
4239
  writeFileSync(path, `${JSON.stringify(payload, null, 2)}
4239
4240
  `, "utf8");
4240
4241
  return path;
@@ -4243,12 +4244,12 @@ function getManifestMachine(machineId, path = getManifestPath()) {
4243
4244
  return readManifest(path).machines.find((machine) => machine.id === machineId) || null;
4244
4245
  }
4245
4246
  function detectCurrentMachineManifest() {
4246
- const machineId = process.env["HASNA_MACHINES_MACHINE_ID"] || hostname();
4247
+ const machineId = process.env["HASNA_MACHINES_MACHINE_ID"] || hostname2();
4247
4248
  const user = userInfo().username;
4248
4249
  const bunDir = dirname3(process.execPath);
4249
4250
  return {
4250
4251
  id: machineId,
4251
- hostname: hostname(),
4252
+ hostname: hostname2(),
4252
4253
  sshAddress: `${user}@${machineId}`,
4253
4254
  tailscaleName: machineId,
4254
4255
  platform: normalizePlatform(),
@@ -4263,11 +4264,11 @@ function detectCurrentMachineManifest() {
4263
4264
 
4264
4265
  // src/remote.ts
4265
4266
  import { spawnSync as spawnSync2 } from "child_process";
4266
- import { hostname as hostname4 } from "os";
4267
+ import { hostname as hostname5 } from "os";
4267
4268
 
4268
4269
  // src/db.ts
4269
4270
  import { Database } from "bun:sqlite";
4270
- import { hostname as hostname2 } from "os";
4271
+ import { hostname as hostname3 } from "os";
4271
4272
  class SqliteAdapter {
4272
4273
  raw;
4273
4274
  constructor(path) {
@@ -4332,7 +4333,7 @@ function getDb(path = getDbPath()) {
4332
4333
  return getAdapter(path).raw;
4333
4334
  }
4334
4335
  function getLocalMachineId() {
4335
- return process.env["HASNA_MACHINES_MACHINE_ID"] || hostname2();
4336
+ return process.env["HASNA_MACHINES_MACHINE_ID"] || hostname3();
4336
4337
  }
4337
4338
  function listHeartbeats(machineId) {
4338
4339
  const db = getDb();
@@ -4366,7 +4367,7 @@ function recordSyncRun(machineId, status, actions) {
4366
4367
 
4367
4368
  // src/topology.ts
4368
4369
  import { existsSync as existsSync4 } from "fs";
4369
- import { arch as arch2, hostname as hostname3, platform as platform2, userInfo as userInfo2 } from "os";
4370
+ import { arch as arch2, hostname as hostname4, platform as platform2, userInfo as userInfo2 } from "os";
4370
4371
  import { spawnSync } from "child_process";
4371
4372
  var MACHINES_CONSUMER_CONTRACT_VERSION = 1;
4372
4373
  var MACHINES_PACKAGE_NAME = "@hasna/machines";
@@ -4589,7 +4590,7 @@ function discoverMachineTopology(options = {}) {
4589
4590
  capabilities: getMachinesConsumerCapabilities(),
4590
4591
  generated_at: now.toISOString(),
4591
4592
  local_machine_id: localMachineId,
4592
- local_hostname: hostname3(),
4593
+ local_hostname: hostname4(),
4593
4594
  current_platform: normalizePlatform2(),
4594
4595
  manifest_path_known: existsSync4(getManifestPath()),
4595
4596
  machines,
@@ -4614,7 +4615,7 @@ function routeTargetMatches(machine, requested) {
4614
4615
  }
4615
4616
  function findRouteMachine(topology, requestedMachineId) {
4616
4617
  const requested = normalizeMachineAlias(requestedMachineId);
4617
- if (requested === "local" || requested === "localhost" || requested === normalizeMachineAlias(hostname3()) || requested === normalizeMachineAlias(topology.local_machine_id)) {
4618
+ if (requested === "local" || requested === "localhost" || requested === normalizeMachineAlias(hostname4()) || requested === normalizeMachineAlias(topology.local_machine_id)) {
4618
4619
  return {
4619
4620
  machine: topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? null,
4620
4621
  matchedBy: "local_alias"
@@ -5239,7 +5240,7 @@ function shellQuote3(value) {
5239
5240
  return `'${value.replace(/'/g, "'\\''")}'`;
5240
5241
  }
5241
5242
  function machineIsLocal(machineId, localMachineId) {
5242
- return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname4();
5243
+ return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname5();
5243
5244
  }
5244
5245
  function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
5245
5246
  if (machineIsLocal(machineId, localMachineId)) {
@@ -5291,20 +5292,21 @@ function shellQuote4(value) {
5291
5292
  }
5292
5293
  function buildAppCommand(machine, app) {
5293
5294
  const packageName = getPackageName(app);
5295
+ const quotedPackageName = shellQuote4(packageName);
5294
5296
  const manager = getAppManager(machine, app);
5295
5297
  if (manager === "custom") {
5296
5298
  return packageName;
5297
5299
  }
5298
5300
  if (machine.platform === "macos") {
5299
5301
  if (manager === "cask") {
5300
- return `brew install --cask ${packageName}`;
5302
+ return `brew install --cask ${quotedPackageName}`;
5301
5303
  }
5302
- return `brew install ${packageName}`;
5304
+ return `brew install ${quotedPackageName}`;
5303
5305
  }
5304
5306
  if (machine.platform === "windows") {
5305
- return `winget install ${packageName}`;
5307
+ return `winget install ${quotedPackageName}`;
5306
5308
  }
5307
- return `sudo apt-get install -y ${packageName}`;
5309
+ return `sudo apt-get install -y ${quotedPackageName}`;
5308
5310
  }
5309
5311
  function buildAppProbeCommand(machine, app) {
5310
5312
  const packageName = shellQuote4(getPackageName(app));
@@ -6106,9 +6108,10 @@ function manifestList() {
6106
6108
  return readManifest();
6107
6109
  }
6108
6110
  function manifestAdd(machine) {
6111
+ const validatedMachine = machineSchema.parse(machine);
6109
6112
  const manifest = readManifest();
6110
- const nextMachines = manifest.machines.filter((entry) => entry.id !== machine.id);
6111
- nextMachines.push(machine);
6113
+ const nextMachines = manifest.machines.filter((entry) => entry.id !== validatedMachine.id);
6114
+ nextMachines.push(validatedMachine);
6112
6115
  const nextManifest = { ...manifest, machines: nextMachines };
6113
6116
  writeManifest(nextManifest);
6114
6117
  return nextManifest;
@@ -6374,6 +6377,7 @@ function renderDashboardHtml() {
6374
6377
  }
6375
6378
 
6376
6379
  // src/commands/setup.ts
6380
+ import { homedir as homedir4 } from "os";
6377
6381
  function quote3(value) {
6378
6382
  return `'${value.replace(/'/g, `'\\''`)}'`;
6379
6383
  }
@@ -6421,11 +6425,11 @@ function buildPackageSteps(machine) {
6421
6425
  const manager = pkg.manager || (machine.platform === "macos" ? "brew" : "apt");
6422
6426
  let command = pkg.name;
6423
6427
  if (manager === "bun") {
6424
- command = `bun install -g ${pkg.name}`;
6428
+ command = `bun install -g ${quote3(pkg.name)}`;
6425
6429
  } else if (manager === "brew") {
6426
- command = `brew install ${pkg.name}`;
6430
+ command = `brew install ${quote3(pkg.name)}`;
6427
6431
  } else if (manager === "apt") {
6428
- command = `sudo apt-get install -y ${pkg.name}`;
6432
+ command = `sudo apt-get install -y ${quote3(pkg.name)}`;
6429
6433
  }
6430
6434
  return {
6431
6435
  id: `package-${index + 1}`,
@@ -6443,7 +6447,7 @@ function buildSetupPlan(machineId) {
6443
6447
  const target = selected || {
6444
6448
  id: currentMachineId,
6445
6449
  platform: "linux",
6446
- workspacePath: "~/workspace"
6450
+ workspacePath: `${homedir4()}/workspace`
6447
6451
  };
6448
6452
  const steps = [...buildBaseSteps(target), ...buildPackageSteps(target)];
6449
6453
  return {
@@ -6490,6 +6494,7 @@ function runSetup(machineId, options = {}) {
6490
6494
 
6491
6495
  // src/commands/sync.ts
6492
6496
  import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
6497
+ import { homedir as homedir5 } from "os";
6493
6498
  function quote4(value) {
6494
6499
  return `'${value.replace(/'/g, `'\\''`)}'`;
6495
6500
  }
@@ -6507,13 +6512,13 @@ function packageCheckCommand(machine, packageName, manager = machine.platform ==
6507
6512
  }
6508
6513
  function packageInstallCommand(machine, packageName, manager = machine.platform === "macos" ? "brew" : "apt") {
6509
6514
  if (manager === "bun") {
6510
- return `bun install -g ${packageName}`;
6515
+ return `bun install -g ${quote4(packageName)}`;
6511
6516
  }
6512
6517
  if (manager === "brew") {
6513
- return `brew install ${packageName}`;
6518
+ return `brew install ${quote4(packageName)}`;
6514
6519
  }
6515
6520
  if (manager === "apt") {
6516
- return `sudo apt-get install -y ${packageName}`;
6521
+ return `sudo apt-get install -y ${quote4(packageName)}`;
6517
6522
  }
6518
6523
  return packageName;
6519
6524
  }
@@ -6566,7 +6571,7 @@ function buildSyncPlan(machineId) {
6566
6571
  const target = selected || {
6567
6572
  id: currentMachineId,
6568
6573
  platform: "linux",
6569
- workspacePath: "~/workspace"
6574
+ workspacePath: `${homedir5()}/workspace`
6570
6575
  };
6571
6576
  const actions = [
6572
6577
  ...detectPackageActions(target),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/machines",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "description": "Machine fleet management CLI + MCP for developers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",