@odla-ai/cli 0.28.2 → 0.29.0

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/bin.js CHANGED
@@ -174,7 +174,7 @@ function absoluteEntryPath(entryPath) {
174
174
 
175
175
  // src/bin.ts
176
176
  var argv = process.argv.slice(2);
177
- requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-2WZIFXA6.js")).runCli()).catch((err) => {
177
+ requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-DNOAYDBR.js")).runCli()).catch((err) => {
178
178
  console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));
179
179
  process.exitCode = exitCodeFor(err);
180
180
  });
@@ -1131,7 +1131,7 @@ var DEFAULT_ENVS = ["dev"];
1131
1131
  var DEFAULT_SERVICES = ["db", "ai"];
1132
1132
  var configImportSerial = 0;
1133
1133
  var GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events";
1134
- async function loadProjectConfig(configPath = "odla.config.mjs") {
1134
+ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1135
1135
  const resolved = resolve2(configPath);
1136
1136
  if (!existsSync3(resolved)) {
1137
1137
  throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
@@ -1144,7 +1144,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs") {
1144
1144
  const envs = unique2(raw.envs?.length ? raw.envs : DEFAULT_ENVS);
1145
1145
  const services = unique2(raw.services?.length ? raw.services : DEFAULT_SERVICES);
1146
1146
  validateServices(services, resolved);
1147
- validateCalendarConfig(raw, envs, services, resolved);
1147
+ validateCalendarConfig(raw, unique2([...envs, ...options.additionalEnvs ?? []]), services, resolved);
1148
1148
  const local = {
1149
1149
  tokenFile: resolve2(rootDir, raw.local?.tokenFile ?? ".odla/dev-token.json"),
1150
1150
  credentialsFile: resolve2(rootDir, raw.local?.credentialsFile ?? ".odla/credentials.local.json"),
@@ -1194,12 +1194,12 @@ function buildPlan(cfg) {
1194
1194
  }
1195
1195
  function calendarServiceConfig(cfg, env) {
1196
1196
  if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
1197
- if (!cfg.envs.includes(env)) throw new Error(`calendar env "${env}" is not declared in config envs`);
1197
+ if (!cfg.envs.includes(env) && env !== "prod") throw new Error(`calendar env "${env}" is not declared in config envs`);
1198
1198
  const google = cfg.calendar?.google;
1199
1199
  if (!google) throw new Error("calendar.google is required when the calendar service is enabled");
1200
- const availability = unique2(
1201
- (google.availabilityCalendars?.[env] ?? google.calendars?.[env]).map((id) => id.trim())
1202
- );
1200
+ const configured = google.availabilityCalendars?.[env] ?? google.calendars?.[env];
1201
+ if (!configured?.length) throw new Error(`calendar.google.availabilityCalendars.${env} is required`);
1202
+ const availability = unique2(configured.map((id) => id.trim()));
1203
1203
  return {
1204
1204
  provider: "google",
1205
1205
  access: "book",
@@ -1266,13 +1266,18 @@ function validateCalendarConfig(cfg, envs, services, path) {
1266
1266
  }
1267
1267
  const availability = google[availabilityKey];
1268
1268
  if (!isRecord6(availability)) throw new Error(`${path}: calendar.google.${availabilityKey} must map env names to calendar ids`);
1269
- const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env));
1269
+ const unknownEnv = Object.keys(availability).find((env) => !envs.includes(env) && env !== "prod");
1270
1270
  if (unknownEnv) throw new Error(`${path}: calendar.google.${availabilityKey}.${unknownEnv} is not in config envs`);
1271
1271
  for (const env of envs) {
1272
1272
  const ids = availability[env];
1273
1273
  if (!Array.isArray(ids) || ids.length === 0) {
1274
1274
  throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1275
1275
  }
1276
+ }
1277
+ for (const [env, ids] of Object.entries(availability)) {
1278
+ if (!Array.isArray(ids) || ids.length === 0) {
1279
+ throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must be a non-empty array`);
1280
+ }
1276
1281
  if (ids.length > 10) {
1277
1282
  throw new Error(`${path}: calendar.google.${availabilityKey}.${env} must contain at most 10 calendar ids`);
1278
1283
  }
@@ -1282,7 +1287,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
1282
1287
  }
1283
1288
  if (google.bookingCalendar !== void 0) {
1284
1289
  if (!isRecord6(google.bookingCalendar)) throw new Error(`${path}: calendar.google.bookingCalendar must map env names to one calendar id`);
1285
- const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env));
1290
+ const unknownBookingEnv = Object.keys(google.bookingCalendar).find((env) => !envs.includes(env) && env !== "prod");
1286
1291
  if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingCalendar.${unknownBookingEnv} is not in config envs`);
1287
1292
  for (const [env, value2] of Object.entries(google.bookingCalendar)) {
1288
1293
  if (!safeText3(value2, 1024)) {
@@ -1292,7 +1297,7 @@ function validateCalendarConfig(cfg, envs, services, path) {
1292
1297
  }
1293
1298
  if (google.bookingPageUrl !== void 0) {
1294
1299
  if (!isRecord6(google.bookingPageUrl)) throw new Error(`${path}: calendar.google.bookingPageUrl must map env names to HTTPS URLs or null`);
1295
- const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env));
1300
+ const unknownBookingEnv = Object.keys(google.bookingPageUrl).find((env) => !envs.includes(env) && env !== "prod");
1296
1301
  if (unknownBookingEnv) throw new Error(`${path}: calendar.google.bookingPageUrl.${unknownBookingEnv} is not in config envs`);
1297
1302
  for (const [env, value2] of Object.entries(google.bookingPageUrl)) {
1298
1303
  if (value2 !== null && !safeHttpsUrl(value2)) {
@@ -1838,7 +1843,7 @@ async function authCommand(parsed, deps = {}) {
1838
1843
  import { tenantIdFor } from "@odla-ai/apps";
1839
1844
  function resolveEnv(cfg, requested) {
1840
1845
  const env = requested ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
1841
- if (!env || !cfg.envs.includes(env)) {
1846
+ if (!env || !cfg.envs.includes(env) && env !== "prod") {
1842
1847
  throw new Error(`env "${env ?? ""}" is not declared in ${cfg.configPath}`);
1843
1848
  }
1844
1849
  return env;
@@ -1848,12 +1853,10 @@ function resolveTenant(cfg, requested) {
1848
1853
  return { env, tenant: tenantIdFor(cfg.app.id, env) };
1849
1854
  }
1850
1855
  function bothTenants(cfg) {
1851
- for (const env of ["dev", "prod"]) {
1852
- if (!cfg.envs.includes(env)) {
1853
- throw new Error(
1854
- `env "${env}" is not declared in ${cfg.configPath} \u2014 add it to \`envs\` and run \`odla-ai provision --yes\` before transferring data between environments`
1855
- );
1856
- }
1856
+ if (!cfg.envs.includes("dev")) {
1857
+ throw new Error(
1858
+ `env "dev" is not declared in ${cfg.configPath} \u2014 sandbox/live transfers require an existing sandbox`
1859
+ );
1857
1860
  }
1858
1861
  return { sandbox: tenantIdFor(cfg.app.id, "dev"), live: tenantIdFor(cfg.app.id, "prod") };
1859
1862
  }
@@ -1941,7 +1944,7 @@ function errorMessage(body) {
1941
1944
  // src/human-session.ts
1942
1945
  async function requireStudioHuman(configPath, action2, destination = "app", env) {
1943
1946
  const cfg = await loadProjectConfig(configPath);
1944
- const appEnv = env && cfg.envs.includes(env) ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
1947
+ const appEnv = env && (cfg.envs.includes(env) || env === "prod") ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
1945
1948
  const path = destination === "app" ? `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/settings/app` : `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/${destination}`;
1946
1949
  throw new Error(
1947
1950
  `human_session_required: ${action2} requires the signed-in owner in Studio. A CLI device token is an agent credential, so another approval or retry cannot work. ${new URL(path, cfg.platformUrl).href}`
@@ -2586,8 +2589,7 @@ async function ensureCalendarConnected(ctx, options) {
2586
2589
  async function lifecycleContext(options, optionalProjectCapabilities = []) {
2587
2590
  const cfg = await loadProjectConfig(options.configPath);
2588
2591
  if (!cfg.services.includes("calendar")) throw new Error("calendar service is not enabled in config services");
2589
- const env = options.env ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0]);
2590
- if (!env || !cfg.envs.includes(env)) throw new Error(`env "${env ?? ""}" is not declared in ${options.configPath}`);
2592
+ const env = resolveEnv(cfg, options.env);
2591
2593
  calendarServiceConfig(cfg, env);
2592
2594
  const out = options.stdout ?? console;
2593
2595
  const doFetch = options.fetch ?? fetch;
@@ -2788,8 +2790,7 @@ function printGroup(out, heading, items) {
2788
2790
  import { DEFAULT_CATALOG } from "@odla-ai/ai";
2789
2791
  async function aiModels(options = {}) {
2790
2792
  const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
2791
- const env = options.env ?? cfg.envs[0] ?? "dev";
2792
- if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
2793
+ const env = resolveEnv(cfg, options.env);
2793
2794
  const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
2794
2795
  url.searchParams.set("env", env);
2795
2796
  const response2 = await (options.fetch ?? fetch)(url);
@@ -4335,10 +4336,7 @@ async function secretsPushAfterPreflight(options) {
4335
4336
  async function secretsPushImpl(options, preflight) {
4336
4337
  const out = options.stdout ?? console;
4337
4338
  const cfg = await loadProjectConfig(options.configPath);
4338
- const env = options.env;
4339
- if (!cfg.envs.includes(env)) {
4340
- throw new Error(`env "${env}" is not in config envs (${cfg.envs.join(", ")})`);
4341
- }
4339
+ const env = resolveEnv(cfg, options.env);
4342
4340
  if (PROD_ENV_NAMES.has(env) && !options.yes) {
4343
4341
  throw new Error(`refusing to push a secret to "${env}" without --yes`);
4344
4342
  }
@@ -4448,14 +4446,12 @@ async function resolveVaultWrite(options) {
4448
4446
  const out = options.stdout ?? console;
4449
4447
  const doFetch = options.fetch ?? fetch;
4450
4448
  const cfg = await loadProjectConfig(options.configPath);
4451
- if (!cfg.envs.includes(options.env)) {
4452
- throw new Error(`env "${options.env}" is not in config envs (${cfg.envs.join(", ")})`);
4453
- }
4454
- if (PROD_ENV_NAMES2.has(options.env) && !options.yes) {
4455
- throw new Error(`refusing to store a secret for "${options.env}" without --yes`);
4449
+ const env = resolveEnv(cfg, options.env);
4450
+ if (PROD_ENV_NAMES2.has(env) && !options.yes) {
4451
+ throw new Error(`refusing to store a secret for "${env}" without --yes`);
4456
4452
  }
4457
4453
  const value2 = await secretInputValue(options, "secret");
4458
- return { cfg, tenantId: tenantIdFor3(cfg.app.id, options.env), value: value2, doFetch, out };
4454
+ return { cfg, tenantId: tenantIdFor3(cfg.app.id, env), value: value2, doFetch, out };
4459
4455
  }
4460
4456
 
4461
4457
  // src/skill.ts
@@ -4742,8 +4738,7 @@ function listFiles(dir) {
4742
4738
  async function smoke(options) {
4743
4739
  const out = options.stdout ?? console;
4744
4740
  const cfg = await loadProjectConfig(options.configPath);
4745
- const env = options.env ?? (cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod");
4746
- if (!cfg.envs.includes(env)) throw new Error(`env "${env}" is not declared in ${displayPath(cfg.configPath, cfg.rootDir)}`);
4741
+ const env = resolveEnv(cfg, options.env);
4747
4742
  const credentials = readCredentials(cfg.local.credentialsFile);
4748
4743
  if (!credentials) {
4749
4744
  throw new Error(`local credentials missing: ${displayPath(cfg.local.credentialsFile, cfg.rootDir)}. Run "odla-ai provision --write-dev-vars".`);
@@ -8819,7 +8814,7 @@ Usage:
8819
8814
  odla-ai security report <job-id> [--json]
8820
8815
  odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
8821
8816
  odla-ai security run [target] --self --ack-redacted-source
8822
- odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
8817
+ odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
8823
8818
  odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
8824
8819
  odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
8825
8820
  odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
@@ -8919,6 +8914,9 @@ Commands:
8919
8914
  platform Read canonical fleet health, releases, provider load/freshness,
8920
8915
  explicit unknowns, and next actions through a read-only grant.
8921
8916
  provision Register services, compose integrations, persist credentials, optionally push secrets.
8917
+ "provision --live --yes" initializes only the live instance of
8918
+ an existing sandbox app and enables every configured service;
8919
+ no edit to the dev-first envs list is required.
8922
8920
  smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
8923
8921
  skill Same installer; --agent accepts all, claude, codex, cursor,
8924
8922
  copilot, gemini, or agents (repeatable or comma-separated).
@@ -8931,8 +8929,10 @@ Safety:
8931
8929
  Every app has two databases on odla.ai: a sandbox (env "dev", tenant
8932
8930
  <appId>--dev) and a live one (env "prod", tenant <appId>). Both are served by
8933
8931
  production odla.ai \u2014 there is no separate odla to point at. New projects get
8934
- the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
8935
- provision the live database; use --dry-run first to inspect the resolved plan.
8932
+ the sandbox only. Run "provision --live --dry-run", then "provision --live
8933
+ --yes" to initialize live from that existing sandbox project's config. Add
8934
+ "prod" to envs only when ordinary provision runs should manage both; use
8935
+ --dry-run first to inspect either resolved plan.
8936
8936
  A real provision first verifies both the released CLI version and the exact
8937
8937
  pinned versions of every external @odla-ai runtime module before importing
8938
8938
  the command graph. A stale workspace module blocks with its resolved path and
@@ -10879,10 +10879,33 @@ async function safeText7(res) {
10879
10879
  }
10880
10880
  }
10881
10881
 
10882
+ // src/provision-live.ts
10883
+ function liveProvisionConfig(cfg) {
10884
+ if (!cfg.envs.includes("dev")) {
10885
+ throw new Error(
10886
+ `--live initializes an existing sandbox, but env "dev" is not declared in ${cfg.configPath}; use normal "odla-ai provision --yes" for a prod-only project`
10887
+ );
10888
+ }
10889
+ if (cfg.services.includes("calendar")) {
10890
+ const google = cfg.calendar?.google;
10891
+ const availability = google?.availabilityCalendars ?? google?.calendars;
10892
+ if (!availability?.prod?.length) {
10893
+ throw new Error(
10894
+ `--live requires calendar.google.availabilityCalendars.prod in ${cfg.configPath}; live uses a separate Google Calendar connection, so sandbox calendar ids are not copied`
10895
+ );
10896
+ }
10897
+ }
10898
+ return { ...cfg, envs: ["prod"] };
10899
+ }
10900
+
10882
10901
  // src/provision.ts
10883
10902
  async function provision(options) {
10884
10903
  const out = options.stdout ?? console;
10885
- const cfg = await loadProjectConfig(options.configPath);
10904
+ const loaded = await loadProjectConfig(
10905
+ options.configPath,
10906
+ options.live ? { additionalEnvs: ["prod"] } : void 0
10907
+ );
10908
+ const cfg = options.live ? liveProvisionConfig(loaded) : loaded;
10886
10909
  const plan = buildPlan(cfg);
10887
10910
  const hasDb = cfg.services.includes("db");
10888
10911
  const hasO11y = cfg.services.includes("o11y");
@@ -10896,6 +10919,7 @@ async function provision(options) {
10896
10919
  out.log(` platform: ${plan.platformUrl}`);
10897
10920
  out.log(` db: ${plan.dbEndpoint}`);
10898
10921
  out.log(` envs: ${plan.envs.join(", ")}`);
10922
+ if (options.live) out.log(" target: live setup (existing sandbox app)");
10899
10923
  out.log(` services: ${plan.services.join(", ")}`);
10900
10924
  out.log(` integrations: ${plan.integrations.length ? plan.integrations.join(", ") : "none"}`);
10901
10925
  const productionEnvs = plan.envs.filter((env) => env === "prod" || env === "production");
@@ -10956,6 +10980,10 @@ async function provision(options) {
10956
10980
  const existing = await apps.resolveApp(cfg.app.id);
10957
10981
  if (existing) {
10958
10982
  out.log(`app: ${cfg.app.id} already exists`);
10983
+ } else if (options.live) {
10984
+ throw new Error(
10985
+ `cannot set up live for "${cfg.app.id}": the sandbox app does not exist yet; run "odla-ai provision" first`
10986
+ );
10959
10987
  } else {
10960
10988
  try {
10961
10989
  await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
@@ -12908,6 +12936,7 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
12908
12936
  async function provisionCommand(parsed, dependencies) {
12909
12937
  assertArgs(parsed, [
12910
12938
  "config",
12939
+ "live",
12911
12940
  "dry-run",
12912
12941
  "rotate-keys",
12913
12942
  "rotate-o11y-token",
@@ -12924,6 +12953,7 @@ async function provisionCommand(parsed, dependencies) {
12924
12953
  const writeDevVars2 = parsed.options["write-dev-vars"];
12925
12954
  const options = {
12926
12955
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
12956
+ live: parsed.options.live === true,
12927
12957
  dryRun: parsed.options["dry-run"] === true,
12928
12958
  rotateKeys: parsed.options["rotate-keys"] === true,
12929
12959
  rotateO11yToken: parsed.options["rotate-o11y-token"] === true,
@@ -13026,4 +13056,4 @@ export {
13026
13056
  isTerminalHostedSecurityStatus,
13027
13057
  runCli
13028
13058
  };
13029
- //# sourceMappingURL=chunk-STJCWZJU.js.map
13059
+ //# sourceMappingURL=chunk-YTVLTADT.js.map