@onexapis/cli 1.1.16 → 1.1.18

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 (63) hide show
  1. package/README.md +117 -51
  2. package/bin/onexthm.js +4 -0
  3. package/dist/cli.js +750 -328
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cli.mjs +747 -325
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/index.js +162 -299
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +162 -299
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/preview/preview-app.tsx +175 -53
  12. package/package.json +14 -12
  13. package/templates/default/.env.example +1 -1
  14. package/templates/default/.mcp.json +8 -0
  15. package/templates/default/CLAUDE.md +941 -0
  16. package/templates/default/bundle-entry.ts +18 -0
  17. package/templates/default/index.ts +26 -0
  18. package/templates/default/package.json +37 -0
  19. package/templates/default/pages/about.ts +66 -0
  20. package/templates/default/pages/home.ts +93 -0
  21. package/templates/default/pages/showcase.ts +146 -0
  22. package/templates/default/sections/about/about-default.tsx +237 -0
  23. package/templates/default/sections/about/about.schema.ts +259 -0
  24. package/templates/default/sections/about/index.ts +15 -0
  25. package/templates/default/sections/cta/cta-default.tsx +180 -0
  26. package/templates/default/sections/cta/cta.schema.ts +210 -0
  27. package/templates/default/sections/cta/index.ts +11 -0
  28. package/templates/default/sections/features/features-default.tsx +154 -0
  29. package/templates/default/sections/features/features.schema.ts +330 -0
  30. package/templates/default/sections/features/index.ts +11 -0
  31. package/templates/default/sections/gallery/gallery-default.tsx +134 -0
  32. package/templates/default/sections/gallery/gallery.schema.ts +397 -0
  33. package/templates/default/sections/gallery/index.ts +11 -0
  34. package/templates/default/sections/hero/hero-default.tsx +212 -0
  35. package/templates/default/sections/hero/hero.schema.ts +273 -0
  36. package/templates/default/sections/hero/index.ts +15 -0
  37. package/templates/default/sections/stats/index.ts +11 -0
  38. package/templates/default/sections/stats/stats-default.tsx +103 -0
  39. package/templates/default/sections/stats/stats.schema.ts +266 -0
  40. package/templates/default/sections/testimonials/index.ts +11 -0
  41. package/templates/default/sections/testimonials/testimonials-default.tsx +130 -0
  42. package/templates/default/sections/testimonials/testimonials.schema.ts +371 -0
  43. package/templates/default/sections-registry.ts +32 -0
  44. package/templates/default/theme.config.ts +107 -0
  45. package/templates/default/theme.layout.ts +21 -0
  46. package/templates/default/tsconfig.json +16 -7
  47. package/templates/default/README.md.ejs +0 -129
  48. package/templates/default/esbuild.config.js +0 -81
  49. package/templates/default/package.json.ejs +0 -31
  50. package/templates/default/src/config.ts.ejs +0 -98
  51. package/templates/default/src/index.ts.ejs +0 -11
  52. package/templates/default/src/layout.ts +0 -23
  53. package/templates/default/src/manifest.ts.ejs +0 -47
  54. package/templates/default/src/pages/home.ts.ejs +0 -37
  55. package/templates/default/src/sections/footer/footer-default.tsx +0 -28
  56. package/templates/default/src/sections/footer/footer.schema.ts +0 -45
  57. package/templates/default/src/sections/footer/index.ts +0 -2
  58. package/templates/default/src/sections/header/header-default.tsx +0 -61
  59. package/templates/default/src/sections/header/header.schema.ts +0 -46
  60. package/templates/default/src/sections/header/index.ts +0 -2
  61. package/templates/default/src/sections/hero/hero-default.tsx +0 -52
  62. package/templates/default/src/sections/hero/hero.schema.ts +0 -52
  63. package/templates/default/src/sections/hero/index.ts +0 -2
package/dist/cli.js CHANGED
@@ -6,20 +6,20 @@ var ora = require('ora');
6
6
  var esbuild = require('esbuild');
7
7
  var path8 = require('path');
8
8
  var fs7 = require('fs/promises');
9
- var crypto = require('crypto');
9
+ var crypto2 = require('crypto');
10
10
  var glob = require('glob');
11
- var os = require('os');
11
+ var module$1 = require('module');
12
+ var os3 = require('os');
12
13
  var dotenv = require('dotenv');
13
14
  var fs = require('fs-extra');
14
15
  var ejs = require('ejs');
15
16
  var child_process = require('child_process');
16
17
  var commander = require('commander');
17
- var module$1 = require('module');
18
18
  var fs2 = require('fs');
19
19
  var inquirer = require('inquirer');
20
20
  var archiver = require('archiver');
21
21
  var FormData = require('form-data');
22
- var fetch = require('node-fetch');
22
+ var fetch2 = require('node-fetch');
23
23
  var clientS3 = require('@aws-sdk/client-s3');
24
24
  var AdmZip = require('adm-zip');
25
25
  var chokidar = require('chokidar');
@@ -52,8 +52,8 @@ var ora__default = /*#__PURE__*/_interopDefault(ora);
52
52
  var esbuild__namespace = /*#__PURE__*/_interopNamespace(esbuild);
53
53
  var path8__default = /*#__PURE__*/_interopDefault(path8);
54
54
  var fs7__default = /*#__PURE__*/_interopDefault(fs7);
55
- var crypto__default = /*#__PURE__*/_interopDefault(crypto);
56
- var os__default = /*#__PURE__*/_interopDefault(os);
55
+ var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
56
+ var os3__default = /*#__PURE__*/_interopDefault(os3);
57
57
  var dotenv__default = /*#__PURE__*/_interopDefault(dotenv);
58
58
  var fs__default = /*#__PURE__*/_interopDefault(fs);
59
59
  var ejs__default = /*#__PURE__*/_interopDefault(ejs);
@@ -61,7 +61,7 @@ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
61
61
  var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
62
62
  var archiver__default = /*#__PURE__*/_interopDefault(archiver);
63
63
  var FormData__default = /*#__PURE__*/_interopDefault(FormData);
64
- var fetch__default = /*#__PURE__*/_interopDefault(fetch);
64
+ var fetch2__default = /*#__PURE__*/_interopDefault(fetch2);
65
65
  var AdmZip__default = /*#__PURE__*/_interopDefault(AdmZip);
66
66
  var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
67
67
  var http__default = /*#__PURE__*/_interopDefault(http);
@@ -138,7 +138,7 @@ __export(compile_theme_exports, {
138
138
  compilePreviewRuntime: () => compilePreviewRuntime,
139
139
  compileStandaloneTheme: () => compileStandaloneTheme,
140
140
  compileStandaloneThemeDev: () => compileStandaloneThemeDev,
141
- generateManifest: () => generateManifest2
141
+ generateManifest: () => generateManifest
142
142
  });
143
143
  async function resolveNodeModulesFile(startDir, relativePath) {
144
144
  let dir = startDir;
@@ -285,6 +285,12 @@ function createThemeDepsStubPlugin(themePath) {
285
285
  if (!result.errors.length) return result;
286
286
  } catch {
287
287
  }
288
+ try {
289
+ const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)) || __filename);
290
+ const resolved = req.resolve(args.path);
291
+ if (resolved) return { path: resolved, namespace: "file" };
292
+ } catch {
293
+ }
288
294
  return { path: args.path, namespace };
289
295
  });
290
296
  };
