sim-setup 1.0.2-preview.48.1 → 1.0.3-preview.49.1

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.
Files changed (3) hide show
  1. package/README.md +16 -0
  2. package/dist/index.js +203 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -153,6 +153,22 @@ npx sim-setup doctor --json
153
153
  Use `config` to answer “what is configured?” and `status` to answer “what is
154
154
  running and healthy?”
155
155
 
156
+ ### Install the desktop app
157
+
158
+ `desktop` resolves the macOS installer from your own deployment and prints the
159
+ server URL to enter in the app:
160
+
161
+ ```bash
162
+ npx sim-setup desktop
163
+ npx sim-setup desktop --url https://sim.example.com
164
+ npx sim-setup desktop --no-open
165
+ ```
166
+
167
+ The desktop app is not tied to sim.ai — it bakes in only a default server, and
168
+ every Sim deployment already serves `/api/desktop/update/download` and the
169
+ update feed installed apps poll. Install the signed build, then point it at your
170
+ deployment with **Sim → Server…**. Nothing has to be built or signed by you.
171
+
156
172
  ### Add or change capabilities
157
173
 
158
174
  Configure one capability without walking through the complete wizard:
package/dist/index.js CHANGED
@@ -36106,6 +36106,178 @@ var init_feature_setup = __esm(() => {
36106
36106
  init_theme();
36107
36107
  });
36108
36108
 
