orkestrator 2.26.2-beta → 2.27.0-beta

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/client.js CHANGED
@@ -131,22 +131,22 @@ __export(exports_standalone_service, {
131
131
  writeServiceConfig: () => writeServiceConfig
132
132
  });
133
133
  import { execFile } from "child_process";
134
- import { existsSync as existsSync3, readFileSync as readFileSync3, readlinkSync } from "fs";
134
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readlinkSync } from "fs";
135
135
  import { copyFile, mkdir, readFile, rename, rm, symlink, writeFile } from "fs/promises";
136
- import path12 from "path";
136
+ import path13 from "path";
137
137
  import { promisify } from "util";
138
138
  function standaloneLayout(dataDir) {
139
- const root = path12.join(dataDir, STANDALONE_DIRECTORY);
139
+ const root = path13.join(dataDir, STANDALONE_DIRECTORY);
140
140
  return {
141
141
  root,
142
- releases: path12.join(root, "releases"),
143
- current: path12.join(root, "current"),
144
- previous: path12.join(root, "previous"),
145
- serviceFile: path12.join(root, "service.json"),
146
- stateFile: path12.join(root, "launcher-state.json"),
147
- restartMarker: path12.join(root, "restart-requested"),
148
- launcher: path12.join(root, "launcher.js"),
149
- logs: path12.join(root, "logs")
142
+ releases: path13.join(root, "releases"),
143
+ current: path13.join(root, "current"),
144
+ previous: path13.join(root, "previous"),
145
+ serviceFile: path13.join(root, "service.json"),
146
+ stateFile: path13.join(root, "launcher-state.json"),
147
+ restartMarker: path13.join(root, "restart-requested"),
148
+ launcher: path13.join(root, "launcher.js"),
149
+ logs: path13.join(root, "logs")
150
150
  };
151
151
  }