@@ -293,11 +299,19 @@ function createThemeDepsStubPlugin(themePath) {
293
299
  const stubs = {
294
300
  "next/image": `
295
301
  import React from 'react';
296
- const Image = (props) => {
297
- const { src, alt, width, height, fill, priority, ...rest } = props;
302
+ const Image = React.forwardRef((props, ref) => {
303
+ const { src, alt, width, height, fill, priority, sizes, quality, placeholder, blurDataURL, onLoad, onError, style, className, ...rest } = props;
298
304
  const imgSrc = typeof src === 'object' ? src.src : src;
299
- return React.createElement('img', { src: imgSrc, alt, width: fill ? undefined : width, height: fill ? undefined : height, loading: priority ? 'eager' : 'lazy', ...rest });
300
- };
305
+ const fillStyle = fill ? { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: style?.objectFit || 'cover', display: 'block' } : {};
306
+ const mergedStyle = { ...fillStyle, ...style };
307
+ return React.createElement('img', {
308
+ ref, src: imgSrc, alt,
309
+ width: fill ? undefined : width, height: fill ? undefined : height,
310
+ loading: priority ? 'eager' : 'lazy',
311
+ style: Object.keys(mergedStyle).length > 0 ? mergedStyle : undefined,
312
+ className, onLoad, onError, ...rest,
313
+ });
314
+ });
301
315
  export default Image;
302
316
  `,
303
317
  "next/link": `
@@ -331,7 +345,10 @@ export function headers() { return new Headers(); }
331
345
  if (!lucideThemeScanned) {
332
346
  lucideThemeScanned = true;
333
347
  try {
334
- const scanned = await scanImportsFromPackage(themePath, "lucide-react");
348
+ const scanned = await scanImportsFromPackage(
349
+ themePath,
350
+ "lucide-react"
351
+ );
335
352
  for (const names of Object.values(scanned)) {
336
353
  for (const name of names) lucideImports.add(name);
337
354
  }
@@ -394,10 +411,13 @@ export function useFormContext() { return useForm(); }
394
411
  loader: "js"
395
412
  }));
396
413
  tryResolveOrStub(/^@hookform\/resolvers/, "hookform-resolvers-stub");
397
- build2.onLoad({ filter: /.*/, namespace: "hookform-resolvers-stub" }, () => ({
398
- contents: `export function zodResolver() { return () => ({ values: {}, errors: {} }); }`,
399
- loader: "js"
400
- }));
414
+ build2.onLoad(
415
+ { filter: /.*/, namespace: "hookform-resolvers-stub" },
416
+ () => ({
417
+ contents: `export function zodResolver() { return () => ({ values: {}, errors: {} }); }`,
418
+ loader: "js"
419
+ })
420
+ );
401
421
  tryResolveOrStub(/^next-intl$/, "next-intl-stub");
402
422
  build2.onLoad({ filter: /.*/, namespace: "next-intl-stub" }, () => ({
403
423
  contents: `
@@ -506,7 +526,7 @@ async function contentHashEntry(outputDir) {
506
526
  logger.warning("No entry file found in output, skipping content hash");
507
527
  return;
508
528
  }
509
- const hash2 = crypto__default.default.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
529
+ const hash2 = crypto2__default.default.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
510
530
  const hashedName2 = `bundle-entry-${hash2}.js`;
511
531
  const indexMapPath = path8__default.default.join(outputDir, "index.js.map");
512
532
  const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
@@ -524,7 +544,7 @@ async function contentHashEntry(outputDir) {
524
544
  logger.info(`Entry hashed: ${hashedName2}`);
525
545
  return;
526
546
  }
527
- const hash = crypto__default.default.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
547
+ const hash = crypto2__default.default.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
528
548
  const hashedName = `bundle-entry-${hash}.js`;
529
549
  const hashedMapName = `bundle-entry-${hash}.js.map`;
530
550
  entryContent = entryContent.replace(
@@ -562,7 +582,7 @@ async function extractDataRequirements(themePath) {
562
582
  }
563
583
  return requirements;
564
584
  }
565
- async function generateManifest2(themeName, themePath, outputDir) {
585
+ async function generateManifest(themeName, themePath, outputDir) {
566
586
  let version2 = "1.0.0";
567
587
  let themeId = themeName;
568
588
  try {
@@ -655,7 +675,11 @@ async function compileStandaloneTheme(themePath, themeName) {
655
675
  banner: {
656
676
  js: '"use client";'
657
677
  },
658
- plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath), createThemeDepsStubPlugin(themePath)],
678
+ plugins: [
679
+ reactGlobalPlugin,
680
+ createCoreGlobalPlugin(themePath),
681
+ createThemeDepsStubPlugin(themePath)
682
+ ],
659
683
  external: [],
660
684
  alias: {
661
685
  events: "events/",
@@ -693,7 +717,7 @@ async function compileStandaloneTheme(themePath, themeName) {
693
717
  } catch {
694
718
  }
695
719
  await contentHashEntry(outputDir);
696
- await generateManifest2(themeName, themePath, outputDir);
720
+ await generateManifest(themeName, themePath, outputDir);
697
721
  await generateThemeData(themePath, outputDir, themeName);
698
722
  if (result.metafile) {
699
723
  const outputs = result.metafile.outputs;
@@ -737,7 +761,11 @@ async function compileStandaloneThemeDev(themePath, themeName) {
737
761
  banner: {
738
762
  js: '"use client";'
739
763
  },
740
- plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath), createThemeDepsStubPlugin(themePath)],
764
+ plugins: [
765
+ reactGlobalPlugin,
766
+ createCoreGlobalPlugin(themePath),
767
+ createThemeDepsStubPlugin(themePath)
768
+ ],
741
769
  external: [],
742
770
  alias: {
743
771
  events: "events/",
@@ -770,7 +798,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
770
798
  };
771
799
  const context2 = await esbuild__namespace.context(buildOptions);
772
800
  await context2.rebuild();
773
- await generateManifest2(themeName, themePath, outputDir);
801
+ await generateManifest(themeName, themePath, outputDir);
774
802
  await generateThemeData(themePath, outputDir, themeName);
775
803
  return { context: context2, outputDir };
776
804
  }
@@ -872,7 +900,16 @@ ${locations.join("\n")}`
872
900
  path8__default.default.join(themePath, "node_modules", "@onexapis", "core", "src"),
873
901
  path8__default.default.join(themePath, "..", "..", "packages", "core", "src"),
874
902
  // monorepo sibling
875
- path8__default.default.join(__dirname, "..", "..", "..", "..", "packages", "core", "src")
903
+ path8__default.default.join(
904
+ __dirname,
905
+ "..",
906
+ "..",
907
+ "..",
908
+ "..",
909
+ "packages",
910
+ "core",
911
+ "src"
912
+ )
876
913
  // from CLI src
877
914
  ];
878
915
  let coreSourceDir = null;
@@ -886,7 +923,10 @@ ${locations.join("\n")}`
886
923
  }
887
924
  if (coreSourceDir) {
888
925
  try {
889
- const scanned = await scanImportsFromPackage(coreSourceDir, "lucide-react");
926
+ const scanned = await scanImportsFromPackage(
927
+ coreSourceDir,
928
+ "lucide-react"
929
+ );
890
930
  for (const names of Object.values(scanned)) {
891
931
  for (const name of names) lucideIconNames.add(name);
892
932
  }
@@ -907,7 +947,10 @@ ${locations.join("\n")}`
907
947
  const mjsFiles = await glob.glob("*.mjs", { cwd: candidate });
908
948
  const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
909
949
  for (const file of mjsFiles) {
910
- const content = await fs7__default.default.readFile(path8__default.default.join(candidate, file), "utf-8");
950
+ const content = await fs7__default.default.readFile(
951
+ path8__default.default.join(candidate, file),
952
+ "utf-8"
953
+ );
911
954
  for (const match of content.matchAll(importRegex)) {
912
955
  for (const name of match[1].split(",")) {
913
956
  const original = name.trim().split(/\s+as\s+/)[0].trim();
@@ -923,7 +966,10 @@ ${locations.join("\n")}`
923
966
  }
924
967
  }
925
968
  try {
926
- const scanned = await scanImportsFromPackage(themePath, "lucide-react");
969
+ const scanned = await scanImportsFromPackage(
970
+ themePath,
971
+ "lucide-react"
972
+ );
927
973
  for (const names of Object.values(scanned)) {
928
974
  for (const name of names) lucideIconNames.add(name);
929
975
  }
@@ -953,14 +999,39 @@ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true :
953
999
  if (!result.errors.length) return result;
954
1000
  } catch {
955
1001
  }
1002
+ try {
1003
+ const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)) || __filename);
1004
+ const cjsPath = req.resolve("framer-motion");
1005
+ const pkgDir = cjsPath.replace(/[/\\]dist[/\\].*$/, "");
1006
+ const esmEntry = path8__default.default.join(pkgDir, "dist", "es", "index.mjs");
1007
+ const { existsSync } = await import('fs');
1008
+ if (existsSync(esmEntry)) {
1009
+ return { path: esmEntry, namespace: "file" };
1010
+ }
1011
+ return { path: cjsPath, namespace: "file" };
1012
+ } catch {
1013
+ }
956
1014
  return { path: args.path, namespace: "motion-stub" };
957
1015
  });
