@rudderhq/cli 0.1.0-canary.2 → 0.1.0-canary.4

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/index.js CHANGED
@@ -1109,7 +1109,7 @@ var init_secret = __esm({
1109
1109
 
1110
1110
  // ../packages/shared/src/validators/agent.ts
1111
1111
  import { z as z10 } from "zod";
1112
- var agentPermissionsSchema, agentInstructionsBundleModeSchema, updateAgentInstructionsBundleSchema, upsertAgentInstructionsFileSchema, agentRuntimeConfigSchema, optionalAgentNameSchema, createAgentSchema, createAgentHireSchema, updateAgentSchema, updateAgentInstructionsPathSchema, createAgentKeySchema, wakeAgentSchema, resetAgentSessionSchema, testAgentRuntimeEnvironmentSchema, updateAgentPermissionsSchema;
1112
+ var agentPermissionsSchema, agentInstructionsBundleModeSchema, updateAgentInstructionsBundleSchema, upsertAgentInstructionsFileSchema, agentRuntimeConfigSchema, optionalAgentNameSchema, uploadedAgentIconSchema, customAgentIconSchema, agentIconSchema, createAgentSchema, createAgentHireSchema, updateAgentSchema, updateAgentInstructionsPathSchema, createAgentKeySchema, wakeAgentSchema, resetAgentSessionSchema, testAgentRuntimeEnvironmentSchema, updateAgentPermissionsSchema;
1113
1113
  var init_agent = __esm({
1114
1114
  "../packages/shared/src/validators/agent.ts"() {
1115
1115
  "use strict";
@@ -1150,11 +1150,28 @@ var init_agent = __esm({
1150
1150
  },
1151
1151
  z10.string().trim().min(1).optional()
1152
1152
  );
1153
+ uploadedAgentIconSchema = z10.string().regex(
1154
+ /^asset:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
1155
+ "Invalid uploaded avatar reference"
1156
+ );
1157
+ customAgentIconSchema = z10.string().trim().min(1).max(24).refine((value) => !value.toLowerCase().startsWith("asset:"), "Invalid uploaded avatar reference").refine((value) => !/[<>\u0000-\u001f\u007f]/u.test(value), "Icon cannot contain markup or control characters");
1158
+ agentIconSchema = z10.preprocess(
1159
+ (value) => {
1160
+ if (typeof value !== "string") return value;
1161
+ const trimmed = value.trim();
1162
+ return trimmed.length > 0 ? trimmed : null;
1163
+ },
1164
+ z10.union([
1165
+ z10.enum(AGENT_ICON_NAMES),
1166
+ uploadedAgentIconSchema,
1167
+ customAgentIconSchema
1168
+ ]).nullable()
1169
+ );
1153
1170
  createAgentSchema = z10.object({
1154
1171
  name: optionalAgentNameSchema,
1155
1172
  role: z10.enum(AGENT_ROLES).optional().default("general"),
1156
1173
  title: z10.string().optional().nullable(),
1157
- icon: z10.enum(AGENT_ICON_NAMES).optional().nullable(),
1174
+ icon: agentIconSchema.optional(),
1158
1175
  reportsTo: z10.string().uuid().optional().nullable(),
1159
1176
  capabilities: z10.string().optional().nullable(),
1160
1177
  desiredSkills: z10.array(z10.string().min(1)).optional(),
@@ -7866,6 +7883,9 @@ function isTransientBinaryPath(candidatePath) {
7866
7883
  return normalized.includes("/_npx/");
7867
7884
  }
7868
7885
  function hasGlobalInstalledPackage(packageName, execFileSyncImpl = execFileSync) {
7886
+ return getGlobalInstalledPackageVersion(packageName, execFileSyncImpl) !== null;
7887
+ }
7888
+ function getGlobalInstalledPackageVersion(packageName, execFileSyncImpl = execFileSync) {
7869
7889
  try {
7870
7890
  const output = execFileSyncImpl(
7871
7891
  process.platform === "win32" ? "npm.cmd" : "npm",
@@ -7876,9 +7896,9 @@ function hasGlobalInstalledPackage(packageName, execFileSyncImpl = execFileSync)
7876
7896
  }
7877
7897
  );
7878
7898
  const parsed = JSON.parse(output);
7879
- return Boolean(parsed.dependencies?.[packageName]?.version);
7899
+ return parsed.dependencies?.[packageName]?.version ?? null;
7880
7900
  } catch {
7881
- return false;
7901
+ return null;
7882
7902
  }
7883
7903
  }
7884
7904
  function hasPersistentBinaryOnPath(execFileSyncImpl = execFileSync) {
@@ -9878,21 +9898,25 @@ ${err instanceof Error ? err.message : String(err)}`
9878
9898
 
9879
9899
  // src/commands/start.ts
9880
9900
  init_install();
9881
- import { spawnSync as spawnSync2 } from "node:child_process";
9901
+ import { spawn, spawnSync as spawnSync2 } from "node:child_process";
9882
9902
  import { createHash as createHash3 } from "node:crypto";
9883
- import { createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync } from "node:fs";
9884
- import { chmod, mkdtemp } from "node:fs/promises";
9885
- import { tmpdir } from "node:os";
9903
+ import { constants as fsConstants, createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync } from "node:fs";
9904
+ import { access, chmod, copyFile, cp, mkdtemp, mkdir, readFile as readFile3, readdir as readdir2, rm, writeFile as writeFile2 } from "node:fs/promises";
9905
+ import { homedir, tmpdir } from "node:os";
9886
9906
  import path9 from "node:path";
9887
9907
  import { Readable } from "node:stream";
9888
9908
  import { pipeline } from "node:stream/promises";
9909
+ import { setTimeout as delay } from "node:timers/promises";
9889
9910
  import { fileURLToPath as fileURLToPath3 } from "node:url";
9890
9911
  import * as p13 from "@clack/prompts";
9891
9912
  import pc8 from "picocolors";
9892
9913
  var DEFAULT_DESKTOP_RELEASE_REPO = "Undertone0809/rudder";
9914
+ var DESKTOP_UPDATE_QUIT_ARG = "--rudder-update-quit";
9893
9915
  var STABLE_SEMVER_RE = /^[0-9]+\.[0-9]+\.[0-9]+$/;
9894
9916
  var CANARY_SEMVER_RE = /^[0-9]+\.[0-9]+\.[0-9]+-canary\.[0-9]+$/;
9895
9917
  var CLI_REGISTRY_LATEST_URL = "https://registry.npmjs.org/@rudderhq%2fcli/latest";
9918
+ var DESKTOP_APP_NAME = "Rudder";
9919
+ var DESKTOP_METADATA_FILE = ".rudder-desktop-install.json";
9896
9920
  function resolveCurrentCliVersion(env = process.env) {
9897
9921
  const envPackageName = env.npm_package_name?.trim();
9898
9922
  const envPackageVersion = env.npm_package_version?.trim();
@@ -9916,6 +9940,9 @@ function resolveCliInstallSpec(version, env = process.env) {
9916
9940
  if (version && version !== "latest") return `${CLI_NPM_PACKAGE_NAME}@${version}`;
9917
9941
  return resolvePersistentCliInstallSpec(env);
9918
9942
  }
9943
+ function isPersistentCliVersionCurrent(version, installedVersion) {
9944
+ return Boolean(version && version !== "latest" && installedVersion === version);
9945
+ }
9919
9946
  function compareStableSemver(a, b) {
9920
9947
  const aMatch = a.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)$/);
9921
9948
  const bMatch = b.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)$/);
@@ -9955,18 +9982,59 @@ function resolveDesktopReleaseTag(version) {
9955
9982
  if (STABLE_SEMVER_RE.test(version)) return `v${version}`;
9956
9983
  if (CANARY_SEMVER_RE.test(version)) return `canary/v${version}`;
9957
9984
  throw new Error(
9958
- `Desktop installer lookup requires a release version like 0.1.0 or 0.1.0-canary.0. Received ${version}.`
9985
+ `Desktop release lookup requires a release version like 0.1.0 or 0.1.0-canary.0. Received ${version}.`
9959
9986
  );
9960
9987
  }
9961
9988
  function resolveDesktopAssetTarget(platform = process.platform, arch = process.arch) {
9962
- const normalizedArch = arch === "x64" || arch === "arm64" ? arch : null;
9963
- if (!normalizedArch) {
9964
- throw new Error(`Rudder Desktop does not publish installers for ${platform}/${arch}.`);
9989
+ if (platform === "darwin") {
9990
+ if (arch !== "x64" && arch !== "arm64") {
9991
+ throw new Error(`Rudder Desktop does not publish portable assets for ${platform}/${arch}.`);
9992
+ }
9993
+ return { platform: "macos", arch, extension: ".zip" };
9965
9994
  }
9966
- if (platform === "darwin") return { platform: "macos", arch: normalizedArch, extension: ".dmg" };
9967
- if (platform === "win32") return { platform: "windows", arch: normalizedArch, extension: ".exe" };
9968
- if (platform === "linux") return { platform: "linux", arch: normalizedArch, extension: ".AppImage" };
9969
- throw new Error(`Rudder Desktop does not publish installers for ${platform}.`);
9995
+ if (platform === "win32") return { platform: "windows", arch: "x64", extension: ".zip" };
9996
+ if (platform === "linux") {
9997
+ if (arch !== "x64") {
9998
+ throw new Error(`Rudder Desktop does not publish portable assets for ${platform}/${arch}.`);
9999
+ }
10000
+ return { platform: "linux", arch: "x64", extension: ".AppImage" };
10001
+ }
10002
+ throw new Error(`Rudder Desktop does not publish portable assets for ${platform}.`);
10003
+ }
10004
+ function resolveDefaultDesktopInstallRoot(target, env = process.env, homeDir = homedir()) {
10005
+ if (target.platform === "macos") return path9.join(homeDir, "Applications");
10006
+ if (target.platform === "windows") {
10007
+ const localAppData = env.LOCALAPPDATA?.trim() || path9.join(homeDir, "AppData", "Local");
10008
+ return path9.join(localAppData, "Programs", DESKTOP_APP_NAME);
10009
+ }
10010
+ return path9.join(homeDir, ".local", "share", "rudder");
10011
+ }
10012
+ function resolveDesktopInstallPaths(target, installRoot) {
10013
+ const root = path9.resolve(installRoot);
10014
+ if (target.platform === "macos") {
10015
+ const appPath2 = path9.join(root, `${DESKTOP_APP_NAME}.app`);
10016
+ return {
10017
+ installRoot: root,
10018
+ appPath: appPath2,
10019
+ executablePath: path9.join(appPath2, "Contents", "MacOS", DESKTOP_APP_NAME),
10020
+ metadataPath: path9.join(root, DESKTOP_METADATA_FILE)
10021
+ };
10022
+ }
10023
+ if (target.platform === "windows") {
10024
+ return {
10025
+ installRoot: root,
10026
+ appPath: root,
10027
+ executablePath: path9.join(root, `${DESKTOP_APP_NAME}.exe`),
10028
+ metadataPath: path9.join(root, DESKTOP_METADATA_FILE)
10029
+ };
10030
+ }
10031
+ const appPath = path9.join(root, `${DESKTOP_APP_NAME}.AppImage`);
10032
+ return {
10033
+ installRoot: root,
10034
+ appPath,
10035
+ executablePath: appPath,
10036
+ metadataPath: path9.join(root, DESKTOP_METADATA_FILE)
10037
+ };
9970
10038
  }
9971
10039
  function normalizeAssetName(name) {
9972
10040
  return name.toLowerCase().replaceAll("_", "-").replaceAll(" ", "-");
@@ -9979,6 +10047,7 @@ function scoreDesktopAsset(asset, target) {
9979
10047
  let score = 1;
9980
10048
  if (normalized.includes("rudder")) score += 2;
9981
10049
  if (normalized.includes(target.platform)) score += 4;
10050
+ if (normalized.includes("portable")) score += 6;
9982
10051
  if (target.platform === "macos" && (normalized.includes("macos") || normalized.includes("darwin") || normalized.includes("mac-"))) {
9983
10052
  score += 4;
9984
10053
  }
@@ -10044,28 +10113,284 @@ function parseChecksumFile(contents) {
10044
10113
  }
10045
10114
  return checksums;
10046
10115
  }
10047
- async function verifyChecksum(installerPath, checksumAsset, outputDir) {
10048
- if (!checksumAsset) return false;
10116
+ function resolveAssetChecksum(checksums, assetName) {
10117
+ const expected = checksums.get(path9.basename(assetName));
10118
+ if (!expected) {
10119
+ throw new Error(`Desktop release checksums do not include ${path9.basename(assetName)}.`);
10120
+ }
10121
+ return expected;
10122
+ }
10123
+ function assertChecksumMatch(filePath, expected) {
10124
+ const actual = checksumForFile(filePath);
10125
+ if (actual !== expected.toLowerCase()) {
10126
+ throw new Error(`Checksum mismatch for ${path9.basename(filePath)}.`);
10127
+ }
10128
+ return actual;
10129
+ }
10130
+ async function downloadChecksums(checksumAsset, outputDir) {
10131
+ if (!checksumAsset) {
10132
+ throw new Error("Desktop release is missing SHASUMS256.txt.");
10133
+ }
10049
10134
  const checksumPath = await downloadAsset(checksumAsset, outputDir);
10050
- const checksums = parseChecksumFile(readFileSync(checksumPath, "utf8"));
10051
- const expected = checksums.get(path9.basename(installerPath));
10052
- if (!expected) return false;
10053
- const actual = checksumForFile(installerPath);
10054
- if (actual !== expected) {
10055
- throw new Error(`Checksum mismatch for ${path9.basename(installerPath)}.`);
10135
+ return parseChecksumFile(readFileSync(checksumPath, "utf8"));
10136
+ }
10137
+ async function pathExists(targetPath) {
10138
+ try {
10139
+ await access(targetPath, fsConstants.F_OK);
10140
+ return true;
10141
+ } catch {
10142
+ return false;
10056
10143
  }
10057
- return true;
10058
10144
  }
10059
- function openInstaller(installerPath, target) {
10145
+ function runChecked(command, args, options = {}) {
10146
+ const result = spawnSync2(command, args, {
10147
+ encoding: "utf8",
10148
+ stdio: ["ignore", "pipe", "pipe"],
10149
+ ...options
10150
+ });
10151
+ if (result.status === 0) return;
10152
+ const output = [result.stdout, result.stderr].filter((value) => typeof value === "string" && value.trim().length > 0).join("\n").trim();
10153
+ throw new Error(`${command} ${args.join(" ")} failed${output ? `: ${output}` : ""}`);
10154
+ }
10155
+ function powershellQuote(value) {
10156
+ return `'${value.replaceAll("'", "''")}'`;
10157
+ }
10158
+ async function extractZip(zipPath, outputDir, target) {
10159
+ await rm(outputDir, { recursive: true, force: true });
10160
+ await mkdir(outputDir, { recursive: true });
10161
+ if (target.platform === "macos") {
10162
+ runChecked("ditto", ["-x", "-k", zipPath, outputDir]);
10163
+ return;
10164
+ }
10165
+ if (target.platform === "windows") {
10166
+ runChecked("powershell.exe", [
10167
+ "-NoProfile",
10168
+ "-ExecutionPolicy",
10169
+ "Bypass",
10170
+ "-Command",
10171
+ `Expand-Archive -LiteralPath ${powershellQuote(zipPath)} -DestinationPath ${powershellQuote(outputDir)} -Force`
10172
+ ]);
10173
+ return;
10174
+ }
10175
+ throw new Error(`Zip assets are not supported for ${target.platform}.`);
10176
+ }
10177
+ async function findPath(root, predicate, maxDepth = 5) {
10178
+ async function visit(dir, depth) {
10179
+ const entries = await readdir2(dir, { withFileTypes: true });
10180
+ for (const entry of entries) {
10181
+ const fullPath = path9.join(dir, entry.name);
10182
+ if (predicate(fullPath, entry.isDirectory())) return fullPath;
10183
+ if (entry.isDirectory() && depth < maxDepth) {
10184
+ const nested = await visit(fullPath, depth + 1);
10185
+ if (nested) return nested;
10186
+ }
10187
+ }
10188
+ return null;
10189
+ }
10190
+ return await visit(root, 0);
10191
+ }
10192
+ async function findMacApp(extractDir) {
10193
+ const direct = path9.join(extractDir, `${DESKTOP_APP_NAME}.app`);
10194
+ if (await pathExists(direct)) return direct;
10195
+ const found = await findPath(extractDir, (filePath, isDirectory) => isDirectory && path9.basename(filePath) === `${DESKTOP_APP_NAME}.app`);
10196
+ if (!found) throw new Error(`Portable macOS archive did not contain ${DESKTOP_APP_NAME}.app.`);
10197
+ return found;
10198
+ }
10199
+ async function findWindowsAppDir(extractDir) {
10200
+ const direct = path9.join(extractDir, `${DESKTOP_APP_NAME}.exe`);
10201
+ if (await pathExists(direct)) return extractDir;
10202
+ const executable = await findPath(extractDir, (filePath, isDirectory) => !isDirectory && path9.basename(filePath).toLowerCase() === `${DESKTOP_APP_NAME.toLowerCase()}.exe`);
10203
+ if (!executable) throw new Error(`Portable Windows archive did not contain ${DESKTOP_APP_NAME}.exe.`);
10204
+ return path9.dirname(executable);
10205
+ }
10206
+ async function readInstallMetadata(metadataPath) {
10207
+ try {
10208
+ const parsed = JSON.parse(await readFile3(metadataPath, "utf8"));
10209
+ if (parsed.version !== 1) return null;
10210
+ return parsed;
10211
+ } catch {
10212
+ return null;
10213
+ }
10214
+ }
10215
+ function isInstalledDesktopCurrent(metadata, releaseTag, assetName, assetChecksum) {
10216
+ return Boolean(
10217
+ metadata && metadata.releaseTag === releaseTag && metadata.assetName === assetName && metadata.assetChecksum === assetChecksum
10218
+ );
10219
+ }
10220
+ function buildForceQuitCommand(target) {
10221
+ if (target.platform === "windows") return { command: "taskkill.exe", args: ["/IM", `${DESKTOP_APP_NAME}.exe`, "/T", "/F"] };
10222
+ return { command: "pkill", args: ["-x", DESKTOP_APP_NAME] };
10223
+ }
10224
+ function forceQuitDesktopProcesses(target) {
10225
+ const command = buildForceQuitCommand(target);
10226
+ spawnSync2(command.command, command.args, { stdio: "ignore" });
10227
+ }
10228
+ function isRunningInsideDesktopExecutable() {
10229
+ return path9.basename(process.execPath).toLowerCase().startsWith(DESKTOP_APP_NAME.toLowerCase());
10230
+ }
10231
+ async function waitForUpdateQuitResponse(responsePath, timeoutMs = 8e3) {
10232
+ const startedAt = Date.now();
10233
+ while (Date.now() - startedAt < timeoutMs) {
10234
+ if (await pathExists(responsePath)) {
10235
+ return JSON.parse(await readFile3(responsePath, "utf8"));
10236
+ }
10237
+ await delay(200);
10238
+ }
10239
+ return null;
10240
+ }
10241
+ async function requestDesktopQuit(executablePath, target) {
10242
+ if (!await pathExists(executablePath)) return { ok: true, status: "not_running" };
10243
+ const responsePath = path9.join(tmpdir(), `rudder-update-quit-${process.pid}-${Date.now()}.json`);
10244
+ const result = spawnSync2(executablePath, [`${DESKTOP_UPDATE_QUIT_ARG}=${responsePath}`], {
10245
+ stdio: "ignore",
10246
+ timeout: 5e3
10247
+ });
10248
+ if (result.error && target.platform === "windows") {
10249
+ return null;
10250
+ }
10251
+ try {
10252
+ return await waitForUpdateQuitResponse(responsePath);
10253
+ } finally {
10254
+ await rm(responsePath, { force: true });
10255
+ }
10256
+ }
10257
+ async function removePathWithRetry(targetPath, attempts = 5) {
10258
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
10259
+ try {
10260
+ await rm(targetPath, { recursive: true, force: true });
10261
+ if (!await pathExists(targetPath)) return true;
10262
+ } catch {
10263
+ }
10264
+ await delay(500);
10265
+ }
10266
+ return false;
10267
+ }
10268
+ async function prepareForDesktopReplace(paths, target) {
10269
+ const hasManagedExecutable = await pathExists(paths.executablePath);
10270
+ if (hasManagedExecutable) {
10271
+ const quitResponse = await requestDesktopQuit(paths.executablePath, target);
10272
+ if (quitResponse && !quitResponse.ok && quitResponse.status === "active_runs") {
10273
+ throw new Error(
10274
+ `Rudder Desktop has ${quitResponse.totalRuns} active run${quitResponse.totalRuns === 1 ? "" : "s"}. Stop active work, then rerun start.`
10275
+ );
10276
+ }
10277
+ await delay(1e3);
10278
+ } else if (!isRunningInsideDesktopExecutable()) {
10279
+ forceQuitDesktopProcesses(target);
10280
+ }
10281
+ const replacePath = target.platform === "windows" ? paths.installRoot : paths.appPath;
10282
+ if (await removePathWithRetry(replacePath)) return;
10283
+ forceQuitDesktopProcesses(target);
10284
+ await delay(1e3);
10285
+ if (await removePathWithRetry(replacePath, 6)) return;
10286
+ throw new Error(`Failed to replace existing Rudder Desktop at ${replacePath}. Close Rudder and rerun start.`);
10287
+ }
10288
+ async function installPortableDesktop(installerPath, paths, target) {
10289
+ await mkdir(paths.installRoot, { recursive: true });
10290
+ if (target.platform === "linux") {
10291
+ await copyFile(installerPath, paths.appPath);
10292
+ await chmod(paths.appPath, 493);
10293
+ return;
10294
+ }
10295
+ const extractDir = await mkdtemp(path9.join(tmpdir(), "rudder-desktop-extract."));
10296
+ try {
10297
+ await extractZip(installerPath, extractDir, target);
10298
+ if (target.platform === "macos") {
10299
+ const appSource2 = await findMacApp(extractDir);
10300
+ await cp(appSource2, paths.appPath, { recursive: true });
10301
+ return;
10302
+ }
10303
+ const appSource = await findWindowsAppDir(extractDir);
10304
+ await mkdir(path9.dirname(paths.installRoot), { recursive: true });
10305
+ await cp(appSource, paths.installRoot, { recursive: true });
10306
+ } finally {
10307
+ await rm(extractDir, { recursive: true, force: true });
10308
+ }
10309
+ }
10310
+ async function removeMacQuarantine(paths, target) {
10311
+ if (target.platform !== "macos") return;
10312
+ const result = spawnSync2("xattr", ["-dr", "com.apple.quarantine", paths.appPath], { stdio: "ignore" });
10313
+ if (result.status !== 0) {
10314
+ p13.log.warn(`Could not remove macOS quarantine attributes from ${paths.appPath}.`);
10315
+ }
10316
+ }
10317
+ function quoteDesktopExec(value) {
10318
+ return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
10319
+ }
10320
+ function buildLinuxDesktopEntry(executablePath) {
10321
+ return [
10322
+ "[Desktop Entry]",
10323
+ "Type=Application",
10324
+ "Name=Rudder",
10325
+ `Exec=${quoteDesktopExec(executablePath)}`,
10326
+ "Terminal=false",
10327
+ "Categories=Development;",
10328
+ ""
10329
+ ].join("\n");
10330
+ }
10331
+ async function writeLinuxLaunchers(paths) {
10332
+ const desktopDir = path9.join(homedir(), ".local", "share", "applications");
10333
+ await mkdir(desktopDir, { recursive: true });
10334
+ await writeFile2(path9.join(desktopDir, "rudder.desktop"), buildLinuxDesktopEntry(paths.executablePath), "utf8");
10335
+ const binDir = path9.join(homedir(), ".local", "bin");
10336
+ await mkdir(binDir, { recursive: true });
10337
+ const wrapperPath = path9.join(binDir, "rudder-desktop");
10338
+ const escaped = paths.executablePath.replaceAll("'", `'"'"'`);
10339
+ await writeFile2(wrapperPath, `#!/bin/sh
10340
+ exec '${escaped}' "$@"
10341
+ `, "utf8");
10342
+ await chmod(wrapperPath, 493);
10343
+ }
10344
+ function buildWindowsShortcutScript(executablePath) {
10345
+ const appData = process.env.APPDATA?.trim() || path9.join(homedir(), "AppData", "Roaming");
10346
+ const shortcutPath = path9.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Rudder.lnk");
10347
+ return [
10348
+ "$shell = New-Object -ComObject WScript.Shell",
10349
+ `$shortcut = $shell.CreateShortcut(${powershellQuote(shortcutPath)})`,
10350
+ `$shortcut.TargetPath = ${powershellQuote(executablePath)}`,
10351
+ `$shortcut.WorkingDirectory = ${powershellQuote(path9.dirname(executablePath))}`,
10352
+ "$shortcut.Save()"
10353
+ ].join("; ");
10354
+ }
10355
+ async function createPlatformLaunchers(paths, target) {
10356
+ if (target.platform === "linux") {
10357
+ await writeLinuxLaunchers(paths);
10358
+ return;
10359
+ }
10360
+ if (target.platform === "windows") {
10361
+ const result = spawnSync2("powershell.exe", [
10362
+ "-NoProfile",
10363
+ "-ExecutionPolicy",
10364
+ "Bypass",
10365
+ "-Command",
10366
+ buildWindowsShortcutScript(paths.executablePath)
10367
+ ], { stdio: "ignore" });
10368
+ if (result.status !== 0) p13.log.warn("Could not create the Windows Start Menu shortcut.");
10369
+ }
10370
+ }
10371
+ function launchDesktop(paths, target) {
10060
10372
  if (target.platform === "macos") {
10061
- spawnSync2("open", [installerPath], { stdio: "inherit" });
10373
+ spawn("open", [paths.appPath], { detached: true, stdio: "ignore" }).unref();
10062
10374
  return;
10063
10375
  }
10064
10376
  if (target.platform === "windows") {
10065
- spawnSync2("cmd.exe", ["/c", "start", "", installerPath], { stdio: "inherit" });
10377
+ spawn("cmd.exe", ["/c", "start", "", paths.executablePath], { detached: true, stdio: "ignore" }).unref();
10066
10378
  return;
10067
10379
  }
10068
- spawnSync2("xdg-open", [installerPath], { stdio: "inherit" });
10380
+ spawn(paths.executablePath, [], { detached: true, stdio: "ignore" }).unref();
10381
+ }
10382
+ async function writeInstallMetadata(paths, releaseTag, assetName, assetChecksum) {
10383
+ mkdirSync2(path9.dirname(paths.metadataPath), { recursive: true });
10384
+ const metadata = {
10385
+ version: 1,
10386
+ releaseTag,
10387
+ assetName,
10388
+ assetChecksum,
10389
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
10390
+ };
10391
+ mkdirSync2(paths.installRoot, { recursive: true });
10392
+ await writeFile2(paths.metadataPath, `${JSON.stringify(metadata, null, 2)}
10393
+ `, "utf8");
10069
10394
  }
10070
10395
  async function startCommand(opts) {
10071
10396
  const installCli = opts.cli !== false;
@@ -10084,8 +10409,11 @@ async function startCommand(opts) {
10084
10409
  if (installCli) {
10085
10410
  const installSpec = resolveCliInstallSpec(version);
10086
10411
  const command = `npm install --global ${installSpec}`;
10412
+ const installedVersion = getGlobalInstalledPackageVersion(CLI_NPM_PACKAGE_NAME);
10087
10413
  p13.log.step("Preparing persistent CLI");
10088
- if (dryRun) {
10414
+ if (isPersistentCliVersionCurrent(version, installedVersion)) {
10415
+ p13.log.success(`${pc8.cyan("rudder")} CLI ${version} is already installed.`);
10416
+ } else if (dryRun) {
10089
10417
  p13.log.message(`[dry-run] ${command}`);
10090
10418
  } else {
10091
10419
  p13.log.message(pc8.dim(`Running: ${command}`));
@@ -10100,32 +10428,45 @@ async function startCommand(opts) {
10100
10428
  if (installDesktop) {
10101
10429
  const target = resolveDesktopAssetTarget();
10102
10430
  const tag = resolveDesktopReleaseTag(version);
10431
+ const installRoot = opts.desktopInstallDir ? path9.resolve(opts.desktopInstallDir) : resolveDefaultDesktopInstallRoot(target);
10432
+ const installPaths = resolveDesktopInstallPaths(target, installRoot);
10103
10433
  const outputDir = opts.outputDir ? path9.resolve(opts.outputDir) : await mkdtemp(path9.join(tmpdir(), "rudder-desktop-installer."));
10104
- p13.log.step("Starting desktop app");
10434
+ p13.log.step("Installing desktop app");
10105
10435
  p13.log.message(`Release: ${pc8.cyan(`${repo}@${tag}`)}`);
10106
10436
  p13.log.message(`Target: ${pc8.cyan(`${target.platform}/${target.arch}`)}`);
10437
+ p13.log.message(`Install: ${pc8.cyan(installPaths.appPath)}`);
10107
10438
  if (dryRun) {
10108
- p13.log.message(`[dry-run] Would resolve and download/open the matching desktop installer to ${outputDir}`);
10439
+ p13.log.message(`[dry-run] Would resolve, download, verify, install, and ${opts.open === false ? "not launch" : "launch"} Rudder Desktop.`);
10109
10440
  p13.outro(pc8.green("Dry run complete."));
10110
10441
  return;
10111
10442
  }
10112
10443
  const release = await fetchGithubRelease(repo, tag);
10113
10444
  const asset = selectDesktopAsset(release.assets ?? [], target);
10114
10445
  if (!asset) {
10115
- throw new Error(`No Rudder Desktop installer found for ${target.platform}/${target.arch} in ${repo}@${release.tag_name}.`);
10116
- }
10117
- const installerPath = await downloadAsset(asset, outputDir);
10118
- const checksumVerified = await verifyChecksum(installerPath, selectChecksumAsset(release.assets ?? []), outputDir);
10119
- if (target.platform === "linux") {
10120
- await chmod(installerPath, 493);
10446
+ throw new Error(`No Rudder Desktop portable asset found for ${target.platform}/${target.arch} in ${repo}@${release.tag_name}.`);
10447
+ }
10448
+ const checksums = await downloadChecksums(selectChecksumAsset(release.assets ?? []), outputDir);
10449
+ const expectedChecksum = resolveAssetChecksum(checksums, asset.name);
10450
+ const metadata = await readInstallMetadata(installPaths.metadataPath);
10451
+ if (isInstalledDesktopCurrent(metadata, release.tag_name, asset.name, expectedChecksum) && await pathExists(installPaths.executablePath)) {
10452
+ p13.log.success(`Rudder Desktop is already installed at ${pc8.cyan(installPaths.appPath)}.`);
10453
+ await removeMacQuarantine(installPaths, target);
10454
+ await createPlatformLaunchers(installPaths, target);
10455
+ } else {
10456
+ const installerPath = await downloadAsset(asset, outputDir);
10457
+ const checksum = assertChecksumMatch(installerPath, expectedChecksum);
10458
+ p13.log.success(`Downloaded and verified ${pc8.cyan(path9.basename(installerPath))}`);
10459
+ p13.log.message("Replacing existing Rudder Desktop if needed.");
10460
+ await prepareForDesktopReplace(installPaths, target);
10461
+ await installPortableDesktop(installerPath, installPaths, target);
10462
+ await removeMacQuarantine(installPaths, target);
10463
+ await createPlatformLaunchers(installPaths, target);
10464
+ await writeInstallMetadata(installPaths, release.tag_name, asset.name, checksum);
10465
+ p13.log.success(`Installed Rudder Desktop to ${pc8.cyan(installPaths.appPath)}.`);
10121
10466
  }
10122
- p13.log.success(`Downloaded ${pc8.cyan(path9.basename(installerPath))}`);
10123
- if (checksumVerified) p13.log.success("Verified SHA-256 checksum.");
10124
10467
  if (opts.open !== false) {
10125
- openInstaller(installerPath, target);
10126
- p13.log.message(`Installer path: ${pc8.cyan(installerPath)}`);
10127
- } else {
10128
- p13.log.message(`Installer path: ${pc8.cyan(installerPath)}`);
10468
+ launchDesktop(installPaths, target);
10469
+ p13.log.success("Rudder Desktop launched.");
10129
10470
  }
10130
10471
  }
10131
10472
  p13.outro(pc8.green("Rudder start complete."));
@@ -10167,7 +10508,7 @@ async function addAllowedHostname(host, opts) {
10167
10508
  }
10168
10509
 
10169
10510
  // src/commands/heartbeat-run.ts
10170
- import { setTimeout as delay } from "node:timers/promises";
10511
+ import { setTimeout as delay2 } from "node:timers/promises";
10171
10512
  import pc19 from "picocolors";
10172
10513
 
10173
10514
  // ../packages/agent-runtimes/claude-local/src/cli/format-event.ts
@@ -11160,7 +11501,7 @@ function getCLIAdapter(type) {
11160
11501
  import pc18 from "picocolors";
11161
11502
 
11162
11503
  // src/client/board-auth.ts
11163
- import { spawn } from "node:child_process";
11504
+ import { spawn as spawn2 } from "node:child_process";
11164
11505
  import fs10 from "node:fs";
11165
11506
  import path10 from "node:path";
11166
11507
  import pc17 from "picocolors";
@@ -11277,16 +11618,16 @@ function openUrl(url) {
11277
11618
  const platform = process.platform;
11278
11619
  try {
11279
11620
  if (platform === "darwin") {
11280
- const child2 = spawn("open", [url], { detached: true, stdio: "ignore" });
11621
+ const child2 = spawn2("open", [url], { detached: true, stdio: "ignore" });
11281
11622
  child2.unref();
11282
11623
  return true;
11283
11624
  }
11284
11625
  if (platform === "win32") {
11285
- const child2 = spawn("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
11626
+ const child2 = spawn2("cmd", ["/c", "start", "", url], { detached: true, stdio: "ignore" });
11286
11627
  child2.unref();
11287
11628
  return true;
11288
11629
  }
11289
- const child = spawn("xdg-open", [url], { detached: true, stdio: "ignore" });
11630
+ const child = spawn2("xdg-open", [url], { detached: true, stdio: "ignore" });
11290
11631
  child.unref();
11291
11632
  return true;
11292
11633
  } catch {
@@ -12015,7 +12356,7 @@ async function heartbeatRun(opts) {
12015
12356
  logOffset += Buffer.byteLength(logResult.content, "utf8");
12016
12357
  }
12017
12358
  }
12018
- await delay(POLL_INTERVAL_MS);
12359
+ await delay2(POLL_INTERVAL_MS);
12019
12360
  }
12020
12361
  if (finalStatus) {
12021
12362
  if (!debug && stdoutJsonBuffer.trim()) {
@@ -12230,7 +12571,7 @@ function registerContextCommands(program) {
12230
12571
  }
12231
12572
 
12232
12573
  // src/commands/client/company.ts
12233
- import { mkdir, readdir as readdir2, readFile as readFile3, stat, writeFile as writeFile2 } from "node:fs/promises";
12574
+ import { mkdir as mkdir2, readdir as readdir3, readFile as readFile4, stat, writeFile as writeFile3 } from "node:fs/promises";
12234
12575
  import path14 from "node:path";
12235
12576
  import * as p16 from "@clack/prompts";
12236
12577
  import pc22 from "picocolors";
@@ -12963,7 +13304,7 @@ function normalizeGithubImportSource(input, refOverride) {
12963
13304
  }
12964
13305
  return buildGithubImportUrl({ owner, repo, ref });
12965
13306
  }
12966
- async function pathExists(inputPath) {
13307
+ async function pathExists2(inputPath) {
12967
13308
  try {
12968
13309
  await stat(path14.resolve(inputPath));
12969
13310
  return true;
@@ -12972,7 +13313,7 @@ async function pathExists(inputPath) {
12972
13313
  }
12973
13314
  }
12974
13315
  async function collectPackageFiles(root, current, files) {
12975
- const entries = await readdir2(current, { withFileTypes: true });
13316
+ const entries = await readdir3(current, { withFileTypes: true });
12976
13317
  for (const entry of entries) {
12977
13318
  if (entry.name.startsWith(".git")) continue;
12978
13319
  const absolutePath = path14.join(current, entry.name);
@@ -12983,14 +13324,14 @@ async function collectPackageFiles(root, current, files) {
12983
13324
  if (!entry.isFile()) continue;
12984
13325
  const relativePath = path14.relative(root, absolutePath).replace(/\\/g, "/");
12985
13326
  if (!shouldIncludePortableFile(relativePath)) continue;
12986
- files[relativePath] = readPortableFileEntry(relativePath, await readFile3(absolutePath));
13327
+ files[relativePath] = readPortableFileEntry(relativePath, await readFile4(absolutePath));
12987
13328
  }
12988
13329
  }
12989
13330
  async function resolveInlineSourceFromPath(inputPath) {
12990
13331
  const resolved = path14.resolve(inputPath);
12991
13332
  const resolvedStat = await stat(resolved);
12992
13333
  if (resolvedStat.isFile() && path14.extname(resolved).toLowerCase() === ".zip") {
12993
- const archive = await readZipArchive(await readFile3(resolved));
13334
+ const archive = await readZipArchive(await readFile4(resolved));
12994
13335
  const filteredFiles = Object.fromEntries(
12995
13336
  Object.entries(archive.files).filter(([relativePath]) => shouldIncludePortableFile(relativePath))
12996
13337
  );
@@ -13009,16 +13350,16 @@ async function resolveInlineSourceFromPath(inputPath) {
13009
13350
  }
13010
13351
  async function writeExportToFolder(outDir, exported) {
13011
13352
  const root = path14.resolve(outDir);
13012
- await mkdir(root, { recursive: true });
13353
+ await mkdir2(root, { recursive: true });
13013
13354
  for (const [relativePath, content] of Object.entries(exported.files)) {
13014
13355
  const normalized = relativePath.replace(/\\/g, "/");
13015
13356
  const filePath = path14.join(root, normalized);
13016
- await mkdir(path14.dirname(filePath), { recursive: true });
13357
+ await mkdir2(path14.dirname(filePath), { recursive: true });
13017
13358
  const writeValue = portableFileEntryToWriteValue(content);
13018
13359
  if (typeof writeValue === "string") {
13019
- await writeFile2(filePath, writeValue, "utf8");
13360
+ await writeFile3(filePath, writeValue, "utf8");
13020
13361
  } else {
13021
- await writeFile2(filePath, writeValue);
13362
+ await writeFile3(filePath, writeValue);
13022
13363
  }
13023
13364
  }
13024
13365
  }
@@ -13029,7 +13370,7 @@ async function confirmOverwriteExportDirectory(outDir) {
13029
13370
  if (!stats.isDirectory()) {
13030
13371
  throw new Error(`Export output path ${root} exists and is not a directory.`);
13031
13372
  }
13032
- const entries = await readdir2(root);
13373
+ const entries = await readdir3(root);
13033
13374
  if (entries.length === 0) return;
13034
13375
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
13035
13376
  throw new Error(`Export output directory ${root} already contains files. Re-run interactively or choose an empty directory.`);
@@ -13226,7 +13567,7 @@ function registerCompanyCommands(program) {
13226
13567
  throw new Error("Target existing organization requires --org-id (or context default orgId).");
13227
13568
  }
13228
13569
  let sourcePayload;
13229
- const treatAsLocalPath = !isHttpUrl(from) && await pathExists(from);
13570
+ const treatAsLocalPath = !isHttpUrl(from) && await pathExists2(from);
13230
13571
  const isGithubSource = isGithubUrl(from) || isGithubShorthand(from) && !treatAsLocalPath;
13231
13572
  if (isHttpUrl(from) || isGithubSource) {
13232
13573
  if (!isGithubUrl(from) && !isGithubShorthand(from)) {
@@ -14272,7 +14613,7 @@ function filterIssueRows(rows, match) {
14272
14613
  init_src();
14273
14614
 
14274
14615
  // ../packages/agent-runtime-utils/src/server-utils.ts
14275
- import { constants as fsConstants, promises as fs12 } from "node:fs";
14616
+ import { constants as fsConstants2, promises as fs12 } from "node:fs";
14276
14617
  import path15 from "node:path";
14277
14618
  var MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
14278
14619
  var MAX_EXCERPT_BYTES = 32 * 1024;
@@ -19173,7 +19514,7 @@ var DATA_DIR_OPTION_HELP = "Rudder data directory root (isolates state from ~/.r
19173
19514
  var LOCAL_ENV_OPTION_HELP = "Local environment profile (dev, prod_local, e2e)";
19174
19515
  function createProgram() {
19175
19516
  const program = new Command();
19176
- program.name("rudder").description("Rudder CLI \u2014 setup, diagnose, and configure your instance").version("0.1.0-canary.2");
19517
+ program.name("rudder").description("Rudder CLI \u2014 setup, diagnose, and configure your instance").version("0.1.0-canary.4");
19177
19518
  program.option("--local-env <name>", LOCAL_ENV_OPTION_HELP);
19178
19519
  program.hook("preAction", (_thisCommand, actionCommand) => {
19179
19520
  const options = actionCommand.optsWithGlobals();
@@ -19185,7 +19526,7 @@ function createProgram() {
19185
19526
  });
19186
19527
  loadRudderEnvFile(options.config);
19187
19528
  });
19188
- program.command("start").description("Start Rudder Desktop and prepare the matching persistent CLI").option("--no-cli", "Skip persistent CLI installation").option("--no-desktop", "Skip desktop app installation").option("--version <version>", "Rudder version to start (default: current CLI version)").option("--repo <owner/repo>", "GitHub repository that hosts desktop releases").option("--output-dir <path>", "Directory for the downloaded desktop installer").option("--no-open", "Download the desktop installer without opening it").option("--no-version-check", "Skip checking npm for a newer Rudder CLI version").option("--dry-run", "Print the start actions without changing the machine", false).action(startCommand);
19529
+ program.command("start").description("Start Rudder Desktop and prepare the matching persistent CLI").option("--no-cli", "Skip persistent CLI installation").option("--no-desktop", "Skip desktop app installation").option("--version <version>", "Rudder version to start (default: current CLI version)").option("--repo <owner/repo>", "GitHub repository that hosts desktop releases").option("--output-dir <path>", "Directory for downloaded desktop release assets").option("--desktop-install-dir <path>", "Directory for the portable Desktop install").option("--no-open", "Install Desktop without launching it").option("--no-version-check", "Skip checking npm for a newer Rudder CLI version").option("--dry-run", "Print the start actions without changing the machine", false).action(startCommand);
19189
19530
  program.command("onboard").description("Interactive first-run setup wizard").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("-y, --yes", "Accept defaults (quickstart + start immediately)", false).option("--run", "Start Rudder immediately after saving config", false).action(onboard);
19190
19531
  program.command("doctor").description("Run diagnostic checks on your Rudder setup").option("-c, --config <path>", "Path to config file").option("-d, --data-dir <path>", DATA_DIR_OPTION_HELP).option("--repair", "Attempt to repair issues automatically").alias("--fix").option("-y, --yes", "Skip repair confirmation prompts").action(async (opts) => {
19191
19532
  await doctor(opts);