@onexapis/cli 1.1.64 → 1.1.65

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/cli.js CHANGED
@@ -1807,21 +1807,43 @@ function getValidCategories() {
1807
1807
  ];
1808
1808
  }
1809
1809
  var AUTH_DIR = path9__default.default.join(os__default.default.homedir(), ".onexthm");
1810
- var AUTH_FILE = path9__default.default.join(AUTH_DIR, "auth.json");
1811
- function getApiUrl() {
1812
- return process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
1810
+ var ENV_URLS = {
1811
+ dev: "https://platform-dev.onexeos.com",
1812
+ prod: "https://platform-staging.onexeos.com"
1813
+ };
1814
+ function getAuthFile(env = "dev") {
1815
+ const newFile = path9__default.default.join(AUTH_DIR, `auth-${env}.json`);
1816
+ if (env === "dev") {
1817
+ const legacyFile = path9__default.default.join(AUTH_DIR, "auth.json");
1818
+ if (fs__default.default.existsSync(legacyFile) && !fs__default.default.existsSync(newFile)) {
1819
+ try {
1820
+ fs__default.default.moveSync(legacyFile, newFile);
1821
+ } catch {
1822
+ try {
1823
+ fs__default.default.copySync(legacyFile, newFile);
1824
+ fs__default.default.removeSync(legacyFile);
1825
+ } catch {
1826
+ }
1827
+ }
1828
+ }
1829
+ }
1830
+ return newFile;
1831
+ }
1832
+ function getApiUrl(env = "dev") {
1833
+ return process.env.ONEXTHM_API_URL || ENV_URLS[env];
1813
1834
  }
1814
- async function saveAuthTokens(tokens) {
1835
+ async function saveAuthTokens(tokens, env = "dev") {
1815
1836
  await fs__default.default.ensureDir(AUTH_DIR);
1816
1837
  const key = getMachineKey();
1817
1838
  const data = JSON.stringify(tokens);
1818
1839
  const encrypted = encrypt(data, key);
1819
- await fs__default.default.writeFile(AUTH_FILE, encrypted, "utf-8");
1840
+ await fs__default.default.writeFile(getAuthFile(env), encrypted, "utf-8");
1820
1841
  }
1821
- function loadAuthTokens() {
1842
+ function loadAuthTokens(env = "dev") {
1822
1843
  try {
1823
- if (!fs__default.default.existsSync(AUTH_FILE)) return null;
1824
- const encrypted = fs__default.default.readFileSync(AUTH_FILE, "utf-8");
1844
+ const file = getAuthFile(env);
1845
+ if (!fs__default.default.existsSync(file)) return null;
1846
+ const encrypted = fs__default.default.readFileSync(file, "utf-8");
1825
1847
  const key = getMachineKey();
1826
1848
  const data = decrypt(encrypted, key);
1827
1849
  return JSON.parse(data);
@@ -1829,34 +1851,34 @@ function loadAuthTokens() {
1829
1851
  return null;
1830
1852
  }
1831
1853
  }
1832
- async function clearAuthTokens() {
1854
+ async function clearAuthTokens(env = "dev") {
1833
1855
  try {
1834
- await fs__default.default.remove(AUTH_FILE);
1856
+ await fs__default.default.remove(getAuthFile(env));
1835
1857
  } catch {
1836
1858
  }
1837
1859
  }
1838
1860
  function isTokenExpired(tokens) {
1839
1861
  return Date.now() / 1e3 > tokens.expiresAt - 300;
1840
1862
  }
1841
- async function getValidTokens() {
1842
- const tokens = loadAuthTokens();
1863
+ async function getValidTokens(env = "dev") {
1864
+ const tokens = loadAuthTokens(env);
1843
1865
  if (!tokens) return null;
1844
1866
  if (!isTokenExpired(tokens)) return tokens;
1845
1867
  try {
1846
- const apiUrl = getApiUrl();
1868
+ const apiUrl = getApiUrl(env);
1847
1869
  const response = await fetch(`${apiUrl}/auth/refresh`, {
1848
1870
  method: "POST",
1849
1871
  headers: { "Content-Type": "application/json" },
1850
1872
  body: JSON.stringify({ refresh_token: tokens.refreshToken })
1851
1873
  });
1852
1874
  if (!response.ok) {
1853
- await clearAuthTokens();
1875
+ await clearAuthTokens(env);
1854
1876
  return null;
1855
1877
  }
1856
1878
  const data = await response.json();
1857
1879
  const body = data.statusCode ? data.body : data;
1858
1880
  if (!body.IdToken) {
1859
- await clearAuthTokens();
1881
+ await clearAuthTokens(env);
1860
1882
  return null;
1861
1883
  }
1862
1884
  const refreshed = {
@@ -1865,17 +1887,19 @@ async function getValidTokens() {
1865
1887
  idToken: body.IdToken,
1866
1888
  expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
1867
1889
  };
1868
- await saveAuthTokens(refreshed);
1890
+ await saveAuthTokens(refreshed, env);
1869
1891
  return refreshed;
1870
1892
  } catch {
1871
- await clearAuthTokens();
1893
+ await clearAuthTokens(env);
1872
1894
  return null;
1873
1895
  }
1874
1896
  }
1875
- async function authenticatedFetch(url, init) {
1876
- const tokens = await getValidTokens();
1897
+ async function authenticatedFetch(url, init, env = "dev") {
1898
+ const tokens = await getValidTokens(env);
1877
1899
  if (!tokens) {
1878
- throw new Error("Not logged in. Run: onexthm login");
1900
+ throw new Error(
1901
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
1902
+ );
1879
1903
  }
1880
1904
  const headers = new Headers(init?.headers);
1881
1905
  headers.set("Authorization", `Bearer ${tokens.idToken}`);
@@ -1959,7 +1983,7 @@ async function initCommand(projectName, options = {}) {
1959
1983
  }
1960
1984
  if (!options.yes) {
1961
1985
  try {
1962
- const apiUrl = getApiUrl();
1986
+ const apiUrl = getApiUrl(options.env ?? "dev");
1963
1987
  const controller = new AbortController();
1964
1988
  const timeout = setTimeout(() => controller.abort(), 3e3);
1965
1989
  const response = await fetch(
@@ -3908,9 +3932,7 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3908
3932
  console.log();
3909
3933
  console.log(chalk4__default.default.white("2. Check API URL configuration:"));
3910
3934
  console.log(chalk4__default.default.gray(` Current API URL: ${apiUrl}`));
3911
- console.log(
3912
- chalk4__default.default.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3913
- );
3935
+ console.log(chalk4__default.default.gray(" Override with ONEXTHM_API_URL env var if needed"));
3914
3936
  console.log();
3915
3937
  console.log(chalk4__default.default.white("3. Pin a specific version (CI/production):"));
3916
3938
  console.log(
@@ -3920,15 +3942,17 @@ function showDownloadFailureHelp(themeId, apiUrl) {
3920
3942
  }
3921
3943
  async function downloadCommand(options) {
3922
3944
  logger.header("Download Theme");
3945
+ const env = options.env ?? "dev";
3946
+ const apiUrl = getApiUrl(env);
3947
+ logger.info(`Environment: ${env} (${apiUrl})`);
3923
3948
  const spinner = ora__default.default("Initializing download...").start();
3924
- if (options.bucket || options.environment) {
3949
+ if (options.bucket) {
3925
3950
  spinner.stop();
3926
3951
  logger.warning(
3927
- "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3952
+ "--bucket is deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3928
3953
  );
3929
3954
  spinner.start();
3930
3955
  }
3931
- const apiUrl = getApiUrl();
3932
3956
  try {
3933
3957
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3934
3958
  const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
@@ -3985,6 +4009,7 @@ async function downloadCommand(options) {
3985
4009
  console.log(
3986
4010
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
3987
4011
  );
4012
+ console.log(chalk4__default.default.cyan(" Env: ") + chalk4__default.default.white(env));
3988
4013
  console.log(chalk4__default.default.cyan(" Source: ") + chalk4__default.default.white(apiUrl));
3989
4014
  console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
3990
4015
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
@@ -4027,11 +4052,9 @@ async function resolveLatestVersion2(apiUrl, themeId) {
4027
4052
  }
4028
4053
  return latest;
4029
4054
  }
4030
- async function fetchSourceZip(apiUrl, themeId, version2) {
4055
+ async function fetchSourceZip(apiUrl, themeId, version2, env) {
4031
4056
  const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
4032
- const response = await authenticatedFetch(url, {
4033
- method: "GET"
4034
- });
4057
+ const response = await authenticatedFetch(url, { method: "GET" }, env);
4035
4058
  if (!response.ok) {
4036
4059
  if (response.status === 404) {
4037
4060
  throw new Error(
@@ -4040,7 +4063,7 @@ async function fetchSourceZip(apiUrl, themeId, version2) {
4040
4063
  }
4041
4064
  if (response.status === 401 || response.status === 403) {
4042
4065
  throw new Error(
4043
- `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
4066
+ `Not authorized to download source for "${themeId}". Run \`onexthm login --env ${env}\` first.`
4044
4067
  );
4045
4068
  }
4046
4069
  throw new Error(
@@ -4159,14 +4182,19 @@ async function renameTheme(themeDir, oldName, newName) {
4159
4182
  }
4160
4183
  async function cloneCommand(themeName, options) {
4161
4184
  logger.header("Clone Theme Source");
4162
- if (options.bucket || options.environment) {
4185
+ const env = options.env ?? "dev";
4186
+ const apiUrl = getApiUrl(env);
4187
+ logger.info(`Environment: ${env} (${apiUrl})`);
4188
+ if (options.bucket) {
4163
4189
  logger.warning(
4164
- "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
4190
+ "--bucket is deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
4165
4191
  );
4166
4192
  }
4167
- const tokens = await getValidTokens();
4193
+ const tokens = await getValidTokens(env);
4168
4194
  if (!tokens) {
4169
- logger.error("Not logged in. Run: onexthm login");
4195
+ logger.error(
4196
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
4197
+ );
4170
4198
  process.exit(1);
4171
4199
  }
4172
4200
  let newName = options.name;
@@ -4175,7 +4203,6 @@ async function cloneCommand(themeName, options) {
4175
4203
  }
4176
4204
  const spinner = ora__default.default("Initializing clone...").start();
4177
4205
  try {
4178
- const apiUrl = getApiUrl();
4179
4206
  const outputDir = options.output || path9__default.default.resolve(process.cwd(), newName);
4180
4207
  if (await fs__default.default.pathExists(outputDir)) {
4181
4208
  spinner.fail(chalk4__default.default.red(`Directory already exists: ${outputDir}`));
@@ -4195,7 +4222,7 @@ async function cloneCommand(themeName, options) {
4195
4222
  spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
4196
4223
  let zipBuffer;
4197
4224
  try {
4198
- zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
4225
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version2, env);
4199
4226
  } catch (error) {
4200
4227
  spinner.fail(chalk4__default.default.red(error.message));
4201
4228
  console.log();
@@ -4229,7 +4256,7 @@ async function cloneCommand(themeName, options) {
4229
4256
  [
4230
4257
  "# API Configuration (enables real data in preview)",
4231
4258
  "# Get your Company ID from the OneX dashboard",
4232
- "NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
4259
+ `NEXT_PUBLIC_API_URL=${apiUrl}`,
4233
4260
  "NEXT_PUBLIC_COMPANY_ID=",
4234
4261
  ""
4235
4262
  ].join("\n")
@@ -4282,6 +4309,7 @@ async function cloneCommand(themeName, options) {
4282
4309
  console.log(
4283
4310
  chalk4__default.default.cyan(" Source: ") + chalk4__default.default.gray(`${themeName}@${version2}`)
4284
4311
  );
4312
+ console.log(chalk4__default.default.cyan(" Env: ") + chalk4__default.default.white(env));
4285
4313
  console.log(chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(newName));
4286
4314
  console.log(chalk4__default.default.cyan(" Location: ") + chalk4__default.default.white(outputDir));
4287
4315
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
@@ -4558,9 +4586,13 @@ async function configCommand() {
4558
4586
 
4559
4587
  // src/commands/login.ts
4560
4588
  init_logger();
4561
- async function loginCommand() {
4589
+ async function loginCommand(options = {}) {
4590
+ const env = options.env ?? "dev";
4591
+ const apiUrl = getApiUrl(env);
4562
4592
  logger.header("OneX Theme Developer Login");
4563
- const existing = loadAuthTokens();
4593
+ logger.info(`Environment: ${env} (${apiUrl})`);
4594
+ logger.newLine();
4595
+ const existing = loadAuthTokens(env);
4564
4596
  if (existing) {
4565
4597
  logger.info(`Already logged in as: ${existing.user.email}`);
4566
4598
  const { relogin } = await inquirer__default.default.prompt([
@@ -4589,7 +4621,6 @@ async function loginCommand() {
4589
4621
  ]);
4590
4622
  logger.startSpinner("Logging in...");
4591
4623
  try {
4592
- const apiUrl = getApiUrl();
4593
4624
  const response = await fetch(`${apiUrl}/auth/login`, {
4594
4625
  method: "POST",
4595
4626
  headers: { "Content-Type": "application/json" },
@@ -4624,15 +4655,18 @@ async function loginCommand() {
4624
4655
  userId: claims.sub
4625
4656
  }
4626
4657
  };
4627
- await saveAuthTokens(tokens);
4658
+ await saveAuthTokens(tokens, env);
4628
4659
  logger.stopSpinner(true, "Logged in!");
4629
4660
  logger.newLine();
4630
- logger.info(` Email: ${tokens.user.email}`);
4631
- if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4661
+ logger.info(` Environment: ${env}`);
4662
+ logger.info(` Email: ${tokens.user.email}`);
4663
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4632
4664
  if (tokens.user.companyId)
4633
- logger.info(` Company: ${tokens.user.companyId}`);
4665
+ logger.info(` Company: ${tokens.user.companyId}`);
4634
4666
  logger.newLine();
4635
- logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
4667
+ logger.success(
4668
+ `Token stored securely in ~/.onexthm/auth-${env}.json (encrypted)`
4669
+ );
4636
4670
  } catch (error) {
4637
4671
  logger.stopSpinner(false, "Login failed");
4638
4672
  logger.error(error instanceof Error ? error.message : "Connection failed");
@@ -4642,32 +4676,37 @@ async function loginCommand() {
4642
4676
 
4643
4677
  // src/commands/logout.ts
4644
4678
  init_logger();
4645
- async function logoutCommand() {
4646
- const tokens = loadAuthTokens();
4679
+ async function logoutCommand(options = {}) {
4680
+ const env = options.env ?? "dev";
4681
+ const tokens = loadAuthTokens(env);
4647
4682
  if (!tokens) {
4648
- logger.info("Not logged in.");
4683
+ logger.info(`Not logged in to ${env} environment.`);
4649
4684
  return;
4650
4685
  }
4651
- await clearAuthTokens();
4652
- logger.success(`Logged out (was: ${tokens.user.email})`);
4686
+ await clearAuthTokens(env);
4687
+ logger.success(`Logged out of ${env} (was: ${tokens.user.email})`);
4653
4688
  }
4654
4689
 
4655
4690
  // src/commands/whoami.ts
4656
4691
  init_logger();
4657
- async function whoamiCommand() {
4658
- const tokens = loadAuthTokens();
4692
+ async function whoamiCommand(options = {}) {
4693
+ const env = options.env ?? "dev";
4694
+ const tokens = loadAuthTokens(env);
4659
4695
  if (!tokens) {
4660
- logger.error("Not logged in. Run: onexthm login");
4696
+ logger.error(
4697
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
4698
+ );
4661
4699
  process.exit(1);
4662
4700
  }
4663
4701
  const expired = isTokenExpired(tokens);
4664
4702
  logger.header("OneX Theme Developer");
4665
- logger.info(` Email: ${tokens.user.email}`);
4666
- if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4703
+ logger.info(` Environment: ${env} (${getApiUrl(env)})`);
4704
+ logger.info(` Email: ${tokens.user.email}`);
4705
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4667
4706
  if (tokens.user.companyId)
4668
- logger.info(` Company: ${tokens.user.companyId}`);
4707
+ logger.info(` Company: ${tokens.user.companyId}`);
4669
4708
  logger.info(
4670
- ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4709
+ ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4671
4710
  );
4672
4711
  }
4673
4712
 
@@ -4762,10 +4801,15 @@ function buildAssetMap(entries) {
4762
4801
 
4763
4802
  // src/commands/publish.ts
4764
4803
  async function publishCommand(options) {
4804
+ const env = options.env ?? "dev";
4765
4805
  logger.header("OneX Theme Publish");
4766
- const tokens = await getValidTokens();
4806
+ logger.info(`Environment: ${env} (${getApiUrl(env)})`);
4807
+ logger.newLine();
4808
+ const tokens = await getValidTokens(env);
4767
4809
  if (!tokens) {
4768
- logger.error("Not logged in. Run: onexthm login");
4810
+ logger.error(
4811
+ `Not logged in to ${env} environment. Run: onexthm login --env ${env}`
4812
+ );
4769
4813
  process.exit(1);
4770
4814
  }
4771
4815
  logger.info(`Logged in as: ${tokens.user.email}`);
@@ -4816,7 +4860,7 @@ async function publishCommand(options) {
4816
4860
  logger.info(`Theme: ${themeId}`);
4817
4861
  logger.info(`Version: ${version2}`);
4818
4862
  logger.newLine();
4819
- const apiUrl = getApiUrl();
4863
+ const apiUrl = getApiUrl(env);
4820
4864
  logger.startSpinner("Registering theme...");
4821
4865
  try {
4822
4866
  const regResponse = await authenticatedFetch(
@@ -4833,7 +4877,8 @@ async function publishCommand(options) {
4833
4877
  tags: pkg.keywords || [],
4834
4878
  thumbnail_url: pkg.onex?.thumbnail || ""
4835
4879
  })
4836
- }
4880
+ },
4881
+ env
4837
4882
  );
4838
4883
  const regData = await regResponse.json();
4839
4884
  const regBody = regData.statusCode ? regData.body : regData;
@@ -4855,7 +4900,8 @@ async function publishCommand(options) {
4855
4900
  try {
4856
4901
  const checkResponse = await authenticatedFetch(
4857
4902
  `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
4858
- { method: "GET" }
4903
+ { method: "GET" },
4904
+ env
4859
4905
  );
4860
4906
  const checkData = await checkResponse.json();
4861
4907
  const checkBody = checkData.statusCode ? checkData.body : checkData;
@@ -4920,7 +4966,7 @@ Or use the --bump flag:
4920
4966
  logger.startSpinner(`Uploading ${videoAssets.length} video(s)...`);
4921
4967
  try {
4922
4968
  for (const video of videoAssets) {
4923
- const url = await uploadVideoMultipart(apiUrl, themeId, video);
4969
+ const url = await uploadVideoMultipart(apiUrl, themeId, video, env);
4924
4970
  videoUrls[video.originalPath] = url;
4925
4971
  }
4926
4972
  logger.stopSpinner(true, `Uploaded ${videoAssets.length} video(s)`);
@@ -4962,7 +5008,8 @@ Or use the --bump flag:
4962
5008
  content_type: a.contentType
4963
5009
  }))
4964
5010
  })
4965
- }
5011
+ },
5012
+ env
4966
5013
  );
4967
5014
  const pubData = await pubResponse.json();
4968
5015
  const pubBody = pubData.statusCode ? pubData.body : pubData;
@@ -4988,6 +5035,12 @@ Or use the --bump flag:
4988
5035
  }
4989
5036
  if (assetUploads.length > 0) {
4990
5037
  logger.startSpinner(`Uploading ${assetUploads.length} asset(s) to S3...`);
5038
+ if (assetUploads[0]) {
5039
+ logger.log(
5040
+ ` [debug] sample presigned PUT URL: ${assetUploads[0].upload_url}`
5041
+ );
5042
+ logger.log(` [debug] sample s3_key: ${assetUploads[0].s3_key}`);
5043
+ }
4991
5044
  const CONCURRENCY = 8;
4992
5045
  const byHashedPath = new Map(regularAssets.map((a) => [a.hashedPath, a]));
4993
5046
  const queue = [...assetUploads];
@@ -5014,6 +5067,13 @@ Or use the --bump flag:
5014
5067
  body: buf
5015
5068
  });
5016
5069
  if (!res.ok) {
5070
+ if (failed === 0) {
5071
+ const errBody = await res.text().catch(() => "(unreadable)");
5072
+ logger.log(` [debug] PUT ${item.upload_url}`);
5073
+ logger.log(
5074
+ ` [debug] response ${res.status} ${res.statusText}: ${errBody.slice(0, 500)}`
5075
+ );
5076
+ }
5017
5077
  throw new Error(`HTTP ${res.status}`);
5018
5078
  }
5019
5079
  uploaded++;
@@ -5098,7 +5158,8 @@ Or use the --bump flag:
5098
5158
  try {
5099
5159
  const confirmResponse = await authenticatedFetch(
5100
5160
  `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
5101
- { method: "POST" }
5161
+ { method: "POST" },
5162
+ env
5102
5163
  );
5103
5164
  const confirmData = await confirmResponse.json();
5104
5165
  const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
@@ -5144,9 +5205,9 @@ Or use the --bump flag:
5144
5205
  }
5145
5206
  logger.newLine();
5146
5207
  logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
5147
- await uploadThumbnail(apiUrl, themeId, themePath, distDir);
5208
+ await uploadThumbnail(apiUrl, themeId, themePath, distDir, env);
5148
5209
  }
5149
- async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5210
+ async function uploadThumbnail(apiUrl, themeId, themePath, distDir, env = "dev") {
5150
5211
  const THUMBNAIL_CANDIDATES = [
5151
5212
  { file: "thumbnail.png", mime: "image/png" },
5152
5213
  { file: "thumbnail.jpg", mime: "image/jpeg" },
@@ -5181,9 +5242,19 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5181
5242
  }
5182
5243
  }
5183
5244
  logger.startSpinner("Uploading thumbnail...");
5245
+ const imageUploadUrl = `${apiUrl}/media/images/upload`;
5246
+ const imageRequestBody = {
5247
+ prefix: `themes/${themeId}`,
5248
+ image: imageBase64 ? `${imageBase64.slice(0, 60)}... [${Math.round(imageBase64.length * 3 / 4 / 1024)} KB]` : null,
5249
+ name: "thumbnail.png"
5250
+ };
5251
+ logger.log(` \u2192 POST ${imageUploadUrl}`);
5252
+ logger.log(
5253
+ ` \u2192 body: ${JSON.stringify({ ...imageRequestBody, image: imageBase64 ? `<base64 ${mimeType} truncated>` : null })}`
5254
+ );
5184
5255
  try {
5185
5256
  const uploadRes = await authenticatedFetch(
5186
- `${apiUrl}/media/images/upload`,
5257
+ imageUploadUrl,
5187
5258
  {
5188
5259
  method: "POST",
5189
5260
  body: JSON.stringify({
@@ -5191,30 +5262,56 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
5191
5262
  image: imageBase64,
5192
5263
  name: "thumbnail.png"
5193
5264
  })
5194
- }
5265
+ },
5266
+ env
5195
5267
  );
5196
- const uploadData = await uploadRes.json();
5268
+ logger.log(` \u2190 HTTP ${uploadRes.status} ${uploadRes.statusText}`);
5269
+ const uploadRawText = await uploadRes.text();
5270
+ logger.log(` \u2190 raw response: ${uploadRawText.slice(0, 500)}`);
5271
+ let uploadData;
5272
+ try {
5273
+ uploadData = JSON.parse(uploadRawText);
5274
+ } catch {
5275
+ throw new Error(
5276
+ `Image upload returned non-JSON (HTTP ${uploadRes.status}): ${uploadRawText.slice(0, 200)}`
5277
+ );
5278
+ }
5197
5279
  const uploadBody = uploadData.statusCode ? uploadData.body : uploadData;
5198
5280
  if (!uploadRes.ok || !uploadBody.url) {
5199
- throw new Error(uploadBody.error || "Upload failed");
5281
+ throw new Error(
5282
+ `Image upload failed \u2014 HTTP ${uploadRes.status}: ${uploadBody.error || uploadBody.message || JSON.stringify(uploadBody).slice(0, 300)}`
5283
+ );
5200
5284
  }
5285
+ const patchUrl = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`;
5286
+ logger.log(` \u2192 PATCH ${patchUrl}`);
5201
5287
  const patchRes = await authenticatedFetch(
5202
- `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`,
5288
+ patchUrl,
5203
5289
  {
5204
5290
  method: "PATCH",
5205
5291
  body: JSON.stringify({ thumbnail_url: uploadBody.url })
5206
- }
5292
+ },
5293
+ env
5207
5294
  );
5295
+ logger.log(` \u2190 HTTP ${patchRes.status} ${patchRes.statusText}`);
5208
5296
  if (!patchRes.ok) {
5209
- const patchData = await patchRes.json();
5210
- const patchBody = patchData.statusCode ? patchData.body : patchData;
5211
- throw new Error(patchBody.error || "Failed to set thumbnail");
5297
+ const patchRawText = await patchRes.text();
5298
+ logger.log(` \u2190 raw response: ${patchRawText.slice(0, 500)}`);
5299
+ let patchBody = {};
5300
+ try {
5301
+ patchBody = JSON.parse(patchRawText);
5302
+ } catch {
5303
+ }
5304
+ patchBody = patchBody.statusCode ? patchBody.body : patchBody;
5305
+ throw new Error(
5306
+ `Thumbnail patch failed \u2014 HTTP ${patchRes.status}: ${patchBody.error || patchBody.message || patchRawText.slice(0, 200)}`
5307
+ );
5212
5308
  }
5213
5309
  logger.stopSpinner(true, "Thumbnail set");
5214
5310
  } catch (err) {
5215
- logger.stopSpinner(false, "Thumbnail upload skipped");
5311
+ logger.stopSpinner(false, "Thumbnail upload failed");
5312
+ logger.error(err instanceof Error ? err.message : String(err));
5216
5313
  logger.info(
5217
- `Theme published successfully. Thumbnail can be updated later.`
5314
+ "Theme published successfully. Thumbnail can be updated later."
5218
5315
  );
5219
5316
  }
5220
5317
  }
@@ -5278,25 +5375,40 @@ async function findFreePort(start) {
5278
5375
  srv.on("error", () => resolve(findFreePort(start + 1)));
5279
5376
  });
5280
5377
  }
5281
- async function uploadVideoMultipart(apiUrl, themeId, video) {
5378
+ async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
5282
5379
  const fileName = path9__default.default.basename(video.originalPath);
5380
+ const videoInitUrl = `${apiUrl}/media/videos/multipart/init`;
5381
+ const videoInitBody = {
5382
+ file_name: fileName,
5383
+ content_type: video.contentType,
5384
+ file_size: video.size,
5385
+ prefix: `themes/${themeId}/assets`
5386
+ };
5387
+ logger.log(` \u2192 POST ${videoInitUrl}`);
5388
+ logger.log(` \u2192 body: ${JSON.stringify(videoInitBody)}`);
5283
5389
  const initRes = await authenticatedFetch(
5284
- `${apiUrl}/media/videos/multipart/init`,
5390
+ videoInitUrl,
5285
5391
  {
5286
5392
  method: "POST",
5287
- body: JSON.stringify({
5288
- file_name: fileName,
5289
- content_type: video.contentType,
5290
- file_size: video.size,
5291
- prefix: `themes/${themeId}/assets`
5292
- })
5293
- }
5393
+ body: JSON.stringify(videoInitBody)
5394
+ },
5395
+ env
5294
5396
  );
5295
- const initData = await initRes.json();
5397
+ logger.log(` \u2190 HTTP ${initRes.status} ${initRes.statusText}`);
5398
+ const initRawText = await initRes.text();
5399
+ logger.log(` \u2190 raw response: ${initRawText.slice(0, 500)}`);
5400
+ let initData;
5401
+ try {
5402
+ initData = JSON.parse(initRawText);
5403
+ } catch {
5404
+ throw new Error(
5405
+ `Video init returned non-JSON (HTTP ${initRes.status}): ${initRawText.slice(0, 200)}`
5406
+ );
5407
+ }
5296
5408
  const initBody = initData.statusCode ? initData.body : initData;
5297
5409
  if (!initRes.ok || !initBody.upload_id) {
5298
5410
  throw new Error(
5299
- `Init multipart failed for ${fileName}: ${initBody.error || initRes.status}`
5411
+ `Init multipart failed for ${fileName} \u2014 HTTP ${initRes.status}: ${initBody.error || initBody.message || JSON.stringify(initBody).slice(0, 300)}`
5300
5412
  );
5301
5413
  }
5302
5414
  const { upload_id, file_key, chunk_size, chunk_urls } = initBody;
@@ -5331,25 +5443,47 @@ async function uploadVideoMultipart(apiUrl, themeId, video) {
5331
5443
  )
5332
5444
  );
5333
5445
  parts.sort((a, b) => a.part_number - b.part_number);
5446
+ const videoCompleteUrl = `${apiUrl}/media/videos/multipart/complete`;
5447
+ logger.log(` \u2192 POST ${videoCompleteUrl}`);
5448
+ logger.log(
5449
+ ` \u2192 body: ${JSON.stringify({ upload_id, file_key, parts: `[${parts.length} parts]` })}`
5450
+ );
5334
5451
  const completeRes = await authenticatedFetch(
5335
- `${apiUrl}/media/videos/multipart/complete`,
5452
+ videoCompleteUrl,
5336
5453
  {
5337
5454
  method: "POST",
5338
5455
  body: JSON.stringify({ upload_id, file_key, parts })
5339
- }
5456
+ },
5457
+ env
5340
5458
  );
5341
- const completeData = await completeRes.json();
5459
+ logger.log(` \u2190 HTTP ${completeRes.status} ${completeRes.statusText}`);
5460
+ const completeRawText = await completeRes.text();
5461
+ logger.log(` \u2190 raw response: ${completeRawText.slice(0, 500)}`);
5462
+ let completeData;
5463
+ try {
5464
+ completeData = JSON.parse(completeRawText);
5465
+ } catch {
5466
+ throw new Error(
5467
+ `Video complete returned non-JSON (HTTP ${completeRes.status}): ${completeRawText.slice(0, 200)}`
5468
+ );
5469
+ }
5342
5470
  const completeBody = completeData.statusCode ? completeData.body : completeData;
5343
5471
  if (!completeRes.ok || !completeBody.url) {
5344
5472
  try {
5345
- await authenticatedFetch(`${apiUrl}/media/videos/multipart/abort`, {
5346
- method: "POST",
5347
- body: JSON.stringify({ upload_id, file_key })
5348
- });
5473
+ const abortUrl = `${apiUrl}/media/videos/multipart/abort`;
5474
+ logger.log(` \u2192 POST ${abortUrl} (cleanup)`);
5475
+ await authenticatedFetch(
5476
+ abortUrl,
5477
+ {
5478
+ method: "POST",
5479
+ body: JSON.stringify({ upload_id, file_key })
5480
+ },
5481
+ env
5482
+ );
5349
5483
  } catch {
5350
5484
  }
5351
5485
  throw new Error(
5352
- `Complete multipart failed for ${fileName}: ${completeBody.error || completeRes.status}`
5486
+ `Complete multipart failed for ${fileName} \u2014 HTTP ${completeRes.status}: ${completeBody.error || completeBody.message || JSON.stringify(completeBody).slice(0, 300)}`
5353
5487
  );
5354
5488
  }
5355
5489
  return completeBody.url;
@@ -5612,7 +5746,11 @@ program.command("init").description("Create a new OneX theme project").argument(
5612
5746
  "-t, --template <template>",
5613
5747
  "Template to use (default, minimal)",
5614
5748
  "default"
5615
- ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").action(initCommand);
5749
+ ).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").option(
5750
+ "--env <env>",
5751
+ "Target environment: dev or prod (default: dev)",
5752
+ "dev"
5753
+ ).action(initCommand);
5616
5754
  program.command("create:section").alias("cs").description("Create a new section").argument("<name>", "Name of the section (e.g., hero, features)").option("-t, --theme <theme>", "Theme to create section in").option(
5617
5755
  "-c, --category <category>",
5618
5756
  "Section category (headers, content, footers)"
@@ -5639,16 +5777,36 @@ program.command("download").description("Download a published theme via the webs
5639
5777
  "-v, --version <version>",
5640
5778
  "Theme version (default: latest)",
5641
5779
  "latest"
5642
- ).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5780
+ ).option(
5781
+ "--env <env>",
5782
+ "Target environment: dev or prod (default: dev)",
5783
+ "dev"
5784
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5643
5785
  program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5644
5786
  "-v, --version <version>",
5645
5787
  "Theme version (default: latest)",
5646
5788
  "latest"
5647
- ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5789
+ ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
5790
+ "--env <env>",
5791
+ "Target environment: dev or prod (default: dev)",
5792
+ "dev"
5793
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5648
5794
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5649
- program.command("login").description("Login to OneX platform").action(loginCommand);
5650
- program.command("logout").description("Logout from OneX platform").action(logoutCommand);
5651
- program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
5795
+ program.command("login").description("Login to OneX platform").option(
5796
+ "--env <env>",
5797
+ "Target environment: dev or prod (default: dev)",
5798
+ "dev"
5799
+ ).action(loginCommand);
5800
+ program.command("logout").description("Logout from OneX platform").option(
5801
+ "--env <env>",
5802
+ "Target environment: dev or prod (default: dev)",
5803
+ "dev"
5804
+ ).action(logoutCommand);
5805
+ program.command("whoami").description("Show current logged-in developer").option(
5806
+ "--env <env>",
5807
+ "Target environment: dev or prod (default: dev)",
5808
+ "dev"
5809
+ ).action(whoamiCommand);
5652
5810
  var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
5653
5811
  mcpCmd.command("setup").description(
5654
5812
  "Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme"
@@ -5660,6 +5818,10 @@ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme di
5660
5818
  program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
5661
5819
  "--bump <type>",
5662
5820
  "Auto-bump version before publish (patch|minor|major)"
5821
+ ).option(
5822
+ "--env <env>",
5823
+ "Target environment: dev or prod (default: dev)",
5824
+ "dev"
5663
5825
  ).action(publishCommand);
5664
5826
  program.configureOutput({
5665
5827
  writeErr: (str) => process.stderr.write(chalk4__default.default.red(str))