958
1016
  build2.onLoad({ filter: /.*/, namespace: "motion-stub" }, () => ({
959
1017
  contents: `
960
- const stub = (props) => props.children || null;
961
- const handler = { get: (_, name) => name === '__esModule' ? true : stub };
1018
+ import React from 'react';
1019
+ const MotionComponent = React.forwardRef((props, ref) => {
1020
+ const { initial, animate, exit, variants, transition, whileInView, whileHover, whileTap, viewport, onAnimationComplete, layout, layoutId, style, className, ...rest } = props;
1021
+ return React.createElement(rest.as || 'div', { ref, style, className, ...rest }, rest.children);
1022
+ });
1023
+ const handler = { get: (_, name) => {
1024
+ if (name === '__esModule') return true;
1025
+ if (name === 'create') return () => new Proxy({}, handler);
1026
+ return MotionComponent;
1027
+ }};
962
1028
  export const motion = new Proxy({}, handler);
963
- export const AnimatePresence = stub;
1029
+ export const AnimatePresence = (props) => props.children || null;
1030
+ export const useInView = () => true;
1031
+ export const useAnimation = () => ({ start: () => {}, stop: () => {}, set: () => {} });
1032
+ export const useMotionValue = (v) => ({ get: () => v, set: () => {}, onChange: () => () => {} });
1033
+ export const useTransform = (v) => v;
1034
+ export const useScroll = () => ({ scrollY: { get: () => 0, onChange: () => () => {} }, scrollYProgress: { get: () => 0, onChange: () => () => {} } });
964
1035
  export default { motion, AnimatePresence };
965
1036
  `.trim(),
966
1037
  loader: "jsx"
@@ -983,12 +1054,26 @@ export default { motion, AnimatePresence };
983
1054
  build2.onLoad({ filter: /.*/, namespace: "next-stub" }, (args) => {
984
1055
  const stubs = {
985
1056
  "next/image": `
986
- const Image = (props) => {
987
- const { src, alt, width, height, fill, priority, ...rest } = props;
988
- const imgSrc = typeof src === 'object' ? src.src : src;
989
- return React.createElement('img', { src: imgSrc, alt, width: fill ? undefined : width, height: fill ? undefined : height, loading: priority ? 'eager' : 'lazy', ...rest });
990
- };
991
1057
  import React from 'react';
1058
+ const Image = React.forwardRef((props, ref) => {
1059
+ const { src, alt, width, height, fill, priority, sizes, quality, placeholder, blurDataURL, onLoad, onError, style, className, ...rest } = props;
1060
+ const imgSrc = typeof src === 'object' ? src.src : src;
1061
+ const fillStyle = fill ? { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: style?.objectFit || 'cover', display: 'block' } : {};
1062
+ const mergedStyle = { ...fillStyle, ...style };
1063
+ return React.createElement('img', {
1064
+ ref,
1065
+ src: imgSrc,
1066
+ alt,
1067
+ width: fill ? undefined : width,
1068
+ height: fill ? undefined : height,
1069
+ loading: priority ? 'eager' : 'lazy',
1070
+ style: Object.keys(mergedStyle).length > 0 ? mergedStyle : undefined,
1071
+ className,
1072
+ onLoad,
1073
+ onError,
1074
+ ...rest,
1075
+ });
1076
+ });
992
1077
  export default Image;
993
1078
  `,
994
1079
  "next/link": `
@@ -1449,45 +1534,20 @@ async function initCommand(projectName, options = {}) {
1449
1534
  try {
1450
1535
  fs2__default.default.mkdirSync(projectPath, { recursive: true });
1451
1536
  await copyTemplate(template, projectPath, data);
1452
- const srcPath = path8__default.default.join(projectPath, "src");
1453
- fs2__default.default.mkdirSync(srcPath, { recursive: true });
1454
- const manifestContent = generateManifest(data);
1455
- await writeFile(path8__default.default.join(srcPath, "manifest.ts"), manifestContent);
1456
- const configContent = generateThemeConfig(data);
1457
- await writeFile(path8__default.default.join(srcPath, "config.ts"), configContent);
1458
- const layoutContent = generateThemeLayout(data);
1459
- await writeFile(path8__default.default.join(srcPath, "layout.ts"), layoutContent);
1460
- const indexContent = generateThemeIndex(data);
1461
- await writeFile(path8__default.default.join(srcPath, "index.ts"), indexContent);
1462
- const sectionsPath = path8__default.default.join(srcPath, "sections");
1463
- fs2__default.default.mkdirSync(sectionsPath, { recursive: true });
1464
- await writeFile(
1465
- path8__default.default.join(sectionsPath, "README.md"),
1466
- `# ${displayName} Sections
1467
-
1468
- Add your theme-specific sections here.
1469
- `
1537
+ await renameThemeInFiles(
1538
+ projectPath,
1539
+ name,
1540
+ displayName,
1541
+ description,
1542
+ author
1470
1543
  );
1471
- const blocksPath = path8__default.default.join(srcPath, "blocks");
1472
- fs2__default.default.mkdirSync(blocksPath, { recursive: true });
1473
- await writeFile(
1474
- path8__default.default.join(blocksPath, "README.md"),
1475
- `# ${displayName} Blocks
1476
-
1477
- Add your theme-specific blocks here.
1478
- `
1479
- );
1480
- const pagesPath = path8__default.default.join(srcPath, "pages");
1481
- fs2__default.default.mkdirSync(pagesPath, { recursive: true });
1482
- const homePageContent = generateHomePage(data);
1483
- await writeFile(path8__default.default.join(pagesPath, "home.ts"), homePageContent);
1484
1544
  logger.stopSpinner(true, "Project structure created!");
1485
1545
  if (options.git) {
1486
1546
  logger.startSpinner("Initializing git repository...");
1487
1547
  try {
1488
1548
  child_process.execSync("git init", { cwd: projectPath, stdio: "ignore" });
1489
1549
  child_process.execSync("git add .", { cwd: projectPath, stdio: "ignore" });
1490
- child_process.execSync('git commit -m "Initial commit from onex init"', {
1550
+ child_process.execSync('git commit -m "Initial commit from onexthm init"', {
1491
1551
  cwd: projectPath,
1492
1552
  stdio: "ignore"
1493
1553
  });
@@ -1524,14 +1584,14 @@ Add your theme-specific blocks here.
1524
1584
  logger.log(` npm run dev # Start development mode`);
1525
1585
  logger.newLine();
1526
1586
  logger.section("Theme structure:");
1527
- logger.log(" src/manifest.ts - Theme manifest and exports");
1587
+ logger.log(" bundle-entry.ts - Theme manifest and exports");
1528
1588
  logger.log(
1529
- " src/config.ts - Design tokens (colors, typography, etc.)"
1589
+ " theme.config.ts - Design tokens (colors, typography, etc.)"
1530
1590
  );
1531
- logger.log(" src/layout.ts - Header and footer configuration");
1532
- logger.log(" src/sections/ - Custom sections for your theme");
1533
- logger.log(" src/blocks/ - Reusable blocks");
1534
- logger.log(" src/pages/ - Page configurations");
1591
+ logger.log(" theme.layout.ts - Header and footer configuration");
1592
+ logger.log(" sections/ - Custom sections for your theme");
1593
+ logger.log(" pages/ - Page configurations");
1594
+ logger.log(" CLAUDE.md - AI assistant context");
1535
1595
  logger.newLine();
1536
1596
  logger.success(`Happy theming! \u{1F3A8}`);
1537
1597
  } catch (error) {
@@ -1545,231 +1605,33 @@ Add your theme-specific blocks here.
1545
1605
  process.exit(1);
1546
1606
  }
1547
1607
  }
1548
- function generateManifest(data) {
1549
- return `import type { ThemeExport } from "@onexapis/core";
1550
-
1551
- /**
1552
- * ${data.displayName} Theme Manifest
1553
- * ${data.description}
1554
- */
1555
- export const manifest: ThemeExport = {
1556
- id: "${data.themeName}",
1557
- name: "${data.displayName}",
1558
- description: "${data.description}",
1559
- version: "1.0.0",
1560
- author: "${data.author}",
1561
-
1562
- // Theme configuration
1563
- config: () => import("./config").then((m) => m.themeConfig),
1564
-
1565
- // Theme layout (header/footer sections)
1566
- layout: () => import("./layout").then((m) => m.themeLayout),
1567
-
1568
- // Available sections in this theme
1569
- sections: {
1570
- // Example: hero: () => import("./sections/hero").then((m) => m.heroSchema),
1571
- },
1572
-
1573
- // Available blocks in this theme
1574
- blocks: {
1575
- // Example: productCard: () => import("./blocks/product-card").then((m) => m.productCardDefinition),
1576
- },
1577
-
1578
- // Default pages
1579
- pages: {
1580
- home: () => import("./pages/home").then((m) => m.homePageConfig),
1581
- },
1582
-
1583
- // Supported page types
1584
- supportedPageTypes: ["home", "about", "contact", "custom"],
1585
-
1586
- // Preview image (optional)
1587
- preview: undefined,
1588
-
1589
- // Tags for categorization (optional)
1590
- tags: ["custom"],
1591
- };
1592
-
1593
- export default manifest;
1594
- `;
1595
- }
1596
- function generateThemeConfig(data) {
1597
- return `import type { ThemeConfig } from "@onexapis/core";
1598
-
1599
- /**
1600
- * ${data.displayName} Theme Configuration
1601
- * Design tokens: colors, typography, spacing, etc.
1602
- */
1603
- export const themeConfig: ThemeConfig = {
1604
- // Color palette
1605
- colors: {
1606
- primary: {
1607
- 50: "#eff6ff",
1608
- 100: "#dbeafe",
1609
- 200: "#bfdbfe",
1610
- 300: "#93c5fd",
1611
- 400: "#60a5fa",
1612
- 500: "#3b82f6",
1613
- 600: "#2563eb",
1614
- 700: "#1d4ed8",
1615
- 800: "#1e40af",
1616
- 900: "#1e3a8a",
1617
- },
1618
- secondary: {
1619
- 50: "#f8fafc",
1620
- 100: "#f1f5f9",
1621
- 200: "#e2e8f0",
1622
- 300: "#cbd5e1",
1623
- 400: "#94a3b8",
1624
- 500: "#64748b",
1625
- 600: "#475569",
1626
- 700: "#334155",
1627
- 800: "#1e293b",
1628
- 900: "#0f172a",
1629
- },
1630
- accent: {
1631
- 50: "#fdf4ff",
1632
- 100: "#fae8ff",
1633
- 200: "#f5d0fe",
1634
- 300: "#f0abfc",
1635
- 400: "#e879f9",
1636
- 500: "#d946ef",
1637
- 600: "#c026d3",
1638
- 700: "#a21caf",
1639
- 800: "#86198f",
1640
- 900: "#701a75",
1641
- },
1642
- },
1643
-
1644
- // Typography
1645
- typography: {
1646
- fontFamily: {
1647
- sans: ["Inter", "system-ui", "sans-serif"],
1648
- serif: ["Georgia", "serif"],
1649
- mono: ["Monaco", "monospace"],
1650
- },
1651
- fontSize: {
1652
- xs: "0.75rem",
1653
- sm: "0.875rem",
1654
- base: "1rem",
1655
- lg: "1.125rem",
1656
- xl: "1.25rem",
1657
- "2xl": "1.5rem",
1658
- "3xl": "1.875rem",
1659
- "4xl": "2.25rem",
1660
- "5xl": "3rem",
1661
- },
1662
- },
1663
-
1664
- // Spacing
1665
- spacing: {
1666
- xs: "0.5rem",
1667
- sm: "1rem",
1668
- md: "1.5rem",
1669
- lg: "2rem",
1670
- xl: "3rem",
1671
- "2xl": "4rem",
1672
- "3xl": "6rem",
1673
- "4xl": "8rem",
1674
- },
1675
-
1676
- // Border radius
1677
- borderRadius: {
1678
- none: "0",
1679
- sm: "0.125rem",
1680
- md: "0.375rem",
1681
- lg: "0.5rem",
1682
- xl: "0.75rem",
1683
- full: "9999px",
1684
- },
1685
-
1686
- // Breakpoints
1687
- breakpoints: {
1688
- sm: "640px",
1689
- md: "768px",
1690
- lg: "1024px",
1691
- xl: "1280px",
1692
- "2xl": "1536px",
1693
- },
1694
- };
1695
- `;
1696
- }
1697
- function generateThemeLayout(data) {
1698
- return `import type { ThemeLayoutConfig } from "@onexapis/core";
1699
-
1700
- /**
1701
- * ${data.themeName} Theme Layout
1702
- * Define header and footer sections
1703
- */
1704
- export const themeLayout: ThemeLayoutConfig = {
1705
- // Header section configuration
1706
- header: undefined,
1707
- // Example:
1708
- // header: {
1709
- // type: "header",
1710
- // template: "default",
1711
- // enabled: true,
1712
- // settings: {},
1713
- // },
1714
-
1715
- // Footer section configuration
1716
- footer: undefined,
1717
- // Example:
1718
- // footer: {
1719
- // type: "footer",
1720
- // template: "default",
1721
- // enabled: true,
1722
- // settings: {},
1723
- // },
1724
- };
1725
- `;
1726
- }
1727
- function generateThemeIndex(data) {
1728
- return `/**
1729
- * ${data.themeNamePascal} Theme
1730
- */
1731
-
1732
- export { manifest as ${data.themeNamePascal}Manifest } from "./manifest";
1733
- export { themeConfig as ${data.themeNamePascal}Config } from "./config";
1734
- export { themeLayout as ${data.themeNamePascal}Layout } from "./layout";
1735
- `;
1736
- }
1737
- function generateHomePage(data) {
1738
- return `import type { PageConfig } from "@onexapis/core";
1739
-
1740
- /**
1741
- * Home Page Configuration
1742
- */
1743
- export const homePageConfig: PageConfig = {
1744
- type: "home",
1745
- title: "${data.displayName}",
1746
- description: "Welcome to ${data.displayName}",
1747
-
1748
- // SEO metadata
1749
- seo: {
1750
- title: "${data.displayName} - Home",
1751
- description: "Welcome to ${data.displayName}",
1752
- keywords: [],
1753
- ogImage: undefined,
1754
- },
1755
-
1756
- // Page sections
1757
- sections: [
1758
- // Add your sections here
1759
- // Example:
1760
- // {
1761
- // id: "hero-1",
1762
- // type: "hero",
1763
- // template: "default",
1764
- // order: 0,
1765
- // enabled: true,
1766
- // settings: {},
1767
- // components: [],
1768
- // blocks: [],
1769
- // },
1770
- ],
1771
- };
1772
- `;
1608
+ async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
1609
+ const configPath = path8__default.default.join(projectPath, "theme.config.ts");
1610
+ if (fs2__default.default.existsSync(configPath)) {
1611
+ let content = fs2__default.default.readFileSync(configPath, "utf-8");
1612
+ content = content.replace(
1613
+ /name: "My Simple Theme"/,
1614
+ `name: "${displayName}"`
1615
+ );
1616
+ content = content.replace(
1617
+ /description: ".*?"/,
1618
+ `description: "${description}"`
1619
+ );
1620
+ fs2__default.default.writeFileSync(configPath, content, "utf-8");
1621
+ }
1622
+ const pkgPath = path8__default.default.join(projectPath, "package.json");
1623
+ if (fs2__default.default.existsSync(pkgPath)) {
1624
+ let content = fs2__default.default.readFileSync(pkgPath, "utf-8");
1625
+ content = content.replace(
1626
+ /@onex-themes\/my-simple/g,
1627
+ `@onex-themes/${themeName}`
1628
+ );
1629
+ content = content.replace(
1630
+ /"description": ".*?"/,
1631
+ `"description": "${description}"`
1632
+ );
1633
+ fs2__default.default.writeFileSync(pkgPath, content, "utf-8");
1634
+ }
1773
1635
  }
1774
1636
 
1775
1637
  // src/commands/create-section.ts
@@ -2837,7 +2699,7 @@ async function buildCommand(options) {
2837
2699
  logger.stopSpinner(true, "Lint passed");
2838
2700
  const pkgJson = fs__default.default.readJsonSync(packageJsonPath);
2839
2701
  const buildScript = pkgJson.scripts?.build || "";
2840
- const isRecursive = buildScript.includes("onex build") || buildScript.includes("onex-cli build");
2702
+ const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex-cli build");
2841
2703
  logger.startSpinner(
2842
2704
  options.watch ? "Building (watch mode)..." : "Building..."
2843
2705
  );
@@ -2983,7 +2845,7 @@ async function packageCommand(options) {
2983
2845
  logger.newLine();
2984
2846
  logger.section("Next steps:");
2985
2847
  logger.log(
2986
- ` onex deploy --package ${path8__default.default.relative(process.cwd(), outputPath)}`
2848
+ ` onexthm deploy --package ${path8__default.default.relative(process.cwd(), outputPath)}`
2987
2849
  );
2988
2850
  } catch (error) {
2989
2851
  logger.stopSpinner(false, "Failed to create package");
@@ -3045,7 +2907,7 @@ async function deployCommand(options) {
3045
2907
  } else if (options.theme) {
3046
2908
  const distDir = path8__default.default.join(process.cwd(), "dist");
3047
2909
  if (!fs__default.default.existsSync(distDir)) {
3048
- logger.error("No dist/ directory found. Run 'onex package' first.");
2910
+ logger.error("No dist/ directory found. Run 'onexthm package' first.");
3049
2911
  process.exit(1);
3050
2912
  }
3051
2913
  const files = fs__default.default.readdirSync(distDir);
@@ -3054,7 +2916,7 @@ async function deployCommand(options) {
3054
2916
  );
3055
2917
  if (packageFiles.length === 0) {
3056
2918
  logger.error(`No package found for theme "${options.theme}".`);
3057
- logger.info("Run: onex package --theme " + options.theme);
2919
+ logger.info("Run: onexthm package --theme " + options.theme);
3058
2920
  process.exit(1);
3059
2921
  }
3060
2922
  packageFiles.sort().reverse();
@@ -3062,8 +2924,8 @@ async function deployCommand(options) {
3062
2924
  } else {
3063
2925
  logger.error("Either --package or --theme must be specified.");
3064
2926
  logger.info("Examples:");
3065
- logger.log(" onex deploy --package dist/tinan-1.0.0.zip");
3066
- logger.log(" onex deploy --theme tinan");
2927
+ logger.log(" onexthm deploy --package dist/tinan-1.0.0.zip");
2928
+ logger.log(" onexthm deploy --theme tinan");
3067
2929
  process.exit(1);
3068
2930
  }
3069
2931
  if (!fs__default.default.existsSync(packagePath)) {
@@ -3079,7 +2941,7 @@ async function deployCommand(options) {
3079
2941
  logger.log(`Path: ${path8__default.default.relative(process.cwd(), packagePath)}`);
3080
2942
  logger.newLine();
3081
2943
  const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
3082
- const uploadEndpoint = `${apiUrl}/api/themes/upload`;
2944
+ const uploadEndpoint = `${apiUrl}/website-api/themes/upload`;
3083
2945
  logger.section("Uploading to API Server");
3084
2946
  logger.info(`Endpoint: ${uploadEndpoint}`);
3085
2947
  logger.newLine();
@@ -3096,7 +2958,7 @@ async function deployCommand(options) {
3096
2958
  if (options.environment) {
3097
2959
  formData.append("environment", options.environment);
3098
2960
  }
3099
- const response = await fetch__default.default(uploadEndpoint, {
2961
+ const response = await fetch2__default.default(uploadEndpoint, {
3100
2962
  method: "POST",
3101
2963
  body: formData,
3102
2964
  headers: formData.getHeaders()
@@ -3177,7 +3039,7 @@ function getBucketName(env) {
3177
3039
  return process.env.BUCKET_NAME;
3178
3040
  }
3179
3041
  const environment = env || process.env.ENVIRONMENT || "staging";
3180
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3042
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3181
3043
  }
3182
3044
  async function findCompiledThemeDir(themeId, version2) {
3183
3045
  const searchPaths = [path8__default.default.resolve(process.cwd(), "dist")];
@@ -3284,7 +3146,7 @@ async function uploadCommand(options) {
3284
3146
  if (!compiledDir) {
3285
3147
  spinner.fail(
3286
3148
  chalk4__default.default.red(
3287
- `Compiled theme not found for ${themeId}@${version2}. Run 'onex build' first.`
3149
+ `Compiled theme not found for ${themeId}@${version2}. Run 'onexthm build' first.`
3288
3150
  )
3289
3151
  );
3290
3152
  logger.info(chalk4__default.default.gray(`Expected location:
@@ -3293,7 +3155,7 @@ async function uploadCommand(options) {
3293
3155
  }
3294
3156
  spinner.succeed(`Found compiled theme at: ${compiledDir}`);
3295
3157
  spinner.start("Creating bundle.zip...");
3296
- const tmpDir = os__default.default.tmpdir();
3158
+ const tmpDir = os3__default.default.tmpdir();
3297
3159
  const bundleZipPath = path8__default.default.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
3298
3160
  await createZipFromDir(compiledDir, bundleZipPath);
3299
3161
  const bundleZipBuffer = await fs__default.default.readFile(bundleZipPath);
@@ -3446,7 +3308,7 @@ function getBucketName2(env) {
3446
3308
  return process.env.BUCKET_NAME;
3447
3309
  }
3448
3310
  const environment = env || process.env.ENVIRONMENT || "staging";
3449
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3311
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3450
3312
  }
3451
3313
  async function streamToString(stream) {
3452
3314
  const chunks = [];
@@ -3516,7 +3378,7 @@ function showDownloadFailureHelp(themeId, bucket) {
3516
3378
  console.log(chalk4__default.default.white("1. Compile and upload the theme:"));
3517
3379
  console.log(chalk4__default.default.gray(` cd themes/${themeId}`));
3518
3380
  console.log(chalk4__default.default.gray(" pnpm build"));
3519
- console.log(chalk4__default.default.gray(" onex upload"));
3381
+ console.log(chalk4__default.default.gray(" onexthm upload"));
3520
3382
  console.log();
3521
3383
  console.log(chalk4__default.default.white("2. Verify AWS credentials are set:"));
3522
3384
  console.log(
@@ -3647,7 +3509,7 @@ function getBucketName3(env) {
3647
3509
  return process.env.BUCKET_NAME;
3648
3510
  }
3649
3511
  const environment = env || process.env.ENVIRONMENT || "staging";
3650
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3512
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3651
3513
  }
3652
3514
  async function streamToString2(stream) {
3653
3515
  const chunks = [];
@@ -3691,8 +3553,8 @@ function runInstall(cwd) {
3691
3553
  });
3692
3554
  }
3693
3555
  async function promptThemeName(originalName) {
3694
- const { default: inquirer6 } = await import('inquirer');
3695
- const { themeName } = await inquirer6.prompt([
3556
+ const { default: inquirer7 } = await import('inquirer');
3557
+ const { themeName } = await inquirer7.prompt([
3696
3558
  {
3697
3559
  type: "input",
3698
3560
  name: "themeName",
@@ -3818,7 +3680,7 @@ async function cloneCommand(themeName, options) {
3818
3680
  chalk4__default.default.yellow("The theme source may not have been uploaded yet.")
3819
3681
  );
3820
3682
  console.log(
3821
- chalk4__default.default.gray(`Upload source with: onex upload --theme ${themeName}`)
3683
+ chalk4__default.default.gray(`Upload source with: onexthm upload --theme ${themeName}`)
3822
3684
  );
3823
3685
  console.log();
3824
3686
  process.exit(1);
@@ -3845,7 +3707,7 @@ async function cloneCommand(themeName, options) {
3845
3707
  [
3846
3708
  "# API Configuration (enables real data in preview)",
3847
3709
  "# Get your Company ID from the OneX dashboard",
3848
- "NEXT_PUBLIC_API_URL=https://api-dev.onexeos.com",
3710
+ "NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
3849
3711
  "NEXT_PUBLIC_COMPANY_ID=",
3850
3712
  ""
3851
3713
  ].join("\n")
@@ -3887,7 +3749,7 @@ async function cloneCommand(themeName, options) {
3887
3749
  if (options.install === false) {
3888
3750
  console.log(chalk4__default.default.gray(" pnpm install"));
3889
3751
  }
3890
- console.log(chalk4__default.default.gray(" onex build"));
3752
+ console.log(chalk4__default.default.gray(" onexthm build"));
3891
3753
  console.log();
3892
3754
  } catch (error) {
3893
3755
  spinner.fail(chalk4__default.default.red(`Clone failed: ${error.message}`));
@@ -3915,6 +3777,7 @@ var MIME_TYPES = {
3915
3777
  };
3916
3778
  function createDevServer(options) {
3917
3779
  const clients = /* @__PURE__ */ new Set();
3780
+ const themeDataPath = path8__default.default.join(options.distDir, "theme-data.json");
3918
3781
  const server = http__default.default.createServer((req, res) => {
3919
3782
  res.setHeader("Access-Control-Allow-Origin", "*");
3920
3783
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
@@ -3928,7 +3791,9 @@ function createDevServer(options) {
3928
3791
  const pathname = url.pathname;
3929
3792
  if (pathname === "/" || pathname === "/index.html") {
3930
3793
  res.writeHead(200, { "Content-Type": "text/html" });
3931
- res.end(generatePreviewHTML(options.themeName));
3794
+ res.end(
3795
+ generatePreviewHTML(options.themeName, options.port, themeDataPath)
3796
+ );
3932
3797
  return;
3933
3798
  }
3934
3799
  if (pathname === "/preview-runtime.js") {
@@ -3947,6 +3812,37 @@ function createDevServer(options) {
3947
3812
  serveFile(res, assetPath);
3948
3813
  return;
3949
3814
  }
3815
+ if (pathname.startsWith("/themes/")) {
3816
+ const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
3817
+ if (match) {
3818
+ const assetPath = path8__default.default.join(options.themePath, "assets", match[1]);
3819
+ if (!assetPath.startsWith(path8__default.default.join(options.themePath, "assets"))) {
3820
+ res.writeHead(403);
3821
+ res.end("Forbidden");
3822
+ return;
3823
+ }
3824
+ serveFile(res, assetPath);
3825
+ return;
3826
+ }
3827
+ }
3828
+ if (pathname.startsWith("/assets/")) {
3829
+ const subpath = pathname.replace(/^\/assets\//, "");
3830
+ const segments = subpath.split("/");
3831
+ let assetPath;
3832
+ if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
3833
+ assetPath = path8__default.default.join(
3834
+ options.themePath,
3835
+ "assets",
3836
+ segments.slice(1).join("/")
3837
+ );
3838
+ } else {
3839
+ assetPath = path8__default.default.join(options.themePath, "assets", subpath);
3840
+ }
3841
+ if (assetPath.startsWith(path8__default.default.join(options.themePath, "assets")) && fs2__default.default.existsSync(assetPath)) {
3842
+ serveFile(res, assetPath);
3843
+ return;
3844
+ }
3845
+ }
3950
3846
  const filePath = path8__default.default.join(options.distDir, pathname);
3951
3847
  if (!filePath.startsWith(options.distDir)) {
3952
3848
  res.writeHead(403);
@@ -3957,7 +3853,9 @@ function createDevServer(options) {
3957
3853
  serveFile(res, filePath);
3958
3854
  } else {
3959
3855
  res.writeHead(200, { "Content-Type": "text/html" });
3960
- res.end(generatePreviewHTML(options.themeName));
3856
+ res.end(
3857
+ generatePreviewHTML(options.themeName, options.port, themeDataPath)
3858
+ );
3961
3859
  }
3962
3860
  });
3963
3861
  const wss = new ws.WebSocketServer({ server });
@@ -3998,17 +3896,82 @@ function serveFile(res, filePath) {
3998
3896
  res.end("Internal Server Error");
3999
3897
  }
4000
3898
  }
4001
- function generatePreviewHTML(themeName, port) {
3899
+ function generatePreviewHTML(themeName, port, themeDataPath) {
3900
+ let fontLinks = "";
3901
+ let fontVarsCSS = "";
3902
+ if (themeDataPath) {
3903
+ try {
3904
+ const themeData = JSON.parse(fs2__default.default.readFileSync(themeDataPath, "utf-8"));
3905
+ const typography = (themeData?.themeConfig || themeData?.theme?.config)?.typography?.fontFamily;
3906
+ if (typography) {
3907
+ const fontFamilies = /* @__PURE__ */ new Set();
3908
+ for (const value of Object.values(typography)) {
3909
+ const primary = value.split(",")[0].trim();
3910
+ if (primary && ![
3911
+ "serif",
3912
+ "sans-serif",
3913
+ "monospace",
3914
+ "system-ui",
3915
+ "Georgia",
3916
+ "Inter",
3917
+ "Consolas"
3918
+ ].includes(primary)) {
3919
+ fontFamilies.add(primary);
3920
+ }
3921
+ }
3922
+ if (fontFamilies.size > 0) {
3923
+ const families = Array.from(fontFamilies).map(
3924
+ (f) => `family=${f.replace(/\s+/g, "+")}:wght@300;400;500;600;700;800;900`
3925
+ ).join("&");
3926
+ fontLinks = `<link rel="preconnect" href="https://fonts.googleapis.com">
3927
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
3928
+ <link href="https://fonts.googleapis.com/css2?${families}&display=swap" rel="stylesheet">`;
3929
+ }
3930
+ const heading = typography.heading || typography.body || "system-ui";
3931
+ const body = typography.body || "system-ui";
3932
+ fontVarsCSS = `
3933
+ :root {
3934
+ --font-heading: ${heading};
3935
+ --font-body: ${body};
3936
+ }
3937
+ body { font-family: var(--font-body); }
3938
+ h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }`;
3939
+ }
3940
+ } catch {
3941
+ }
3942
+ }
4002
3943
  return `<!DOCTYPE html>
4003
3944
  <html lang="en">
4004
3945
  <head>
4005
3946
  <meta charset="UTF-8">
4006
3947
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
4007
3948
  <title>OneX Dev \u2014 ${themeName}</title>
3949
+ ${fontLinks}
4008
3950
  <!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
4009
3951
  <script src="https://cdn.tailwindcss.com"></script>
3952
+ <script>
3953
+ tailwind.config = {
3954
+ theme: {
3955
+ extend: {
3956
+ aspectRatio: {
3957
+ '2/1': '2 / 1',
3958
+ '3/2': '3 / 2',
3959
+ '4/3': '4 / 3',
3960
+ '3/4': '3 / 4',
3961
+ '5/4': '5 / 4',
3962
+ '16/9': '16 / 9',
3963
+ '21/9': '21 / 9',
3964
+ },
3965
+ fontFamily: {
3966
+ playfair: ['Playfair Display', 'Georgia', 'serif'],
3967
+ roboto: ['Roboto', 'system-ui', 'sans-serif'],
3968
+ },
3969
+ },
3970
+ },
3971
+ }
3972
+ </script>
4010
3973
  <style>
4011
- #onex-preview-root { margin-top: 0; }
3974
+ #onex-preview-root { margin-top: 0; }${fontVarsCSS}
4012
3975
  </style>
4013
3976
  </head>
4014
3977
  <body>
@@ -4092,7 +4055,7 @@ async function devCommand(options) {
4092
4055
  logger.info(`File changed: ${filePath}`);
4093
4056
  try {
4094
4057
  await context2.rebuild();
4095
- await generateManifest2(themeName, themePath, outputDir);
4058
+ await generateManifest(themeName, themePath, outputDir);
4096
4059
  server.broadcast({ type: "reload", timestamp: Date.now() });
4097
4060
  logger.success("Rebuilt successfully");
4098
4061
  } catch (error) {
@@ -4127,7 +4090,7 @@ async function devCommand(options) {
4127
4090
 
4128
4091
  // src/commands/config.ts
4129
4092
  init_logger();
4130
- var CONFIG_DIR = path8__default.default.join(os__default.default.homedir(), ".onex");
4093
+ var CONFIG_DIR = path8__default.default.join(os3__default.default.homedir(), ".onexthm");
4131
4094
  var CONFIG_FILE = path8__default.default.join(CONFIG_DIR, ".env");
4132
4095
  var CONFIG_ENTRIES = [
4133
4096
  {
@@ -4157,7 +4120,7 @@ var CONFIG_ENTRIES = [
4157
4120
  key: "NEXT_PUBLIC_API_URL",
4158
4121
  label: "API URL",
4159
4122
  required: false,
4160
- defaultValue: "https://api-dev.onexeos.com"
4123
+ defaultValue: "https://platform-dev.onexeos.com"
4161
4124
  },
4162
4125
  {
4163
4126
  key: "NEXT_PUBLIC_COMPANY_ID",
@@ -4181,7 +4144,7 @@ function parseEnvFile(content) {
4181
4144
  function serializeEnv(values) {
4182
4145
  const lines = [
4183
4146
  "# OneX CLI Configuration",
4184
- "# Generated by: onex config",
4147
+ "# Generated by: onexthm config",
4185
4148
  ""
4186
4149
  ];
4187
4150
  lines.push("# AWS / S3 Configuration");
@@ -4268,6 +4231,461 @@ async function configCommand() {
4268
4231
  );
4269
4232
  }
4270
4233
 
4234
+ // src/commands/login.ts
4235
+ init_logger();
4236
+ var AUTH_DIR = path8__default.default.join(os3__default.default.homedir(), ".onexthm");
4237
+ var AUTH_FILE = path8__default.default.join(AUTH_DIR, "auth.json");
4238
+ function getApiUrl() {
4239
+ return process.env.NEXT_PUBLIC_API_URL || process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
4240
+ }
4241
+ async function saveAuthTokens(tokens) {
4242
+ await fs__default.default.ensureDir(AUTH_DIR);
4243
+ const key = getMachineKey();
4244
+ const data = JSON.stringify(tokens);
4245
+ const encrypted = encrypt(data, key);
4246
+ await fs__default.default.writeFile(AUTH_FILE, encrypted, "utf-8");
4247
+ }
4248
+ function loadAuthTokens() {
4249
+ try {
4250
+ if (!fs__default.default.existsSync(AUTH_FILE)) return null;
4251
+ const encrypted = fs__default.default.readFileSync(AUTH_FILE, "utf-8");
4252
+ const key = getMachineKey();
4253
+ const data = decrypt(encrypted, key);
4254
+ return JSON.parse(data);
4255
+ } catch {
4256
+ return null;
4257
+ }
4258
+ }
4259
+ async function clearAuthTokens() {
4260
+ try {
4261
+ await fs__default.default.remove(AUTH_FILE);
4262
+ } catch {
4263
+ }
4264
+ }
4265
+ function isTokenExpired(tokens) {
4266
+ return Date.now() / 1e3 > tokens.expiresAt - 60;
4267
+ }
4268
+ async function getValidTokens() {
4269
+ const tokens = loadAuthTokens();
4270
+ if (!tokens) return null;
4271
+ if (!isTokenExpired(tokens)) return tokens;
4272
+ try {
4273
+ const apiUrl = getApiUrl();
4274
+ const response = await fetch(`${apiUrl}/auth/refresh`, {
4275
+ method: "POST",
4276
+ headers: { "Content-Type": "application/json" },
4277
+ body: JSON.stringify({ refresh_token: tokens.refreshToken })
4278
+ });
4279
+ if (!response.ok) {
4280
+ await clearAuthTokens();
4281
+ return null;
4282
+ }
4283
+ const data = await response.json();
4284
+ const body = data.statusCode ? data.body : data;
4285
+ const refreshed = {
4286
+ ...tokens,
4287
+ accessToken: body.AccessToken || tokens.accessToken,
4288
+ idToken: body.IdToken || tokens.idToken,
4289
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
4290
+ };
4291
+ await saveAuthTokens(refreshed);
4292
+ return refreshed;
4293
+ } catch {
4294
+ await clearAuthTokens();
4295
+ return null;
4296
+ }
4297
+ }
4298
+ async function authenticatedFetch(url, init) {
4299
+ const tokens = await getValidTokens();
4300
+ if (!tokens) {
4301
+ throw new Error("Not logged in. Run: onexthm login");
4302
+ }
4303
+ const headers = new Headers(init?.headers);
4304
+ headers.set("Authorization", `Bearer ${tokens.idToken}`);
4305
+ headers.set("Content-Type", "application/json");
4306
+ return fetch(url, { ...init, headers });
4307
+ }
4308
+ function getMachineKey() {
4309
+ let seed;
4310
+ if (process.platform === "darwin") {
4311
+ seed = `onexthm:${os3__default.default.hostname()}:${os3__default.default.userInfo().username}`;
4312
+ } else if (process.platform === "linux") {
4313
+ try {
4314
+ seed = `onexthm:${fs__default.default.readFileSync("/etc/machine-id", "utf-8").trim()}`;
4315
+ } catch {
4316
+ seed = `onexthm:${os3__default.default.hostname()}:${os3__default.default.userInfo().username}`;
4317
+ }
4318
+ } else {
4319
+ seed = `onexthm:${os3__default.default.hostname()}:${os3__default.default.userInfo().username}`;
4320
+ }
4321
+ return crypto2__default.default.createHash("sha256").update(seed).digest();
4322
+ }
4323
+ function encrypt(text, key) {
4324
+ const iv = crypto2__default.default.randomBytes(16);
4325
+ const cipher = crypto2__default.default.createCipheriv("aes-256-gcm", key, iv);
4326
+ let encrypted = cipher.update(text, "utf-8", "hex");
4327
+ encrypted += cipher.final("hex");
4328
+ const tag = cipher.getAuthTag();
4329
+ return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
4330
+ }
4331
+ function decrypt(text, key) {
4332
+ const [ivHex, tagHex, encrypted] = text.split(":");
4333
+ const iv = Buffer.from(ivHex, "hex");
4334
+ const tag = Buffer.from(tagHex, "hex");
4335
+ const decipher = crypto2__default.default.createDecipheriv("aes-256-gcm", key, iv);
4336
+ decipher.setAuthTag(tag);
4337
+ let decrypted = decipher.update(encrypted, "hex", "utf-8");
4338
+ decrypted += decipher.final("utf-8");
4339
+ return decrypted;
4340
+ }
4341
+ function parseJwtClaims(idToken) {
4342
+ try {
4343
+ const payload = idToken.split(".")[1];
4344
+ const decoded = Buffer.from(payload, "base64url").toString("utf-8");
4345
+ return JSON.parse(decoded);
4346
+ } catch {
4347
+ return {};
4348
+ }
4349
+ }
4350
+
4351
+ // src/commands/login.ts
4352
+ async function loginCommand() {
4353
+ logger.header("OneX Theme Developer Login");
4354
+ const existing = loadAuthTokens();
4355
+ if (existing) {
4356
+ logger.info(`Already logged in as: ${existing.user.email}`);
4357
+ const { relogin } = await inquirer__default.default.prompt([
4358
+ {
4359
+ type: "confirm",
4360
+ name: "relogin",
4361
+ message: "Re-login with different account?",
4362
+ default: false
4363
+ }
4364
+ ]);
4365
+ if (!relogin) return;
4366
+ }
4367
+ const { email, password } = await inquirer__default.default.prompt([
4368
+ {
4369
+ type: "input",
4370
+ name: "email",
4371
+ message: "Email:",
4372
+ validate: (input) => input.includes("@") ? true : "Enter a valid email"
4373
+ },
4374
+ {
4375
+ type: "password",
4376
+ name: "password",
4377
+ message: "Password:",
4378
+ validate: (input) => input.length >= 6 ? true : "Password too short"
4379
+ }
4380
+ ]);
4381
+ logger.startSpinner("Logging in...");
4382
+ try {
4383
+ const apiUrl = getApiUrl();
4384
+ const response = await fetch(`${apiUrl}/auth/login`, {
4385
+ method: "POST",
4386
+ headers: { "Content-Type": "application/json" },
4387
+ body: JSON.stringify({ username: email, password })
4388
+ });
4389
+ const raw = await response.json();
4390
+ const data = raw.statusCode ? raw.body : raw;
4391
+ if (!response.ok || data.error) {
4392
+ logger.stopSpinner(false, "Login failed");
4393
+ logger.error(data.message || data.error || "Invalid credentials");
4394
+ process.exit(1);
4395
+ }
4396
+ const idToken = data.IdToken;
4397
+ const accessToken = data.AccessToken;
4398
+ const refreshToken = data.RefreshToken;
4399
+ const expiresIn = data.ExpiresIn || 3600;
4400
+ if (!idToken) {
4401
+ logger.stopSpinner(false, "Login failed");
4402
+ logger.error("No token received from server");
4403
+ process.exit(1);
4404
+ }
4405
+ const claims = parseJwtClaims(idToken);
4406
+ const tokens = {
4407
+ accessToken,
4408
+ idToken,
4409
+ refreshToken,
4410
+ expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
4411
+ user: {
4412
+ email: claims.email || email,
4413
+ name: claims.name,
4414
+ companyId: claims["custom:company_id"],
4415
+ userId: claims.sub
4416
+ }
4417
+ };
4418
+ await saveAuthTokens(tokens);
4419
+ logger.stopSpinner(true, "Logged in!");
4420
+ logger.newLine();
4421
+ logger.info(` Email: ${tokens.user.email}`);
4422
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4423
+ if (tokens.user.companyId)
4424
+ logger.info(` Company: ${tokens.user.companyId}`);
4425
+ logger.newLine();
4426
+ logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
4427
+ } catch (error) {
4428
+ logger.stopSpinner(false, "Login failed");
4429
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4430
+ process.exit(1);
4431
+ }
4432
+ }
4433
+
4434
+ // src/commands/logout.ts
4435
+ init_logger();
4436
+ async function logoutCommand() {
4437
+ const tokens = loadAuthTokens();
4438
+ if (!tokens) {
4439
+ logger.info("Not logged in.");
4440
+ return;
4441
+ }
4442
+ await clearAuthTokens();
4443
+ logger.success(`Logged out (was: ${tokens.user.email})`);
4444
+ }
4445
+
4446
+ // src/commands/whoami.ts
4447
+ init_logger();
4448
+ async function whoamiCommand() {
4449
+ const tokens = loadAuthTokens();
4450
+ if (!tokens) {
4451
+ logger.error("Not logged in. Run: onexthm login");
4452
+ process.exit(1);
4453
+ }
4454
+ const expired = isTokenExpired(tokens);
4455
+ logger.header("OneX Theme Developer");
4456
+ logger.info(` Email: ${tokens.user.email}`);
4457
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4458
+ if (tokens.user.companyId)
4459
+ logger.info(` Company: ${tokens.user.companyId}`);
4460
+ logger.info(
4461
+ ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4462
+ );
4463
+ }
4464
+
4465
+ // src/commands/publish.ts
4466
+ init_logger();
4467
+ async function publishCommand(options) {
4468
+ logger.header("OneX Theme Publish");
4469
+ const tokens = await getValidTokens();
4470
+ if (!tokens) {
4471
+ logger.error("Not logged in. Run: onexthm login");
4472
+ process.exit(1);
4473
+ }
4474
+ logger.info(`Logged in as: ${tokens.user.email}`);
4475
+ let themePath;
4476
+ if (options.theme) {
4477
+ themePath = path8__default.default.resolve(options.theme);
4478
+ } else {
4479
+ const isThemeDir = [
4480
+ "theme.config.ts",
4481
+ "bundle-entry.ts",
4482
+ "manifest.ts"
4483
+ ].some((f) => fs__default.default.existsSync(path8__default.default.join(process.cwd(), f)));
4484
+ if (isThemeDir) {
4485
+ themePath = process.cwd();
4486
+ } else {
4487
+ logger.error(
4488
+ "Not in a theme directory. Run from theme root or use --theme flag."
4489
+ );
4490
+ process.exit(1);
4491
+ }
4492
+ }
4493
+ const pkgPath = path8__default.default.join(themePath, "package.json");
4494
+ if (!fs__default.default.existsSync(pkgPath)) {
4495
+ logger.error("No package.json found in theme directory");
4496
+ process.exit(1);
4497
+ }
4498
+ const pkg = fs__default.default.readJsonSync(pkgPath);
4499
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path8__default.default.basename(themePath);
4500
+ const version2 = pkg.version || "1.0.0";
4501
+ logger.newLine();
4502
+ logger.info(`Theme: ${themeId}`);
4503
+ logger.info(`Version: ${version2}`);
4504
+ logger.newLine();
4505
+ const apiUrl = getApiUrl();
4506
+ logger.startSpinner("Registering theme...");
4507
+ try {
4508
+ const regResponse = await authenticatedFetch(
4509
+ `${apiUrl}/website-api/themes/register`,
4510
+ {
4511
+ method: "POST",
4512
+ body: JSON.stringify({
4513
+ themeId,
4514
+ name: pkg.displayName || themeId,
4515
+ description: pkg.description || "",
4516
+ email: tokens.user.email
4517
+ })
4518
+ }
4519
+ );
4520
+ const regData = await regResponse.json();
4521
+ const regBody = regData.statusCode ? regData.body : regData;
4522
+ if (!regResponse.ok && regBody.error && !regBody.error.includes("already registered")) {
4523
+ logger.stopSpinner(false, "Registration failed");
4524
+ logger.error(regBody.error);
4525
+ process.exit(1);
4526
+ }
4527
+ logger.stopSpinner(true, regBody.message || "Theme registered");
4528
+ } catch (error) {
4529
+ logger.stopSpinner(false, "Registration failed");
4530
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4531
+ process.exit(1);
4532
+ }
4533
+ logger.startSpinner("Building theme...");
4534
+ try {
4535
+ const { execSync: execSync3 } = await import('child_process');
4536
+ execSync3(
4537
+ "npx tsup bundle-entry.ts --format esm --target es2020 --outDir dist",
4538
+ {
4539
+ cwd: themePath,
4540
+ stdio: "ignore"
4541
+ }
4542
+ );
4543
+ logger.stopSpinner(true, "Theme compiled");
4544
+ } catch {
4545
+ logger.stopSpinner(false, "Build failed");
4546
+ logger.error("Run 'onexthm build' to see build errors");
4547
+ process.exit(1);
4548
+ }
4549
+ logger.startSpinner("Getting upload URL...");
4550
+ let bundleUploadUrl;
4551
+ let sourceUploadUrl;
4552
+ try {
4553
+ const pubResponse = await authenticatedFetch(
4554
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`,
4555
+ {
4556
+ method: "POST",
4557
+ body: JSON.stringify({ version: version2 })
4558
+ }
4559
+ );
4560
+ const pubData = await pubResponse.json();
4561
+ const pubBody = pubData.statusCode ? pubData.body : pubData;
4562
+ if (!pubResponse.ok || !pubBody.bundleUploadUrl) {
4563
+ logger.stopSpinner(false, "Failed to get upload URL");
4564
+ logger.error(pubBody.error || "Server error");
4565
+ process.exit(1);
4566
+ }
4567
+ bundleUploadUrl = pubBody.bundleUploadUrl;
4568
+ sourceUploadUrl = pubBody.sourceUploadUrl;
4569
+ logger.stopSpinner(true, "Upload URL obtained");
4570
+ } catch (error) {
4571
+ logger.stopSpinner(false, "Failed");
4572
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4573
+ process.exit(1);
4574
+ }
4575
+ logger.startSpinner("Uploading bundle...");
4576
+ try {
4577
+ const archiver3 = await import('archiver');
4578
+ const { createWriteStream } = await import('fs');
4579
+ const distDir = path8__default.default.join(themePath, "dist");
4580
+ if (!fs__default.default.existsSync(distDir)) {
4581
+ logger.stopSpinner(false, "No dist/ directory");
4582
+ logger.error("Build the theme first: onexthm build");
4583
+ process.exit(1);
4584
+ }
4585
+ const bundleZipPath = path8__default.default.join(themePath, "dist", "bundle.zip");
4586
+ await createZip(distDir, bundleZipPath, ["bundle.zip"]);
4587
+ const bundleBuffer = fs__default.default.readFileSync(bundleZipPath);
4588
+ const bundleRes = await fetch(bundleUploadUrl, {
4589
+ method: "PUT",
4590
+ headers: { "Content-Type": "application/zip" },
4591
+ body: bundleBuffer
4592
+ });
4593
+ if (!bundleRes.ok) {
4594
+ throw new Error(`Upload failed: ${bundleRes.status}`);
4595
+ }
4596
+ const sizeMB = (bundleBuffer.length / 1024 / 1024).toFixed(2);
4597
+ logger.stopSpinner(true, `Bundle uploaded (${sizeMB} MB)`);
4598
+ } catch (error) {
4599
+ logger.stopSpinner(false, "Upload failed");
4600
+ logger.error(error instanceof Error ? error.message : "Upload error");
4601
+ process.exit(1);
4602
+ }
4603
+ logger.startSpinner("Uploading source...");
4604
+ try {
4605
+ const sourceZipPath = path8__default.default.join(themePath, "dist", "source.zip");
4606
+ await createZip(themePath, sourceZipPath, [
4607
+ "node_modules",
4608
+ "dist",
4609
+ ".git",
4610
+ ".env",
4611
+ ".env.local"
4612
+ ]);
4613
+ const sourceBuffer = fs__default.default.readFileSync(sourceZipPath);
4614
+ const sourceRes = await fetch(sourceUploadUrl, {
4615
+ method: "PUT",
4616
+ headers: { "Content-Type": "application/zip" },
4617
+ body: sourceBuffer
4618
+ });
4619
+ if (!sourceRes.ok) {
4620
+ throw new Error(`Source upload failed: ${sourceRes.status}`);
4621
+ }
4622
+ const sizeMB = (sourceBuffer.length / 1024 / 1024).toFixed(2);
4623
+ logger.stopSpinner(true, `Source uploaded (${sizeMB} MB)`);
4624
+ } catch (error) {
4625
+ logger.stopSpinner(false, "Source upload failed");
4626
+ logger.info("Source upload skipped (bundle was uploaded successfully)");
4627
+ }
4628
+ logger.startSpinner("Scanning and publishing...");
4629
+ try {
4630
+ const confirmResponse = await authenticatedFetch(
4631
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
4632
+ { method: "POST" }
4633
+ );
4634
+ const confirmData = await confirmResponse.json();
4635
+ const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
4636
+ if (!confirmResponse.ok || !confirmBody.success) {
4637
+ logger.stopSpinner(false, "Publishing failed");
4638
+ if (confirmBody.violations) {
4639
+ logger.error("Theme rejected \u2014 security violations found:");
4640
+ for (const v of confirmBody.violations) {
4641
+ logger.log(` \u274C ${v.file}: ${v.violation}`);
4642
+ }
4643
+ } else {
4644
+ logger.error(confirmBody.error || "Unknown error");
4645
+ }
4646
+ if (confirmBody.warnings?.length) {
4647
+ logger.newLine();
4648
+ logger.info("Warnings:");
4649
+ for (const w of confirmBody.warnings) {
4650
+ logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
4651
+ }
4652
+ }
4653
+ process.exit(1);
4654
+ }
4655
+ logger.stopSpinner(true, confirmBody.message || "Published!");
4656
+ if (confirmBody.warnings?.length) {
4657
+ logger.newLine();
4658
+ logger.info("Warnings (non-blocking):");
4659
+ for (const w of confirmBody.warnings) {
4660
+ logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
4661
+ }
4662
+ }
4663
+ } catch (error) {
4664
+ logger.stopSpinner(false, "Publishing failed");
4665
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4666
+ process.exit(1);
4667
+ }
4668
+ logger.newLine();
4669
+ logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
4670
+ }
4671
+ async function createZip(sourceDir, outputPath, exclude) {
4672
+ const archiver3 = (await import('archiver')).default;
4673
+ const { createWriteStream } = await import('fs');
4674
+ return new Promise((resolve, reject) => {
4675
+ const output = createWriteStream(outputPath);
4676
+ const archive = archiver3("zip", { zlib: { level: 9 } });
4677
+ output.on("close", resolve);
4678
+ archive.on("error", reject);
4679
+ archive.pipe(output);
4680
+ archive.glob("**/*", {
4681
+ cwd: sourceDir,
4682
+ ignore: exclude.map((e) => `${e}/**`),
4683
+ dot: false
4684
+ });
4685
+ archive.finalize();
4686
+ });
4687
+ }
4688
+
4271
4689
  // src/cli.ts
4272
4690
  try {
4273
4691
  const projectRoot = getProjectRoot();
@@ -4279,13 +4697,13 @@ try {
4279
4697
  } catch {
4280
4698
  }
4281
4699
  dotenv__default.default.config({
4282
- path: path8__default.default.join(os__default.default.homedir(), ".onex", ".env"),
4700
+ path: path8__default.default.join(os3__default.default.homedir(), ".onexthm", ".env"),
4283
4701
  quiet: true
4284
4702
  });
4285
4703
  var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
4286
4704
  var { version } = require2("../package.json");
4287
4705
  var program = new commander.Command();
4288
- program.name("onex").description("CLI tool for OneX theme development").version(version);
4706
+ program.name("onexthm").description("CLI tool for OneX theme development").version(version);
4289
4707
  program.command("init").description("Create a new OneX theme project").argument("[project-name]", "Name of the project").option(
4290
4708
  "-t, --template <template>",
4291
4709
  "Template to use (default, minimal)",
@@ -4336,6 +4754,10 @@ program.command("clone").description("Clone theme source code from S3").argument
4336
4754
  "staging"
4337
4755
  ).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
4338
4756
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
4757
+ program.command("login").description("Login to OneX platform").action(loginCommand);
4758
+ program.command("logout").description("Logout from OneX platform").action(logoutCommand);
4759
+ program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
4760
+ program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").action(publishCommand);
4339
4761
  program.configureOutput({
4340
4762
  writeErr: (str) => process.stderr.write(chalk4__default.default.red(str))
4341
4763
  });