@hasna/machines 0.0.23 → 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/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,23 +7624,28 @@ 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;
7630
7636
  var MACHINES_PACKAGE_NAME = "@hasna/machines";
7637
+ var DEFAULT_MACHINE_RESOLVER_TTL_MS = 24 * 60 * 60 * 1000;
7631
7638
  var MACHINES_CONSUMER_CAPABILITIES = {
7632
7639
  topology: true,
7633
7640
  compatibility: true,
7634
7641
  route_resolution: true,
7635
7642
  cli_json_fallback: true,
7636
7643
  workspace_path_mapping: true,
7637
- workspace_diagnostics: true
7644
+ workspace_diagnostics: true,
7645
+ schema_artifacts: true,
7646
+ cacheability_metadata: true,
7647
+ resolver_snapshots: true,
7648
+ field_capability_descriptors: true
7638
7649
  };
7639
7650
  function getMachinesConsumerCapabilities() {
7640
7651
  return { ...MACHINES_CONSUMER_CAPABILITIES };
@@ -7842,7 +7853,7 @@ function discoverMachineTopology(options = {}) {
7842
7853
  capabilities: getMachinesConsumerCapabilities(),
7843
7854
  generated_at: now.toISOString(),
7844
7855
  local_machine_id: localMachineId,
7845
- local_hostname: hostname3(),
7856
+ local_hostname: hostname4(),
7846
7857
  current_platform: normalizePlatform2(),
7847
7858
  manifest_path_known: existsSync5(getManifestPath()),
7848
7859
  machines,
@@ -7867,7 +7878,7 @@ function routeTargetMatches(machine, requested) {
7867
7878
  }
7868
7879
  function findRouteMachine(topology, requestedMachineId) {
7869
7880
  const requested = normalizeMachineAlias(requestedMachineId);
7870
- 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)) {
7871
7882
  return {
7872
7883
  machine: topology.machines.find((machine) => machine.machine_id === topology.local_machine_id) ?? null,
7873
7884
  matchedBy: "local_alias"
@@ -7905,11 +7916,69 @@ function routeConfidence(input) {
7905
7916
  return "low";
7906
7917
  return "none";
7907
7918
  }
7919
+ function addMilliseconds(date, milliseconds) {
7920
+ return new Date(date.getTime() + milliseconds).toISOString();
7921
+ }
7922
+ function routeAuthority(input) {
7923
+ if (!input.machine)
7924
+ return "unresolved";
7925
+ if (input.matchedBy === "fallback")
7926
+ return "fallback";
7927
+ if (input.selectedHint?.kind === "local")
7928
+ return "live_topology";
7929
+ if (input.selectedHint?.kind === "tailscale" || input.machine.tailscale.online !== null)
7930
+ return "live_topology";
7931
+ if (input.machine.manifest_declared)
7932
+ return "manifest";
7933
+ return "open-machines";
7934
+ }
7935
+ function workspaceAuthority(paths) {
7936
+ const sources = [paths.workspace_root.source, paths.project_root.source, paths.open_files_root.source];
7937
+ if (sources.some((source) => source === "argument"))
7938
+ return "argument";
7939
+ if (sources.some((source) => source === "manifest_metadata"))
7940
+ return "manifest_metadata";
7941
+ if (sources.some((source) => source === "manifest"))
7942
+ return "manifest";
7943
+ if (sources.some((source) => source === "inferred"))
7944
+ return "inferred";
7945
+ if (sources.every((source) => source === "unresolved"))
7946
+ return "unresolved";
7947
+ return "open-machines";
7948
+ }
7949
+ function cacheability(input) {
7950
+ const ttlMs = input.ttlMs === undefined ? DEFAULT_MACHINE_RESOLVER_TTL_MS : input.ttlMs;
7951
+ const expiresAt = typeof ttlMs === "number" && ttlMs > 0 ? addMilliseconds(input.observedAt, ttlMs) : null;
7952
+ const stale = expiresAt ? input.now.getTime() > new Date(expiresAt).getTime() : false;
7953
+ const confidenceCacheable = input.confidence !== "none" && input.confidence !== "low";
7954
+ const cacheable = input.ok && confidenceCacheable && !stale && input.authority !== "unresolved";
7955
+ const reasons = [...input.reasons];
7956
+ if (!input.ok)
7957
+ reasons.push("resolver_not_ok");
7958
+ if (!confidenceCacheable)
7959
+ reasons.push(`low_confidence:${input.confidence}`);
7960
+ if (stale)
7961
+ reasons.push("stale");
7962
+ if (input.authority === "unresolved")
7963
+ reasons.push("unresolved_authority");
7964
+ return {
7965
+ observed_at: input.observedAt.toISOString(),
7966
+ verified_at: input.ok ? input.now.toISOString() : null,
7967
+ expires_at: expiresAt,
7968
+ ttl_ms: typeof ttlMs === "number" && ttlMs > 0 ? ttlMs : null,
7969
+ source_authority: input.authority,
7970
+ confidence: input.confidence,
7971
+ cacheable,
7972
+ stale,
7973
+ reasons: [...new Set(reasons)].sort()
7974
+ };
7975
+ }
7908
7976
  function resolveMachineRoute(machineId, options = {}) {
7977
+ const now = options.now ?? new Date;
7909
7978
  const topology = options.topology ?? discoverMachineTopology(options);
7910
7979
  const warnings = [...topology.warnings];
7911
7980
  const { machine, matchedBy } = findRouteMachine(topology, machineId);
7912
- const generatedAt = (options.now ?? new Date).toISOString();
7981
+ const generatedAt = now.toISOString();
7913
7982
  if (!machine) {
7914
7983
  warnings.push(`machine_not_found:${machineId}`);
7915
7984
  return {
@@ -7933,16 +8002,27 @@ function resolveMachineRoute(machineId, options = {}) {
7933
8002
  tailscale_online: null,
7934
8003
  selected_hint: null
7935
8004
  },
8005
+ cacheability: cacheability({
8006
+ ok: false,
8007
+ observedAt: now,
8008
+ now,
8009
+ ttlMs: options.resolverTtlMs,
8010
+ authority: "unresolved",
8011
+ confidence: "none",
8012
+ reasons: [`machine_not_found:${machineId}`]
8013
+ }),
7936
8014
  warnings
7937
8015
  };
7938
8016
  }
7939
8017
  const selectedHint = selectRouteHint(machine.route_hints);
7940
8018
  const route = selectedHint?.kind ?? machine.ssh.route ?? "unknown";
7941
8019
  const local = route === "local" || machine.machine_id === topology.local_machine_id;
8020
+ const confidence = routeConfidence({ machine, hint: selectedHint, matchedBy });
8021
+ const ok = Boolean(selectedHint?.target);
7942
8022
  return {
7943
8023
  schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
7944
8024
  package: topology.package,
7945
- ok: Boolean(selectedHint?.target),
8025
+ ok,
7946
8026
  machine_id: machine.machine_id,
7947
8027
  requested_machine_id: machineId,
7948
8028
  generated_at: generatedAt,
@@ -7950,7 +8030,7 @@ function resolveMachineRoute(machineId, options = {}) {
7950
8030
  source: route,
7951
8031
  target: selectedHint?.target ?? null,
7952
8032
  command_target: selectedHint?.target ?? null,
7953
- confidence: routeConfidence({ machine, hint: selectedHint, matchedBy }),
8033
+ confidence,
7954
8034
  local,
7955
8035
  evidence: {
7956
8036
  topology: true,
@@ -7960,6 +8040,15 @@ function resolveMachineRoute(machineId, options = {}) {
7960
8040
  tailscale_online: machine.tailscale.online,
7961
8041
  selected_hint: selectedHint
7962
8042
  },
8043
+ cacheability: cacheability({
8044
+ ok,
8045
+ observedAt: now,
8046
+ now,
8047
+ ttlMs: options.resolverTtlMs,
8048
+ authority: routeAuthority({ machine, selectedHint, matchedBy }),
8049
+ confidence,
8050
+ reasons: selectedHint ? [] : ["route_target_unresolved"]
8051
+ }),
7963
8052
  warnings
7964
8053
  };
7965
8054
  }
@@ -8256,10 +8345,11 @@ function metadataKeysForDiagnostics(metadata) {
8256
8345
  return Object.keys(metadata).filter((key) => !/(secret|token|key|password|credential)/i.test(key)).sort();
8257
8346
  }
8258
8347
  function resolveMachineWorkspace(options) {
8348
+ const now = options.now ?? new Date;
8259
8349
  const topology = options.topology ?? discoverMachineTopology(options);
8260
8350
  const warnings = [...topology.warnings];
8261
8351
  const { machine, matchedBy } = findRouteMachine(topology, options.machineId);
8262
- const generatedAt = (options.now ?? new Date).toISOString();
8352
+ const generatedAt = now.toISOString();
8263
8353
  const repoName = options.repoName ?? options.projectId;
8264
8354
  const openFilesRepoName = options.openFilesRepoName ?? "open-files";
8265
8355
  if (!machine) {
@@ -8286,6 +8376,15 @@ function resolveMachineWorkspace(options) {
8286
8376
  manifest_declared: null,
8287
8377
  metadata_keys: []
8288
8378
  },
8379
+ cacheability: cacheability({
8380
+ ok: false,
8381
+ observedAt: now,
8382
+ now,
8383
+ ttlMs: options.resolverTtlMs,
8384
+ authority: "unresolved",
8385
+ confidence: "none",
8386
+ reasons: [`machine_not_found:${options.machineId}`]
8387
+ }),
8289
8388
  warnings
8290
8389
  };
8291
8390
  const diagnostics2 = workspaceDiagnostics({
@@ -8317,10 +8416,16 @@ function resolveMachineWorkspace(options) {
8317
8416
  warnings.push(`open_files_root_inferred:${options.projectId}`);
8318
8417
  if (!projectRootPath)
8319
8418
  warnings.push(`project_root_unresolved:${options.projectId}`);
8419
+ const workspacePaths = {
8420
+ workspace_root: { path: workspaceRootPath, source: workspaceRootSource },
8421
+ project_root: { path: projectRootPath, source: projectRootSource },
8422
+ open_files_root: { path: openFilesRootPath, source: openFilesRootSource }
8423
+ };
8424
+ const workspaceOk = Boolean(projectRootPath);
8320
8425
  const resolution = {
8321
8426
  schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
8322
8427
  package: topology.package,
8323
- ok: Boolean(projectRootPath),
8428
+ ok: workspaceOk,
8324
8429
  requested_machine_id: options.machineId,
8325
8430
  machine_id: machine.machine_id,
8326
8431
  generated_at: generatedAt,
@@ -8335,11 +8440,7 @@ function resolveMachineWorkspace(options) {
8335
8440
  trust_status: trustStatus(machine),
8336
8441
  auth_status: authStatus(machine)
8337
8442
  },
8338
- paths: {
8339
- workspace_root: { path: workspaceRootPath, source: workspaceRootSource },
8340
- project_root: { path: projectRootPath, source: projectRootSource },
8341
- open_files_root: { path: openFilesRootPath, source: openFilesRootSource }
8342
- },
8443
+ paths: workspacePaths,
8343
8444
  diagnostics: [],
8344
8445
  repair_hints: [],
8345
8446
  evidence: {
@@ -8348,6 +8449,15 @@ function resolveMachineWorkspace(options) {
8348
8449
  manifest_declared: machine.manifest_declared,
8349
8450
  metadata_keys: metadataKeysForDiagnostics(metadata)
8350
8451
  },
8452
+ cacheability: cacheability({
8453
+ ok: workspaceOk,
8454
+ observedAt: now,
8455
+ now,
8456
+ ttlMs: options.resolverTtlMs,
8457
+ authority: workspaceAuthority(workspacePaths),
8458
+ confidence: workspaceOk ? "medium" : "none",
8459
+ reasons: projectRootPath ? [] : [`project_root_unresolved:${options.projectId}`]
8460
+ }),
8351
8461
  warnings
8352
8462
  };
8353
8463
  const diagnostics = workspaceDiagnostics({
@@ -8393,7 +8503,7 @@ function shellQuote3(value) {
8393
8503
  return `'${value.replace(/'/g, "'\\''")}'`;
8394
8504
  }
8395
8505
  function machineIsLocal(machineId, localMachineId) {
8396
- return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname4();
8506
+ return machineId === "local" || machineId === "localhost" || machineId === localMachineId || machineId === hostname5();
8397
8507
  }
8398
8508
  function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
8399
8509
  if (machineIsLocal(machineId, localMachineId)) {
@@ -8445,20 +8555,21 @@ function shellQuote4(value) {
8445
8555
  }
8446
8556
  function buildAppCommand(machine, app) {
8447
8557
  const packageName = getPackageName(app);
8558
+ const quotedPackageName = shellQuote4(packageName);
8448
8559
  const manager = getAppManager(machine, app);
8449
8560
  if (manager === "custom") {
8450
8561
  return packageName;
8451
8562
  }
8452
8563
  if (machine.platform === "macos") {
8453
8564
  if (manager === "cask") {
8454
- return `brew install --cask ${packageName}`;
8565
+ return `brew install --cask ${quotedPackageName}`;
8455
8566
  }
8456
- return `brew install ${packageName}`;
8567
+ return `brew install ${quotedPackageName}`;
8457
8568
  }
8458
8569
  if (machine.platform === "windows") {
8459
- return `winget install ${packageName}`;
8570
+ return `winget install ${quotedPackageName}`;
8460
8571
  }
8461
- return `sudo apt-get install -y ${packageName}`;
8572
+ return `sudo apt-get install -y ${quotedPackageName}`;
8462
8573
  }
8463
8574
  function buildAppProbeCommand(machine, app) {
8464
8575
  const packageName = shellQuote4(getPackageName(app));
@@ -9041,8 +9152,55 @@ function listPorts(machineId) {
9041
9152
  };
9042
9153
  }
9043
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
+
9044
9201
  // src/commands/sync.ts
9045
9202
  import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync5, symlinkSync, copyFileSync } from "fs";
9203
+ import { homedir as homedir5 } from "os";
9046
9204
  init_paths();
9047
9205
  init_db();
9048
9206
  function quote4(value) {
@@ -9062,13 +9220,13 @@ function packageCheckCommand(machine, packageName, manager = machine.platform ==
9062
9220
  }
9063
9221
  function packageInstallCommand(machine, packageName, manager = machine.platform === "macos" ? "brew" : "apt") {
9064
9222
  if (manager === "bun") {
9065
- return `bun install -g ${packageName}`;
9223
+ return `bun install -g ${quote4(packageName)}`;
9066
9224
  }
9067
9225
  if (manager === "brew") {
9068
- return `brew install ${packageName}`;
9226
+ return `brew install ${quote4(packageName)}`;
9069
9227
  }
9070
9228
  if (manager === "apt") {
9071
- return `sudo apt-get install -y ${packageName}`;
9229
+ return `sudo apt-get install -y ${quote4(packageName)}`;
9072
9230
  }
9073
9231
  return packageName;
9074
9232
  }
@@ -9121,7 +9279,7 @@ function buildSyncPlan(machineId) {
9121
9279
  const target = selected || {
9122
9280
  id: currentMachineId,
9123
9281
  platform: "linux",
9124
- workspacePath: "~/workspace"
9282
+ workspacePath: `${homedir5()}/workspace`
9125
9283
  };
9126
9284
  const actions = [
9127
9285
  ...detectPackageActions(target),
@@ -9388,7 +9546,7 @@ var DEFAULT_COMMANDS = [
9388
9546
  function defaultPackages() {
9389
9547
  return [{ name: "@hasna/machines", command: "machines", expectedVersion: getPackageVersion(), required: true }];
9390
9548
  }
9391
- function shellQuote6(value) {
9549
+ function shellQuote7(value) {
9392
9550
  return `'${value.replace(/'/g, "'\\''")}'`;
9393
9551
  }
9394
9552
  function commandId(value) {
@@ -9439,7 +9597,7 @@ function defaultRunner2(machineId, command) {
9439
9597
  return runMachineCommand(machineId, command);
9440
9598
  }
9441
9599
  function inspectCommand(machineId, spec, runner) {
9442
- const command = shellQuote6(spec.command);
9600
+ const command = shellQuote7(spec.command);
9443
9601
  const versionArgs = spec.versionArgs ?? "--version";
9444
9602
  const script = [
9445
9603
  `cmd=${command}`,
@@ -9468,7 +9626,7 @@ function fieldCommand(field) {
9468
9626
  }
9469
9627
  function inspectWorkspace(machineId, spec, runner) {
9470
9628
  const script = [
9471
- `path=${shellQuote6(spec.path)}`,
9629
+ `path=${shellQuote7(spec.path)}`,
9472
9630
  'printf "exists=%s\\n" "$(test -d "$path" && printf yes || printf no)"',
9473
9631
  'pkg="$path/package.json"',
9474
9632
  'printf "package_json=%s\\n" "$(test -f "$pkg" && printf yes || printf no)"',
@@ -11214,6 +11372,11 @@ manifestCommand.command("add").description("Add or replace a machine in the flee
11214
11372
  console.log(JSON.stringify(manifestAdd(machine2), null, 2));
11215
11373
  return;
11216
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
+ }
11217
11380
  const packages = Array.isArray(options["package"]) ? options["package"].map((name) => ({ name: String(name) })) : undefined;
11218
11381
  const files = Array.isArray(options["file"]) ? options["file"].map((value) => {
11219
11382
  const [source, target, mode] = String(value).split(":");
@@ -11519,6 +11682,68 @@ program2.command("ssh").description("Choose the best SSH route for a machine").r
11519
11682
  }
11520
11683
  console.log(buildSshCommand(options.machine, options.cmd));
11521
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
+ });
11522
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) => {
11523
11748
  const result = listPorts(options.machine);
11524
11749
  console.log(JSON.stringify(result, null, 2));
@@ -11683,4 +11908,10 @@ healCommand.command("uninstall").description("Remove the systemd watchdog servic
11683
11908
  }
11684
11909
  console.log(renderList("uninstall", uninstallHealService()));
11685
11910
  });
11686
- 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"}