@onexapis/cli 1.1.17 → 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 (61) hide show
  1. package/README.md +82 -16
  2. package/dist/cli.js +522 -286
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cli.mjs +519 -283
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/index.js +47 -270
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +47 -270
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +1 -1
  11. package/templates/default/.env.example +1 -1
  12. package/templates/default/.mcp.json +8 -0
  13. package/templates/default/CLAUDE.md +941 -0
  14. package/templates/default/bundle-entry.ts +18 -0
  15. package/templates/default/index.ts +26 -0
  16. package/templates/default/package.json +37 -0
  17. package/templates/default/pages/about.ts +66 -0
  18. package/templates/default/pages/home.ts +93 -0
  19. package/templates/default/pages/showcase.ts +146 -0
  20. package/templates/default/sections/about/about-default.tsx +237 -0
  21. package/templates/default/sections/about/about.schema.ts +259 -0
  22. package/templates/default/sections/about/index.ts +15 -0
  23. package/templates/default/sections/cta/cta-default.tsx +180 -0
  24. package/templates/default/sections/cta/cta.schema.ts +210 -0
  25. package/templates/default/sections/cta/index.ts +11 -0
  26. package/templates/default/sections/features/features-default.tsx +154 -0
  27. package/templates/default/sections/features/features.schema.ts +330 -0
  28. package/templates/default/sections/features/index.ts +11 -0
  29. package/templates/default/sections/gallery/gallery-default.tsx +134 -0
  30. package/templates/default/sections/gallery/gallery.schema.ts +397 -0
  31. package/templates/default/sections/gallery/index.ts +11 -0
  32. package/templates/default/sections/hero/hero-default.tsx +212 -0
  33. package/templates/default/sections/hero/hero.schema.ts +273 -0
  34. package/templates/default/sections/hero/index.ts +15 -0
  35. package/templates/default/sections/stats/index.ts +11 -0
  36. package/templates/default/sections/stats/stats-default.tsx +103 -0
  37. package/templates/default/sections/stats/stats.schema.ts +266 -0
  38. package/templates/default/sections/testimonials/index.ts +11 -0
  39. package/templates/default/sections/testimonials/testimonials-default.tsx +130 -0
  40. package/templates/default/sections/testimonials/testimonials.schema.ts +371 -0
  41. package/templates/default/sections-registry.ts +32 -0
  42. package/templates/default/theme.config.ts +107 -0
  43. package/templates/default/theme.layout.ts +21 -0
  44. package/templates/default/tsconfig.json +16 -7
  45. package/templates/default/README.md.ejs +0 -129
  46. package/templates/default/esbuild.config.js +0 -81
  47. package/templates/default/package.json.ejs +0 -31
  48. package/templates/default/src/config.ts.ejs +0 -98
  49. package/templates/default/src/index.ts.ejs +0 -11
  50. package/templates/default/src/layout.ts +0 -23
  51. package/templates/default/src/manifest.ts.ejs +0 -47
  52. package/templates/default/src/pages/home.ts.ejs +0 -37
  53. package/templates/default/src/sections/footer/footer-default.tsx +0 -28
  54. package/templates/default/src/sections/footer/footer.schema.ts +0 -45
  55. package/templates/default/src/sections/footer/index.ts +0 -2
  56. package/templates/default/src/sections/header/header-default.tsx +0 -61
  57. package/templates/default/src/sections/header/header.schema.ts +0 -46
  58. package/templates/default/src/sections/header/index.ts +0 -2
  59. package/templates/default/src/sections/hero/hero-default.tsx +0 -52
  60. package/templates/default/src/sections/hero/hero.schema.ts +0 -52
  61. package/templates/default/src/sections/hero/index.ts +0 -2
package/dist/cli.mjs CHANGED
@@ -4,10 +4,10 @@ import ora from 'ora';
4
4
  import * as esbuild from 'esbuild';
5
5
  import path8 from 'path';
6
6
  import fs7 from 'fs/promises';
7
- import crypto from 'crypto';
7
+ import crypto2 from 'crypto';
8
8
  import { glob } from 'glob';
9
9
  import { createRequire } from 'module';
10
- import os from 'os';
10
+ import os3 from 'os';
11
11
  import dotenv from 'dotenv';
12
12
  import fs from 'fs-extra';
13
13
  import ejs from 'ejs';
@@ -17,7 +17,7 @@ import fs2 from 'fs';
17
17
  import inquirer from 'inquirer';
18
18
  import archiver from 'archiver';
19
19
  import FormData from 'form-data';