152
152
  function readJson(file) {
@@ -192,28 +192,28 @@ async function writeAtomically(file, contents) {
192
192
  }
193
193
  function linkedVersion(link) {
194
194
  try {
195
- return path12.basename(readlinkSync(link));
195
+ return path13.basename(readlinkSync(link));
196
196
  } catch {
197
197
  return null;
198
198
  }
199
199
  }
200
200
  function releaseDirectory(layout, version) {
201
- return path12.join(layout.releases, version);
201
+ return path13.join(layout.releases, version);
202
202
  }
203
203
  function releasePackageRoot(releaseDir) {
204
- return path12.join(releaseDir, "node_modules", STANDALONE_PACKAGE);
204
+ return path13.join(releaseDir, "node_modules", STANDALONE_PACKAGE);
205
205
  }
206
206
  function releaseEntry(releaseDir) {
207
- return path12.join(releasePackageRoot(releaseDir), "bin", "orkestrator.js");
207
+ return path13.join(releasePackageRoot(releaseDir), "bin", "orkestrator.js");
208
208
  }
209
209
  function releaseLauncher(releaseDir) {
210
- return path12.join(releasePackageRoot(releaseDir), "bin", "orkestrator-launcher.js");
210
+ return path13.join(releasePackageRoot(releaseDir), "bin", "orkestrator-launcher.js");
211
211
  }
212
212
  function readReleaseManifest(releaseDir) {
213
- const manifest = readJson(path12.join(releasePackageRoot(releaseDir), "package.json"));
213
+ const manifest = readJson(path13.join(releasePackageRoot(releaseDir), "package.json"));
214
214
  if (!manifest || typeof manifest.version !== "string")
215
215
  return null;
216
- if (!existsSync3(releaseEntry(releaseDir)))
216
+ if (!existsSync4(releaseEntry(releaseDir)))
217
217
  return null;
218
218
  return {
219
219
  version: manifest.version,
@@ -226,11 +226,11 @@ async function installRelease(layout, version, options2) {
226
226
  return target;
227
227
  const run = options2.run ?? runFile;
228
228
  await mkdir(layout.releases, { recursive: true });
229
- const staging = path12.join(layout.releases, `.staging-${version}-${process.pid}`);
229
+ const staging = path13.join(layout.releases, `.staging-${version}-${process.pid}`);
230
230
  await rm(staging, { recursive: true, force: true });
231
231
  await mkdir(staging, { recursive: true });
232
232
  try {
233
- await writeFile(path12.join(staging, "package.json"), `${JSON.stringify({
233
+ await writeFile(path13.join(staging, "package.json"), `${JSON.stringify({
234
234
  name: "orkestrator-release",
235
235
  private: true,
236
236
  dependencies: { [STANDALONE_PACKAGE]: version }
@@ -4876,26 +4876,26 @@ import {
4876
4876
  symlink as symlink2,
4877
4877
  utimes
4878
4878
  } from "fs/promises";
4879
- import path14 from "path";
4879
+ import path15 from "path";
4880
4880
  import { pipeline } from "stream/promises";
4881
4881
  import { createGunzip } from "zlib";
4882
4882
  function delay(milliseconds) {
4883
4883
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
4884
4884
  }
4885
4885
  function artifactDirectory(rootDir, artifact) {
4886
- return path14.join(rootDir, artifact.name, artifact.version, `${artifact.platform}-${artifact.architecture}`);
4886
+ return path15.join(rootDir, artifact.name, artifact.version, `${artifact.platform}-${artifact.architecture}`);
4887
4887
  }
4888
4888
  function artifactExecutablePath(rootDir, artifact) {
4889
- return path14.join(artifactDirectory(rootDir, artifact), artifact.executable.fileName);
4889
+ return path15.join(artifactDirectory(rootDir, artifact), artifact.executable.fileName);
4890
4890
  }
4891
4891
  function upstreamExecutablePath(rootDir, artifact) {
4892
- return path14.join(artifactDirectory(rootDir, artifact), `.upstream-${artifact.executable.fileName}`);
4892
+ return path15.join(artifactDirectory(rootDir, artifact), `.upstream-${artifact.executable.fileName}`);
4893
4893
  }
4894
4894
  function artifactCompanions(artifact) {
4895
4895
  return artifact.companions ?? [];
4896
4896
  }
4897
4897
  function companionPath(rootDir, artifact, companion) {
4898
- return path14.join(artifactDirectory(rootDir, artifact), companion.fileName);
4898
+ return path15.join(artifactDirectory(rootDir, artifact), companion.fileName);
4899
4899
  }
4900
4900
  function artifactArchiveBytes(artifact) {
4901
4901
  return artifactCompanions(artifact).reduce((total, companion) => total + companion.archive.size, artifact.archive.size);
@@ -4919,7 +4919,7 @@ function assertValidArtifactMetadata(artifact) {
4919
4919
  }
4920
4920
  const paths = new Set;
4921
4921
  for (const file of artifact.archive.bundleFiles) {
4922
- const normalized = path14.posix.normalize(file.path);
4922
+ const normalized = path15.posix.normalize(file.path);
4923
4923
  if (normalized !== file.path || normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") || paths.has(normalized) || !Number.isSafeInteger(file.size) || file.size < 1 || !/^[a-f0-9]{64}$/.test(file.sha256)) {
4924
4924
  throw new Error(`${artifact.name} manifest has an unsafe bundle file`);
4925
4925
  }
@@ -4945,7 +4945,7 @@ function assertValidArtifactMetadata(artifact) {
4945
4945
  }
4946
4946
  const activationNames = new Set([artifact.name, artifact.executable.fileName]);
4947
4947
  for (const alias of artifact.activationAliases ?? []) {
4948
- if (!alias || alias.startsWith(".") || alias.includes("/") || alias.includes("\\") || alias !== path14.basename(alias) || activationNames.has(alias)) {
4948
+ if (!alias || alias.startsWith(".") || alias.includes("/") || alias.includes("\\") || alias !== path15.basename(alias) || activationNames.has(alias)) {
4949
4949
  throw new Error(`${artifact.name} manifest has an unsafe or duplicate activation alias`);
4950
4950
  }
4951
4951
  activationNames.add(alias);
@@ -4955,7 +4955,7 @@ function assertValidArtifactMetadata(artifact) {
4955
4955
  if (companion.archive.format !== "zip" && companion.archive.format !== "tar.gz") {
4956
4956
  throw new Error(`${artifact.name} companion has an unsupported archive format`);
4957
4957
  }
4958
- if (!companion.fileName || companion.fileName.startsWith(".") || companion.fileName.includes("/") || companion.fileName.includes("\\") || companion.fileName !== path14.basename(companion.fileName)) {
4958
+ if (!companion.fileName || companion.fileName.startsWith(".") || companion.fileName.includes("/") || companion.fileName.includes("\\") || companion.fileName !== path15.basename(companion.fileName)) {
4959
4959
  throw new Error(`${artifact.name} companion has an unsafe file name`);
4960
4960
  }
4961
4961
  if (companionNames.has(companion.fileName)) {
@@ -5021,7 +5021,7 @@ async function isValidExecutable(rootDir, artifact) {
5021
5021
  if (!await isValidUpstreamExecutable(rootDir, artifact))
5022
5022
  return false;
5023
5023
  for (const bundled of artifact.archive.bundleFiles ?? []) {
5024
- if (!await isValidFile(path14.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
5024
+ if (!await isValidFile(path15.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
5025
5025
  return false;
5026
5026
  }
5027
5027
  }
@@ -5038,7 +5038,7 @@ async function isValidExecutable(rootDir, artifact) {
5038
5038
  if ((file.mode & 511) !== 320)
5039
5039
  await chmod(executablePath, 320);
5040
5040
  for (const bundled of artifact.archive.bundleFiles ?? []) {
5041
- if (!await isValidFile(path14.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
5041
+ if (!await isValidFile(path15.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
5042
5042
  return false;
5043
5043
  }
5044
5044
  }
@@ -5059,7 +5059,7 @@ async function bundleTreeEntries(directory, artifact) {
5059
5059
  const visit = async (currentDirectory, prefix = "") => {
5060
5060
  for (const entry of await readdir2(currentDirectory, { withFileTypes: true })) {
5061
5061
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
5062
- const filePath = path14.join(currentDirectory, entry.name);
5062
+ const filePath = path15.join(currentDirectory, entry.name);
5063
5063
  if (entry.isDirectory()) {
5064
5064
  await visit(filePath, relativePath);
5065
5065
  } else if (entry.isFile() && !entry.isSymbolicLink()) {
@@ -5174,7 +5174,7 @@ async function initializeInstallLock(lockPath, owner, openLockFile) {
5174
5174
  }
5175
5175
  }
5176
5176
  async function acquireInstallLock(rootDir, onProgress, timings, openLockFile, ownerProcessExists) {
5177
- const lockPath = path14.join(rootDir, INSTALL_LOCK);
5177
+ const lockPath = path15.join(rootDir, INSTALL_LOCK);
5178
5178
  const startedAt = Date.now();
5179
5179
  let announcedWait = false;
5180
5180
  while (Date.now() - startedAt < timings.lockWaitTimeoutMs) {
@@ -5355,8 +5355,8 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
5355
5355
  stream.resume();
5356
5356
  return;
5357
5357
  }
5358
- const normalized = path14.posix.normalize(relative);
5359
- if (normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") || path14.isAbsolute(normalized)) {
5358
+ const normalized = path15.posix.normalize(relative);
5359
+ if (normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") || path15.isAbsolute(normalized)) {
5360
5360
  extract2.destroy(new Error(`Unsafe path in ${label} bundle`));
5361
5361
  stream.resume();
5362
5362
  return;
@@ -5368,7 +5368,7 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
5368
5368
  stream.resume();
5369
5369
  return;
5370
5370
  }
5371
- const destination = path14.join(destinationDirectory, ...normalized.split("/"));
5371
+ const destination = path15.join(destinationDirectory, ...normalized.split("/"));
5372
5372
  if (header.type === "directory") {
5373
5373
  stream.once("end", next);
5374
5374
  stream.resume();
@@ -5386,7 +5386,7 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
5386
5386
  }
5387
5387
  if (header.name === archive.entryPath)
5388
5388
  foundExecutable = true;
5389
- mkdir3(path14.dirname(destination), { recursive: true, mode: 448 }).then(() => pipeline(stream, createWriteStream(destination, {
5389
+ mkdir3(path15.dirname(destination), { recursive: true, mode: 448 }).then(() => pipeline(stream, createWriteStream(destination, {
5390
5390
  flags: "wx",
5391
5391
  mode: (header.mode ?? 0) & 73 ? 320 : 256
5392
5392
  }))).then(() => next(), (error) => extract2.destroy(error instanceof Error ? error : new Error(String(error))));
@@ -5548,7 +5548,7 @@ async function repairInvalidMacSignature(executablePath, artifact, timeoutMs, sp
5548
5548
  await runCodesign(["--sign", "-", "--force", executablePath], `${artifact.name} could not be ad-hoc signed after source verification`, timeoutMs, spawnProcess);
5549
5549
  }
5550
5550
  async function prepareRepairableExecutable(sourcePath, executablePath, artifact, skipExecutableProbeForTests, processTimeoutMs, spawnProcess) {
5551
- const temporaryPath = path14.join(path14.dirname(executablePath), `.repair-${artifact.executable.fileName}-${randomUUID4()}.tmp`);
5551
+ const temporaryPath = path15.join(path15.dirname(executablePath), `.repair-${artifact.executable.fileName}-${randomUUID4()}.tmp`);
5552
5552
  try {
5553
5553
  await copyFile2(sourcePath, temporaryPath);
5554
5554
  await chmod(temporaryPath, 448);
@@ -5580,8 +5580,8 @@ async function refreshRepairableArtifact(rootDir, artifact, skipExecutableProbeF
5580
5580
  async function stageCompanions(stagingDirectory, artifact, companions, fetchImpl, onBytes, startBytes, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
5581
5581
  let downloadedBytes = startBytes;
5582
5582
  for (const companion of companions) {
5583
- const companionArchivePath = path14.join(stagingDirectory, `.archive-${companion.fileName}.${archiveExtension(companion.archive)}`);
5584
- const companionFilePath = path14.join(stagingDirectory, companion.fileName);
5583
+ const companionArchivePath = path15.join(stagingDirectory, `.archive-${companion.fileName}.${archiveExtension(companion.archive)}`);
5584
+ const companionFilePath = path15.join(stagingDirectory, companion.fileName);
5585
5585
  const precedingBytes = downloadedBytes;
5586
5586
  await downloadArchive(companion.fileName, companion.archive, companionArchivePath, fetchImpl, (received) => onBytes(precedingBytes + received), allowInsecureDownloadsForTests, timings.downloadTimeoutMs);
5587
5587
  downloadedBytes += companion.archive.size;
@@ -5606,28 +5606,28 @@ async function stageCompanions(stagingDirectory, artifact, companions, fetchImpl
5606
5606
  }
5607
5607
  }
5608
5608
  async function repairArtifactCompanions(rootDir, artifact, companions, fetchImpl, onBytes, onVerify, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
5609
- const stagingDirectory = await mkdtemp2(path14.join(rootDir, `.staging-${artifact.name}-`));
5609
+ const stagingDirectory = await mkdtemp2(path15.join(rootDir, `.staging-${artifact.name}-`));
5610
5610
  try {
5611
5611
  await stageCompanions(stagingDirectory, artifact, companions, fetchImpl, onBytes, 0, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess);
5612
5612
  onVerify();
5613
5613
  const destinationDirectory = artifactDirectory(rootDir, artifact);
5614
5614
  for (const companion of companions) {
5615
- await rename2(path14.join(stagingDirectory, companion.fileName), path14.join(destinationDirectory, companion.fileName));
5615
+ await rename2(path15.join(stagingDirectory, companion.fileName), path15.join(destinationDirectory, companion.fileName));
5616
5616
  }
5617
5617
  } finally {
5618
5618
  await rm3(stagingDirectory, { recursive: true, force: true });
5619
5619
  }
5620
5620
  }
5621
5621
  async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
5622
- const stagingDirectory = await mkdtemp2(path14.join(rootDir, `.staging-${artifact.name}-`));
5623
- const archivePath = path14.join(stagingDirectory, `archive.${archiveExtension(artifact.archive)}`);
5624
- const executablePath = path14.join(stagingDirectory, artifact.executable.fileName);
5625
- const extractedPath = artifact.executable.repairInvalidMacSignature ? path14.join(stagingDirectory, `.upstream-${artifact.executable.fileName}`) : executablePath;
5622
+ const stagingDirectory = await mkdtemp2(path15.join(rootDir, `.staging-${artifact.name}-`));
5623
+ const archivePath = path15.join(stagingDirectory, `archive.${archiveExtension(artifact.archive)}`);
5624
+ const executablePath = path15.join(stagingDirectory, artifact.executable.fileName);
5625
+ const extractedPath = artifact.executable.repairInvalidMacSignature ? path15.join(stagingDirectory, `.upstream-${artifact.executable.fileName}`) : executablePath;
5626
5626
  try {
5627
5627
  await downloadArchive(artifact.name, artifact.archive, archivePath, fetchImpl, onBytes, allowInsecureDownloadsForTests, timings.downloadTimeoutMs);
5628
5628
  if (artifact.archive.bundleRoot) {
5629
5629
  await extractTarGzipBundle(archivePath, stagingDirectory, artifact.name, artifact.archive);
5630
- const bundledExecutable = path14.join(stagingDirectory, ...artifact.archive.entryPath.slice(artifact.archive.bundleRoot.length).split("/"));
5630
+ const bundledExecutable = path15.join(stagingDirectory, ...artifact.archive.entryPath.slice(artifact.archive.bundleRoot.length).split("/"));
5631
5631
  if (bundledExecutable !== extractedPath) {
5632
5632
  await rename2(bundledExecutable, extractedPath);
5633
5633
  }
@@ -5678,7 +5678,7 @@ async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify,
5678
5678
  await stageCompanions(stagingDirectory, artifact, artifactCompanions(artifact), fetchImpl, onBytes, artifact.archive.size, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess);
5679
5679
  onVerify();
5680
5680
  const destinationDirectory = artifactDirectory(rootDir, artifact);
5681
- await mkdir3(path14.dirname(destinationDirectory), { recursive: true, mode: 448 });
5681
+ await mkdir3(path15.dirname(destinationDirectory), { recursive: true, mode: 448 });
5682
5682
  await rm3(destinationDirectory, { recursive: true, force: true });
5683
5683
  await rename2(stagingDirectory, destinationDirectory);
5684
5684
  await chmod(destinationDirectory, 448);
@@ -5690,16 +5690,16 @@ async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify,
5690
5690
  }
5691
5691
  async function cleanStagingDirectories(rootDir) {
5692
5692
  const entries = await readdir2(rootDir, { withFileTypes: true });
5693
- await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(".staging-")).map((entry) => rm3(path14.join(rootDir, entry.name), { recursive: true, force: true })));
5693
+ await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(".staging-")).map((entry) => rm3(path15.join(rootDir, entry.name), { recursive: true, force: true })));
5694
5694
  }
5695
5695
  async function touchArtifactDirectory(rootDir, artifact) {
5696
5696
  const now = new Date;
5697
- await utimes(path14.dirname(artifactDirectory(rootDir, artifact)), now, now).catch(() => {
5697
+ await utimes(path15.dirname(artifactDirectory(rootDir, artifact)), now, now).catch(() => {
5698
5698
  return;
5699
5699
  });
5700
5700
  }
5701
5701
  function versionLeaseDirectory(rootDir, name, version) {
5702
- return path14.join(rootDir, LEASE_DIRECTORY, name, version);
5702
+ return path15.join(rootDir, LEASE_DIRECTORY, name, version);
5703
5703
  }
5704
5704
  async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
5705
5705
  const leaseKey = `${rootDir}\x00${artifact.name}\x00${artifact.version}`;
@@ -5714,7 +5714,7 @@ async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
5714
5714
  };
5715
5715
  const directory = versionLeaseDirectory(rootDir, artifact.name, artifact.version);
5716
5716
  await mkdir3(directory, { recursive: true, mode: 448 });
5717
- const leasePath = path14.join(directory, `${owner.pid}-${owner.token}.json`);
5717
+ const leasePath = path15.join(directory, `${owner.pid}-${owner.token}.json`);
5718
5718
  const handle = await openLeaseFile(leasePath, "wx", 384);
5719
5719
  try {
5720
5720
  await handle.writeFile(JSON.stringify(owner), "utf8");
@@ -5735,7 +5735,7 @@ async function versionHasLiveLease(rootDir, name, version, ownerProcessExists) {
5735
5735
  const entries = await readdir2(directory, { withFileTypes: true }).catch(() => []);
5736
5736
  let live = false;
5737
5737
  await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => {
5738
- const leasePath = path14.join(directory, entry.name);
5738
+ const leasePath = path15.join(directory, entry.name);
5739
5739
  try {
5740
5740
  const parsed = JSON.parse(await readFile2(leasePath, "utf8"));
5741
5741
  if (typeof parsed.token === "string" && typeof parsed.pid === "number" && typeof parsed.createdAt === "string" && ownerProcessExists(parsed.pid)) {
@@ -5758,11 +5758,11 @@ async function pruneSupersededVersions(rootDir, artifacts, retainSupersededMs, o
5758
5758
  }
5759
5759
  const staleBefore = Date.now() - retainSupersededMs;
5760
5760
  await Promise.all(Array.from(keepByName, async ([name, keep]) => {
5761
- const toolDir = path14.join(rootDir, name);
5761
+ const toolDir = path15.join(rootDir, name);
5762
5762
  try {
5763
5763
  const entries = await readdir2(toolDir, { withFileTypes: true });
5764
5764
  await Promise.all(entries.filter((entry) => entry.isDirectory() && !keep.has(entry.name)).map(async (entry) => {
5765
- const versionDir = path14.join(toolDir, entry.name);
5765
+ const versionDir = path15.join(toolDir, entry.name);
5766
5766
  try {
5767
5767
  if ((await stat2(versionDir)).mtimeMs > staleBefore)
5768
5768
  return;
@@ -5803,8 +5803,8 @@ function activationSetId(artifacts) {
5803
5803
  return createHash4("sha256").update(JSON.stringify(identity)).digest("hex");
5804
5804
  }
5805
5805
  async function activateExecutables(rootDir, artifacts) {
5806
- const binRoot = path14.join(rootDir, "bin");
5807
- const binDir = path14.join(binRoot, activationSetId(artifacts));
5806
+ const binRoot = path15.join(rootDir, "bin");
5807
+ const binDir = path15.join(binRoot, activationSetId(artifacts));
5808
5808
  await mkdir3(binRoot, { recursive: true, mode: 448 });
5809
5809
  const binRootEntry = await lstat(binRoot);
5810
5810
  if (!binRootEntry.isDirectory() || binRootEntry.isSymbolicLink()) {
@@ -5819,8 +5819,8 @@ async function activateExecutables(rootDir, artifacts) {
5819
5819
  await chmod(binDir, 448);
5820
5820
  const executables = {};
5821
5821
  const activate = async (linkName, target) => {
5822
- const activePath = path14.join(binDir, linkName);
5823
- const temporaryLink = path14.join(binDir, `.${linkName}-${randomUUID4()}.tmp`);
5822
+ const activePath = path15.join(binDir, linkName);
5823
+ const temporaryLink = path15.join(binDir, `.${linkName}-${randomUUID4()}.tmp`);
5824
5824
  const existingTarget = await readlink(activePath).catch(() => null);
5825
5825
  if (existingTarget !== target) {
5826
5826
  await symlink2(target, temporaryLink, "file");
@@ -5868,7 +5868,7 @@ async function ensurePinnedToolchains(options2) {
5868
5868
  const timings = { ...DEFAULT_TIMINGS, ...options2.timingsForTests };
5869
5869
  const spawnProcess = options2.spawnForTests ?? spawn;
5870
5870
  const ownerProcessExists = options2.processExistsForTests ?? ((pid) => processExists(pid, options2.processKillForTests));
5871
- const rootDir = path14.join(options2.dataDir, TOOLCHAIN_DIRECTORY);
5871
+ const rootDir = path15.join(options2.dataDir, TOOLCHAIN_DIRECTORY);
5872
5872
  const totalTools = artifacts.length;
5873
5873
  const toolFractions = new Map(artifacts.map((artifact) => [artifact.name, 0]));
5874
5874
  const progress = (value) => {
@@ -6033,7 +6033,7 @@ var init_agent_platforms = __esm(() => {
6033
6033
 
6034
6034
  // ../toolchain/src/platform-selection.ts
6035
6035
  import { promises as fs7 } from "fs";
6036
- import path15 from "path";
6036
+ import path16 from "path";
6037
6037
  async function readJsonFile(filePath) {
6038
6038
  try {
6039
6039
  return JSON.parse(await fs7.readFile(filePath, "utf8"));
@@ -6042,7 +6042,7 @@ async function readJsonFile(filePath) {
6042
6042
  }
6043
6043
  }
6044
6044
  async function loadAgentPlatformSelection(dataDir) {
6045
- const config = await readJsonFile(path15.join(dataDir, AGENT_CONFIG_FILE));
6045
+ const config = await readJsonFile(path16.join(dataDir, AGENT_CONFIG_FILE));
6046
6046
  if (config && typeof config === "object" && !Array.isArray(config)) {
6047
6047
  const global2 = config.global;
6048
6048
  if (global2 && typeof global2 === "object" && !Array.isArray(global2)) {
@@ -6058,7 +6058,7 @@ async function loadAgentPlatformSelection(dataDir) {
6058
6058
  needsFirstRunChoice: false
6059
6059
  };
6060
6060
  }
6061
- const sidecar = await readJsonFile(path15.join(dataDir, AGENT_PLATFORM_SELECTION_FILE));
6061
+ const sidecar = await readJsonFile(path16.join(dataDir, AGENT_PLATFORM_SELECTION_FILE));
6062
6062
  if (sidecar && typeof sidecar === "object" && !Array.isArray(sidecar)) {
6063
6063
  const enabled = normalizeAgentPlatforms(sidecar.enabled, []);
6064
6064
  if (enabled.length > 0)
@@ -6074,7 +6074,7 @@ var init_platform_selection = __esm(() => {
6074
6074
  // ../toolchain/src/install.ts
6075
6075
  import { randomUUID as randomUUID5 } from "crypto";
6076
6076
  import { lstat as lstat2, mkdir as mkdir4, readdir as readdir3, readlink as readlink2, rename as rename3, rm as rm4, symlink as symlink3 } from "fs/promises";
6077
- import path16 from "path";
6077
+ import path17 from "path";
6078
6078
  async function resolveToolchainSelection(options2) {
6079
6079
  const known = Object.keys(PINNED_TOOL_VERSIONS);
6080
6080
  const withSupportTools = (chosen) => known.filter((name) => chosen.has(name) || isSupportToolName(name));
@@ -6133,16 +6133,16 @@ async function installPinnedToolchains(options2) {
6133
6133
  };
6134
6134
  }
6135
6135
  async function toolsInCurrent(rootDir) {
6136
- const entries = await readdir3(path16.join(rootDir, "bin", CURRENT_TOOLCHAIN_LINK)).catch(() => []);
6136
+ const entries = await readdir3(path17.join(rootDir, "bin", CURRENT_TOOLCHAIN_LINK)).catch(() => []);
6137
6137
  return Object.keys(PINNED_TOOL_VERSIONS).filter((tool) => entries.includes(tool));
6138
6138
  }
6139
6139
  async function pointCurrentAt(rootDir, binDir) {
6140
- const binRoot = path16.join(rootDir, "bin");
6141
- if (path16.dirname(binDir) !== binRoot) {
6140
+ const binRoot = path17.join(rootDir, "bin");
6141
+ if (path17.dirname(binDir) !== binRoot) {
6142
6142
  throw new Error(`Activation directory ${binDir} is not directly inside ${binRoot}`);
6143
6143
  }
6144
- const link = path16.join(binRoot, CURRENT_TOOLCHAIN_LINK);
6145
- const target = path16.basename(binDir);
6144
+ const link = path17.join(binRoot, CURRENT_TOOLCHAIN_LINK);
6145
+ const target = path17.basename(binDir);
6146
6146
  await mkdir4(binRoot, { recursive: true, mode: 448 });
6147
6147
  const existing = await lstat2(link).catch(() => null);
6148
6148
  if (existing && !existing.isSymbolicLink()) {
@@ -6150,7 +6150,7 @@ async function pointCurrentAt(rootDir, binDir) {
6150
6150
  }
6151
6151
  if (existing && await readlink2(link) === target)
6152
6152
  return link;
6153
- const temporary = path16.join(binRoot, `.${CURRENT_TOOLCHAIN_LINK}-${randomUUID5()}.tmp`);
6153
+ const temporary = path17.join(binRoot, `.${CURRENT_TOOLCHAIN_LINK}-${randomUUID5()}.tmp`);
6154
6154
  await symlink3(target, temporary, "dir");
6155
6155
  try {
6156
6156
  await rename3(temporary, link);
@@ -7444,7 +7444,7 @@ import path4 from "path";
7444
7444
  // ../../apps/backend/package.json
7445
7445
  var package_default = {
7446
7446
  name: "@orkestrator/backend",
7447
- version: "2.26.2-beta",
7447
+ version: "2.27.0-beta",
7448
7448
  private: true,
7449
7449
  type: "module",
7450
7450
  exports: {
@@ -7508,6 +7508,7 @@ var DEFAULT_ALLOWED_DOMAINS = Object.freeze([
7508
7508
  var AGENT_NETWORK_DOMAINS_BY_PLATFORM = Object.freeze({
7509
7509
  codex: Object.freeze(["chatgpt.com", "auth.openai.com"]),
7510
7510
  cursor: Object.freeze([
7511
+ "api.cursor.com",
7511
7512
  "api2.cursor.sh",
7512
7513
  "api3.cursor.sh",
7513
7514
  "api4.cursor.sh",
@@ -8257,6 +8258,10 @@ function defaultSleep(ms, signal) {
8257
8258
  function signalName(signal) {
8258
8259
  return signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
8259
8260
  }
8261
+ function throwIfInterrupted(signal) {
8262
+ if (signal?.aborted)
8263
+ throw new CliError("observation-interrupted", "Interrupted");
8264
+ }
8260
8265
  async function observe(check, options2) {
8261
8266
  if (options2.timeoutMs > PUBLIC_API_LIMITS.waitMaxMs) {
8262
8267
  throw new CliError("invalid-input", "--timeout may be at most 24h");
@@ -10859,18 +10864,185 @@ var hostCommands = [
10859
10864
 
10860
10865
  // src/client/commands/service.ts
10861
10866
  import { spawnSync } from "child_process";
10862
- import { existsSync as existsSync4 } from "fs";
10867
+ import { existsSync as existsSync5 } from "fs";
10863
10868
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
10864
- import path13 from "path";
10869
+ import path14 from "path";
10870
+
10871
+ // src/client/service-connection.ts
10872
+ import path11 from "path";
10873
+ async function currentDescriptorGeneration(dataDir) {
10874
+ try {
10875
+ return (await readDescriptor(path11.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE))).generation;
10876
+ } catch {
10877
+ return null;
10878
+ }
10879
+ }
10880
+ async function awaitServiceConnection(dataDir, previousGeneration, options2) {
10881
+ const file = path11.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE);
10882
+ try {
10883
+ const descriptor = await observe(async () => {
10884
+ try {
10885
+ const value = await readDescriptor(file);
10886
+ const fresh = value.generation !== previousGeneration && path11.resolve(value.dataDir) === path11.resolve(dataDir) && processIsAlive(value.pid);
10887
+ return fresh ? { done: true, value } : { done: false };
10888
+ } catch {
10889
+ return { done: false };
10890
+ }
10891
+ }, options2);
10892
+ const connection = {
10893
+ browserUrl: descriptor.browserUrl ?? (isLoopbackHost(new URL(descriptor.url).hostname) || descriptor.endpoints?.some((endpoint) => endpoint.url === descriptor.url) ? null : descriptor.url),
10894
+ authFile: descriptor.authFile,
10895
+ token: null
10896
+ };
10897
+ try {
10898
+ connection.token = await readTokenFile(descriptor.authFile, true);
10899
+ } catch (error) {
10900
+ if (!(error instanceof CliError))
10901
+ throw error;
10902
+ connection.credentialError = error.message;
10903
+ }
10904
+ throwIfInterrupted(options2.signal);
10905
+ return connection;
10906
+ } catch (error) {
10907
+ if (error instanceof ObservationStopped) {
10908
+ if (error.reason === "deadline")
10909
+ return null;
10910
+ throw new CliError("observation-interrupted", "Stopped waiting for the backend to start");
10911
+ }
10912
+ throw error;
10913
+ }
10914
+ }
10915
+
10916
+ // src/client/service-setup.ts
10917
+ import { existsSync as existsSync3 } from "fs";
10918
+ import os5 from "os";
10919
+ var HOSTED_WEB_ORIGINS = "https://orkestrator.dev,https://www.orkestrator.dev";
10920
+ var SSH_LOOPBACK_PORT = "34125";
10921
+ var MACOS_TAILSCALE_APP_CLI = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
10922
+ var systemSetupProbe = {
10923
+ hasTailscaleAddress: () => Object.values(os5.networkInterfaces()).some((entries) => (entries ?? []).some((entry) => entry.family === "IPv4" && isTailscaleIPv4(entry.address))),
10924
+ findTailscaleCli: () => {
10925
+ if (process.platform === "darwin" && existsSync3(MACOS_TAILSCALE_APP_CLI)) {
10926
+ return MACOS_TAILSCALE_APP_CLI;
10927
+ }
10928
+ return typeof Bun === "undefined" ? null : Bun.which("tailscale");
10929
+ }
10930
+ };
10931
+ function isTailscaleIPv4(address) {
10932
+ const [first, second] = address.split(".").map(Number);
10933
+ return first === 100 && second !== undefined && second >= 64 && second <= 127;
10934
+ }
10935
+ function cancelled() {
10936
+ return new CliError("invalid-input", "Setup cancelled; nothing was installed");
10937
+ }
10938
+ async function ask(io, prompt) {
10939
+ const answer = await io.question(prompt);
10940
+ if (answer === null)
10941
+ throw cancelled();
10942
+ return answer.trim();
10943
+ }
10944
+ async function choose(io, title, choices) {
10945
+ io.stderr(`${title}
10946
+ `);
10947
+ choices.forEach((choice, index) => io.stderr(` ${index + 1}) ${choice.label}
10948
+ `));
10949
+ for (;; ) {
10950
+ const answer = await ask(io, `Choose 1-${choices.length} [1]: `);
10951
+ if (answer === "")
10952
+ return choices[0].value;
10953
+ const picked = choices[Number(answer) - 1];
10954
+ if (/^\d+$/.test(answer) && picked)
10955
+ return picked.value;
10956
+ io.stderr(`Enter a number from 1 to ${choices.length}.
10957
+ `);
10958
+ }
10959
+ }
10960
+ async function confirm(io, prompt, fallback) {
10961
+ for (;; ) {
10962
+ const answer = (await ask(io, `${prompt} ${fallback ? "[Y/n]" : "[y/N]"} `)).toLowerCase();
10963
+ if (answer === "")
10964
+ return fallback;
10965
+ if (answer === "y" || answer === "yes")
10966
+ return true;
10967
+ if (answer === "n" || answer === "no")
10968
+ return false;
10969
+ io.stderr(`Answer y or n.
10970
+ `);
10971
+ }
10972
+ }
10973
+ function quote(arg) {
10974
+ return /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
10975
+ }
10976
+ async function askServeArguments(io, options2) {
10977
+ const source = io;
10978
+ io = {
10979
+ ...source,
10980
+ question: async (prompt) => {
10981
+ throwIfInterrupted(options2.signal);
10982
+ const answer = await source.question(prompt, options2.signal);
10983
+ throwIfInterrupted(options2.signal);
10984
+ return answer;
10985
+ }
10986
+ };
10987
+ throwIfInterrupted(options2.signal);
10988
+ const probe = options2.probe ?? systemSetupProbe;
10989
+ io.stderr(`Set up the Orkestrator backend service.
10990
+
10991
+ `);
10992
+ const access = await choose(io, "How will you connect to this machine?", [
10993
+ {
10994
+ value: "serve",
10995
+ label: "Tailscale with HTTPS (Tailscale Serve): the desktop app, the CLI and orkestrator.dev"
10996
+ },
10997
+ { value: "tailnet", label: "Tailscale address over HTTP: the desktop app and the CLI" },
10998
+ { value: "ssh", label: "SSH only: the desktop app through an SSH tunnel, no Tailscale" }
10999
+ ]);
11000
+ const args = [];
11001
+ if (access === "ssh")
11002
+ args.push("--host", "127.0.0.1", "--allow-non-tailscale-bind");
11003
+ if (access !== "ssh" && !probe.hasTailscaleAddress()) {
11004
+ io.stderr(`
11005
+ This machine has no Tailscale address yet. Connect Tailscale before the service starts, or it will not serve.
11006
+ `);
11007
+ }
11008
+ if (access === "serve") {
11009
+ args.push("--tailscale-serve");
11010
+ if (!probe.findTailscaleCli()) {
11011
+ io.stderr(`
11012
+ The Tailscale CLI was not found, and Tailscale Serve needs it.
11013
+ `);
11014
+ const cli = await ask(io, "Path to the tailscale executable (empty to keep `tailscale`): ");
11015
+ if (cli !== "")
11016
+ args.push("--tailscale-bin", cli);
11017
+ }
11018
+ if (await confirm(io, `
11019
+ Allow the hosted web client at orkestrator.dev?`, true)) {
11020
+ args.push("--allowed-origins", HOSTED_WEB_ORIGINS);
11021
+ }
11022
+ }
11023
+ const ssh = access === "ssh" || await confirm(io, `
11024
+ Also accept desktop connections over SSH (loopback port ${SSH_LOOPBACK_PORT})?`, false);
11025
+ if (ssh)
11026
+ args.push("--loopback-client-port", SSH_LOOPBACK_PORT);
11027
+ const command = [...options2.installCommand, "--", ...args].map(quote).join(" ");
11028
+ io.stderr(`
11029
+ Equivalent command, for scripts:
11030
+ ${command}
11031
+
11032
+ `);
11033
+ if (!await confirm(io, "Install with these settings?", true))
11034
+ throw cancelled();
11035
+ return args;
11036
+ }
10865
11037
 
10866
11038
  // src/client/service-units.ts
10867
11039
  import { createHash as createHash3 } from "crypto";
10868
- import os5 from "os";
10869
- import path11 from "path";
11040
+ import os6 from "os";
11041
+ import path12 from "path";
10870
11042
  function suffix(dataDir, defaultDataDir) {
10871
- if (path11.resolve(dataDir) === path11.resolve(defaultDataDir))
11043
+ if (path12.resolve(dataDir) === path12.resolve(defaultDataDir))
10872
11044
  return "";
10873
- return `-${createHash3("sha256").update(path11.resolve(dataDir)).digest("hex").slice(0, 8)}`;
11045
+ return `-${createHash3("sha256").update(path12.resolve(dataDir)).digest("hex").slice(0, 8)}`;
10874
11046
  }
10875
11047
  function xml(text) {
10876
11048
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
@@ -10879,7 +11051,7 @@ function systemdWord(text) {
10879
11051
  return `"${text.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`;
10880
11052
  }
10881
11053
  function serviceUnit(options2) {
10882
- const home = options2.home ?? os5.homedir();
11054
+ const home = options2.home ?? os6.homedir();
10883
11055
  const tag = suffix(options2.dataDir, options2.defaultDataDir);
10884
11056
  const pathValue = options2.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
10885
11057
  if (options2.platform === "darwin") {
@@ -10889,7 +11061,7 @@ function serviceUnit(options2) {
10889
11061
  return {
10890
11062
  manager: "launchd",
10891
11063
  name,
10892
- file: path11.join(home, "Library", "LaunchAgents", `${name}.plist`),
11064
+ file: path12.join(home, "Library", "LaunchAgents", `${name}.plist`),
10893
11065
  contents: `<?xml version="1.0" encoding="UTF-8"?>
10894
11066
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
10895
11067
  <plist version="1.0">
@@ -10923,11 +11095,11 @@ ${args}
10923
11095
  };
10924
11096
  }
10925
11097
  const name = `orkestrator-backend${tag}.service`;
10926
- const configHome = options2.env.XDG_CONFIG_HOME ?? path11.join(home, ".config");
11098
+ const configHome = options2.env.XDG_CONFIG_HOME ?? path12.join(home, ".config");
10927
11099
  return {
10928
11100
  manager: "systemd",
10929
11101
  name,
10930
- file: path11.join(configHome, "systemd", "user", name),
11102
+ file: path12.join(configHome, "systemd", "user", name),
10931
11103
  contents: `[Unit]
10932
11104
  Description=Orkestrator backend (${options2.dataDir})
10933
11105
  After=network-online.target
@@ -10980,9 +11152,26 @@ var DATA_DIR_OPTION = {
10980
11152
  valueName: "DIR",
10981
11153
  description: "Backend data directory. Defaults to ORKESTRATOR_DATA_DIR, then the platform default."
10982
11154
  };
11155
+ var DEFAULT_READY_WAIT_MS = 3 * 60 * 1000;
10983
11156
  var RESERVED_SERVE_FLAGS = new Set(["--data-dir", "--standalone-updater", "--desktop-updater"]);
10984
11157
  function dataDirectory(context, parsed) {
10985
- return path13.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
11158
+ return path14.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
11159
+ }
11160
+ function installCommandLine(parsed) {
11161
+ const line = ["bunx", "orkestrator", "service", "install"];
11162
+ for (const name of ["data-dir", "release", "timeout"]) {
11163
+ const value = parsed.options[name];
11164
+ if (typeof value === "string")
11165
+ line.push(`--${name}`, value);
11166
+ else if (typeof value === "number")
11167
+ line.push(`--${name}`, `${Math.ceil(value / 1000)}s`);
11168
+ }
11169
+ for (const name of ["skip-start", "dry-run"]) {
11170
+ if (parsed.options[name] === true)
11171
+ line.push(`--${name}`);
11172
+ }
11173
+ line.push("--yes");
11174
+ return line;
10986
11175
  }
10987
11176
  function unitFor(context, dataDir, bun) {
10988
11177
  if (process.platform !== "darwin" && process.platform !== "linux") {
@@ -10999,7 +11188,7 @@ function unitFor(context, dataDir, bun) {
10999
11188
  defaultDataDir: defaultDataDir(process.platform, context.io.env),
11000
11189
  bun,
11001
11190
  launcher: layout.launcher,
11002
- logFile: path13.join(layout.logs, "service.log"),
11191
+ logFile: path14.join(layout.logs, "service.log"),
11003
11192
  env: context.io.env
11004
11193
  })
11005
11194
  };
@@ -11025,8 +11214,13 @@ var serviceCommands = [
11025
11214
  "previous release when a new one fails to start twice.",
11026
11215
  "",
11027
11216
  "Arguments after `--` are passed to `orkestrator serve` (for example `--tailscale-serve`).",
11217
+ "With none, in a terminal, it asks how clients will connect (Tailscale Serve, the",
11218
+ "tailnet address or SSH) and prints the equivalent command; --yes skips the questions.",
11028
11219
  "The Bun running this command is the Bun the service uses; reinstall to change it.",
11029
- "On Linux, run `loginctl enable-linger $USER` so the service runs without a login."
11220
+ "On Linux, run `loginctl enable-linger $USER` so the service runs without a login.",
11221
+ "",
11222
+ "Once the service is running, the address it serves and its gateway token are printed.",
11223
+ "The token is shown only in this terminal: never in --output json or the service log."
11030
11224
  ].join(`
11031
11225
  `),
11032
11226
  positionals: [],
@@ -11047,11 +11241,23 @@ var serviceCommands = [
11047
11241
  name: "dry-run",
11048
11242
  kind: "boolean",
11049
11243
  description: "Show what would be written without installing anything."
11244
+ },
11245
+ {
11246
+ name: "yes",
11247
+ kind: "boolean",
11248
+ description: "Install with the arguments after `--` (or none) without asking."
11249
+ },
11250
+ {
11251
+ name: "timeout",
11252
+ kind: "duration",
11253
+ valueName: "DURATION",
11254
+ description: "How long to wait for the service to start before printing its address and token. Default 3m; 0 skips the wait."
11050
11255
  }
11051
11256
  ],
11052
11257
  acceptsRest: true,
11053
11258
  local: true,
11054
11259
  examples: [
11260
+ "orkestrator service install",
11055
11261
  "orkestrator service install -- --tailscale-serve",
11056
11262
  "orkestrator service install --release 2.22.0 -- --host 100.64.0.1",
11057
11263
  "orkestrator service install --dry-run"
@@ -11066,6 +11272,15 @@ var serviceCommands = [
11066
11272
  if (reserved) {
11067
11273
  throw new CliError("invalid-input", `${reserved} is set by the service; leave it out`);
11068
11274
  }
11275
+ const interactive = parsed.rest.length === 0 && parsed.options.yes !== true && context.global.output === "human" && context.io.stdinIsTty && context.io.question !== undefined;
11276
+ const readyWaitMs = typeof parsed.options.timeout === "number" ? parsed.options.timeout : DEFAULT_READY_WAIT_MS;
11277
+ if (readyWaitMs > PUBLIC_API_LIMITS.waitMaxMs) {
11278
+ throw new CliError("invalid-input", "--timeout may be at most 24h");
11279
+ }
11280
+ const serveArgs = interactive ? await askServeArguments(context.io, {
11281
+ installCommand: installCommandLine(parsed),
11282
+ signal: context.signal
11283
+ }) : parsed.rest;
11069
11284
  const bun = process.execPath;
11070
11285
  const bunVersion = typeof Bun === "undefined" ? "" : Bun.version;
11071
11286
  const { service, layout, unit } = await unitFor(context, dataDir, bun);
@@ -11074,7 +11289,7 @@ var serviceCommands = [
11074
11289
  version,
11075
11290
  bun,
11076
11291
  bunVersion,
11077
- args: parsed.rest,
11292
+ args: serveArgs,
11078
11293
  unit: { manager: unit.manager, name: unit.name, file: unit.file },
11079
11294
  release: service.releaseDirectory(layout, version)
11080
11295
  };
@@ -11088,7 +11303,7 @@ var serviceCommands = [
11088
11303
  ["release", `${version} \u2192 ${summary.release}`],
11089
11304
  ["bun", `${bun} (${bunVersion})`],
11090
11305
  ["unit", `${unit.manager} ${unit.name} \u2192 ${unit.file}`],
11091
- ["serve arguments", parsed.rest.join(" ") || "(none)"]
11306
+ ["serve arguments", serveArgs.join(" ") || "(none)"]
11092
11307
  ]),
11093
11308
  "",
11094
11309
  "Dry run: nothing was installed."
@@ -11097,6 +11312,7 @@ var serviceCommands = [
11097
11312
  }
11098
11313
  context.io.stderr(`Installing orkestrator@${version}\u2026
11099
11314
  `);
11315
+ throwIfInterrupted(context.signal);
11100
11316
  let releaseDir;
11101
11317
  try {
11102
11318
  releaseDir = await service.installRelease(layout, version, { bun });
@@ -11110,28 +11326,59 @@ var serviceCommands = [
11110
11326
  dataDir,
11111
11327
  bun,
11112
11328
  bunVersion,
11113
- args: parsed.rest,
11329
+ args: serveArgs,
11114
11330
  installedAt: new Date().toISOString()
11115
11331
  });
11116
11332
  await mkdir2(layout.logs, { recursive: true });
11117
- await mkdir2(path13.dirname(unit.file), { recursive: true });
11333
+ await mkdir2(path14.dirname(unit.file), { recursive: true });
11118
11334
  await writeFile2(unit.file, unit.contents);
11119
11335
  const started = parsed.options["skip-start"] !== true;
11336
+ const logFile = path14.join(layout.logs, "service.log");
11337
+ let connection = null;
11120
11338
  if (started) {
11339
+ const previousGeneration = await currentDescriptorGeneration(dataDir);
11121
11340
  const commands = serviceManagerCommands(unit);
11122
11341
  commands.load.forEach((argv, index) => runManager(argv, unit.manager === "launchd" && index === 0));
11342
+ if (readyWaitMs > 0) {
11343
+ context.io.stderr(`Waiting for the backend to start\u2026
11344
+ `);
11345
+ connection = await awaitServiceConnection(dataDir, previousGeneration, {
11346
+ timeoutMs: readyWaitMs,
11347
+ signal: context.signal
11348
+ });
11349
+ }
11123
11350
  }
11124
11351
  return {
11125
11352
  action: "service.install",
11126
- result: { dryRun: false, started, ...summary },
11353
+ result: {
11354
+ dryRun: false,
11355
+ started,
11356
+ ready: connection !== null,
11357
+ browserUrl: connection?.browserUrl ?? null,
11358
+ authFile: connection?.authFile ?? null,
11359
+ ...connection?.credentialError ? { credentialError: connection.credentialError } : {},
11360
+ ...summary
11361
+ },
11127
11362
  human: [
11128
11363
  ...keyValues([
11129
11364
  ["release", `${version} (${releaseDir})`],
11130
11365
  ["unit", `${unit.manager} ${unit.name}`],
11131
- ["logs", path13.join(layout.logs, "service.log")]
11366
+ ["logs", logFile]
11132
11367
  ]),
11133
11368
  "",
11134
11369
  started ? "The service is running. Remote clients can update it from Settings \u2192 Machines or `orkestrator host update`." : `The unit is written but not loaded; load it with: ${serviceManagerCommands(unit).load.at(-1).join(" ")}`,
11370
+ ...connection ? [
11371
+ "",
11372
+ ...keyValues([
11373
+ ["address", connection.browserUrl ?? "(no browser listener; see the log)"],
11374
+ ["auth file", connection.authFile],
11375
+ ...context.io.stdoutIsTty && context.global.output === "human" && connection.token ? [["token", connection.token]] : []
11376
+ ]),
11377
+ ...connection.credentialError ? [`Credential file error: ${connection.credentialError}`] : context.io.stdoutIsTty && context.global.output === "human" ? ["Keep the token private: it grants full access to this backend."] : ["Read the gateway token locally from the private auth file."]
11378
+ ] : started && readyWaitMs > 0 ? [
11379
+ "",
11380
+ `The backend has not reported ready yet. Follow ${logFile}; its address and token file are logged there.`
11381
+ ] : [],
11135
11382
  ...unit.manager === "systemd" ? ["Run `loginctl enable-linger $USER` so it keeps running after you log out."] : []
11136
11383
  ]
11137
11384
  };
@@ -11154,7 +11401,7 @@ var serviceCommands = [
11154
11401
  const dataDir = dataDirectory(context, parsed);
11155
11402
  const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
11156
11403
  const config = service.readServiceConfig(layout);
11157
- if (existsSync4(unit.file)) {
11404
+ if (existsSync5(unit.file)) {
11158
11405
  for (const argv of serviceManagerCommands(unit).unload)
11159
11406
  runManager(argv, true);
11160
11407
  await rm2(unit.file, { force: true });
@@ -11182,7 +11429,7 @@ var serviceCommands = [
11182
11429
  const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
11183
11430
  const config = service.readServiceConfig(layout);
11184
11431
  const state = service.readLauncherState(layout);
11185
- const installed = existsSync4(unit.file);
11432
+ const installed = existsSync5(unit.file);
11186
11433
  const running = installed ? runManager(serviceManagerCommands(unit).status, true).ok : false;
11187
11434
  const result = {
11188
11435
  dataDir,
@@ -12375,9 +12622,9 @@ var sessionCommands = [
12375
12622
  ];
12376
12623
 
12377
12624
  // src/client/commands/toolchain.ts
12378
- import path17 from "path";
12625
+ import path18 from "path";
12379
12626
  function dataDirectory2(context, parsed) {
12380
- return path17.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
12627
+ return path18.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
12381
12628
  }
12382
12629
  function requestedTools(parsed) {
12383
12630
  const raw = parsed.options.tool;
@@ -12480,7 +12727,7 @@ var toolchainCommands = [
12480
12727
  const rows = installed.tools.map((tool) => ({
12481
12728
  tool,
12482
12729
  version: installed.versions[tool] ?? "",
12483
- executable: path17.join(installed.currentBinDir, path17.basename(installed.executables[tool]))
12730
+ executable: path18.join(installed.currentBinDir, path18.basename(installed.executables[tool]))
12484
12731
  }));
12485
12732
  return {
12486
12733
  action: "toolchain.install",
@@ -12744,6 +12991,53 @@ function defaultHuman(result) {
12744
12991
  `);
12745
12992
  }
12746
12993
 
12994
+ // src/client/questions.ts
12995
+ import { createInterface } from "readline/promises";
12996
+ function terminalQuestions(input, output, onInterrupt) {
12997
+ let reader;
12998
+ let ended = false;
12999
+ return {
13000
+ async question(prompt, signal) {
13001
+ throwIfInterrupted(signal);
13002
+ if (ended)
13003
+ return null;
13004
+ if (!reader) {
13005
+ reader = createInterface({ input, output });
13006
+ reader.once("close", () => {
13007
+ ended = true;
13008
+ });
13009
+ reader.on("SIGINT", onInterrupt);
13010
+ }
13011
+ const current = reader;
13012
+ let onClose = () => {};
13013
+ let onAbort = () => {};
13014
+ try {
13015
+ return await new Promise((resolve, reject) => {
13016
+ onClose = () => resolve(null);
13017
+ onAbort = () => {
13018
+ reject(new CliError("observation-interrupted", "Interrupted"));
13019
+ current.close();
13020
+ };
13021
+ current.once("close", onClose);
13022
+ signal?.addEventListener("abort", onAbort, { once: true });
13023
+ if (signal?.aborted) {
13024
+ onAbort();
13025
+ return;
13026
+ }
13027
+ current.question(prompt).then(resolve, reject);
13028
+ });
13029
+ } finally {
13030
+ current.off("close", onClose);
13031
+ signal?.removeEventListener("abort", onAbort);
13032
+ }
13033
+ },
13034
+ close() {
13035
+ ended = true;
13036
+ reader?.close();
13037
+ }
13038
+ };
13039
+ }
13040
+
12747
13041
  // src/client/main.ts
12748
13042
  var CLIENT_COMMANDS = [
12749
13043
  ...connectionCommands,
@@ -12864,6 +13158,7 @@ async function runClientProcess(argv, version) {
12864
13158
  };
12865
13159
  process.on("SIGINT", onSignal);
12866
13160
  process.on("SIGTERM", onSignal);
13161
+ const questions = terminalQuestions(process.stdin, process.stderr, () => onSignal("SIGINT"));
12867
13162
  const io = {
12868
13163
  stdout: (text) => process.stdout.write(text),
12869
13164
  stdoutBytes: (bytes) => process.stdout.write(Buffer.from(bytes)),
@@ -12875,11 +13170,14 @@ async function runClientProcess(argv, version) {
12875
13170
  env: process.env,
12876
13171
  cwd: process.cwd(),
12877
13172
  now: () => Date.now(),
12878
- stdinIsTty: Boolean(process.stdin.isTTY)
13173
+ stdinIsTty: Boolean(process.stdin.isTTY),
13174
+ stdoutIsTty: Boolean(process.stdout.isTTY),
13175
+ question: questions.question
12879
13176
  };
12880
13177
  try {
12881
13178
  return await runClient(argv, { io, version, signal: controller.signal });
12882
13179
  } finally {
13180
+ questions.close();
12883
13181
  process.off("SIGINT", onSignal);
12884
13182
  process.off("SIGTERM", onSignal);
12885
13183
  }