sim-setup 1.0.1 → 1.0.2-preview.34.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 +6 -0
  2. package/dist/index.js +437 -417
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,3 +9,9 @@ npx sim-setup
9
9
  Outside a Sim source checkout, the command creates a Docker Compose installation using published
10
10
  images. Inside a Sim source checkout, use `bun run sim-setup` to expose the complete development
11
11
  and deployment wizard.
12
+
13
+ To connect or replace the Chat API key without rerunning the full wizard:
14
+
15
+ ```bash
16
+ npx sim-setup add chat
17
+ ```
package/dist/index.js CHANGED
@@ -1521,9 +1521,12 @@ var init_env_capabilities = __esm(() => {
1521
1521
  ...new Set([
1522
1522
  ...CORE_CONFIGURATION_KEYS,
1523
1523
  ...ENV_CAPABILITIES.flatMap(capabilityKeys),
1524
+ "COPILOT_API_KEY",
1524
1525
  "EMAIL_VERIFICATION_ENABLED",
1526
+ "NEXT_PUBLIC_CHAT_DISABLED",
1525
1527
  "NEXT_PUBLIC_E2B_ENABLED",
1526
1528
  "NEXT_PUBLIC_SANDBOXES_ENABLED",
1529
+ "SIM_AGENT_API_URL",
1527
1530
  ...Object.values(LLM_KEY_POOLS).flatMap((pool) => [
1528
1531
  ...pool.keys,
1529
1532
  ..."fallbackKey" in pool ? [pool.fallbackKey] : []
@@ -27897,6 +27900,7 @@ var init_capability_config = __esm(() => {
27897
27900
  id: setup.definition.id,
27898
27901
  label: setup.label
27899
27902
  })),
27903
+ { id: "chat", label: "Chat" },
27900
27904
  { id: "llm", label: "LLM API keys" },
27901
27905
  { id: "integration", label: "OAuth integration" }
27902
27906
  ];
@@ -32992,7 +32996,7 @@ var init_setup_status = __esm(() => {
32992
32996
  init_theme();
32993
32997
  SECRET_KEYS2 = new Set(["BETTER_AUTH_SECRET", "ENCRYPTION_KEY", "INTERNAL_API_SECRET"]);
32994
32998
  URL_KEYS2 = new Set(["DATABASE_URL", "BETTER_AUTH_URL", "NEXT_PUBLIC_APP_URL"]);
32995
- FEATURE_ORDER = SETUP_FEATURES.flatMap((feature) => feature.id === "integration" ? [] : [feature.id]);
32999
+ FEATURE_ORDER = SETUP_FEATURES.flatMap((feature) => feature.id === "chat" || feature.id === "integration" ? [] : [feature.id]);
32996
33000
  });
32997
33001
 
32998
33002
  // ../../node_modules/fast-string-truncated-width/dist/utils.js
@@ -35251,418 +35255,10 @@ var init_capability_setup = __esm(() => {
35251
35255
  init_prompter();
35252
35256
  });
35253
35257
 
35254
- // src/feature-setup.ts
35255
- var exports_feature_setup = {};
35256
- __export(exports_feature_setup, {
35257
- setupFeatureUsage: () => setupFeatureUsage,
35258
- runFeatureSetup: () => runFeatureSetup,
35259
- resolveFeatureSetupDestination: () => resolveFeatureSetupDestination,
35260
- reconcileLlmSetup: () => reconcileLlmSetup
35261
- });
35262
- function isSetupFeatureId(value) {
35263
- return SETUP_FEATURES.some((feature) => feature.id === value);
35264
- }
35265
- async function setupIntegration(requestedId, vars) {
35266
- if (!requestedId) {
35267
- throw new Error("Missing integration id. Example: npx sim-setup add integration slack");
35268
- }
35269
- const providerId = resolveOAuthClientCapabilityId(requestedId);
35270
- if (!providerId) {
35271
- throw new Error(`Unknown OAuth integration "${requestedId}". Expected one of: ${Object.keys(OAUTH_CLIENT_CAPABILITIES).join(", ")}`);
35272
- }
35273
- const fields = getOAuthClientSetupFields(providerId);
35274
- const values2 = {};
35275
- for (const field of fields) {
35276
- const existing = vars.get(field.key);
35277
- if (field.input === "secret") {
35278
- const value = await password2({
35279
- message: existing ? `${field.key} (Currently used); leave empty to keep it` : field.key,
35280
- validate: (candidate) => candidate || existing ? undefined : "required"
35281
- });
35282
- const resolved = value || existing;
35283
- if (!resolved)
35284
- throw new Error(`${field.key} was not provided`);
35285
- values2[field.key] = resolved;
35286
- } else {
35287
- values2[field.key] = await text2({
35288
- message: `${field.key}${existing ? " (Currently used)" : ""}`,
35289
- initialValue: existing,
35290
- validate: (candidate) => candidate ? undefined : "required"
35291
- });
35292
- }
35293
- }
35294
- log2.info(`Configured the ${providerId} OAuth client.`);
35295
- return values2;
35296
- }
35297
- function reconcileLlmSetup(providerId, values2) {
35298
- const pool = LLM_KEY_POOLS[providerId];
35299
- const fields = [...pool.keys, ..."fallbackKey" in pool ? [pool.fallbackKey] : []];
35300
- return {
35301
- values: values2,
35302
- remove: fields.filter((key) => !Object.hasOwn(values2, key))
35303
- };
35304
- }
35305
- async function setupLlm(vars) {
35306
- const currentProvider = Object.entries(LLM_KEY_POOLS).find(([, pool2]) => [...pool2.keys, ..."fallbackKey" in pool2 ? [pool2.fallbackKey] : []].some((key) => vars.has(key)))?.[0];
35307
- const provider = await select3({
35308
- message: "LLM key pool?",
35309
- options: Object.keys(LLM_KEY_POOLS).map((id) => ({
35310
- value: id,
35311
- label: id,
35312
- hint: id === currentProvider ? "Currently used" : undefined
35313
- })),
35314
- initialValue: currentProvider
35315
- });
35316
- const pool = LLM_KEY_POOLS[provider];
35317
- const keys = pool.keys;
35318
- const values2 = {};
35319
- for (const [index, key] of keys.entries()) {
35320
- const legacyKey = index === 0 && "fallbackKey" in pool ? pool.fallbackKey : undefined;
35321
- const existingKey = vars.has(key) ? key : legacyKey;
35322
- const existing = existingKey ? vars.get(existingKey) : undefined;
35323
- const value = await password2({
35324
- message: existing ? `${key} (${existingKey} is currently used); leave empty to keep it` : `${key}${index === 0 ? "" : " (empty to finish)"}`,
35325
- validate: index === 0 ? (candidate) => candidate || existing ? undefined : "required" : undefined
35326
- });
35327
- const resolved = value || existing;
35328
- if (!resolved)
35329
- break;
35330
- values2[key] = resolved;
35331
- }
35332
- return reconcileLlmSetup(provider, values2);
35333
- }
35334
- function setupFeatureUsage() {
35335
- return SETUP_FEATURES.map((feature) => feature.id === "integration" ? "integration <slug>" : feature.id).join(" | ");
35336
- }
35337
- function resolveFeatureSetupDestination(sources) {
35338
- if (sources.length === 0) {
35339
- throw new Error("No Sim configuration was detected. Run npx sim-setup first.");
35340
- }
35341
- const managed = sources.filter((source2) => source2.managedByCurrentCheckout);
35342
- if (managed.length === 0) {
35343
- throw new Error("No effective configuration is safely writable by this checkout. Process overrides, higher-precedence development env files, external Compose projects, and Helm releases must be updated at their source. Run npx sim-setup config for the detected sources.");
35344
- }
35345
- if (managed.length > 1) {
35346
- throw new Error(`More than one effective configuration is writable by this checkout (${managed.map((source2) => source2.label).join(", ")}). Run npx sim-setup config and remove the ambiguity before configuring a feature.`);
35347
- }
35348
- const source = managed[0];
35349
- if (!source.values) {
35350
- throw new Error(`${source.label} is managed by this checkout, but its effective environment could not be resolved. Run npx sim-setup config and fix the reported source error first.`);
35351
- }
35352
- if (source.kind === "helm") {
35353
- throw new Error("Helm configuration cannot be updated by npx sim-setup add. Update the release Secret or values and upgrade the release.");
35354
- }
35355
- return {
35356
- source,
35357
- target: source.kind === "compose" ? "root" : "sim",
35358
- vars: source.values,
35359
- containerized: source.kind === "compose"
35360
- };
35361
- }
35362
- async function runFeatureSetup(feature, args) {
35363
- if (!isSetupFeatureId(feature)) {
35364
- throw new Error(`Unknown setup feature "${feature}". Expected: ${setupFeatureUsage()}`);
35365
- }
35366
- const destination = resolveFeatureSetupDestination(discoverConfigurationSources());
35367
- const { target, vars } = destination;
35368
- let values2;
35369
- let remove;
35370
- const capabilitySetup = getCapabilitySetup(feature);
35371
- if (capabilitySetup) {
35372
- const result = await promptCapabilitySetup(capabilitySetup, vars, {
35373
- containerized: destination.containerized
35374
- });
35375
- values2 = result.values;
35376
- remove = result.remove;
35377
- } else if (feature === "integration") {
35378
- values2 = await setupIntegration(args[0], vars);
35379
- remove = [];
35380
- } else if (feature === "llm") {
35381
- const result = await setupLlm(vars);
35382
- values2 = result.values;
35383
- remove = result.remove;
35384
- } else {
35385
- throw new Error(`Setup feature ${feature} has no handler`);
35386
- }
35387
- reconcileEnvValues(target, remove, values2);
35388
- const label = SETUP_FEATURES.find((item) => item.id === feature)?.label;
35389
- outro2(theme.accent(destination.containerized ? `${label} written to .env. Recreate the app container for it to take effect.` : `${label} configured.`));
35390
- }
35391
- var init_feature_setup = __esm(() => {
35392
- init_env_capabilities();
35393
- init_capability_config();
35394
- init_capability_setup();
35395
- init_configuration_sources();
35396
- init_env_files();
35397
- init_prompter();
35398
- init_theme();
35399
- });
35400
-
35401
- // src/doctor.ts
35402
- var exports_doctor = {};
35403
- __export(exports_doctor, {
35404
- runDoctor: () => runDoctor
35405
- });
35406
- function render(findings, fixedCount) {
35407
- console.log(`
35408
- ${theme.heading("◆ Sim doctor")}
35409
- `);
35410
- for (const group of GROUP_ORDER) {
35411
- const groupFindings = findings.filter((f2) => f2.group === group);
35412
- if (groupFindings.length === 0)
35413
- continue;
35414
- console.log(theme.heading(GROUP_TITLES[group]));
35415
- for (const finding of groupFindings) {
35416
- console.log(` ${glyph[finding.status]} ${finding.message}`);
35417
- if (finding.fix && finding.status !== "pass") {
35418
- console.log(` ${theme.muted(`fix: ${finding.fix}`)}`);
35419
- }
35420
- }
35421
- console.log();
35422
- }
35423
- const counts = {
35424
- pass: findings.filter((f2) => f2.status === "pass").length,
35425
- warn: findings.filter((f2) => f2.status === "warn").length,
35426
- fail: findings.filter((f2) => f2.status === "fail").length
35427
- };
35428
- const summary = [`${counts.pass} passed`];
35429
- if (counts.warn)
35430
- summary.push(theme.warn(`${counts.warn} warning${counts.warn > 1 ? "s" : ""}`));
35431
- if (counts.fail)
35432
- summary.push(theme.error(`${counts.fail} failed`));
35433
- if (fixedCount)
35434
- summary.push(theme.success(`${fixedCount} fixed`));
35435
- console.log(summary.join(theme.muted(" · ")));
35436
- }
35437
- async function runDoctor(options) {
35438
- let findings = await runChecks(loadCheckContext(true));
35439
- let fixedCount = 0;
35440
- if (options.fix) {
35441
- const fixable = findings.filter((f2) => (f2.status === "fail" || f2.status === "warn") && f2.autofix);
35442
- for (const finding of fixable) {
35443
- finding.autofix?.();
35444
- fixedCount++;
35445
- }
35446
- if (fixedCount > 0)
35447
- findings = await runChecks(loadCheckContext(true));
35448
- }
35449
- if (options.json) {
35450
- console.log(JSON.stringify(findings.map(({ autofix: _autofix, ...rest }) => rest), null, 2));
35451
- } else {
35452
- render(findings, fixedCount);
35453
- }
35454
- return findings.some((f2) => f2.status === "fail") ? 1 : 0;
35455
- }
35456
- var GROUP_TITLES, GROUP_ORDER;
35457
- var init_doctor = __esm(() => {
35458
- init_checks();
35459
- init_theme();
35460
- GROUP_TITLES = {
35461
- files: "Env files",
35462
- schema: "Schema",
35463
- consistency: "Consistency",
35464
- coherence: "Coherence",
35465
- live: "Live"
35466
- };
35467
- GROUP_ORDER = ["files", "schema", "consistency", "coherence", "live"];
35468
- });
35469
-
35470
- // src/compose-asset.ts
35471
- import { createHash } from "node:crypto";
35472
- import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
35473
- import path6 from "node:path";
35474
- import { fileURLToPath } from "node:url";
35475
- function sha2562(contents) {
35476
- return createHash("sha256").update(contents).digest("hex");
35477
- }
35478
- function readManagedState(root) {
35479
- const file = path6.join(root, MANAGED_STATE_FILE);
35480
- if (!existsSync6(file))
35481
- return null;
35482
- const parsed = JSON.parse(readFileSync6(file, "utf8"));
35483
- if (typeof parsed !== "object" || parsed === null || !("schemaVersion" in parsed) || parsed.schemaVersion !== 1 || !("composeSha256" in parsed) || typeof parsed.composeSha256 !== "string") {
35484
- throw new Error(`${file} is not a valid sim-setup managed-state file`);
35485
- }
35486
- return { schemaVersion: 1, composeSha256: parsed.composeSha256 };
35487
- }
35488
- function writeManagedState(root, contents) {
35489
- const state = { schemaVersion: 1, composeSha256: sha2562(contents) };
35490
- writeFileSync2(path6.join(root, MANAGED_STATE_FILE), `${JSON.stringify(state, null, 2)}
35491
- `);
35492
- }
35493
- function packagedComposeFile() {
35494
- const moduleDirectory = path6.dirname(fileURLToPath(import.meta.url));
35495
- return path6.basename(moduleDirectory) === "src" ? path6.resolve(moduleDirectory, "../../..", COMPOSE_FILE2) : path6.join(moduleDirectory, COMPOSE_FILE2);
35496
- }
35497
- function ensureProductionComposeFile(context = SETUP_CONTEXT) {
35498
- const destination = path6.join(context.root, COMPOSE_FILE2);
35499
- if (context.kind === "source") {
35500
- if (!existsSync6(destination)) {
35501
- throw new Error(`Sim source checkout is missing ${COMPOSE_FILE2}`);
35502
- }
35503
- return destination;
35504
- }
35505
- const bundled = packagedComposeFile();
35506
- if (!existsSync6(bundled)) {
35507
- throw new Error(`The sim-setup package is missing its bundled ${COMPOSE_FILE2}`);
35508
- }
35509
- mkdirSync2(context.root, { recursive: true });
35510
- const packaged = readFileSync6(bundled, "utf8");
35511
- if (existsSync6(destination)) {
35512
- const current = readFileSync6(destination, "utf8");
35513
- if (current === packaged) {
35514
- writeManagedState(context.root, packaged);
35515
- return destination;
35516
- }
35517
- if (!current.includes(SIM_COMPOSE_MARKER2)) {
35518
- throw new Error(`${destination} exists but is not a recognized Sim Compose file`);
35519
- }
35520
- const managed = readManagedState(context.root);
35521
- if (!managed || managed.composeSha256 !== sha2562(current)) {
35522
- throw new Error(`${destination} has local changes; preserve or remove your customizations before updating it.`);
35523
- }
35524
- }
35525
- copyFileSync(bundled, destination);
35526
- writeManagedState(context.root, packaged);
35527
- return destination;
35528
- }
35529
- var COMPOSE_FILE2 = "docker-compose.prod.yml", SIM_COMPOSE_MARKER2 = "ghcr.io/simstudioai/simstudio", MANAGED_STATE_FILE = ".sim-setup.json";
35530
- var init_compose_asset = __esm(() => {
35531
- init_context();
35532
- });
35533
-
35534
- // src/compose-project.ts
35535
- import { createHash as createHash2 } from "node:crypto";
35536
- import path7 from "node:path";
35537
- function standaloneComposeProjectName(root) {
35538
- const digest = createHash2("sha256").update(path7.resolve(root)).digest("hex").slice(0, 12);
35539
- return `sim-${digest}`;
35540
- }
35541
- function legacyComposeProjectName(root) {
35542
- const name = path7.basename(path7.resolve(root)).toLowerCase().replace(/[^a-z0-9_-]/g, "");
35543
- if (!/^[a-z0-9]/.test(name)) {
35544
- throw new Error(`Cannot derive the existing Compose project name from ${root}; set COMPOSE_PROJECT_NAME in its .env file.`);
35545
- }
35546
- return name;
35547
- }
35548
- var init_compose_project = () => {};
35549
-
35550
- // src/docker.ts
35551
- import { spawnSync as spawnSync4 } from "node:child_process";
35552
- import { existsSync as existsSync7 } from "node:fs";
35553
- import { homedir } from "node:os";
35554
- import { join } from "node:path";
35555
- function daemonUp() {
35556
- return spawnSync4("docker", ["info"], { stdio: "ignore" }).status === 0;
35557
- }
35558
- function installed() {
35559
- return executableExists("docker");
35560
- }
35561
- function orbstackSelected() {
35562
- const host = process.env.DOCKER_HOST;
35563
- if (host)
35564
- return host.includes(".orbstack/");
35565
- const result = spawnSync4("docker", ["context", "show"], { encoding: "utf8" });
35566
- return result.status === 0 && result.stdout.trim() === "orbstack";
35567
- }
35568
- function appInstalled(app) {
35569
- return APP_DIRS.some((dir) => existsSync7(join(dir, app.bundle)));
35570
- }
35571
- function macDockerApp() {
35572
- if (orbstackSelected())
35573
- return { app: ORBSTACK_APP, explicit: true };
35574
- const orbstackOnly = appInstalled(ORBSTACK_APP) && !appInstalled(DOCKER_DESKTOP_APP);
35575
- return { app: orbstackOnly ? ORBSTACK_APP : DOCKER_DESKTOP_APP, explicit: false };
35576
- }
35577
- function openApp(app) {
35578
- return spawnSync4("open", ["-a", app.name], { stdio: "ignore" }).status === 0;
35579
- }
35580
- function startDockerApp({ app, explicit }) {
35581
- if (openApp(app))
35582
- return app;
35583
- if (explicit)
35584
- return null;
35585
- const other = app === ORBSTACK_APP ? DOCKER_DESKTOP_APP : ORBSTACK_APP;
35586
- return openApp(other) ? other : null;
35587
- }
35588
- function launchFailed(required, message, hints) {
35589
- if (required)
35590
- throw new SetupError(message, hints);
35591
- log2.warn([message, ...hints].join(`
35592
- `));
35593
- return false;
35594
- }
35595
- async function ensureDocker(required) {
35596
- if (daemonUp())
35597
- return true;
35598
- if (!installed()) {
35599
- if (required)
35600
- throw new SetupError("Docker is not installed.", INSTALL_HINTS);
35601
- return false;
35602
- }
35603
- if (process.platform !== "darwin") {
35604
- if (required) {
35605
- throw new SetupError("Docker is installed but the daemon is not running.", [
35606
- `start it: ${theme.command("sudo systemctl start docker")} (Linux)`
35607
- ]);
35608
- }
35609
- return false;
35610
- }
35611
- const choice = macDockerApp();
35612
- const launch = await confirm2({
35613
- message: `Docker is installed but not running — start ${choice.app.name} now?`,
35614
- initialValue: true
35615
- });
35616
- if (!launch) {
35617
- if (required) {
35618
- throw new SetupError("Docker is required for this mode.", [
35619
- `start ${choice.app.name}, then re-run the wizard`
35620
- ]);
35621
- }
35622
- return false;
35623
- }
35624
- const app = startDockerApp(choice);
35625
- if (!app) {
35626
- return choice.explicit ? launchFailed(required, "The docker CLI is pointed at OrbStack, which is not installed.", [
35627
- `reinstall it: ${theme.command("brew install orbstack")}`,
35628
- `or point the CLI elsewhere: unset DOCKER_HOST and DOCKER_CONTEXT, then ${theme.command("docker context use <name>")}`
35629
- ]) : launchFailed(required, "Found the docker CLI, but no app to start.", NO_APP_HINTS);
35630
- }
35631
- const spin = spinner2();
35632
- spin.start(`Waiting for the Docker daemon (${app.name})…`);
35633
- const up = await waitFor(async () => daemonUp(), 90000, 2000);
35634
- spin.stop(up ? "Docker is running" : `${glyph.fail} daemon did not come up`);
35635
- if (!up) {
35636
- return launchFailed(required, `${app.name} did not start within 90s.`, [
35637
- app === ORBSTACK_APP ? "open OrbStack manually once to finish its first-run setup, then re-run" : "first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run"
35638
- ]);
35639
- }
35640
- return true;
35641
- }
35642
- var INSTALL_HINTS, NO_APP_HINTS, ORBSTACK_APP, DOCKER_DESKTOP_APP, APP_DIRS;
35643
- var init_docker = __esm(() => {
35644
- init_errors();
35645
- init_executables();
35646
- init_probes();
35647
- init_prompter();
35648
- init_theme();
35649
- INSTALL_HINTS = [
35650
- "install Docker Desktop: https://docker.com/products/docker-desktop",
35651
- `or OrbStack (lighter on macOS): ${theme.command("brew install orbstack")}`
35652
- ];
35653
- NO_APP_HINTS = [
35654
- ...INSTALL_HINTS,
35655
- `or start your existing runtime its own way, e.g. ${theme.command("colima start")}`
35656
- ];
35657
- ORBSTACK_APP = { name: "OrbStack", bundle: "OrbStack.app" };
35658
- DOCKER_DESKTOP_APP = { name: "Docker", bundle: "Docker.app" };
35659
- APP_DIRS = ["/Applications", join(homedir(), "Applications")];
35660
- });
35661
-
35662
35258
  // ../security/src/hash.ts
35663
- import { createHash as createHash3 } from "node:crypto";
35259
+ import { createHash } from "node:crypto";
35664
35260
  function sha256Base64Url(input) {
35665
- const hash = createHash3("sha256");
35261
+ const hash = createHash("sha256");
35666
35262
  if (typeof input === "string") {
35667
35263
  hash.update(input, "utf8");
35668
35264
  } else {
@@ -35716,19 +35312,19 @@ var init_string = __esm(() => {
35716
35312
  });
35717
35313
 
35718
35314
  // src/cli-auth.ts
35719
- import { spawnSync as spawnSync5 } from "node:child_process";
35315
+ import { spawnSync as spawnSync4 } from "node:child_process";
35720
35316
  function openBrowser(url) {
35721
35317
  if (process.env.SIM_SETUP_NO_BROWSER)
35722
35318
  return;
35723
35319
  if (process.platform === "win32") {
35724
- spawnSync5("cmd", ["/c", "start", '""', `"${url}"`], {
35320
+ spawnSync4("cmd", ["/c", "start", '""', `"${url}"`], {
35725
35321
  stdio: "ignore",
35726
35322
  windowsVerbatimArguments: true
35727
35323
  });
35728
35324
  return;
35729
35325
  }
35730
35326
  const command = process.platform === "darwin" ? "open" : "xdg-open";
35731
- spawnSync5(command, [url], { stdio: "ignore" });
35327
+ spawnSync4(command, [url], { stdio: "ignore" });
35732
35328
  }
35733
35329
  function createPairingCode() {
35734
35330
  const chars = generateShortId(8, PAIRING_ALPHABET);
@@ -35751,8 +35347,8 @@ function normalizeAuthOrigin(origin) {
35751
35347
  const prefix = parsed.pathname.replace(/\/+$/, "");
35752
35348
  return `${parsed.origin}${prefix}`;
35753
35349
  }
35754
- function authUrl(origin, path8) {
35755
- return `${normalizeAuthOrigin(origin)}${path8}`;
35350
+ function authUrl(origin, path6) {
35351
+ return `${normalizeAuthOrigin(origin)}${path6}`;
35756
35352
  }
35757
35353
  function buildApprovalUrl(origin, request, challenge, pairing) {
35758
35354
  const query = new URLSearchParams({ request, challenge, pairing });
@@ -36111,6 +35707,430 @@ var init_steps = __esm(() => {
36111
35707
  };
36112
35708
  });
36113
35709
 
35710
+ // src/feature-setup.ts
35711
+ var exports_feature_setup = {};
35712
+ __export(exports_feature_setup, {
35713
+ setupFeatureUsage: () => setupFeatureUsage,
35714
+ runFeatureSetup: () => runFeatureSetup,
35715
+ resolveFeatureSetupDestination: () => resolveFeatureSetupDestination,
35716
+ reconcileLlmSetup: () => reconcileLlmSetup
35717
+ });
35718
+ function isSetupFeatureId(value) {
35719
+ return SETUP_FEATURES.some((feature) => feature.id === value);
35720
+ }
35721
+ async function setupIntegration(requestedId, vars) {
35722
+ if (!requestedId) {
35723
+ throw new Error("Missing integration id. Example: npx sim-setup add integration slack");
35724
+ }
35725
+ const providerId = resolveOAuthClientCapabilityId(requestedId);
35726
+ if (!providerId) {
35727
+ throw new Error(`Unknown OAuth integration "${requestedId}". Expected one of: ${Object.keys(OAUTH_CLIENT_CAPABILITIES).join(", ")}`);
35728
+ }
35729
+ const fields = getOAuthClientSetupFields(providerId);
35730
+ const values2 = {};
35731
+ for (const field of fields) {
35732
+ const existing = vars.get(field.key);
35733
+ if (field.input === "secret") {
35734
+ const value = await password2({
35735
+ message: existing ? `${field.key} (Currently used); leave empty to keep it` : field.key,
35736
+ validate: (candidate) => candidate || existing ? undefined : "required"
35737
+ });
35738
+ const resolved = value || existing;
35739
+ if (!resolved)
35740
+ throw new Error(`${field.key} was not provided`);
35741
+ values2[field.key] = resolved;
35742
+ } else {
35743
+ values2[field.key] = await text2({
35744
+ message: `${field.key}${existing ? " (Currently used)" : ""}`,
35745
+ initialValue: existing,
35746
+ validate: (candidate) => candidate ? undefined : "required"
35747
+ });
35748
+ }
35749
+ }
35750
+ log2.info(`Configured the ${providerId} OAuth client.`);
35751
+ return values2;
35752
+ }
35753
+ function reconcileLlmSetup(providerId, values2) {
35754
+ const pool = LLM_KEY_POOLS[providerId];
35755
+ const fields = [...pool.keys, ..."fallbackKey" in pool ? [pool.fallbackKey] : []];
35756
+ return {
35757
+ values: values2,
35758
+ remove: fields.filter((key) => !Object.hasOwn(values2, key))
35759
+ };
35760
+ }
35761
+ async function setupLlm(vars) {
35762
+ const currentProvider = Object.entries(LLM_KEY_POOLS).find(([, pool2]) => [...pool2.keys, ..."fallbackKey" in pool2 ? [pool2.fallbackKey] : []].some((key) => vars.has(key)))?.[0];
35763
+ const provider = await select3({
35764
+ message: "LLM key pool?",
35765
+ options: Object.keys(LLM_KEY_POOLS).map((id) => ({
35766
+ value: id,
35767
+ label: id,
35768
+ hint: id === currentProvider ? "Currently used" : undefined
35769
+ })),
35770
+ initialValue: currentProvider
35771
+ });
35772
+ const pool = LLM_KEY_POOLS[provider];
35773
+ const keys = pool.keys;
35774
+ const values2 = {};
35775
+ for (const [index, key] of keys.entries()) {
35776
+ const legacyKey = index === 0 && "fallbackKey" in pool ? pool.fallbackKey : undefined;
35777
+ const existingKey = vars.has(key) ? key : legacyKey;
35778
+ const existing = existingKey ? vars.get(existingKey) : undefined;
35779
+ const value = await password2({
35780
+ message: existing ? `${key} (${existingKey} is currently used); leave empty to keep it` : `${key}${index === 0 ? "" : " (empty to finish)"}`,
35781
+ validate: index === 0 ? (candidate) => candidate || existing ? undefined : "required" : undefined
35782
+ });
35783
+ const resolved = value || existing;
35784
+ if (!resolved)
35785
+ break;
35786
+ values2[key] = resolved;
35787
+ }
35788
+ return reconcileLlmSetup(provider, values2);
35789
+ }
35790
+ async function setupChat(vars) {
35791
+ const overrides = mothershipOverride();
35792
+ const copilotKey = await promptCopilotKey(vars.get("COPILOT_API_KEY"));
35793
+ if (!copilotKey) {
35794
+ throw new Error("Chat setup did not receive an API key. No configuration was changed.");
35795
+ }
35796
+ return {
35797
+ ...overrides,
35798
+ COPILOT_API_KEY: copilotKey,
35799
+ ...chatFlagValues(copilotKey)
35800
+ };
35801
+ }
35802
+ function setupFeatureUsage() {
35803
+ return SETUP_FEATURES.map((feature) => feature.id === "integration" ? "integration <slug>" : feature.id).join(" | ");
35804
+ }
35805
+ function resolveFeatureSetupDestination(sources) {
35806
+ if (sources.length === 0) {
35807
+ throw new Error("No Sim configuration was detected. Run npx sim-setup first.");
35808
+ }
35809
+ const managed = sources.filter((source2) => source2.managedByCurrentCheckout);
35810
+ if (managed.length === 0) {
35811
+ throw new Error("No effective configuration is safely writable by this checkout. Process overrides, higher-precedence development env files, external Compose projects, and Helm releases must be updated at their source. Run npx sim-setup config for the detected sources.");
35812
+ }
35813
+ if (managed.length > 1) {
35814
+ throw new Error(`More than one effective configuration is writable by this checkout (${managed.map((source2) => source2.label).join(", ")}). Run npx sim-setup config and remove the ambiguity before configuring a feature.`);
35815
+ }
35816
+ const source = managed[0];
35817
+ if (!source.values) {
35818
+ throw new Error(`${source.label} is managed by this checkout, but its effective environment could not be resolved. Run npx sim-setup config and fix the reported source error first.`);
35819
+ }
35820
+ if (source.kind === "helm") {
35821
+ throw new Error("Helm configuration cannot be updated by npx sim-setup add. Update the release Secret or values and upgrade the release.");
35822
+ }
35823
+ return {
35824
+ source,
35825
+ target: source.kind === "compose" ? "root" : "sim",
35826
+ vars: source.values,
35827
+ containerized: source.kind === "compose"
35828
+ };
35829
+ }
35830
+ async function runFeatureSetup(feature, args) {
35831
+ if (!isSetupFeatureId(feature)) {
35832
+ throw new Error(`Unknown setup feature "${feature}". Expected: ${setupFeatureUsage()}`);
35833
+ }
35834
+ const destination = resolveFeatureSetupDestination(discoverConfigurationSources());
35835
+ const { target, vars } = destination;
35836
+ let values2;
35837
+ let remove;
35838
+ const capabilitySetup = getCapabilitySetup(feature);
35839
+ if (capabilitySetup) {
35840
+ const result = await promptCapabilitySetup(capabilitySetup, vars, {
35841
+ containerized: destination.containerized
35842
+ });
35843
+ values2 = result.values;
35844
+ remove = result.remove;
35845
+ } else if (feature === "chat") {
35846
+ values2 = await setupChat(vars);
35847
+ remove = [];
35848
+ } else if (feature === "integration") {
35849
+ values2 = await setupIntegration(args[0], vars);
35850
+ remove = [];
35851
+ } else if (feature === "llm") {
35852
+ const result = await setupLlm(vars);
35853
+ values2 = result.values;
35854
+ remove = result.remove;
35855
+ } else {
35856
+ throw new Error(`Setup feature ${feature} has no handler`);
35857
+ }
35858
+ reconcileEnvValues(target, remove, values2);
35859
+ const label = SETUP_FEATURES.find((item) => item.id === feature)?.label;
35860
+ outro2(theme.accent(destination.containerized ? `${label} written to .env. Recreate the app container for it to take effect.` : `${label} configured.`));
35861
+ }
35862
+ var init_feature_setup = __esm(() => {
35863
+ init_env_capabilities();
35864
+ init_capability_config();
35865
+ init_capability_setup();
35866
+ init_configuration_sources();
35867
+ init_env_files();
35868
+ init_prompter();
35869
+ init_steps();
35870
+ init_theme();
35871
+ });
35872
+
35873
+ // src/doctor.ts
35874
+ var exports_doctor = {};
35875
+ __export(exports_doctor, {
35876
+ runDoctor: () => runDoctor
35877
+ });
35878
+ function render(findings, fixedCount) {
35879
+ console.log(`
35880
+ ${theme.heading("◆ Sim doctor")}
35881
+ `);
35882
+ for (const group of GROUP_ORDER) {
35883
+ const groupFindings = findings.filter((f2) => f2.group === group);
35884
+ if (groupFindings.length === 0)
35885
+ continue;
35886
+ console.log(theme.heading(GROUP_TITLES[group]));
35887
+ for (const finding of groupFindings) {
35888
+ console.log(` ${glyph[finding.status]} ${finding.message}`);
35889
+ if (finding.fix && finding.status !== "pass") {
35890
+ console.log(` ${theme.muted(`fix: ${finding.fix}`)}`);
35891
+ }
35892
+ }
35893
+ console.log();
35894
+ }
35895
+ const counts = {
35896
+ pass: findings.filter((f2) => f2.status === "pass").length,
35897
+ warn: findings.filter((f2) => f2.status === "warn").length,
35898
+ fail: findings.filter((f2) => f2.status === "fail").length
35899
+ };
35900
+ const summary = [`${counts.pass} passed`];
35901
+ if (counts.warn)
35902
+ summary.push(theme.warn(`${counts.warn} warning${counts.warn > 1 ? "s" : ""}`));
35903
+ if (counts.fail)
35904
+ summary.push(theme.error(`${counts.fail} failed`));
35905
+ if (fixedCount)
35906
+ summary.push(theme.success(`${fixedCount} fixed`));
35907
+ console.log(summary.join(theme.muted(" · ")));
35908
+ }
35909
+ async function runDoctor(options) {
35910
+ let findings = await runChecks(loadCheckContext(true));
35911
+ let fixedCount = 0;
35912
+ if (options.fix) {
35913
+ const fixable = findings.filter((f2) => (f2.status === "fail" || f2.status === "warn") && f2.autofix);
35914
+ for (const finding of fixable) {
35915
+ finding.autofix?.();
35916
+ fixedCount++;
35917
+ }
35918
+ if (fixedCount > 0)
35919
+ findings = await runChecks(loadCheckContext(true));
35920
+ }
35921
+ if (options.json) {
35922
+ console.log(JSON.stringify(findings.map(({ autofix: _autofix, ...rest }) => rest), null, 2));
35923
+ } else {
35924
+ render(findings, fixedCount);
35925
+ }
35926
+ return findings.some((f2) => f2.status === "fail") ? 1 : 0;
35927
+ }
35928
+ var GROUP_TITLES, GROUP_ORDER;
35929
+ var init_doctor = __esm(() => {
35930
+ init_checks();
35931
+ init_theme();
35932
+ GROUP_TITLES = {
35933
+ files: "Env files",
35934
+ schema: "Schema",
35935
+ consistency: "Consistency",
35936
+ coherence: "Coherence",
35937
+ live: "Live"
35938
+ };
35939
+ GROUP_ORDER = ["files", "schema", "consistency", "coherence", "live"];
35940
+ });
35941
+
35942
+ // src/compose-asset.ts
35943
+ import { createHash as createHash2 } from "node:crypto";
35944
+ import { copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
35945
+ import path6 from "node:path";
35946
+ import { fileURLToPath } from "node:url";
35947
+ function sha2562(contents) {
35948
+ return createHash2("sha256").update(contents).digest("hex");
35949
+ }
35950
+ function readManagedState(root) {
35951
+ const file = path6.join(root, MANAGED_STATE_FILE);
35952
+ if (!existsSync6(file))
35953
+ return null;
35954
+ const parsed = JSON.parse(readFileSync6(file, "utf8"));
35955
+ if (typeof parsed !== "object" || parsed === null || !("schemaVersion" in parsed) || parsed.schemaVersion !== 1 || !("composeSha256" in parsed) || typeof parsed.composeSha256 !== "string") {
35956
+ throw new Error(`${file} is not a valid sim-setup managed-state file`);
35957
+ }
35958
+ return { schemaVersion: 1, composeSha256: parsed.composeSha256 };
35959
+ }
35960
+ function writeManagedState(root, contents) {
35961
+ const state = { schemaVersion: 1, composeSha256: sha2562(contents) };
35962
+ writeFileSync2(path6.join(root, MANAGED_STATE_FILE), `${JSON.stringify(state, null, 2)}
35963
+ `);
35964
+ }
35965
+ function packagedComposeFile() {
35966
+ const moduleDirectory = path6.dirname(fileURLToPath(import.meta.url));
35967
+ return path6.basename(moduleDirectory) === "src" ? path6.resolve(moduleDirectory, "../../..", COMPOSE_FILE2) : path6.join(moduleDirectory, COMPOSE_FILE2);
35968
+ }
35969
+ function ensureProductionComposeFile(context = SETUP_CONTEXT) {
35970
+ const destination = path6.join(context.root, COMPOSE_FILE2);
35971
+ if (context.kind === "source") {
35972
+ if (!existsSync6(destination)) {
35973
+ throw new Error(`Sim source checkout is missing ${COMPOSE_FILE2}`);
35974
+ }
35975
+ return destination;
35976
+ }
35977
+ const bundled = packagedComposeFile();
35978
+ if (!existsSync6(bundled)) {
35979
+ throw new Error(`The sim-setup package is missing its bundled ${COMPOSE_FILE2}`);
35980
+ }
35981
+ mkdirSync2(context.root, { recursive: true });
35982
+ const packaged = readFileSync6(bundled, "utf8");
35983
+ if (existsSync6(destination)) {
35984
+ const current = readFileSync6(destination, "utf8");
35985
+ if (current === packaged) {
35986
+ writeManagedState(context.root, packaged);
35987
+ return destination;
35988
+ }
35989
+ if (!current.includes(SIM_COMPOSE_MARKER2)) {
35990
+ throw new Error(`${destination} exists but is not a recognized Sim Compose file`);
35991
+ }
35992
+ const managed = readManagedState(context.root);
35993
+ if (!managed || managed.composeSha256 !== sha2562(current)) {
35994
+ throw new Error(`${destination} has local changes; preserve or remove your customizations before updating it.`);
35995
+ }
35996
+ }
35997
+ copyFileSync(bundled, destination);
35998
+ writeManagedState(context.root, packaged);
35999
+ return destination;
36000
+ }
36001
+ var COMPOSE_FILE2 = "docker-compose.prod.yml", SIM_COMPOSE_MARKER2 = "ghcr.io/simstudioai/simstudio", MANAGED_STATE_FILE = ".sim-setup.json";
36002
+ var init_compose_asset = __esm(() => {
36003
+ init_context();
36004
+ });
36005
+
36006
+ // src/compose-project.ts
36007
+ import { createHash as createHash3 } from "node:crypto";
36008
+ import path7 from "node:path";
36009
+ function standaloneComposeProjectName(root) {
36010
+ const digest = createHash3("sha256").update(path7.resolve(root)).digest("hex").slice(0, 12);
36011
+ return `sim-${digest}`;
36012
+ }
36013
+ function legacyComposeProjectName(root) {
36014
+ const name = path7.basename(path7.resolve(root)).toLowerCase().replace(/[^a-z0-9_-]/g, "");
36015
+ if (!/^[a-z0-9]/.test(name)) {
36016
+ throw new Error(`Cannot derive the existing Compose project name from ${root}; set COMPOSE_PROJECT_NAME in its .env file.`);
36017
+ }
36018
+ return name;
36019
+ }
36020
+ var init_compose_project = () => {};
36021
+
36022
+ // src/docker.ts
36023
+ import { spawnSync as spawnSync5 } from "node:child_process";
36024
+ import { existsSync as existsSync7 } from "node:fs";
36025
+ import { homedir } from "node:os";
36026
+ import { join } from "node:path";
36027
+ function daemonUp() {
36028
+ return spawnSync5("docker", ["info"], { stdio: "ignore" }).status === 0;
36029
+ }
36030
+ function installed() {
36031
+ return executableExists("docker");
36032
+ }
36033
+ function orbstackSelected() {
36034
+ const host = process.env.DOCKER_HOST;
36035
+ if (host)
36036
+ return host.includes(".orbstack/");
36037
+ const result = spawnSync5("docker", ["context", "show"], { encoding: "utf8" });
36038
+ return result.status === 0 && result.stdout.trim() === "orbstack";
36039
+ }
36040
+ function appInstalled(app) {
36041
+ return APP_DIRS.some((dir) => existsSync7(join(dir, app.bundle)));
36042
+ }
36043
+ function macDockerApp() {
36044
+ if (orbstackSelected())
36045
+ return { app: ORBSTACK_APP, explicit: true };
36046
+ const orbstackOnly = appInstalled(ORBSTACK_APP) && !appInstalled(DOCKER_DESKTOP_APP);
36047
+ return { app: orbstackOnly ? ORBSTACK_APP : DOCKER_DESKTOP_APP, explicit: false };
36048
+ }
36049
+ function openApp(app) {
36050
+ return spawnSync5("open", ["-a", app.name], { stdio: "ignore" }).status === 0;
36051
+ }
36052
+ function startDockerApp({ app, explicit }) {
36053
+ if (openApp(app))
36054
+ return app;
36055
+ if (explicit)
36056
+ return null;
36057
+ const other = app === ORBSTACK_APP ? DOCKER_DESKTOP_APP : ORBSTACK_APP;
36058
+ return openApp(other) ? other : null;
36059
+ }
36060
+ function launchFailed(required, message, hints) {
36061
+ if (required)
36062
+ throw new SetupError(message, hints);
36063
+ log2.warn([message, ...hints].join(`
36064
+ `));
36065
+ return false;
36066
+ }
36067
+ async function ensureDocker(required) {
36068
+ if (daemonUp())
36069
+ return true;
36070
+ if (!installed()) {
36071
+ if (required)
36072
+ throw new SetupError("Docker is not installed.", INSTALL_HINTS);
36073
+ return false;
36074
+ }
36075
+ if (process.platform !== "darwin") {
36076
+ if (required) {
36077
+ throw new SetupError("Docker is installed but the daemon is not running.", [
36078
+ `start it: ${theme.command("sudo systemctl start docker")} (Linux)`
36079
+ ]);
36080
+ }
36081
+ return false;
36082
+ }
36083
+ const choice = macDockerApp();
36084
+ const launch = await confirm2({
36085
+ message: `Docker is installed but not running — start ${choice.app.name} now?`,
36086
+ initialValue: true
36087
+ });
36088
+ if (!launch) {
36089
+ if (required) {
36090
+ throw new SetupError("Docker is required for this mode.", [
36091
+ `start ${choice.app.name}, then re-run the wizard`
36092
+ ]);
36093
+ }
36094
+ return false;
36095
+ }
36096
+ const app = startDockerApp(choice);
36097
+ if (!app) {
36098
+ return choice.explicit ? launchFailed(required, "The docker CLI is pointed at OrbStack, which is not installed.", [
36099
+ `reinstall it: ${theme.command("brew install orbstack")}`,
36100
+ `or point the CLI elsewhere: unset DOCKER_HOST and DOCKER_CONTEXT, then ${theme.command("docker context use <name>")}`
36101
+ ]) : launchFailed(required, "Found the docker CLI, but no app to start.", NO_APP_HINTS);
36102
+ }
36103
+ const spin = spinner2();
36104
+ spin.start(`Waiting for the Docker daemon (${app.name})…`);
36105
+ const up = await waitFor(async () => daemonUp(), 90000, 2000);
36106
+ spin.stop(up ? "Docker is running" : `${glyph.fail} daemon did not come up`);
36107
+ if (!up) {
36108
+ return launchFailed(required, `${app.name} did not start within 90s.`, [
36109
+ app === ORBSTACK_APP ? "open OrbStack manually once to finish its first-run setup, then re-run" : "first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run"
36110
+ ]);
36111
+ }
36112
+ return true;
36113
+ }
36114
+ var INSTALL_HINTS, NO_APP_HINTS, ORBSTACK_APP, DOCKER_DESKTOP_APP, APP_DIRS;
36115
+ var init_docker = __esm(() => {
36116
+ init_errors();
36117
+ init_executables();
36118
+ init_probes();
36119
+ init_prompter();
36120
+ init_theme();
36121
+ INSTALL_HINTS = [
36122
+ "install Docker Desktop: https://docker.com/products/docker-desktop",
36123
+ `or OrbStack (lighter on macOS): ${theme.command("brew install orbstack")}`
36124
+ ];
36125
+ NO_APP_HINTS = [
36126
+ ...INSTALL_HINTS,
36127
+ `or start your existing runtime its own way, e.g. ${theme.command("colima start")}`
36128
+ ];
36129
+ ORBSTACK_APP = { name: "OrbStack", bundle: "OrbStack.app" };
36130
+ DOCKER_DESKTOP_APP = { name: "Docker", bundle: "Docker.app" };
36131
+ APP_DIRS = ["/Applications", join(homedir(), "Applications")];
36132
+ });
36133
+
36114
36134
  // src/urls.ts
36115
36135
  var APP_URL = "http://localhost:3000", APP_SIGNUP_URL;
36116
36136
  var init_urls = __esm(() => {
@@ -38103,7 +38123,7 @@ function parseSetupArguments(rawArgs) {
38103
38123
  init_errors();
38104
38124
  init_theme();
38105
38125
  init_version();
38106
- var SETUP_FEATURES2 = "email | storage | sandbox | jobs | cache | knowledge | knowledge-embeddings | llm | integration <slug>";
38126
+ var SETUP_FEATURES2 = "email | storage | sandbox | jobs | cache | knowledge | knowledge-embeddings | chat | llm | integration <slug>";
38107
38127
  var USAGE = `Usage:
38108
38128
  sim-setup [--quick] [--dir <path>] [--mode compose|dev|k8s]
38109
38129
  sim-setup config show configured capabilities and integrations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sim-setup",
3
- "version": "1.0.1",
3
+ "version": "1.0.2-preview.34.1",
4
4
  "description": "Set up and manage a self-hosted Sim installation",
5
5
  "type": "module",
6
6
  "bin": {