@onexapis/cli 1.1.17 → 1.1.19

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 +621 -294
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cli.mjs +618 -291
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/index.js +73 -278
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +73 -278
  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 +34 -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
@@ -2768,6 +2545,79 @@ async function validateCommand(options) {
2768
2545
  }
2769
2546
  }
2770
2547
  }
2548
+ if (fs.existsSync(sectionsDir)) {
2549
+ const sections = fs.readdirSync(sectionsDir).filter(
2550
+ (name) => fs.statSync(path8.join(sectionsDir, name)).isDirectory()
2551
+ );
2552
+ for (const sectionName of sections) {
2553
+ const sectionPath = path8.join(sectionsDir, sectionName);
2554
+ const tsxFiles = fs.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
2555
+ for (const tsxFile of tsxFiles) {
2556
+ const filePath = path8.join(sectionPath, tsxFile);
2557
+ const content = fs.readFileSync(filePath, "utf-8");
2558
+ const relPath = `sections/${sectionName}/${tsxFile}`;
2559
+ if (!content.includes('"use client"') && !content.includes("'use client'")) {
2560
+ issues.push({
2561
+ type: "error",
2562
+ file: relPath,
2563
+ message: 'Missing "use client" directive at top of file'
2564
+ });
2565
+ }
2566
+ if (!content.includes("ComponentRenderer") && !content.includes("BlockRenderer")) {
2567
+ issues.push({
2568
+ type: "warning",
2569
+ file: relPath,
2570
+ message: "No ComponentRenderer or BlockRenderer found \u2014 sections should use core renderers for content"
2571
+ });
2572
+ }
2573
+ if (!content.includes("data-section-id")) {
2574
+ issues.push({
2575
+ type: "error",
2576
+ file: relPath,
2577
+ message: "Missing data-section-id attribute \u2014 editor cannot select this section"
2578
+ });
2579
+ }
2580
+ if (/\beval\s*\(/.test(content)) {
2581
+ issues.push({
2582
+ type: "error",
2583
+ file: relPath,
2584
+ message: "eval() detected \u2014 arbitrary code execution risk"
2585
+ });
2586
+ }
2587
+ if (content.includes("document.cookie")) {
2588
+ issues.push({
2589
+ type: "error",
2590
+ file: relPath,
2591
+ message: "document.cookie access \u2014 session hijacking risk"
2592
+ });
2593
+ }
2594
+ if (/\brequire\s*\(/.test(content)) {
2595
+ issues.push({
2596
+ type: "warning",
2597
+ file: relPath,
2598
+ message: "require() detected \u2014 themes should use ES module imports"
2599
+ });
2600
+ }
2601
+ }
2602
+ }
2603
+ }
2604
+ const registryPath = path8.join(themePath, "sections-registry.ts");
2605
+ const bundleEntryPath = path8.join(themePath, "bundle-entry.ts");
2606
+ const registryContent = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, "utf-8") : fs.existsSync(bundleEntryPath) ? fs.readFileSync(bundleEntryPath, "utf-8") : "";
2607
+ if (fs.existsSync(sectionsDir) && registryContent) {
2608
+ const sections = fs.readdirSync(sectionsDir).filter(
2609
+ (name) => fs.statSync(path8.join(sectionsDir, name)).isDirectory()
2610
+ );
2611
+ for (const sectionName of sections) {
2612
+ if (!registryContent.includes(`sections/${sectionName}`) && !registryContent.includes(`"${sectionName}"`)) {
2613
+ issues.push({
2614
+ type: "warning",
2615
+ file: `sections/${sectionName}/`,
2616
+ message: "Section not found in sections-registry.ts or bundle-entry.ts \u2014 may not be included in build"
2617
+ });
2618
+ }
2619
+ }
2620
+ }
2771
2621
  logger.stopSpinner(true, "Validation complete");
2772
2622
  const errors = issues.filter((i) => i.type === "error");
2773
2623
  const warnings = issues.filter((i) => i.type === "warning");
@@ -2880,7 +2730,7 @@ async function buildCommand(options) {
2880
2730
  logger.stopSpinner(true, "Lint passed");
2881
2731
  const pkgJson = fs.readJsonSync(packageJsonPath);
2882
2732
  const buildScript = pkgJson.scripts?.build || "";
2883
- const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex-cli build");
2733
+ const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex build") || buildScript.includes("onex-cli build");
2884
2734
  logger.startSpinner(
2885
2735
  options.watch ? "Building (watch mode)..." : "Building..."
2886
2736
  );
@@ -2914,18 +2764,36 @@ function runCommand(command, args, cwd) {
2914
2764
  return new Promise((resolve) => {
2915
2765
  const proc = spawn(command, args, {
2916
2766
  cwd,
2917
- stdio: "pipe",
2767
+ stdio: ["pipe", "pipe", "pipe"],
2918
2768
  shell: true
2919
2769
  });
2920
- let hasError = false;
2770
+ let stdout = "";
2771
+ let stderr = "";
2772
+ proc.stdout.on("data", (data) => {
2773
+ stdout += data.toString();
2774
+ });
2921
2775
  proc.stderr.on("data", (data) => {
2922
- const message = data.toString();
2923
- if (message.includes("error") || message.includes("Error") || message.includes("ERROR")) {
2924
- hasError = true;
2925
- }
2776
+ stderr += data.toString();
2926
2777
  });
2927
2778
  proc.on("close", (code) => {
2928
- resolve(code === 0 && !hasError);
2779
+ if (code !== 0) {
2780
+ const output = (stderr + stdout).trim();
2781
+ if (output) {
2782
+ logger.newLine();
2783
+ for (const line of output.split("\n")) {
2784
+ const t = line.trim();
2785
+ if (!t) continue;
2786
+ if (t.includes("error") || t.includes("Error") || t.includes("TS")) {
2787
+ logger.log(` \u274C ${t}`);
2788
+ } else if (t.includes("warning") || t.includes("Warning")) {
2789
+ logger.log(` \u26A0\uFE0F ${t}`);
2790
+ } else {
2791
+ logger.log(` ${t}`);
2792
+ }
2793
+ }
2794
+ }
2795
+ }
2796
+ resolve(code === 0);
2929
2797
  });
2930
2798
  proc.on("error", () => {
2931
2799
  resolve(false);
@@ -3139,7 +3007,7 @@ async function deployCommand(options) {
3139
3007
  if (options.environment) {
3140
3008
  formData.append("environment", options.environment);
3141
3009
  }
3142
- const response = await fetch(uploadEndpoint, {
3010
+ const response = await fetch2(uploadEndpoint, {
3143
3011
  method: "POST",
3144
3012
  body: formData,
3145
3013
  headers: formData.getHeaders()
@@ -3220,7 +3088,7 @@ function getBucketName(env) {
3220
3088
  return process.env.BUCKET_NAME;
3221
3089
  }
3222
3090
  const environment = env || process.env.ENVIRONMENT || "staging";
3223
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3091
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3224
3092
  }
3225
3093
  async function findCompiledThemeDir(themeId, version2) {
3226
3094
  const searchPaths = [path8.resolve(process.cwd(), "dist")];
@@ -3336,7 +3204,7 @@ async function uploadCommand(options) {
3336
3204
  }
3337
3205
  spinner.succeed(`Found compiled theme at: ${compiledDir}`);
3338
3206
  spinner.start("Creating bundle.zip...");
3339
- const tmpDir = os.tmpdir();
3207
+ const tmpDir = os3.tmpdir();
3340
3208
  const bundleZipPath = path8.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
3341
3209
  await createZipFromDir(compiledDir, bundleZipPath);
3342
3210
  const bundleZipBuffer = await fs.readFile(bundleZipPath);
@@ -3489,7 +3357,7 @@ function getBucketName2(env) {
3489
3357
  return process.env.BUCKET_NAME;
3490
3358
  }
3491
3359
  const environment = env || process.env.ENVIRONMENT || "staging";
3492
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3360
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3493
3361
  }
3494
3362
  async function streamToString(stream) {
3495
3363
  const chunks = [];
@@ -3690,7 +3558,7 @@ function getBucketName3(env) {
3690
3558
  return process.env.BUCKET_NAME;
3691
3559
  }
3692
3560
  const environment = env || process.env.ENVIRONMENT || "staging";
3693
- return environment === "production" ? "onex-themes-prod" : "onex-themes-staging";
3561
+ return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3694
3562
  }
3695
3563
  async function streamToString2(stream) {
3696
3564
  const chunks = [];
@@ -3734,8 +3602,8 @@ function runInstall(cwd) {
3734
3602
  });
3735
3603
  }
3736
3604
  async function promptThemeName(originalName) {
3737
- const { default: inquirer6 } = await import('inquirer');
3738
- const { themeName } = await inquirer6.prompt([
3605
+ const { default: inquirer7 } = await import('inquirer');
3606
+ const { themeName } = await inquirer7.prompt([
3739
3607
  {
3740
3608
  type: "input",
3741
3609
  name: "themeName",
@@ -3888,7 +3756,7 @@ async function cloneCommand(themeName, options) {
3888
3756
  [
3889
3757
  "# API Configuration (enables real data in preview)",
3890
3758
  "# Get your Company ID from the OneX dashboard",
3891
- "NEXT_PUBLIC_API_URL=https://api-dev.onexeos.com",
3759
+ "NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
3892
3760
  "NEXT_PUBLIC_COMPANY_ID=",
3893
3761
  ""
3894
3762
  ].join("\n")
@@ -4236,7 +4104,7 @@ async function devCommand(options) {
4236
4104
  logger.info(`File changed: ${filePath}`);
4237
4105
  try {
4238
4106
  await context2.rebuild();
4239
- await generateManifest2(themeName, themePath, outputDir);
4107
+ await generateManifest(themeName, themePath, outputDir);
4240
4108
  server.broadcast({ type: "reload", timestamp: Date.now() });
4241
4109
  logger.success("Rebuilt successfully");
4242
4110
  } catch (error) {
@@ -4271,7 +4139,7 @@ async function devCommand(options) {
4271
4139
 
4272
4140
  // src/commands/config.ts
4273
4141
  init_logger();
4274
- var CONFIG_DIR = path8.join(os.homedir(), ".onexthm");
4142
+ var CONFIG_DIR = path8.join(os3.homedir(), ".onexthm");
4275
4143
  var CONFIG_FILE = path8.join(CONFIG_DIR, ".env");
4276
4144
  var CONFIG_ENTRIES = [
4277
4145
  {
@@ -4301,7 +4169,7 @@ var CONFIG_ENTRIES = [
4301
4169
  key: "NEXT_PUBLIC_API_URL",
4302
4170
  label: "API URL",
4303
4171
  required: false,
4304
- defaultValue: "https://api-dev.onexeos.com"
4172
+ defaultValue: "https://platform-dev.onexeos.com"
4305
4173
  },
4306
4174
  {
4307
4175
  key: "NEXT_PUBLIC_COMPANY_ID",
@@ -4412,6 +4280,461 @@ async function configCommand() {
4412
4280
  );
4413
4281
  }
4414
4282
 
4283
+ // src/commands/login.ts
4284
+ init_logger();
4285
+ var AUTH_DIR = path8.join(os3.homedir(), ".onexthm");
4286
+ var AUTH_FILE = path8.join(AUTH_DIR, "auth.json");
4287
+ function getApiUrl() {
4288
+ return process.env.NEXT_PUBLIC_API_URL || process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
4289
+ }
4290
+ async function saveAuthTokens(tokens) {
4291
+ await fs.ensureDir(AUTH_DIR);
4292
+ const key = getMachineKey();
4293
+ const data = JSON.stringify(tokens);
4294
+ const encrypted = encrypt(data, key);
4295
+ await fs.writeFile(AUTH_FILE, encrypted, "utf-8");
4296
+ }
4297
+ function loadAuthTokens() {
4298
+ try {
4299
+ if (!fs.existsSync(AUTH_FILE)) return null;
4300
+ const encrypted = fs.readFileSync(AUTH_FILE, "utf-8");
4301
+ const key = getMachineKey();
4302
+ const data = decrypt(encrypted, key);
4303
+ return JSON.parse(data);
4304
+ } catch {
4305
+ return null;
4306
+ }
4307
+ }
4308
+ async function clearAuthTokens() {
4309
+ try {
4310
+ await fs.remove(AUTH_FILE);
4311
+ } catch {
4312
+ }
4313
+ }
4314
+ function isTokenExpired(tokens) {
4315
+ return Date.now() / 1e3 > tokens.expiresAt - 60;
4316
+ }
4317
+ async function getValidTokens() {
4318
+ const tokens = loadAuthTokens();
4319
+ if (!tokens) return null;
4320
+ if (!isTokenExpired(tokens)) return tokens;
4321
+ try {
4322
+ const apiUrl = getApiUrl();
4323
+ const response = await fetch(`${apiUrl}/auth/refresh`, {
4324
+ method: "POST",
4325
+ headers: { "Content-Type": "application/json" },
4326
+ body: JSON.stringify({ refresh_token: tokens.refreshToken })
4327
+ });
4328
+ if (!response.ok) {
4329
+ await clearAuthTokens();
4330
+ return null;
4331
+ }
4332
+ const data = await response.json();
4333
+ const body = data.statusCode ? data.body : data;
4334
+ const refreshed = {
4335
+ ...tokens,
4336
+ accessToken: body.AccessToken || tokens.accessToken,
4337
+ idToken: body.IdToken || tokens.idToken,
4338
+ expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
4339
+ };
4340
+ await saveAuthTokens(refreshed);
4341
+ return refreshed;
4342
+ } catch {
4343
+ await clearAuthTokens();
4344
+ return null;
4345
+ }
4346
+ }
4347
+ async function authenticatedFetch(url, init) {
4348
+ const tokens = await getValidTokens();
4349
+ if (!tokens) {
4350
+ throw new Error("Not logged in. Run: onexthm login");
4351
+ }
4352
+ const headers = new Headers(init?.headers);
4353
+ headers.set("Authorization", `Bearer ${tokens.idToken}`);
4354
+ headers.set("Content-Type", "application/json");
4355
+ return fetch(url, { ...init, headers });
4356
+ }
4357
+ function getMachineKey() {
4358
+ let seed;
4359
+ if (process.platform === "darwin") {
4360
+ seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
4361
+ } else if (process.platform === "linux") {
4362
+ try {
4363
+ seed = `onexthm:${fs.readFileSync("/etc/machine-id", "utf-8").trim()}`;
4364
+ } catch {
4365
+ seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
4366
+ }
4367
+ } else {
4368
+ seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
4369
+ }
4370
+ return crypto2.createHash("sha256").update(seed).digest();
4371
+ }
4372
+ function encrypt(text, key) {
4373
+ const iv = crypto2.randomBytes(16);
4374
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
4375
+ let encrypted = cipher.update(text, "utf-8", "hex");
4376
+ encrypted += cipher.final("hex");
4377
+ const tag = cipher.getAuthTag();
4378
+ return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
4379
+ }
4380
+ function decrypt(text, key) {
4381
+ const [ivHex, tagHex, encrypted] = text.split(":");
4382
+ const iv = Buffer.from(ivHex, "hex");
4383
+ const tag = Buffer.from(tagHex, "hex");
4384
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
4385
+ decipher.setAuthTag(tag);
4386
+ let decrypted = decipher.update(encrypted, "hex", "utf-8");
4387
+ decrypted += decipher.final("utf-8");
4388
+ return decrypted;
4389
+ }
4390
+ function parseJwtClaims(idToken) {
4391
+ try {
4392
+ const payload = idToken.split(".")[1];
4393
+ const decoded = Buffer.from(payload, "base64url").toString("utf-8");
4394
+ return JSON.parse(decoded);
4395
+ } catch {
4396
+ return {};
4397
+ }
4398
+ }
4399
+
4400
+ // src/commands/login.ts
4401
+ async function loginCommand() {
4402
+ logger.header("OneX Theme Developer Login");
4403
+ const existing = loadAuthTokens();
4404
+ if (existing) {
4405
+ logger.info(`Already logged in as: ${existing.user.email}`);
4406
+ const { relogin } = await inquirer.prompt([
4407
+ {
4408
+ type: "confirm",
4409
+ name: "relogin",
4410
+ message: "Re-login with different account?",
4411
+ default: false
4412
+ }
4413
+ ]);
4414
+ if (!relogin) return;
4415
+ }
4416
+ const { email, password } = await inquirer.prompt([
4417
+ {
4418
+ type: "input",
4419
+ name: "email",
4420
+ message: "Email:",
4421
+ validate: (input) => input.includes("@") ? true : "Enter a valid email"
4422
+ },
4423
+ {
4424
+ type: "password",
4425
+ name: "password",
4426
+ message: "Password:",
4427
+ validate: (input) => input.length >= 6 ? true : "Password too short"
4428
+ }
4429
+ ]);
4430
+ logger.startSpinner("Logging in...");
4431
+ try {
4432
+ const apiUrl = getApiUrl();
4433
+ const response = await fetch(`${apiUrl}/auth/login`, {
4434
+ method: "POST",
4435
+ headers: { "Content-Type": "application/json" },
4436
+ body: JSON.stringify({ username: email, password })
4437
+ });
4438
+ const raw = await response.json();
4439
+ const data = raw.statusCode ? raw.body : raw;
4440
+ if (!response.ok || data.error) {
4441
+ logger.stopSpinner(false, "Login failed");
4442
+ logger.error(data.message || data.error || "Invalid credentials");
4443
+ process.exit(1);
4444
+ }
4445
+ const idToken = data.IdToken;
4446
+ const accessToken = data.AccessToken;
4447
+ const refreshToken = data.RefreshToken;
4448
+ const expiresIn = data.ExpiresIn || 3600;
4449
+ if (!idToken) {
4450
+ logger.stopSpinner(false, "Login failed");
4451
+ logger.error("No token received from server");
4452
+ process.exit(1);
4453
+ }
4454
+ const claims = parseJwtClaims(idToken);
4455
+ const tokens = {
4456
+ accessToken,
4457
+ idToken,
4458
+ refreshToken,
4459
+ expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
4460
+ user: {
4461
+ email: claims.email || email,
4462
+ name: claims.name,
4463
+ companyId: claims["custom:company_id"],
4464
+ userId: claims.sub
4465
+ }
4466
+ };
4467
+ await saveAuthTokens(tokens);
4468
+ logger.stopSpinner(true, "Logged in!");
4469
+ logger.newLine();
4470
+ logger.info(` Email: ${tokens.user.email}`);
4471
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4472
+ if (tokens.user.companyId)
4473
+ logger.info(` Company: ${tokens.user.companyId}`);
4474
+ logger.newLine();
4475
+ logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
4476
+ } catch (error) {
4477
+ logger.stopSpinner(false, "Login failed");
4478
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4479
+ process.exit(1);
4480
+ }
4481
+ }
4482
+
4483
+ // src/commands/logout.ts
4484
+ init_logger();
4485
+ async function logoutCommand() {
4486
+ const tokens = loadAuthTokens();
4487
+ if (!tokens) {
4488
+ logger.info("Not logged in.");
4489
+ return;
4490
+ }
4491
+ await clearAuthTokens();
4492
+ logger.success(`Logged out (was: ${tokens.user.email})`);
4493
+ }
4494
+
4495
+ // src/commands/whoami.ts
4496
+ init_logger();
4497
+ async function whoamiCommand() {
4498
+ const tokens = loadAuthTokens();
4499
+ if (!tokens) {
4500
+ logger.error("Not logged in. Run: onexthm login");
4501
+ process.exit(1);
4502
+ }
4503
+ const expired = isTokenExpired(tokens);
4504
+ logger.header("OneX Theme Developer");
4505
+ logger.info(` Email: ${tokens.user.email}`);
4506
+ if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
4507
+ if (tokens.user.companyId)
4508
+ logger.info(` Company: ${tokens.user.companyId}`);
4509
+ logger.info(
4510
+ ` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
4511
+ );
4512
+ }
4513
+
4514
+ // src/commands/publish.ts
4515
+ init_logger();
4516
+ async function publishCommand(options) {
4517
+ logger.header("OneX Theme Publish");
4518
+ const tokens = await getValidTokens();
4519
+ if (!tokens) {
4520
+ logger.error("Not logged in. Run: onexthm login");
4521
+ process.exit(1);
4522
+ }
4523
+ logger.info(`Logged in as: ${tokens.user.email}`);
4524
+ let themePath;
4525
+ if (options.theme) {
4526
+ themePath = path8.resolve(options.theme);
4527
+ } else {
4528
+ const isThemeDir = [
4529
+ "theme.config.ts",
4530
+ "bundle-entry.ts",
4531
+ "manifest.ts"
4532
+ ].some((f) => fs.existsSync(path8.join(process.cwd(), f)));
4533
+ if (isThemeDir) {
4534
+ themePath = process.cwd();
4535
+ } else {
4536
+ logger.error(
4537
+ "Not in a theme directory. Run from theme root or use --theme flag."
4538
+ );
4539
+ process.exit(1);
4540
+ }
4541
+ }
4542
+ const pkgPath = path8.join(themePath, "package.json");
4543
+ if (!fs.existsSync(pkgPath)) {
4544
+ logger.error("No package.json found in theme directory");
4545
+ process.exit(1);
4546
+ }
4547
+ const pkg = fs.readJsonSync(pkgPath);
4548
+ const themeId = pkg.name?.replace("@onex-themes/", "") || path8.basename(themePath);
4549
+ const version2 = pkg.version || "1.0.0";
4550
+ logger.newLine();
4551
+ logger.info(`Theme: ${themeId}`);
4552
+ logger.info(`Version: ${version2}`);
4553
+ logger.newLine();
4554
+ const apiUrl = getApiUrl();
4555
+ logger.startSpinner("Registering theme...");
4556
+ try {
4557
+ const regResponse = await authenticatedFetch(
4558
+ `${apiUrl}/website-api/themes/register`,
4559
+ {
4560
+ method: "POST",
4561
+ body: JSON.stringify({
4562
+ themeId,
4563
+ name: pkg.displayName || themeId,
4564
+ description: pkg.description || "",
4565
+ email: tokens.user.email
4566
+ })
4567
+ }
4568
+ );
4569
+ const regData = await regResponse.json();
4570
+ const regBody = regData.statusCode ? regData.body : regData;
4571
+ if (!regResponse.ok && regBody.error && !regBody.error.includes("already registered")) {
4572
+ logger.stopSpinner(false, "Registration failed");
4573
+ logger.error(regBody.error);
4574
+ process.exit(1);
4575
+ }
4576
+ logger.stopSpinner(true, regBody.message || "Theme registered");
4577
+ } catch (error) {
4578
+ logger.stopSpinner(false, "Registration failed");
4579
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4580
+ process.exit(1);
4581
+ }
4582
+ logger.startSpinner("Building theme...");
4583
+ try {
4584
+ const { execSync: execSync3 } = await import('child_process');
4585
+ execSync3(
4586
+ "npx tsup bundle-entry.ts --format esm --target es2020 --outDir dist",
4587
+ {
4588
+ cwd: themePath,
4589
+ stdio: "ignore"
4590
+ }
4591
+ );
4592
+ logger.stopSpinner(true, "Theme compiled");
4593
+ } catch {
4594
+ logger.stopSpinner(false, "Build failed");
4595
+ logger.error("Run 'onexthm build' to see build errors");
4596
+ process.exit(1);
4597
+ }
4598
+ logger.startSpinner("Getting upload URL...");
4599
+ let bundleUploadUrl;
4600
+ let sourceUploadUrl;
4601
+ try {
4602
+ const pubResponse = await authenticatedFetch(
4603
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`,
4604
+ {
4605
+ method: "POST",
4606
+ body: JSON.stringify({ version: version2 })
4607
+ }
4608
+ );
4609
+ const pubData = await pubResponse.json();
4610
+ const pubBody = pubData.statusCode ? pubData.body : pubData;
4611
+ if (!pubResponse.ok || !pubBody.bundleUploadUrl) {
4612
+ logger.stopSpinner(false, "Failed to get upload URL");
4613
+ logger.error(pubBody.error || "Server error");
4614
+ process.exit(1);
4615
+ }
4616
+ bundleUploadUrl = pubBody.bundleUploadUrl;
4617
+ sourceUploadUrl = pubBody.sourceUploadUrl;
4618
+ logger.stopSpinner(true, "Upload URL obtained");
4619
+ } catch (error) {
4620
+ logger.stopSpinner(false, "Failed");
4621
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4622
+ process.exit(1);
4623
+ }
4624
+ logger.startSpinner("Uploading bundle...");
4625
+ try {
4626
+ const archiver3 = await import('archiver');
4627
+ const { createWriteStream } = await import('fs');
4628
+ const distDir = path8.join(themePath, "dist");
4629
+ if (!fs.existsSync(distDir)) {
4630
+ logger.stopSpinner(false, "No dist/ directory");
4631
+ logger.error("Build the theme first: onexthm build");
4632
+ process.exit(1);
4633
+ }
4634
+ const bundleZipPath = path8.join(themePath, "dist", "bundle.zip");
4635
+ await createZip(distDir, bundleZipPath, ["bundle.zip"]);
4636
+ const bundleBuffer = fs.readFileSync(bundleZipPath);
4637
+ const bundleRes = await fetch(bundleUploadUrl, {
4638
+ method: "PUT",
4639
+ headers: { "Content-Type": "application/zip" },
4640
+ body: bundleBuffer
4641
+ });
4642
+ if (!bundleRes.ok) {
4643
+ throw new Error(`Upload failed: ${bundleRes.status}`);
4644
+ }
4645
+ const sizeMB = (bundleBuffer.length / 1024 / 1024).toFixed(2);
4646
+ logger.stopSpinner(true, `Bundle uploaded (${sizeMB} MB)`);
4647
+ } catch (error) {
4648
+ logger.stopSpinner(false, "Upload failed");
4649
+ logger.error(error instanceof Error ? error.message : "Upload error");
4650
+ process.exit(1);
4651
+ }
4652
+ logger.startSpinner("Uploading source...");
4653
+ try {
4654
+ const sourceZipPath = path8.join(themePath, "dist", "source.zip");
4655
+ await createZip(themePath, sourceZipPath, [
4656
+ "node_modules",
4657
+ "dist",
4658
+ ".git",
4659
+ ".env",
4660
+ ".env.local"
4661
+ ]);
4662
+ const sourceBuffer = fs.readFileSync(sourceZipPath);
4663
+ const sourceRes = await fetch(sourceUploadUrl, {
4664
+ method: "PUT",
4665
+ headers: { "Content-Type": "application/zip" },
4666
+ body: sourceBuffer
4667
+ });
4668
+ if (!sourceRes.ok) {
4669
+ throw new Error(`Source upload failed: ${sourceRes.status}`);
4670
+ }
4671
+ const sizeMB = (sourceBuffer.length / 1024 / 1024).toFixed(2);
4672
+ logger.stopSpinner(true, `Source uploaded (${sizeMB} MB)`);
4673
+ } catch (error) {
4674
+ logger.stopSpinner(false, "Source upload failed");
4675
+ logger.info("Source upload skipped (bundle was uploaded successfully)");
4676
+ }
4677
+ logger.startSpinner("Scanning and publishing...");
4678
+ try {
4679
+ const confirmResponse = await authenticatedFetch(
4680
+ `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
4681
+ { method: "POST" }
4682
+ );
4683
+ const confirmData = await confirmResponse.json();
4684
+ const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
4685
+ if (!confirmResponse.ok || !confirmBody.success) {
4686
+ logger.stopSpinner(false, "Publishing failed");
4687
+ if (confirmBody.violations) {
4688
+ logger.error("Theme rejected \u2014 security violations found:");
4689
+ for (const v of confirmBody.violations) {
4690
+ logger.log(` \u274C ${v.file}: ${v.violation}`);
4691
+ }
4692
+ } else {
4693
+ logger.error(confirmBody.error || "Unknown error");
4694
+ }
4695
+ if (confirmBody.warnings?.length) {
4696
+ logger.newLine();
4697
+ logger.info("Warnings:");
4698
+ for (const w of confirmBody.warnings) {
4699
+ logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
4700
+ }
4701
+ }
4702
+ process.exit(1);
4703
+ }
4704
+ logger.stopSpinner(true, confirmBody.message || "Published!");
4705
+ if (confirmBody.warnings?.length) {
4706
+ logger.newLine();
4707
+ logger.info("Warnings (non-blocking):");
4708
+ for (const w of confirmBody.warnings) {
4709
+ logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
4710
+ }
4711
+ }
4712
+ } catch (error) {
4713
+ logger.stopSpinner(false, "Publishing failed");
4714
+ logger.error(error instanceof Error ? error.message : "Connection failed");
4715
+ process.exit(1);
4716
+ }
4717
+ logger.newLine();
4718
+ logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
4719
+ }
4720
+ async function createZip(sourceDir, outputPath, exclude) {
4721
+ const archiver3 = (await import('archiver')).default;
4722
+ const { createWriteStream } = await import('fs');
4723
+ return new Promise((resolve, reject) => {
4724
+ const output = createWriteStream(outputPath);
4725
+ const archive = archiver3("zip", { zlib: { level: 9 } });
4726
+ output.on("close", resolve);
4727
+ archive.on("error", reject);
4728
+ archive.pipe(output);
4729
+ archive.glob("**/*", {
4730
+ cwd: sourceDir,
4731
+ ignore: exclude.map((e) => `${e}/**`),
4732
+ dot: false
4733
+ });
4734
+ archive.finalize();
4735
+ });
4736
+ }
4737
+
4415
4738
  // src/cli.ts
4416
4739
  try {
4417
4740
  const projectRoot = getProjectRoot();
@@ -4423,7 +4746,7 @@ try {
4423
4746
  } catch {
4424
4747
  }
4425
4748
  dotenv.config({
4426
- path: path8.join(os.homedir(), ".onexthm", ".env"),
4749
+ path: path8.join(os3.homedir(), ".onexthm", ".env"),
4427
4750
  quiet: true
4428
4751
  });
4429
4752
  var require2 = createRequire(import.meta.url);
@@ -4480,6 +4803,10 @@ program.command("clone").description("Clone theme source code from S3").argument
4480
4803
  "staging"
4481
4804
  ).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
4482
4805
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
4806
+ program.command("login").description("Login to OneX platform").action(loginCommand);
4807
+ program.command("logout").description("Logout from OneX platform").action(logoutCommand);
4808
+ program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
4809
+ program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").action(publishCommand);
4483
4810
  program.configureOutput({
4484
4811
  writeErr: (str) => process.stderr.write(chalk4.red(str))
4485
4812
  });