36109
+ // src/urls.ts
36110
+ var APP_URL = "http://localhost:3000", APP_SIGNUP_URL;
36111
+ var init_urls = __esm(() => {
36112
+ APP_SIGNUP_URL = `${APP_URL}/signup`;
36113
+ });
36114
+
36115
+ // src/desktop.ts
36116
+ var exports_desktop = {};
36117
+ __export(exports_desktop, {
36118
+ sanitizeForTerminal: () => sanitizeForTerminal,
36119
+ runDesktop: () => runDesktop,
36120
+ resolveDeploymentUrl: () => resolveDeploymentUrl,
36121
+ probeDownload: () => probeDownload,
36122
+ describeProbe: () => describeProbe
36123
+ });
36124
+ function deploymentKey(value) {
36125
+ try {
36126
+ return new URL(value).origin.toLowerCase();
36127
+ } catch {
36128
+ return value;
36129
+ }
36130
+ }
36131
+ function sanitizeForTerminal(value) {
36132
+ const printable = value.replace(/\p{C}/gu, "").replace(/\p{Z}/gu, " ").replace(/ {2,}/g, " ").trim();
36133
+ return truncate(printable, MAX_INSTALLER_NAME);
36134
+ }
36135
+ function resolveDeploymentUrl(sources, override) {
36136
+ const discovered = sources.flatMap((source) => {
36137
+ const value = source.values?.get(APP_URL_KEY)?.trim();
36138
+ return value ? [{ label: source.label ?? "configuration", value }] : [];
36139
+ });
36140
+ const byDeployment = new Map;
36141
+ for (const { value } of discovered) {
36142
+ byDeployment.set(deploymentKey(value), value);
36143
+ }
36144
+ if (!override && byDeployment.size > 1) {
36145
+ throw new SetupError(`Found ${byDeployment.size} configurations naming different ${APP_URL_KEY} values.`, [
36146
+ ...discovered.map(({ label, value }) => `${label}: ${value}`),
36147
+ "Re-run with --url <deployment url> to say which one the desktop app should use."
36148
+ ]);
36149
+ }
36150
+ const raw = override ?? byDeployment.values().next().value;
36151
+ if (!raw) {
36152
+ return APP_URL;
36153
+ }
36154
+ let url;
36155
+ try {
36156
+ url = new URL(raw.trim());
36157
+ } catch {
36158
+ throw new SetupError(`${APP_URL_KEY} is not a valid URL: ${raw}`, [
36159
+ "Set it to the origin browsers use to reach Sim, e.g. https://sim.example.com"
36160
+ ]);
36161
+ }
36162
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
36163
+ throw new SetupError(`${APP_URL_KEY} must be an http(s) URL: ${raw}`);
36164
+ }
36165
+ return url.origin;
36166
+ }
36167
+ async function probeDownload(downloadUrl, fetchImpl = fetch) {
36168
+ let response;
36169
+ try {
36170
+ response = await fetchImpl(downloadUrl, {
36171
+ redirect: "manual",
36172
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
36173
+ });
36174
+ } catch (error) {
36175
+ return { status: "unreachable", error: getErrorMessage(error, "request failed") };
36176
+ }
36177
+ if (REDIRECT_STATUSES.has(response.status)) {
36178
+ const location = response.headers.get("location");
36179
+ if (!location)
36180
+ return { status: "unexpected", code: response.status };
36181
+ let name = location;
36182
+ try {
36183
+ name = decodeURIComponent(new URL(location).pathname.split("/").pop() ?? location);
36184
+ } catch {}
36185
+ return { status: "ok", installerUrl: location, installerName: sanitizeForTerminal(name) };
36186
+ }
36187
+ if (response.status === 404)
36188
+ return { status: "no-release" };
36189
+ if (response.status === 502)
36190
+ return { status: "feed-unavailable" };
36191
+ return { status: "unexpected", code: response.status };
36192
+ }
36193
+ function describeProbe(probe, appUrl) {
36194
+ switch (probe.status) {
36195
+ case "ok":
36196
+ return { headline: `${glyph.pass} Installer resolved: ${probe.installerName}`, hints: [] };
36197
+ case "no-release":
36198
+ return {
36199
+ headline: `${glyph.fail} This deployment reports no desktop release for its channel.`,
36200
+ hints: [
36201
+ "Stable desktop builds are published on GitHub releases of simstudioai/sim.",
36202
+ "A brand-new fork with no releases of its own will report this."
36203
+ ]
36204
+ };
36205
+ case "feed-unavailable":
36206
+ return {
36207
+ headline: `${glyph.fail} The deployment could not reach the GitHub release feed.`,
36208
+ hints: [
36209
+ "The Sim server needs outbound access to api.github.com and github.com.",
36210
+ "Unauthenticated GitHub API calls are capped at 60/hour per IP — set GITHUB_TOKEN on the Sim server to raise it to 5000/hour."
36211
+ ]
36212
+ };
36213
+ case "unreachable":
36214
+ return {
36215
+ headline: `${glyph.fail} Could not reach ${appUrl} — ${probe.error}`,
36216
+ hints: [
36217
+ `Check that Sim is running and reachable at ${appUrl} (npx sim-setup status).`,
36218
+ "Pass --url if this machine reaches Sim at a different address."
36219
+ ]
36220
+ };
36221
+ case "unexpected":
36222
+ return { headline: `${glyph.fail} The download endpoint answered ${probe.code}.`, hints: [] };
36223
+ }
36224
+ }
36225
+ async function runDesktop(flags) {
36226
+ const appUrl = resolveDeploymentUrl(flags.url ? [] : discoverConfigurationSources(), flags.url);
36227
+ const downloadUrl = `${appUrl}${DOWNLOAD_PATH}`;
36228
+ log2.step(`Deployment: ${theme.accent(appUrl)}`);
36229
+ const spin = spinner2();
36230
+ spin.start("Resolving the desktop installer…");
36231
+ const [probe, feedOk] = await Promise.all([
36232
+ probeDownload(downloadUrl),
36233
+ httpHealth(`${appUrl}${FEED_PATH}`, PROBE_TIMEOUT_MS)
36234
+ ]);
36235
+ const { headline, hints } = describeProbe(probe, appUrl);
36236
+ spin.stop(headline);
36237
+ if (probe.status !== "ok") {
36238
+ for (const hint of hints) {
36239
+ log2.info(hint);
36240
+ }
36241
+ outro2(theme.error("The desktop installer could not be resolved."));
36242
+ return 1;
36243
+ }
36244
+ if (!feedOk) {
36245
+ log2.warn(`${FEED_PATH} did not resolve — the app will install but will not auto-update from this deployment.`);
36246
+ }
36247
+ note2([
36248
+ `1. Download and install Sim:`,
36249
+ ` ${theme.accent(downloadUrl)}`,
36250
+ "",
36251
+ `2. Open Sim, then choose ${theme.command("Sim → Server…")} in the menu bar.`,
36252
+ "",
36253
+ `3. Enter your server URL and press Connect:`,
36254
+ ` ${theme.accent(appUrl)}`,
36255
+ "",
36256
+ theme.muted("Sim relaunches against your deployment and updates from it from then on."),
36257
+ theme.muted("The desktop app is macOS-only today; the web app works everywhere.")
36258
+ ].join(`
36259
+ `), "Connect the desktop app");
36260
+ if (!flags.noOpen) {
36261
+ if (await confirm2({ message: "Download it now?", initialValue: true })) {
36262
+ openBrowser(downloadUrl);
36263
+ }
36264
+ }
36265
+ outro2(theme.accent("Ready."));
36266
+ return 0;
36267
+ }
36268
+ var DOWNLOAD_PATH = "/api/desktop/update/download", FEED_PATH = "/api/desktop/update/latest-mac.yml", PROBE_TIMEOUT_MS = 15000, REDIRECT_STATUSES, APP_URL_KEY = "NEXT_PUBLIC_APP_URL", MAX_INSTALLER_NAME = 120;
36269
+ var init_desktop = __esm(() => {
36270
+ init_string();
36271
+ init_cli_auth();
36272
+ init_configuration_sources();
36273
+ init_errors();
36274
+ init_probes();
36275
+ init_prompter();
36276
+ init_theme();
36277
+ init_urls();
36278
+ REDIRECT_STATUSES = new Set([301, 302, 307, 308]);
36279
+ });
36280
+
36109
36281
  // src/doctor.ts
36110
36282
  var exports_doctor = {};