20
- import fetch from 'node-fetch';
20
+ import fetch2 from 'node-fetch';
21
21
  import { PutObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
22
22
  import AdmZip from 'adm-zip';
23
23
  import chokidar from 'chokidar';
@@ -96,7 +96,7 @@ __export(compile_theme_exports, {
96
96
  compilePreviewRuntime: () => compilePreviewRuntime,
97
97
  compileStandaloneTheme: () => compileStandaloneTheme,
98
98
  compileStandaloneThemeDev: () => compileStandaloneThemeDev,
99
- generateManifest: () => generateManifest2
99
+ generateManifest: () => generateManifest
100
100
  });
101
101
  async function resolveNodeModulesFile(startDir, relativePath) {
102
102
  let dir = startDir;
@@ -484,7 +484,7 @@ async function contentHashEntry(outputDir) {
484
484
  logger.warning("No entry file found in output, skipping content hash");
485
485
  return;
486
486
  }
487
- const hash2 = crypto.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
487
+ const hash2 = crypto2.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
488
488
  const hashedName2 = `bundle-entry-${hash2}.js`;
489
489
  const indexMapPath = path8.join(outputDir, "index.js.map");
490
490
  const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
@@ -502,7 +502,7 @@ async function contentHashEntry(outputDir) {
502
502
  logger.info(`Entry hashed: ${hashedName2}`);
503
503
  return;
504
504
  }
505
- const hash = crypto.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
505
+ const hash = crypto2.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
506
506
  const hashedName = `bundle-entry-${hash}.js`;
507
507
  const hashedMapName = `bundle-entry-${hash}.js.map`;
508
508
  entryContent = entryContent.replace(
@@ -540,7 +540,7 @@ async function extractDataRequirements(themePath) {
540
540
  }
541
541
  return requirements;
542
542
  }
543
- async function generateManifest2(themeName, themePath, outputDir) {
543
+ async function generateManifest(themeName, themePath, outputDir) {
544
544
  let version2 = "1.0.0";
545
545
  let themeId = themeName;
546
546
  try {
@@ -675,7 +675,7 @@ async function compileStandaloneTheme(themePath, themeName) {
675
675
  } catch {
676
676
  }
677
677
  await contentHashEntry(outputDir);
678
- await generateManifest2(themeName, themePath, outputDir);
678
+ await generateManifest(themeName, themePath, outputDir);
679
679
  await generateThemeData(themePath, outputDir, themeName);
680
680
  if (result.metafile) {
681
681
  const outputs = result.metafile.outputs;
@@ -756,7 +756,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
756
756
  };
757
757
  const context2 = await esbuild.context(buildOptions);
758
758
  await context2.rebuild();
759
- await generateManifest2(themeName, themePath, outputDir);
759
+ await generateManifest(themeName, themePath, outputDir);
760
760
  await generateThemeData(themePath, outputDir, themeName);
761
761
  return { context: context2, outputDir };
762
762
  }
@@ -1492,38 +1492,13 @@ async function initCommand(projectName, options = {}) {
1492
1492
  try {
1493
1493
  fs2.mkdirSync(projectPath, { recursive: true });
1494
1494
  await copyTemplate(template, projectPath, data);
1495
- const srcPath = path8.join(projectPath, "src");
1496
- fs2.mkdirSync(srcPath, { recursive: true });
1497
- const manifestContent = generateManifest(data);
1498
- await writeFile(path8.join(srcPath, "manifest.ts"), manifestContent);
1499
- const configContent = generateThemeConfig(data);
1500
- await writeFile(path8.join(srcPath, "config.ts"), configContent);
1501
- const layoutContent = generateThemeLayout(data);
1502
- await writeFile(path8.join(srcPath, "layout.ts"), layoutContent);
1503
- const indexContent = generateThemeIndex(data);
1504
- await writeFile(path8.join(srcPath, "index.ts"), indexContent);
1505
- const sectionsPath = path8.join(srcPath, "sections");
1506
- fs2.mkdirSync(sectionsPath, { recursive: true });
1507
- await writeFile(
1508
- path8.join(sectionsPath, "README.md"),
1509
- `# ${displayName} Sections
1510
-
1511
- Add your theme-specific sections here.
1512
- `
1513
- );
1514
- const blocksPath = path8.join(srcPath, "blocks");
1515
- fs2.mkdirSync(blocksPath, { recursive: true });
1516
- await writeFile(
1517
- path8.join(blocksPath, "README.md"),
1518
- `# ${displayName} Blocks
1519
-
1520
- Add your theme-specific blocks here.
1521
- `
1495
+ await renameThemeInFiles(
1496
+ projectPath,
1497
+ name,
1498
+ displayName,
1499
+ description,
1500
+ author
1522
1501
  );
1523
- const pagesPath = path8.join(srcPath, "pages");
1524
- fs2.mkdirSync(pagesPath, { recursive: true });
1525
- const homePageContent = generateHomePage(data);
1526
- await writeFile(path8.join(pagesPath, "home.ts"), homePageContent);
1527
1502
  logger.stopSpinner(true, "Project structure created!");
1528
1503
  if (options.git) {
1529
1504
  logger.startSpinner("Initializing git repository...");
@@ -1567,14 +1542,14 @@ Add your theme-specific blocks here.
1567
1542
  logger.log(` npm run dev # Start development mode`);
1568
1543
  logger.newLine();
1569
1544
  logger.section("Theme structure:");
1570
- logger.log(" src/manifest.ts - Theme manifest and exports");
1545
+ logger.log(" bundle-entry.ts - Theme manifest and exports");
1571
1546
  logger.log(
1572
- " src/config.ts - Design tokens (colors, typography, etc.)"
1547
+ " theme.config.ts - Design tokens (colors, typography, etc.)"
1573
1548
  );
1574
- logger.log(" src/layout.ts - Header and footer configuration");
1575
- logger.log(" src/sections/ - Custom sections for your theme");
1576
- logger.log(" src/blocks/ - Reusable blocks");
1577
- logger.log(" src/pages/ - Page configurations");
1549
+ logger.log(" theme.layout.ts - Header and footer configuration");
1550
+ logger.log(" sections/ - Custom sections for your theme");
1551
+ logger.log(" pages/ - Page configurations");
1552
+ logger.log(" CLAUDE.md - AI assistant context");
1578
1553
  logger.newLine();
1579
1554
  logger.success(`Happy theming! \u{1F3A8}`);
1580
1555
  } catch (error) {
@@ -1588,231 +1563,33 @@ Add your theme-specific blocks here.
1588
1563
  process.exit(1);
1589
1564
  }
1590
1565
  }
1591
- function generateManifest(data) {
1592
- return `import type { ThemeExport } from "@onexapis/core";
1593
-
1594
- /**
1595
- * ${data.displayName} Theme Manifest
1596
- * ${data.description}
1597
- */
1598
- export const manifest: ThemeExport = {
1599
- id: "${data.themeName}",
1600
- name: "${data.displayName}",
1601
- description: "${data.description}",
1602
- version: "1.0.0",
1603
- author: "${data.author}",
1604
-
1605
- // Theme configuration
1606
- config: () => import("./config").then((m) => m.themeConfig),
1607
-
1608
- // Theme layout (header/footer sections)
1609
- layout: () => import("./layout").then((m) => m.themeLayout),
1610
-
1611
- // Available sections in this theme
1612
- sections: {
1613
- // Example: hero: () => import("./sections/hero").then((m) => m.heroSchema),
1614
- },
1615
-
1616
- // Available blocks in this theme
1617
- blocks: {
1618
- // Example: productCard: () => import("./blocks/product-card").then((m) => m.productCardDefinition),
1619
- },
1620
-
1621
- // Default pages
1622
- pages: {
1623
- home: () => import("./pages/home").then((m) => m.homePageConfig),
1624
- },
1625
-
1626
- // Supported page types
1627
- supportedPageTypes: ["home", "about", "contact", "custom"],
1628
-
1629
- // Preview image (optional)
1630
- preview: undefined,
1631
-
1632
- // Tags for categorization (optional)
1633
- tags: ["custom"],
1634
- };
1635
-
1636
- export default manifest;
1637
- `;
1638
- }
1639
- function generateThemeConfig(data) {
1640
- return `import type { ThemeConfig } from "@onexapis/core";
1641
-
1642
- /**
1643
- * ${data.displayName} Theme Configuration
1644
- * Design tokens: colors, typography, spacing, etc.
1645
- */
1646
- export const themeConfig: ThemeConfig = {
1647
- // Color palette
1648
- colors: {
1649
- primary: {
1650
- 50: "#eff6ff",
1651
- 100: "#dbeafe",
1652
- 200: "#bfdbfe",
1653
- 300: "#93c5fd",
1654
- 400: "#60a5fa",
1655
- 500: "#3b82f6",
1656
- 600: "#2563eb",
1657
- 700: "#1d4ed8",
1658
- 800: "#1e40af",
1659
- 900: "#1e3a8a",
1660
- },
1661
- secondary: {
1662
- 50: "#f8fafc",
1663
- 100: "#f1f5f9",
1664
- 200: "#e2e8f0",
1665
- 300: "#cbd5e1",
1666
- 400: "#94a3b8",
1667
- 500: "#64748b",
1668
- 600: "#475569",
1669
- 700: "#334155",
1670
- 800: "#1e293b",
1671
- 900: "#0f172a",
1672
- },
1673
- accent: {
1674
- 50: "#fdf4ff",
1675
- 100: "#fae8ff",
1676
- 200: "#f5d0fe",
1677
- 300: "#f0abfc",
1678
- 400: "#e879f9",
1679
- 500: "#d946ef",
1680
- 600: "#c026d3",
1681
- 700: "#a21caf",
1682
- 800: "#86198f",
1683
- 900: "#701a75",
1684
- },
1685
- },
1686
-
1687
- // Typography
1688
- typography: {
1689
- fontFamily: {
1690
- sans: ["Inter", "system-ui", "sans-serif"],
1691
- serif: ["Georgia", "serif"],
1692
- mono: ["Monaco", "monospace"],
1693
- },
1694
- fontSize: {
1695
- xs: "0.75rem",
1696
- sm: "0.875rem",
1697
- base: "1rem",
1698
- lg: "1.125rem",
1699
- xl: "1.25rem",
1700
- "2xl": "1.5rem",
1701
- "3xl": "1.875rem",
1702
- "4xl": "2.25rem",
1703
- "5xl": "3rem",
1704
- },
1705
- },
1706
-
1707
- // Spacing
1708
- spacing: {
1709
- xs: "0.5rem",
1710
- sm: "1rem",
1711
- md: "1.5rem",
1712
- lg: "2rem",
1713
- xl: "3rem",
1714
- "2xl": "4rem",
1715
- "3xl": "6rem",
1716
- "4xl": "8rem",
1717
- },
1718
-
1719
- // Border radius
1720
- borderRadius: {
1721
- none: "0",
1722
- sm: "0.125rem",
1723
- md: "0.375rem",
1724
- lg: "0.5rem",
1725
- xl: "0.75rem",
1726
- full: "9999px",
1727
- },
1728
-
1729
- // Breakpoints
1730
- breakpoints: {
1731
- sm: "640px",
1732
- md: "768px",
1733
- lg: "1024px",
1734
- xl: "1280px",
1735
- "2xl": "1536px",
1736
- },
1737
- };
1738
- `;
1739
- }
1740
- function generateThemeLayout(data) {
1741
- return `import type { ThemeLayoutConfig } from "@onexapis/core";
1742
-
1743
- /**
1744
- * ${data.themeName} Theme Layout
1745
- * Define header and footer sections
1746
- */
1747
- export const themeLayout: ThemeLayoutConfig = {
1748
- // Header section configuration
1749
- header: undefined,
1750
- // Example:
1751
- // header: {
1752
- // type: "header",
1753
- // template: "default",
1754
- // enabled: true,
1755
- // settings: {},
1756
- // },
1757
-
1758
- // Footer section configuration
1759
- footer: undefined,
1760
- // Example:
1761
- // footer: {
1762
- // type: "footer",
1763
- // template: "default",
1764
- // enabled: true,
1765
- // settings: {},
1766
- // },
1767
- };
1768
- `;
1769
- }
1770
- function generateThemeIndex(data) {
1771
- return `/**
1772
- * ${data.themeNamePascal} Theme
1773
- */
1774
-
1775
- export { manifest as ${data.themeNamePascal}Manifest } from "./manifest";
1776
- export { themeConfig as ${data.themeNamePascal}Config } from "./config";
1777
- export { themeLayout as ${data.themeNamePascal}Layout } from "./layout";
1778
- `;
1779
- }
1780
- function generateHomePage(data) {
1781
- return `import type { PageConfig } from "@onexapis/core";
1782
-
1783
- /**
1784
- * Home Page Configuration
1785
- */
1786
- export const homePageConfig: PageConfig = {
1787
- type: "home",
1788
- title: "${data.displayName}",
1789
- description: "Welcome to ${data.displayName}",
1790
-
1791
- // SEO metadata
1792
- seo: {
1793
- title: "${data.displayName} - Home",
1794
- description: "Welcome to ${data.displayName}",
1795
- keywords: [],
1796
- ogImage: undefined,
1797
- },
1798
-
1799
- // Page sections
1800
- sections: [
1801
- // Add your sections here
1802
- // Example:
1803
- // {
1804
- // id: "hero-1",
1805
- // type: "hero",
1806
- // template: "default",
1807
- // order: 0,
1808
- // enabled: true,
1809
- // settings: {},
1810
- // components: [],
1811
- // blocks: [],
1812
- // },
1813
- ],
1814
- };
1815
- `;
1566
+ async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
1567
+ const configPath = path8.join(projectPath, "theme.config.ts");
1568
+ if (fs2.existsSync(configPath)) {
1569
+ let content = fs2.readFileSync(configPath, "utf-8");
1570
+ content = content.replace(
1571
+ /name: "My Simple Theme"/,
1572
+ `name: "${displayName}"`
1573
+ );
1574
+ content = content.replace(
1575
+ /description: ".*?"/,
1576
+ `description: "${description}"`
1577
+ );
1578
+ fs2.writeFileSync(configPath, content, "utf-8");
1579
+ }
1580
+ const pkgPath = path8.join(projectPath, "package.json");
1581
+ if (fs2.existsSync(pkgPath)) {
1582
+ let content = fs2.readFileSync(pkgPath, "utf-8");
1583
+ content = content.replace(
1584
+ /@onex-themes\/my-simple/g,
1585
+ `@onex-themes/${themeName}`
1586
+ );
1587
+ content = content.replace(
1588
+ /"description": ".*?"/,
1589
+ `"description": "${description}"`
1590
+ );
1591
+ fs2.writeFileSync(pkgPath, content, "utf-8");
1592
+ }
1816
1593
  }
1817
1594
 
1818
1595
  // src/commands/create-section.ts
@@ -3139,7 +2916,7 @@ async function deployCommand(options) {
3139
2916
  if (options.environment) {
3140
2917
  formData.append("environment", options.environment);
3141
2918
  }
3142
- const response = await fetch(uploadEndpoint, {
2919
+ const response = await fetch2(uploadEndpoint, {
3143
2920
  method: "POST",
3144
2921
  body: formData,
3145
2922
  headers: formData.getHeaders()
@@ -3220,7 +2997,7 @@ function getBucketName(env) {
3220
2997
  return process.env.BUCKET_NAME;
3221
2998
  }
3222
2999
  const environment = env || process.env.ENVIRONMENT || "staging";
3223
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3000
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3224
3001
  }
3225
3002
  async function findCompiledThemeDir(themeId, version2) {
3226
3003
  const searchPaths = [path8.resolve(process.cwd(), "dist")];
@@ -3336,7 +3113,7 @@ async function uploadCommand(options) {
3336
3113
  }
3337
3114
  spinner.succeed(`Found compiled theme at: ${compiledDir}`);
3338
3115
  spinner.start("Creating bundle.zip...");
3339
- const tmpDir = os.tmpdir();
3116
+ const tmpDir = os3.tmpdir();
3340
3117
  const bundleZipPath = path8.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
3341
3118
  await createZipFromDir(compiledDir, bundleZipPath);
3342
3119
  const bundleZipBuffer = await fs.readFile(bundleZipPath);
@@ -3489,7 +3266,7 @@ function getBucketName2(env) {
3489
3266
  return process.env.BUCKET_NAME;
3490
3267
  }
3491
3268
  const environment = env || process.env.ENVIRONMENT || "staging";
3492
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3269
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3493
3270
  }
3494
3271
  async function streamToString(stream) {
3495
3272
  const chunks = [];
@@ -3690,7 +3467,7 @@ function getBucketName3(env) {
3690
3467
  return process.env.BUCKET_NAME;
3691
3468
  }
3692
3469
  const environment = env || process.env.ENVIRONMENT || "staging";
3693
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3470
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3694
3471
  }
3695
3472
  async function streamToString2(stream) {
3696
3473
  const chunks = [];
@@ -3734,8 +3511,8 @@ function runInstall(cwd) {
3734
3511
  });
3735
3512
  }
3736
3513
  async function promptThemeName(originalName) {
3737
- const { default: inquirer6 } = await import('inquirer');
3738
- const { themeName } = await inquirer6.prompt([
3514
+ const { default: inquirer7 } = await import('inquirer');
3515
+ const { themeName } = await inquirer7.prompt([
3739
3516
  {
3740
3517
  type: "input",
3741
3518
  name: "themeName",
@@ -3888,7 +3665,7 @@ async function cloneCommand(themeName, options) {
3888
3665
  [
3889
3666
  "# API Configuration (enables real data in preview)",
3890
3667
  "# Get your Company ID from the OneX dashboard",
3891
- "NEXT_PUBLIC_API_URL=https://api-dev.onexeos.com",
3668
+ "NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
3892
3669
  "NEXT_PUBLIC_COMPANY_ID=",
3893
3670
  ""
3894
3671
  ].join("\n")
@@ -4236,7 +4013,7 @@ async function devCommand(options) {
4236
4013
  logger.info(`File changed: ${filePath}`);
4237
4014
  try {
4238
4015
  await context2.rebuild();
4239
- await generateManifest2(themeName, themePath, outputDir);
4016
+ await generateManifest(themeName, themePath, outputDir);
4240
4017
  server.broadcast({ type: "reload", timestamp: Date.now() });
4241
4018
  logger.success("Rebuilt successfully");
4242
4019
  } catch (error) {
@@ -4271,7 +4048,7 @@ async function devCommand(options) {
4271
4048
 
4272
4049
  // src/commands/config.ts
4273
4050
  init_logger();
4274
- var CONFIG_DIR = path8.join(os.homedir(), ".onexthm");
4051
+ var CONFIG_DIR = path8.join(os3.homedir(), ".onexthm");
4275
4052
  var CONFIG_FILE = path8.join(CONFIG_DIR, ".env");
4276
4053
  var CONFIG_ENTRIES = [
4277
4054
  {
@@ -4301,7 +4078,7 @@ var CONFIG_ENTRIES = [
4301
4078
  key: "NEXT_PUBLIC_API_URL",
4302
4079
  label: "API URL",
4303
4080
  required: false,
4304
- defaultValue: "https://api-dev.onexeos.com"
4081
+ defaultValue: "https://platform-dev.onexeos.com"
4305
4082
  },
4306
4083
  {
4307
4084
  key: "NEXT_PUBLIC_COMPANY_ID",
@@ -4412,6 +4189,461 @@ async function configCommand() {
4412
4189
  );
4413
4190
  }
4414
4191
 
4192
+ // src/commands/login.ts
4193
+ init_logger();
4194
+ var AUTH_DIR = path8.join(os3.homedir(), ".onexthm");
4195
+ var AUTH_FILE = path8.join(AUTH_DIR, "auth.json");
4196
+ function getApiUrl() {
4197
+ return process.env.NEXT_PUBLIC_API_URL || process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
4198
+ }
4199
+ async function saveAuthTokens(tokens) {
4200
+ await fs.ensureDir(AUTH_DIR);
4201
+ const key = getMachineKey();
4202
+ const data = JSON.stringify(tokens);
4203
+ const encrypted = encrypt(data, key);
4204
+ await fs.writeFile(AUTH_FILE, encrypted, "utf-8");
4205
+ }
4206
+ function loadAuthTokens() {
4207
+ try {
4208
+ if (!fs.existsSync(AUTH_FILE)) return null;
4209
+ const encrypted = fs.readFileSync(AUTH_FILE, "utf-8");
4210
+ const key = getMachineKey();
4211
+ const data = decrypt(encrypted, key);
4212
+ return JSON.parse(data);
4213
+ } catch {
4214
+ return null;
4215
+ }
4216
+ }
4217
+ async function clearAuthTokens() {
4218
+ try {
4219
+ await fs.remove(AUTH_FILE);
4220
+ } catch {
4221
+ }
4222
+ }
4223
+ function isTokenExpired(tokens) {
4224
+ return Date.now() / 1e3 > tokens.expiresAt - 60;
4225
+ }
4226
+ async function getValidTokens() {
4227
+ const tokens = loadAuthTokens();
4228
+ if (!tokens) return null;
4229
+ if (!isTokenExpired(tokens)) return tokens;
4230
+ try {
4231
+ const apiUrl = getApiUrl();
4232
+ const response = await fetch(`${apiUrl}/auth/refresh`, {
4233
+ method: "POST",
4234
+ headers: { "Content-Type": "application/json" },
4235
+ body: JSON.stringify({ refresh_token: tokens.refreshToken })
4236
+ });
4237
+ if (!response.ok) {
4238
+ await clearAuthTokens();
4239
+ return null;
4240
+ }
4241
+ const data = await response.json();
4242
+ const body = data.statusCode ? data.body : data;
4243
+ const refreshed = {
4244
+ ...tokens,
4245
+ accessToken: body.AccessToken || tokens.accessToken,
4246
+ idToken: body.IdToken || tokens.idToken,
4247
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
4248
+ };
4249
+ await saveAuthTokens(refreshed);
4250
+ return refreshed;
4251
+ } catch {
4252
+ await clearAuthTokens();
4253
+ return null;
4254
+ }
4255
+ }
4256
+ async function authenticatedFetch(url, init) {
4257
+ const tokens = await getValidTokens();
4258
+ if (!tokens) {
4259
+ throw new Error("Not logged in. Run: onexthm login");
4260
+ }
4261
+ const headers = new Headers(init?.headers);
4262
+ headers.set("Authorization", `Bearer ${tokens.idToken}`);
4263
+ headers.set("Content-Type", "application/json");
4264
+ return fetch(url, { ...init, headers });
4265
+ }
4266
+ function getMachineKey() {
4267
+ let seed;
4268
+ if (process.platform === "darwin") {
4269
+ seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
4270
+ } else if (process.platform === "linux") {
4271
+ try {
4272
+ seed = `onexthm:${fs.readFileSync("/etc/machine-id", "utf-8").trim()}`;
4273
+ } catch {
4274
+ seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
4275
+ }
4276
+ } else {
4277
+ seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
4278
+ }
4279
+ return crypto2.createHash("sha256").update(seed).digest();
4280
+ }
4281
+ function encrypt(text, key) {
4282
+ const iv = crypto2.randomBytes(16);
4283
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
4284
+ let encrypted = cipher.update(text, "utf-8", "hex");
4285
+ encrypted += cipher.final("hex");
4286
+ const tag = cipher.getAuthTag();
4287
+ return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
4288
+ }
4289
+ function decrypt(text, key) {
4290
+ const [ivHex, tagHex, encrypted] = text.split(":");
4291
+ const iv = Buffer.from(ivHex, "hex");
4292
+ const tag = Buffer.from(tagHex, "hex");
4293
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
4294
+ decipher.setAuthTag(tag);
4295
+ let decrypted = decipher.update(encrypted, "hex", "utf-8");
4296
+ decrypted += decipher.final("utf-8");
4297
+ return decrypted;
4298
+ }
4299
+ function parseJwtClaims(idToken) {
4300
+ try {
4301
+ const payload = idToken.split(".")[1];
4302
+ const decoded = Buffer.from(payload, "base64url").toString("utf-8");
4303
+ return JSON.parse(decoded);
4304
+ } catch {
4305
+ return {};
4306
+ }
4307
+ }
4308
+
4309
+ // src/commands/login.ts
4310
+ async function loginCommand() {
4311
+ logger.header("OneX Theme Developer Login");
4312
+ const existing = loadAuthTokens();
4313
+ if (existing) {
4314
+ logger.info(`Already logged in as: ${existing.user.email}`);
4315
+ const { relogin } = await inquirer.prompt([
4316
+ {
4317
+ type: "confirm",
4318
+ name: "relogin",
4319
+ message: "Re-login with different account?",
4320
+ default: false
4321
+ }
4322
+ ]);
4323
+ if (!relogin) return;
4324
+ }
4325
+ const { email, password } = await inquirer.prompt([
4326
+ {
4327
+ type: "input",
4328
+ name: "email",
4329
+ message: "Email:",
4330
+ validate: (input) => input.includes("@") ? true : "Enter a valid email"
4331
+ },
4332
+ {
4333
+ type: "password",
4334
+ name: "password",
4335
+ message: "Password:",
4336
+ validate: (input) => input.length >= 6 ? true : "Password too short"
4337
+ }
4338
+ ]);
4339
+ logger.startSpinner("Logging in...");
4340
+ try {
4341
+ const apiUrl = getApiUrl();
4342
+ const response = await fetch(`${apiUrl}/auth/login`, {
4343
+ method: "POST",
4344
+ headers: { "Content-Type": "application/json" },
4345
+ body: JSON.stringify({ username: email, password })
4346
+ });
4347
+ const raw = await response.json();
4348
+ const data = raw.statusCode ? raw.body : raw;
4349
+ if (!response.ok || data.error) {
4350
+ logger.stopSpinner(false, "Login failed");
4351
+ logger.error(data.message || data.error || "Invalid credentials");
4352
+ process.exit(1);
4353
+ }
4354
+ const idToken = data.IdToken;
4355
+ const accessToken = data.AccessToken;
4356
+ const refreshToken = data.RefreshToken;
4357
+ const expiresIn = data.ExpiresIn || 3600;
4358
+ if (!idToken) {
4359
+ logger.stopSpinner(false, "Login failed");
4360
+ logger.error("No token received from server");
4361
+ process.exit(1);
4362
+ }
4363
+ const claims = parseJwtClaims(idToken);
4364
+ const tokens = {
4365
+ accessToken,
4366
+ idToken,
4367
+ refreshToken,
4368
+ expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
4369
+ user: {
4370
+ email: claims.email || email,
4371
+ name: claims.name,
4372
+ companyId: claims["custom:company_id"],
4373
+ userId: claims.sub
4374
+ }
4375
+ };
4376
+ await saveAuthTokens(tokens);
4377
+ logger.stopSpinner(true, "Logged in!");
4378
+ logger.newLine();
4379
+ logger.info(` Email: ${tokens.user.email}`);
4380
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4381
+ if (tokens.user.companyId)
4382
+ logger.info(` Company: ${tokens.user.companyId}`);
4383
+ logger.newLine();
4384
+ logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
4385
+ } catch (error) {
4386
+ logger.stopSpinner(false, "Login failed");
4387
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4388
+ process.exit(1);
4389
+ }
4390
+ }
4391
+
4392
+ // src/commands/logout.ts
4393
+ init_logger();
4394
+ async function logoutCommand() {
4395
+ const tokens = loadAuthTokens();
4396
+ if (!tokens) {
4397
+ logger.info("Not logged in.");
4398
+ return;
4399
+ }
4400
+ await clearAuthTokens();
4401
+ logger.success(`Logged out (was: ${tokens.user.email})`);
4402
+ }
4403
+
4404
+ // src/commands/whoami.ts
4405
+ init_logger();
4406
+ async function whoamiCommand() {
4407
+ const tokens = loadAuthTokens();
4408
+ if (!tokens) {
4409
+ logger.error("Not logged in. Run: onexthm login");
4410
+ process.exit(1);
4411
+ }
4412
+ const expired = isTokenExpired(tokens);
4413
+ logger.header("OneX Theme Developer");
4414
+ logger.info(` Email: ${tokens.user.email}`);
4415
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4416
+ if (tokens.user.companyId)
4417
+ logger.info(` Company: ${tokens.user.companyId}`);
4418
+ logger.info(
4419
+ ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4420
+ );
4421
+ }
4422
+
4423
+ // src/commands/publish.ts
4424
+ init_logger();
4425
+ async function publishCommand(options) {
4426
+ logger.header("OneX Theme Publish");
4427
+ const tokens = await getValidTokens();
4428
+ if (!tokens) {
4429
+ logger.error("Not logged in. Run: onexthm login");
4430
+ process.exit(1);
4431
+ }
4432
+ logger.info(`Logged in as: ${tokens.user.email}`);
4433
+ let themePath;
4434
+ if (options.theme) {
4435
+ themePath = path8.resolve(options.theme);
4436
+ } else {
4437
+ const isThemeDir = [
4438
+ "theme.config.ts",
4439
+ "bundle-entry.ts",
4440
+ "manifest.ts"
4441
+ ].some((f) => fs.existsSync(path8.join(process.cwd(), f)));
4442
+ if (isThemeDir) {
4443
+ themePath = process.cwd();
4444
+ } else {
4445
+ logger.error(
4446
+ "Not in a theme directory. Run from theme root or use --theme flag."
4447
+ );
4448
+ process.exit(1);
4449
+ }
4450
+ }
4451
+ const pkgPath = path8.join(themePath, "package.json");
4452
+ if (!fs.existsSync(pkgPath)) {
4453
+ logger.error("No package.json found in theme directory");
4454
+ process.exit(1);
4455
+ }
4456
+ const pkg = fs.readJsonSync(pkgPath);
4457
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path8.basename(themePath);
4458
+ const version2 = pkg.version || "1.0.0";
4459
+ logger.newLine();
4460
+ logger.info(`Theme: ${themeId}`);
4461
+ logger.info(`Version: ${version2}`);
4462
+ logger.newLine();
4463
+ const apiUrl = getApiUrl();
4464
+ logger.startSpinner("Registering theme...");
4465
+ try {
4466
+ const regResponse = await authenticatedFetch(
4467
+ `${apiUrl}/website-api/themes/register`,
4468
+ {
4469
+ method: "POST",
4470
+ body: JSON.stringify({
4471
+ themeId,
4472
+ name: pkg.displayName || themeId,
4473
+ description: pkg.description || "",
4474
+ email: tokens.user.email
4475
+ })
4476
+ }
4477
+ );
4478
+ const regData = await regResponse.json();
4479
+ const regBody = regData.statusCode ? regData.body : regData;
4480
+ if (!regResponse.ok && regBody.error && !regBody.error.includes("already registered")) {
4481
+ logger.stopSpinner(false, "Registration failed");
4482
+ logger.error(regBody.error);
4483
+ process.exit(1);
4484
+ }
4485
+ logger.stopSpinner(true, regBody.message || "Theme registered");
4486
+ } catch (error) {
4487
+ logger.stopSpinner(false, "Registration failed");
4488
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4489
+ process.exit(1);
4490
+ }
4491
+ logger.startSpinner("Building theme...");
4492
+ try {
4493
+ const { execSync: execSync3 } = await import('child_process');
4494
+ execSync3(
4495
+ "npx tsup bundle-entry.ts --format esm --target es2020 --outDir dist",
4496
+ {
4497
+ cwd: themePath,
4498
+ stdio: "ignore"
4499
+ }
4500
+ );
4501
+ logger.stopSpinner(true, "Theme compiled");
4502
+ } catch {
4503
+ logger.stopSpinner(false, "Build failed");
4504
+ logger.error("Run 'onexthm build' to see build errors");
4505
+ process.exit(1);
4506
+ }
4507
+ logger.startSpinner("Getting upload URL...");
4508
+ let bundleUploadUrl;
4509
+ let sourceUploadUrl;
4510
+ try {
4511
+ const pubResponse = await authenticatedFetch(
4512
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`,
4513
+ {
4514
+ method: "POST",
4515
+ body: JSON.stringify({ version: version2 })
4516
+ }
4517
+ );
4518
+ const pubData = await pubResponse.json();
4519
+ const pubBody = pubData.statusCode ? pubData.body : pubData;
4520
+ if (!pubResponse.ok || !pubBody.bundleUploadUrl) {
4521
+ logger.stopSpinner(false, "Failed to get upload URL");
4522
+ logger.error(pubBody.error || "Server error");
4523
+ process.exit(1);
4524
+ }
4525
+ bundleUploadUrl = pubBody.bundleUploadUrl;
4526
+ sourceUploadUrl = pubBody.sourceUploadUrl;
4527
+ logger.stopSpinner(true, "Upload URL obtained");
4528
+ } catch (error) {
4529
+ logger.stopSpinner(false, "Failed");
4530
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4531
+ process.exit(1);
4532
+ }
4533
+ logger.startSpinner("Uploading bundle...");
4534
+ try {
4535
+ const archiver3 = await import('archiver');
4536
+ const { createWriteStream } = await import('fs');
4537
+ const distDir = path8.join(themePath, "dist");
4538
+ if (!fs.existsSync(distDir)) {
4539
+ logger.stopSpinner(false, "No dist/ directory");
4540
+ logger.error("Build the theme first: onexthm build");
4541
+ process.exit(1);
4542
+ }
4543
+ const bundleZipPath = path8.join(themePath, "dist", "bundle.zip");
4544
+ await createZip(distDir, bundleZipPath, ["bundle.zip"]);
4545
+ const bundleBuffer = fs.readFileSync(bundleZipPath);
4546
+ const bundleRes = await fetch(bundleUploadUrl, {
4547
+ method: "PUT",
4548
+ headers: { "Content-Type": "application/zip" },
4549
+ body: bundleBuffer
4550
+ });
4551
+ if (!bundleRes.ok) {
4552
+ throw new Error(`Upload failed: ${bundleRes.status}`);
4553
+ }
4554
+ const sizeMB = (bundleBuffer.length / 1024 / 1024).toFixed(2);
4555
+ logger.stopSpinner(true, `Bundle uploaded (${sizeMB} MB)`);
4556
+ } catch (error) {
4557
+ logger.stopSpinner(false, "Upload failed");
4558
+ logger.error(error instanceof Error ? error.message : "Upload error");
4559
+ process.exit(1);
4560
+ }
4561
+ logger.startSpinner("Uploading source...");
4562
+ try {
4563
+ const sourceZipPath = path8.join(themePath, "dist", "source.zip");
4564
+ await createZip(themePath, sourceZipPath, [
4565
+ "node_modules",
4566
+ "dist",
4567
+ ".git",
4568
+ ".env",
4569
+ ".env.local"
4570
+ ]);
4571
+ const sourceBuffer = fs.readFileSync(sourceZipPath);
4572
+ const sourceRes = await fetch(sourceUploadUrl, {
4573
+ method: "PUT",
4574
+ headers: { "Content-Type": "application/zip" },
4575
+ body: sourceBuffer
4576
+ });
4577
+ if (!sourceRes.ok) {
4578
+ throw new Error(`Source upload failed: ${sourceRes.status}`);
4579
+ }
4580
+ const sizeMB = (sourceBuffer.length / 1024 / 1024).toFixed(2);
4581
+ logger.stopSpinner(true, `Source uploaded (${sizeMB} MB)`);
4582
+ } catch (error) {
4583
+ logger.stopSpinner(false, "Source upload failed");
4584
+ logger.info("Source upload skipped (bundle was uploaded successfully)");
4585
+ }
4586
+ logger.startSpinner("Scanning and publishing...");
4587
+ try {
4588
+ const confirmResponse = await authenticatedFetch(
4589
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
4590
+ { method: "POST" }
4591
+ );
4592
+ const confirmData = await confirmResponse.json();
4593
+ const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
4594
+ if (!confirmResponse.ok || !confirmBody.success) {
4595
+ logger.stopSpinner(false, "Publishing failed");
4596
+ if (confirmBody.violations) {
4597
+ logger.error("Theme rejected \u2014 security violations found:");
4598
+ for (const v of confirmBody.violations) {
4599
+ logger.log(` \u274C ${v.file}: ${v.violation}`);
4600
+ }
4601
+ } else {
4602
+ logger.error(confirmBody.error || "Unknown error");
4603
+ }
4604
+ if (confirmBody.warnings?.length) {
4605
+ logger.newLine();
4606
+ logger.info("Warnings:");
4607
+ for (const w of confirmBody.warnings) {
4608
+ logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
4609
+ }
4610
+ }
4611
+ process.exit(1);
4612
+ }
4613
+ logger.stopSpinner(true, confirmBody.message || "Published!");
4614
+ if (confirmBody.warnings?.length) {
4615
+ logger.newLine();
4616
+ logger.info("Warnings (non-blocking):");
4617
+ for (const w of confirmBody.warnings) {
4618
+ logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
4619
+ }
4620
+ }
4621
+ } catch (error) {
4622
+ logger.stopSpinner(false, "Publishing failed");
4623
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4624
+ process.exit(1);
4625
+ }
4626
+ logger.newLine();
4627
+ logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
4628
+ }
4629
+ async function createZip(sourceDir, outputPath, exclude) {
4630
+ const archiver3 = (await import('archiver')).default;
4631
+ const { createWriteStream } = await import('fs');
4632
+ return new Promise((resolve, reject) => {
4633
+ const output = createWriteStream(outputPath);
4634
+ const archive = archiver3("zip", { zlib: { level: 9 } });
4635
+ output.on("close", resolve);
4636
+ archive.on("error", reject);
4637
+ archive.pipe(output);
4638
+ archive.glob("**/*", {
4639
+ cwd: sourceDir,
4640
+ ignore: exclude.map((e) => `${e}/**`),
4641
+ dot: false
4642
+ });
4643
+ archive.finalize();
4644
+ });
4645
+ }
4646
+
4415
4647
  // src/cli.ts
4416
4648
  try {
4417
4649
  const projectRoot = getProjectRoot();
@@ -4423,7 +4655,7 @@ try {
4423
4655
  } catch {
4424
4656
  }
4425
4657
  dotenv.config({
4426
- path: path8.join(os.homedir(), ".onexthm", ".env"),
4658
+ path: path8.join(os3.homedir(), ".onexthm", ".env"),
4427
4659
  quiet: true
4428
4660
  });
4429
4661
  var require2 = createRequire(import.meta.url);
@@ -4480,6 +4712,10 @@ program.command("clone").description("Clone theme source code from S3").argument
4480
4712
  "staging"
4481
4713
  ).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
4482
4714
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
4715
+ program.command("login").description("Login to OneX platform").action(loginCommand);
4716
+ program.command("logout").description("Logout from OneX platform").action(logoutCommand);
4717
+ program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
4718
+ program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").action(publishCommand);
4483
4719
  program.configureOutput({
4484
4720
  writeErr: (str) => process.stderr.write(chalk4.red(str))
4485
4721
  });