36111
36283
  __export(exports_doctor, {
@@ -36367,12 +36539,6 @@ var init_docker = __esm(() => {
36367
36539
  APP_DIRS = ["/Applications", join(homedir(), "Applications")];
36368
36540
  });
36369
36541
 
36370
- // src/urls.ts
36371
- var APP_URL = "http://localhost:3000", APP_SIGNUP_URL;
36372
- var init_urls = __esm(() => {
36373
- APP_SIGNUP_URL = `${APP_URL}/signup`;
36374
- });
36375
-
36376
36542
  // src/modes/k8s.ts
36377
36543
  import { spawn, spawnSync as spawnSync6 } from "node:child_process";
36378
36544
  function run(command, args, failMessage, input) {
@@ -38260,24 +38426,31 @@ function oneFlag(args, flag) {
38260
38426
  fail(`${flag} may only be provided once`);
38261
38427
  return count === 1;
38262
38428
  }
38263
- function parseMode(args) {
38264
- let mode;
38429
+ function parseValueOption(args, flag, requirement) {
38430
+ let value;
38265
38431
  const remaining = [];
38432
+ const prefix = `${flag}=`;
38266
38433
  for (let index = 0;index < args.length; index += 1) {
38267
38434
  const arg = args[index];
38268
- if (arg !== "--mode" && !arg.startsWith("--mode=")) {
38435
+ if (arg !== flag && !arg.startsWith(prefix)) {
38269
38436
  remaining.push(arg);
38270
38437
  continue;
38271
38438
  }
38272
- if (mode)
38273
- fail("--mode may only be provided once");
38274
- const value = arg === "--mode" ? args[++index] : arg.slice("--mode=".length);
38275
- if (value !== "compose" && value !== "dev" && value !== "k8s") {
38276
- fail(`invalid --mode "${value ?? ""}" — expected compose, dev, or k8s`);
38277
- }
38278
- mode = value;
38439
+ if (value !== undefined)
38440
+ fail(`${flag} may only be provided once`);
38441
+ const candidate = arg === flag ? args[++index] : arg.slice(prefix.length);
38442
+ if (!candidate || candidate.startsWith("-"))
38443
+ fail(requirement);
38444
+ value = candidate;
38445
+ }
38446
+ return { value, remaining };
38447
+ }
38448
+ function parseMode(args) {
38449
+ const { value, remaining } = parseValueOption(args, "--mode", "--mode requires a value");
38450
+ if (value !== undefined && value !== "compose" && value !== "dev" && value !== "k8s") {
38451
+ fail(`invalid --mode "${value}" — expected compose, dev, or k8s`);
38279
38452
  }
38280
- return { mode, remaining };
38453
+ return { mode: value, remaining };
38281
38454
  }
38282
38455
  function expectNoArguments(command, args) {
38283
38456
  if (args.length > 0)
@@ -38320,6 +38493,13 @@ function parseCore(args, helpRequested) {
38320
38493
  }
38321
38494
  return { kind: "add", feature, args: featureArgs };
38322
38495
  }
38496
+ if (command === "desktop") {
38497
+ const noOpen = oneFlag(commandArgs, "--no-open");
38498
+ const { value: url, remaining } = parseValueOption(commandArgs.filter((arg) => arg !== "--no-open"), "--url", "--url requires a deployment URL");
38499
+ if (remaining.length > 0)
38500
+ fail(`Unknown desktop option: ${remaining[0]}`);
38501
+ return { kind: "desktop", noOpen, ...url ? { url } : {} };
38502
+ }
38323
38503
  if (command === "doctor") {
38324
38504
  const fix = oneFlag(commandArgs, "--fix");
38325
38505
  const json = oneFlag(commandArgs, "--json");
@@ -38364,6 +38544,7 @@ var USAGE = `Usage:
38364
38544
  sim-setup [--quick] [--dir <path>] [--mode compose|dev|k8s]
38365
38545
  sim-setup config show configured capabilities and integrations
38366
38546
  sim-setup add <feature> configure ${SETUP_FEATURES2}
38547
+ sim-setup desktop [--url <url>] install the macOS desktop app against this deployment
38367
38548
  sim-setup doctor [--fix] [--json] check your setup
38368
38549
  sim-setup start | stop | restart bring your install up / down / cycle
38369
38550
  sim-setup update pull/rebuild and apply Compose images
@@ -38394,6 +38575,11 @@ async function main() {
38394
38575
  await runFeatureSetup2(invocation.feature, invocation.args);
38395
38576
  return;
38396
38577
  }
38578
+ if (invocation.kind === "desktop") {
38579
+ const { runDesktop: runDesktop2 } = await Promise.resolve().then(() => (init_desktop(), exports_desktop));
38580
+ process.exitCode = await runDesktop2({ url: invocation.url, noOpen: invocation.noOpen });
38581
+ return;
38582
+ }
38397
38583
  if (invocation.kind === "doctor") {
38398
38584
  const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), exports_doctor));
38399
38585
  process.exitCode = await runDoctor2({ fix: invocation.fix, json: invocation.json });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sim-setup",
3
- "version": "1.0.2-preview.48.1",
3
+ "version": "1.0.3-preview.49.1",
4
4
  "description": "Set up and manage a self-hosted Sim installation",
5
5
  "type": "module",
6
6
  "bin": {