@onexapis/cli 1.1.44 → 1.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -18,7 +18,6 @@ import inquirer from 'inquirer';
18
18
  import archiver from 'archiver';
19
19
  import FormData from 'form-data';
20
20
  import fetch2 from 'node-fetch';
21
- import { PutObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
22
21
  import AdmZip from 'adm-zip';
23
22
  import chokidar from 'chokidar';
24
23
  import http from 'http';
@@ -2674,12 +2673,12 @@ async function validateCommand(options) {
2674
2673
  }
2675
2674
  themeToValidate = options.theme;
2676
2675
  } else {
2677
- const isThemeDir = [
2676
+ const isThemeDir2 = [
2678
2677
  "theme.config.ts",
2679
2678
  "bundle-entry.ts",
2680
2679
  "manifest.ts"
2681
2680
  ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
2682
- if (isThemeDir) {
2681
+ if (isThemeDir2) {
2683
2682
  themeToValidate = path9.basename(process.cwd());
2684
2683
  logger.info(`Validating current theme: ${themeToValidate}`);
2685
2684
  } else {
@@ -3071,12 +3070,12 @@ async function buildCommand(options) {
3071
3070
  process.exit(1);
3072
3071
  }
3073
3072
  } else {
3074
- const isThemeDir = [
3073
+ const isThemeDir2 = [
3075
3074
  "theme.config.ts",
3076
3075
  "bundle-entry.ts",
3077
3076
  "manifest.ts"
3078
3077
  ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3079
- if (isThemeDir) {
3078
+ if (isThemeDir2) {
3080
3079
  themePath = process.cwd();
3081
3080
  themeName = path9.basename(themePath);
3082
3081
  logger.info(`Building current theme: ${themeName}`);
@@ -3207,12 +3206,12 @@ async function packageCommand(options) {
3207
3206
  process.exit(1);
3208
3207
  }
3209
3208
  } else {
3210
- const isThemeDir = [
3209
+ const isThemeDir2 = [
3211
3210
  "theme.config.ts",
3212
3211
  "bundle-entry.ts",
3213
3212
  "manifest.ts"
3214
3213
  ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
3215
- if (isThemeDir) {
3214
+ if (isThemeDir2) {
3216
3215
  themePath = process.cwd();
3217
3216
  themeName = path9.basename(themePath);
3218
3217
  logger.info(`Packaging current theme: ${themeName}`);
@@ -3443,343 +3442,94 @@ async function deployCommand(options) {
3443
3442
 
3444
3443
  // src/commands/upload.ts
3445
3444
  init_logger();
3446
- function getS3Client() {
3447
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3448
- if (adapterMode === "vps") {
3449
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3450
- const secure = process.env.MINIO_SECURE === "true";
3451
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3452
- return new S3Client({
3453
- endpoint: endpointUrl,
3454
- region: "us-east-1",
3455
- credentials: {
3456
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3457
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3458
- },
3459
- forcePathStyle: true
3460
- });
3461
- }
3462
- if (adapterMode === "local") {
3463
- return new S3Client({
3464
- endpoint: "http://localhost:4569",
3465
- region: "ap-southeast-1",
3466
- credentials: {
3467
- accessKeyId: "S3RVER",
3468
- secretAccessKey: "S3RVER"
3469
- },
3470
- forcePathStyle: true
3471
- });
3472
- }
3473
- return new S3Client({
3474
- region: process.env.AWS_REGION || "ap-southeast-1"
3475
- });
3476
- }
3477
- function getBucketName(env) {
3478
- if (process.env.BUCKET_NAME) {
3479
- return process.env.BUCKET_NAME;
3480
- }
3481
- const environment = env || process.env.ENVIRONMENT || "staging";
3482
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3483
- }
3484
- async function findCompiledThemeDir(themeId, version2) {
3485
- const searchPaths = [path9.resolve(process.cwd(), "dist")];
3486
- for (const dir of searchPaths) {
3487
- if (await fs.pathExists(dir)) {
3488
- const hasManifest = await fs.pathExists(path9.join(dir, "manifest.json"));
3489
- const hasThemeEntry = await fs.pathExists(path9.join(dir, "bundle-entry.js")) || await fs.pathExists(path9.join(dir, "theme.config.js")) || await fs.pathExists(path9.join(dir, "index.js"));
3490
- if (hasManifest || hasThemeEntry) {
3491
- return dir;
3492
- }
3493
- }
3494
- }
3495
- return null;
3496
- }
3497
- async function readManifest() {
3498
- const manifestTsPath = path9.resolve(process.cwd(), "manifest.ts");
3499
- if (await fs.pathExists(manifestTsPath)) {
3500
- try {
3501
- const module = await import(manifestTsPath);
3502
- return module.default || module;
3503
- } catch (error) {
3504
- logger.warning("Failed to import manifest.ts, trying package.json");
3505
- }
3506
- }
3507
- const packageJsonPath = path9.resolve(process.cwd(), "package.json");
3508
- if (await fs.pathExists(packageJsonPath)) {
3509
- const pkg = await fs.readJson(packageJsonPath);
3510
- return {
3511
- themeId: pkg.name?.replace("@onex-themes/", "") || "unknown",
3512
- version: pkg.version || "1.0.0"
3513
- };
3514
- }
3515
- throw new Error(
3516
- "No manifest.ts or package.json found. Are you in a theme directory?"
3445
+ async function uploadCommand(_options) {
3446
+ logger.header("Upload Theme to S3 \u2014 DEPRECATED");
3447
+ console.log();
3448
+ console.log(
3449
+ chalk4.yellow.bold(
3450
+ "`onexthm upload` is deprecated and no longer functional."
3451
+ )
3517
3452
  );
3518
- }
3519
- async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
3520
- return new Promise((resolve, reject) => {
3521
- const output = fs.createWriteStream(outputPath);
3522
- const archive = archiver("zip", { zlib: { level: 6 } });
3523
- output.on("close", () => resolve());
3524
- archive.on("error", (err) => reject(err));
3525
- archive.pipe(output);
3526
- archive.glob("**/*", {
3527
- cwd: sourceDir,
3528
- dot: true,
3529
- ignore: excludePatterns
3530
- });
3531
- archive.finalize();
3532
- });
3533
- }
3534
- async function findSourceDir(themeId, explicitDir) {
3535
- if (explicitDir) {
3536
- if (await fs.pathExists(explicitDir)) return explicitDir;
3537
- return null;
3538
- }
3539
- const searchPaths = [
3540
- process.cwd(),
3541
- path9.resolve(process.cwd(), `../../themes/${themeId}`),
3542
- path9.resolve(process.cwd(), `../themes/${themeId}`)
3543
- ];
3544
- const markers = ["theme.config.ts", "bundle-entry.ts"];
3545
- for (const dir of searchPaths) {
3546
- for (const marker of markers) {
3547
- if (await fs.pathExists(path9.join(dir, marker))) {
3548
- return dir;
3549
- }
3550
- }
3551
- }
3552
- return null;
3553
- }
3554
- async function updateLatestPointer(s3Client, bucket, themeId, version2) {
3555
- const latestData = {
3556
- version: version2,
3557
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
3558
- };
3559
- await s3Client.send(
3560
- new PutObjectCommand({
3561
- Bucket: bucket,
3562
- Key: `themes/${themeId}/latest.json`,
3563
- Body: JSON.stringify(latestData, null, 2),
3564
- ContentType: "application/json"
3565
- })
3453
+ console.log();
3454
+ console.log(
3455
+ "The platform no longer exposes themes via direct S3 access, so this"
3566
3456
  );
3567
- }
3568
- async function uploadCommand(options) {
3569
- logger.header("Upload Theme to S3");
3570
- const spinner = ora("Preparing theme upload...").start();
3571
- try {
3572
- let themeId;
3573
- let version2;
3574
- if (options.theme) {
3575
- themeId = options.theme;
3576
- version2 = options.version || "1.0.0";
3577
- } else {
3578
- const manifest = await readManifest();
3579
- themeId = manifest.themeId;
3580
- version2 = options.version || manifest.version || "1.0.0";
3581
- }
3582
- spinner.text = `Found theme: ${themeId}@${version2}`;
3583
- const bucket = options.bucket || getBucketName(options.environment);
3584
- const s3Client = getS3Client();
3585
- const compiledDir = await findCompiledThemeDir(themeId, version2);
3586
- if (!compiledDir) {
3587
- spinner.fail(
3588
- chalk4.red(
3589
- `Compiled theme not found for ${themeId}@${version2}. Run 'onexthm build' first.`
3590
- )
3591
- );
3592
- logger.info(chalk4.gray(`Expected location:
3593
- - ./dist/`));
3594
- process.exit(1);
3595
- }
3596
- spinner.succeed(`Found compiled theme at: ${compiledDir}`);
3597
- spinner.start("Creating bundle.zip...");
3598
- const tmpDir = os.tmpdir();
3599
- const bundleZipPath = path9.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
3600
- await createZipFromDir(compiledDir, bundleZipPath);
3601
- const bundleZipBuffer = await fs.readFile(bundleZipPath);
3602
- const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
3603
- spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
3604
- if (options.dryRun) {
3605
- spinner.info(chalk4.yellow("Dry run mode \u2014 no files will be uploaded"));
3606
- console.log();
3607
- console.log(chalk4.gray(` bundle.zip: ${bundleSizeMB} MB`));
3608
- console.log(
3609
- chalk4.cyan(
3610
- ` S3 path: s3://${bucket}/themes/${themeId}/${version2}/bundle.zip`
3611
- )
3612
- );
3613
- if (!options.skipSource) {
3614
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
3615
- if (sourceDir) {
3616
- console.log(chalk4.gray(` source dir: ${sourceDir}`));
3617
- console.log(
3618
- chalk4.cyan(
3619
- ` S3 path: s3://${bucket}/themes/${themeId}/${version2}/source.zip`
3620
- )
3621
- );
3622
- } else {
3623
- console.log(
3624
- chalk4.yellow(" source dir: not found (source.zip will be skipped)")
3625
- );
3626
- }
3627
- }
3628
- console.log();
3629
- await fs.remove(bundleZipPath);
3630
- return;
3631
- }
3632
- spinner.start("Uploading bundle.zip to S3...");
3633
- const bundleS3Key = `themes/${themeId}/${version2}/bundle.zip`;
3634
- await s3Client.send(
3635
- new PutObjectCommand({
3636
- Bucket: bucket,
3637
- Key: bundleS3Key,
3638
- Body: bundleZipBuffer,
3639
- ContentType: "application/zip"
3640
- })
3641
- );
3642
- spinner.succeed(
3643
- `Uploaded bundle.zip ${chalk4.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
3644
- );
3645
- await fs.remove(bundleZipPath);
3646
- let sourceUploaded = false;
3647
- if (!options.skipSource) {
3648
- spinner.start("Looking for source directory...");
3649
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
3650
- if (sourceDir) {
3651
- spinner.succeed(`Found source at: ${sourceDir}`);
3652
- spinner.start("Creating source.zip...");
3653
- const sourceZipPath = path9.join(
3654
- tmpDir,
3655
- `${themeId}-${version2}-source.zip`
3656
- );
3657
- await createZipFromDir(sourceDir, sourceZipPath, [
3658
- "node_modules/**",
3659
- "dist/**",
3660
- ".git/**",
3661
- "*.zip",
3662
- ".next/**",
3663
- ".turbo/**"
3664
- ]);
3665
- const sourceZipBuffer = await fs.readFile(sourceZipPath);
3666
- const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
3667
- spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
3668
- spinner.start("Uploading source.zip to S3...");
3669
- const sourceS3Key = `themes/${themeId}/${version2}/source.zip`;
3670
- await s3Client.send(
3671
- new PutObjectCommand({
3672
- Bucket: bucket,
3673
- Key: sourceS3Key,
3674
- Body: sourceZipBuffer,
3675
- ContentType: "application/zip"
3676
- })
3677
- );
3678
- spinner.succeed(
3679
- `Uploaded source.zip ${chalk4.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
3680
- );
3681
- await fs.remove(sourceZipPath);
3682
- sourceUploaded = true;
3683
- } else {
3684
- spinner.warn(
3685
- chalk4.yellow("Source directory not found \u2014 skipping source.zip")
3686
- );
3687
- }
3688
- }
3689
- spinner.start("Updating latest.json pointer...");
3690
- await updateLatestPointer(s3Client, bucket, themeId, version2);
3691
- spinner.succeed("Updated latest.json pointer");
3692
- console.log();
3693
- logger.success(chalk4.green.bold("Theme uploaded successfully!"));
3694
- console.log();
3695
- console.log(
3696
- chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${version2}`)
3697
- );
3698
- console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
3699
- console.log(
3700
- chalk4.cyan(" Files: ") + chalk4.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
3701
- );
3702
- console.log(
3703
- chalk4.cyan(" Path: ") + chalk4.gray(`s3://${bucket}/themes/${themeId}/${version2}/`)
3704
- );
3705
- console.log();
3706
- } catch (error) {
3707
- spinner.fail(chalk4.red(`Upload failed: ${error.message}`));
3708
- logger.error(error.stack || error.message);
3709
- process.exit(1);
3710
- }
3457
+ console.log(
3458
+ "command can no longer reach the bucket. Use `onexthm publish` instead:"
3459
+ );
3460
+ console.log();
3461
+ console.log(chalk4.cyan(" cd themes/your-theme"));
3462
+ console.log(chalk4.cyan(" onexthm login # one-time, refreshes JWT"));
3463
+ console.log(
3464
+ chalk4.cyan(" onexthm publish # builds + uploads + confirms")
3465
+ );
3466
+ console.log();
3467
+ console.log(
3468
+ "`publish` does everything this command did (build, version bump,"
3469
+ );
3470
+ console.log(
3471
+ "bundle + source upload) plus content-hashed asset upload, security"
3472
+ );
3473
+ console.log("scanning, and atomic version registration in one step.");
3474
+ console.log();
3475
+ process.exit(1);
3711
3476
  }
3712
3477
 
3713
3478
  // src/commands/download.ts
3714
3479
  init_logger();
3715
- function getS3Client2() {
3716
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3717
- if (adapterMode === "vps") {
3718
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3719
- const secure = process.env.MINIO_SECURE === "true";
3720
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3721
- return new S3Client({
3722
- endpoint: endpointUrl,
3723
- region: "us-east-1",
3724
- credentials: {
3725
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3726
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3727
- },
3728
- forcePathStyle: true
3729
- });
3480
+ function unwrapEnvelope(raw) {
3481
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3482
+ return raw.body;
3730
3483
  }
3731
- if (adapterMode === "local") {
3732
- return new S3Client({
3733
- endpoint: "http://localhost:4569",
3734
- region: "ap-southeast-1",
3735
- credentials: {
3736
- accessKeyId: "S3RVER",
3737
- secretAccessKey: "S3RVER"
3738
- },
3739
- forcePathStyle: true
3740
- });
3741
- }
3742
- return new S3Client({
3743
- region: process.env.AWS_REGION || "ap-southeast-1"
3744
- });
3484
+ return raw;
3745
3485
  }
3746
- function getBucketName2(env) {
3747
- if (process.env.BUCKET_NAME) {
3748
- return process.env.BUCKET_NAME;
3486
+ async function resolveLatestVersion(apiUrl, themeId) {
3487
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3488
+ let response;
3489
+ try {
3490
+ response = await fetch(url, { cache: "no-store" });
3491
+ } catch (err) {
3492
+ throw new Error(
3493
+ `Network error contacting ${url}: ${err instanceof Error ? err.message : "unknown"}`
3494
+ );
3749
3495
  }
3750
- const environment = env || process.env.ENVIRONMENT || "staging";
3751
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3752
- }
3753
- async function streamToString(stream) {
3754
- const chunks = [];
3755
- for await (const chunk of stream) {
3756
- chunks.push(Buffer.from(chunk));
3496
+ if (!response.ok) {
3497
+ throw new Error(
3498
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3499
+ );
3757
3500
  }
3758
- return Buffer.concat(chunks).toString("utf-8");
3759
- }
3760
- async function streamToBuffer(stream) {
3761
- const chunks = [];
3762
- for await (const chunk of stream) {
3763
- chunks.push(Buffer.from(chunk));
3501
+ const raw = await response.json();
3502
+ const data = unwrapEnvelope(raw);
3503
+ const latest = data?.latest_version;
3504
+ if (typeof latest !== "string" || latest.length === 0) {
3505
+ throw new Error(
3506
+ `Theme "${themeId}" has no published versions yet (no latest_version in response)`
3507
+ );
3764
3508
  }
3765
- return Buffer.concat(chunks);
3509
+ return latest;
3766
3510
  }
3767
- async function resolveLatestVersion(s3Client, bucket, themeId) {
3768
- try {
3769
- const response = await s3Client.send(
3770
- new GetObjectCommand({
3771
- Bucket: bucket,
3772
- Key: `themes/${themeId}/latest.json`
3773
- })
3774
- );
3775
- const body = await streamToString(response.Body);
3776
- const data = JSON.parse(body);
3777
- return data.version;
3778
- } catch (error) {
3511
+ async function downloadBundleZip(apiUrl, themeId, version2) {
3512
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/${encodeURIComponent(version2)}/download`;
3513
+ const response = await fetch(url);
3514
+ if (!response.ok) {
3779
3515
  throw new Error(
3780
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3516
+ `Bundle download failed for "${themeId}@${version2}" (HTTP ${response.status})`
3781
3517
  );
3782
3518
  }
3519
+ const contentType = response.headers.get("content-type") || "";
3520
+ if (contentType.includes("application/json")) {
3521
+ const raw = await response.json();
3522
+ const envelope = raw && typeof raw === "object" && "statusCode" in raw ? raw : { body: raw };
3523
+ const body = envelope.body;
3524
+ if (typeof body !== "string") {
3525
+ throw new Error(
3526
+ "Unexpected /download response shape: expected base64 string in body"
3527
+ );
3528
+ }
3529
+ return Buffer.from(body, "base64");
3530
+ }
3531
+ const arrayBuffer = await response.arrayBuffer();
3532
+ return Buffer.from(arrayBuffer);
3783
3533
  }
3784
3534
  async function createCompatibilityFiles(outputDir, manifest) {
3785
3535
  const entryFile = manifest.output?.entry || "bundle-entry.js";
@@ -3803,47 +3553,58 @@ export * from './bundle-entry.js';
3803
3553
  const pkgJsonPath = path9.join(outputDir, "package.json");
3804
3554
  await fs.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3805
3555
  }
3806
- function showDownloadFailureHelp(themeId, bucket) {
3556
+ function showDownloadFailureHelp(themeId, apiUrl) {
3807
3557
  console.log();
3808
3558
  logger.error(chalk4.red.bold("Theme download failed"));
3809
3559
  console.log();
3810
3560
  console.log(chalk4.yellow("Possible reasons:"));
3811
- console.log(chalk4.gray(" 1. Theme not uploaded to S3 yet"));
3812
- console.log(chalk4.gray(" 2. AWS credentials not configured correctly"));
3813
- console.log(chalk4.gray(" 3. Bucket name or region is incorrect"));
3814
- console.log(chalk4.gray(" 4. bundle.zip is missing or corrupted"));
3561
+ console.log(
3562
+ chalk4.gray(" 1. Theme has not been published yet (run `onexthm publish`)")
3563
+ );
3564
+ console.log(
3565
+ chalk4.gray(
3566
+ " 2. Theme ID is wrong (typo in --theme-id or THEME_ID env var)"
3567
+ )
3568
+ );
3569
+ console.log(
3570
+ chalk4.gray(" 3. API base URL is wrong or the website-api is unreachable")
3571
+ );
3815
3572
  console.log();
3816
3573
  console.log(chalk4.cyan.bold("To fix this:"));
3817
3574
  console.log();
3818
- console.log(chalk4.white("1. Compile and upload the theme:"));
3819
- console.log(chalk4.gray(` cd themes/${themeId}`));
3820
- console.log(chalk4.gray(" pnpm build"));
3821
- console.log(chalk4.gray(" onexthm upload"));
3822
- console.log();
3823
- console.log(chalk4.white("2. Verify AWS credentials are set:"));
3575
+ console.log(chalk4.white("1. Verify the theme is published:"));
3824
3576
  console.log(
3825
- chalk4.gray(" - Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY")
3577
+ chalk4.gray(
3578
+ ` curl -s ${apiUrl}/website-api/themes/${themeId}/versions | jq .latest_version`
3579
+ )
3826
3580
  );
3827
- console.log(chalk4.gray(" - Or use AWS_PROFILE=your-profile"));
3828
- console.log(chalk4.gray(" - Set AWS_REGION (e.g., ap-southeast-1)"));
3829
3581
  console.log();
3830
- console.log(chalk4.white("3. Check bucket configuration:"));
3831
- console.log(chalk4.gray(` Current bucket: ${bucket}`));
3582
+ console.log(chalk4.white("2. Check API URL configuration:"));
3583
+ console.log(chalk4.gray(` Current API URL: ${apiUrl}`));
3832
3584
  console.log(
3833
- chalk4.gray(" Set BUCKET_NAME environment variable if different")
3585
+ chalk4.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3834
3586
  );
3835
3587
  console.log();
3836
- console.log(chalk4.white("4. Verify theme exists in S3:"));
3837
- console.log(chalk4.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
3588
+ console.log(chalk4.white("3. Pin a specific version (CI/production):"));
3589
+ console.log(
3590
+ chalk4.gray(` THEME_VERSION=1.2.3 onexthm download --theme-id ${themeId}`)
3591
+ );
3838
3592
  console.log();
3839
3593
  }
3840
3594
  async function downloadCommand(options) {
3841
- logger.header("Download Theme from S3");
3595
+ logger.header("Download Theme");
3842
3596
  const spinner = ora("Initializing download...").start();
3597
+ if (options.bucket || options.environment) {
3598
+ spinner.stop();
3599
+ logger.warning(
3600
+ "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3601
+ );
3602
+ spinner.start();
3603
+ }
3604
+ const apiUrl = getApiUrl();
3843
3605
  try {
3844
3606
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3845
- const version2 = options.version || process.env.THEME_VERSION || "latest";
3846
- const bucket = options.bucket || getBucketName2(options.environment);
3607
+ const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
3847
3608
  const outputDir = options.output || "./active-theme";
3848
3609
  if (!themeId) {
3849
3610
  spinner.fail(
@@ -3853,12 +3614,10 @@ async function downloadCommand(options) {
3853
3614
  );
3854
3615
  process.exit(1);
3855
3616
  }
3856
- spinner.text = `Downloading ${themeId}@${version2} from ${bucket}...`;
3857
- const s3Client = getS3Client2();
3858
- let resolvedVersion = version2;
3859
- if (version2 === "latest") {
3860
- spinner.text = "Resolving latest version...";
3861
- resolvedVersion = await resolveLatestVersion(s3Client, bucket, themeId);
3617
+ spinner.text = `Resolving ${themeId}@${requestedVersion}...`;
3618
+ let resolvedVersion = requestedVersion;
3619
+ if (requestedVersion === "latest") {
3620
+ resolvedVersion = await resolveLatestVersion(apiUrl, themeId);
3862
3621
  spinner.succeed(
3863
3622
  `Resolved latest version: ${chalk4.cyan(resolvedVersion)}`
3864
3623
  );
@@ -3868,24 +3627,19 @@ async function downloadCommand(options) {
3868
3627
  chalk4.yellow(
3869
3628
  `
3870
3629
  Warning: Resolved "latest" to ${resolvedVersion} in CI environment.
3871
- For production builds, pin to a specific version:
3630
+ For reproducible builds, pin to a specific version:
3872
3631
  THEME_VERSION=${resolvedVersion}
3873
3632
  `
3874
3633
  )
3875
3634
  );
3876
3635
  }
3636
+ } else {
3637
+ spinner.succeed(`Using version: ${chalk4.cyan(resolvedVersion)}`);
3877
3638
  }
3878
3639
  spinner.start(
3879
3640
  `Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
3880
3641
  );
3881
- const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
3882
- const response = await s3Client.send(
3883
- new GetObjectCommand({
3884
- Bucket: bucket,
3885
- Key: s3Key
3886
- })
3887
- );
3888
- const zipBuffer = await streamToBuffer(response.Body);
3642
+ const zipBuffer = await downloadBundleZip(apiUrl, themeId, resolvedVersion);
3889
3643
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
3890
3644
  spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
3891
3645
  spinner.start("Extracting bundle...");
@@ -3904,7 +3658,7 @@ async function downloadCommand(options) {
3904
3658
  console.log(
3905
3659
  chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${resolvedVersion}`)
3906
3660
  );
3907
- console.log(chalk4.cyan(" Bucket: ") + chalk4.white(bucket));
3661
+ console.log(chalk4.cyan(" Source: ") + chalk4.white(apiUrl));
3908
3662
  console.log(chalk4.cyan(" Output: ") + chalk4.white(outputDir));
3909
3663
  console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
3910
3664
  if (manifest.counts) {
@@ -3916,82 +3670,70 @@ async function downloadCommand(options) {
3916
3670
  } catch (error) {
3917
3671
  spinner.fail(chalk4.red("Download failed"));
3918
3672
  logger.error(error.message);
3919
- const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || "unknown";
3920
- const bucket = options.bucket || getBucketName2(options.environment);
3921
- showDownloadFailureHelp(themeId, bucket);
3673
+ const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID || "unknown";
3674
+ showDownloadFailureHelp(themeId, apiUrl);
3922
3675
  process.exit(1);
3923
3676
  }
3924
3677
  }
3925
3678
 
3926
3679
  // src/commands/clone.ts
3927
3680
  init_logger();
3928
- function getS3Client3() {
3929
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3930
- if (adapterMode === "vps") {
3931
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3932
- const secure = process.env.MINIO_SECURE === "true";
3933
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3934
- return new S3Client({
3935
- endpoint: endpointUrl,
3936
- region: "us-east-1",
3937
- credentials: {
3938
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3939
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3940
- },
3941
- forcePathStyle: true
3942
- });
3943
- }
3944
- if (adapterMode === "local") {
3945
- return new S3Client({
3946
- endpoint: "http://localhost:4569",
3947
- region: "ap-southeast-1",
3948
- credentials: {
3949
- accessKeyId: "S3RVER",
3950
- secretAccessKey: "S3RVER"
3951
- },
3952
- forcePathStyle: true
3953
- });
3681
+ function unwrapEnvelope2(raw) {
3682
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3683
+ return raw.body;
3954
3684
  }
3955
- return new S3Client({
3956
- region: process.env.AWS_REGION || "ap-southeast-1"
3957
- });
3685
+ return raw;
3958
3686
  }
3959
- function getBucketName3(env) {
3960
- if (process.env.BUCKET_NAME) {
3961
- return process.env.BUCKET_NAME;
3687
+ async function resolveLatestVersion2(apiUrl, themeId) {
3688
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3689
+ const response = await fetch(url, { cache: "no-store" });
3690
+ if (!response.ok) {
3691
+ throw new Error(
3692
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3693
+ );
3962
3694
  }
3963
- const environment = env || process.env.ENVIRONMENT || "staging";
3964
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3965
- }
3966
- async function streamToString2(stream) {
3967
- const chunks = [];
3968
- for await (const chunk of stream) {
3969
- chunks.push(Buffer.from(chunk));
3695
+ const raw = await response.json();
3696
+ const data = unwrapEnvelope2(raw);
3697
+ const latest = data?.latest_version;
3698
+ if (typeof latest !== "string" || latest.length === 0) {
3699
+ throw new Error(`Theme "${themeId}" has no published versions yet`);
3970
3700
  }
3971
- return Buffer.concat(chunks).toString("utf-8");
3701
+ return latest;
3972
3702
  }
3973
- async function streamToBuffer2(stream) {
3974
- const chunks = [];
3975
- for await (const chunk of stream) {
3976
- chunks.push(Buffer.from(chunk));
3977
- }
3978
- return Buffer.concat(chunks);
3979
- }
3980
- async function resolveLatestVersion2(s3Client, bucket, themeId) {
3981
- try {
3982
- const response = await s3Client.send(
3983
- new GetObjectCommand({
3984
- Bucket: bucket,
3985
- Key: `themes/${themeId}/latest.json`
3986
- })
3703
+ async function fetchSourceZip(apiUrl, themeId, version2) {
3704
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
3705
+ const response = await authenticatedFetch(url, {
3706
+ method: "GET"
3707
+ });
3708
+ if (!response.ok) {
3709
+ if (response.status === 404) {
3710
+ throw new Error(
3711
+ `Source not found for ${themeId}@${version2}. The theme may not have been published with source upload enabled.`
3712
+ );
3713
+ }
3714
+ if (response.status === 401 || response.status === 403) {
3715
+ throw new Error(
3716
+ `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
3717
+ );
3718
+ }
3719
+ throw new Error(
3720
+ `Source URL request failed for "${themeId}@${version2}" (HTTP ${response.status})`
3987
3721
  );
3988
- const body = await streamToString2(response.Body);
3989
- return JSON.parse(body).version;
3990
- } catch (error) {
3722
+ }
3723
+ const raw = await response.json();
3724
+ const data = unwrapEnvelope2(raw);
3725
+ const presignedUrl = data?.download_url;
3726
+ if (typeof presignedUrl !== "string" || presignedUrl.length === 0) {
3727
+ throw new Error("Unexpected /source response shape: missing download_url");
3728
+ }
3729
+ const zipResponse = await fetch(presignedUrl);
3730
+ if (!zipResponse.ok) {
3991
3731
  throw new Error(
3992
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3732
+ `Presigned source download failed (HTTP ${zipResponse.status})`
3993
3733
  );
3994
3734
  }
3735
+ const arrayBuffer = await zipResponse.arrayBuffer();
3736
+ return Buffer.from(arrayBuffer);
3995
3737
  }
3996
3738
  function runInstall(cwd) {
3997
3739
  return new Promise((resolve) => {
@@ -4005,8 +3747,8 @@ function runInstall(cwd) {
4005
3747
  });
4006
3748
  }
4007
3749
  async function promptThemeName(originalName) {
4008
- const { default: inquirer7 } = await import('inquirer');
4009
- const { themeName } = await inquirer7.prompt([
3750
+ const { default: inquirer8 } = await import('inquirer');
3751
+ const { themeName } = await inquirer8.prompt([
4010
3752
  {
4011
3753
  type: "input",
4012
3754
  name: "themeName",
@@ -4090,15 +3832,24 @@ async function renameTheme(themeDir, oldName, newName) {
4090
3832
  }
4091
3833
  async function cloneCommand(themeName, options) {
4092
3834
  logger.header("Clone Theme Source");
3835
+ if (options.bucket || options.environment) {
3836
+ logger.warning(
3837
+ "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
3838
+ );
3839
+ }
3840
+ const tokens = await getValidTokens();
3841
+ if (!tokens) {
3842
+ logger.error("Not logged in. Run: onexthm login");
3843
+ process.exit(1);
3844
+ }
4093
3845
  let newName = options.name;
4094
3846
  if (!newName) {
4095
3847
  newName = await promptThemeName(themeName);
4096
3848
  }
4097
3849
  const spinner = ora("Initializing clone...").start();
4098
3850
  try {
4099
- const bucket = options.bucket || getBucketName3(options.environment);
3851
+ const apiUrl = getApiUrl();
4100
3852
  const outputDir = options.output || path9.resolve(process.cwd(), newName);
4101
- const s3Client = getS3Client3();
4102
3853
  if (await fs.pathExists(outputDir)) {
4103
3854
  spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
4104
3855
  logger.info(
@@ -4111,28 +3862,20 @@ async function cloneCommand(themeName, options) {
4111
3862
  let version2 = options.version || "latest";
4112
3863
  if (version2 === "latest") {
4113
3864
  spinner.text = "Resolving latest version...";
4114
- version2 = await resolveLatestVersion2(s3Client, bucket, themeName);
3865
+ version2 = await resolveLatestVersion2(apiUrl, themeName);
4115
3866
  spinner.succeed(`Resolved latest version: ${chalk4.cyan(version2)}`);
4116
3867
  }
4117
3868
  spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
4118
- const s3Key = `themes/${themeName}/${version2}/source.zip`;
4119
3869
  let zipBuffer;
4120
3870
  try {
4121
- const response = await s3Client.send(
4122
- new GetObjectCommand({
4123
- Bucket: bucket,
4124
- Key: s3Key
4125
- })
4126
- );
4127
- zipBuffer = await streamToBuffer2(response.Body);
3871
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
4128
3872
  } catch (error) {
4129
- spinner.fail(chalk4.red(`Source not found: s3://${bucket}/${s3Key}`));
3873
+ spinner.fail(chalk4.red(error.message));
4130
3874
  console.log();
4131
3875
  console.log(
4132
- chalk4.yellow("The theme source may not have been uploaded yet.")
4133
- );
4134
- console.log(
4135
- chalk4.gray(`Upload source with: onexthm upload --theme ${themeName}`)
3876
+ chalk4.gray(
3877
+ `Verify the theme is published: curl -s ${apiUrl}/website-api/themes/${themeName}/versions`
3878
+ )
4136
3879
  );
4137
3880
  console.log();
4138
3881
  process.exit(1);
@@ -4315,10 +4058,7 @@ function createDevServer(options) {
4315
4058
  return;
4316
4059
  }
4317
4060
  if (segments.length > 1) {
4318
- const fallbackPath = path9.join(
4319
- assetsBase,
4320
- segments.slice(1).join("/")
4321
- );
4061
+ const fallbackPath = path9.join(assetsBase, segments.slice(1).join("/"));
4322
4062
  if (fallbackPath.startsWith(assetsBase) && fs3.existsSync(fallbackPath)) {
4323
4063
  serveFile(res, fallbackPath);
4324
4064
  return;
@@ -4485,12 +4225,12 @@ async function devCommand(options) {
4485
4225
  process.exit(1);
4486
4226
  }
4487
4227
  } else {
4488
- const isThemeDir = [
4228
+ const isThemeDir2 = [
4489
4229
  "theme.config.ts",
4490
4230
  "bundle-entry.ts",
4491
4231
  "manifest.ts"
4492
4232
  ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
4493
- if (isThemeDir) {
4233
+ if (isThemeDir2) {
4494
4234
  themePath = process.cwd();
4495
4235
  themeName = path9.basename(themePath);
4496
4236
  } else {
@@ -4918,12 +4658,12 @@ async function publishCommand(options) {
4918
4658
  if (options.theme) {
4919
4659
  themePath = path9.resolve(options.theme);
4920
4660
  } else {
4921
- const isThemeDir = [
4661
+ const isThemeDir2 = [
4922
4662
  "theme.config.ts",
4923
4663
  "bundle-entry.ts",
4924
4664
  "manifest.ts"
4925
4665
  ].some((f) => fs.existsSync(path9.join(process.cwd(), f)));
4926
- if (isThemeDir) {
4666
+ if (isThemeDir2) {
4927
4667
  themePath = process.cwd();
4928
4668
  } else {
4929
4669
  logger.error(
@@ -5256,11 +4996,11 @@ Or use the --bump flag:
5256
4996
  logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
5257
4997
  }
5258
4998
  async function createZip(sourceDir, outputPath, exclude) {
5259
- const archiver3 = (await import('archiver')).default;
4999
+ const archiver2 = (await import('archiver')).default;
5260
5000
  const { createWriteStream } = await import('fs');
5261
5001
  return new Promise((resolve, reject) => {
5262
5002
  const output = createWriteStream(outputPath);
5263
- const archive = archiver3("zip", { zlib: { level: 9 } });
5003
+ const archive = archiver2("zip", { zlib: { level: 9 } });
5264
5004
  output.on("close", resolve);
5265
5005
  archive.on("error", reject);
5266
5006
  archive.pipe(output);
@@ -5273,6 +5013,210 @@ async function createZip(sourceDir, outputPath, exclude) {
5273
5013
  });
5274
5014
  }
5275
5015
 
5016
+ // src/commands/mcp.ts
5017
+ init_logger();
5018
+ var AI_CONTEXT_FILES = [
5019
+ "CLAUDE.md",
5020
+ "AGENTS.md",
5021
+ ".cursorrules",
5022
+ "THEME_REFERENCE.md",
5023
+ ".mcp.json"
5024
+ ];
5025
+ function resolveTargetDir(opts) {
5026
+ return path9.resolve(opts.cwd ?? process.cwd());
5027
+ }
5028
+ function resolveDefaultTemplateDir() {
5029
+ return path9.join(getTemplatesDir(), "default");
5030
+ }
5031
+ function isThemeDir(dir) {
5032
+ return fs.existsSync(path9.join(dir, "theme.config.ts")) || fs.existsSync(path9.join(dir, "theme.config.js"));
5033
+ }
5034
+ function inspectFiles(templateDir, targetDir) {
5035
+ return AI_CONTEXT_FILES.map((name) => {
5036
+ const templatePath = path9.join(templateDir, name);
5037
+ const targetPath = path9.join(targetDir, name);
5038
+ const exists = fs.existsSync(targetPath);
5039
+ let identical = false;
5040
+ if (exists && fs.existsSync(templatePath)) {
5041
+ try {
5042
+ const a = fs.readFileSync(templatePath, "utf-8");
5043
+ const b = fs.readFileSync(targetPath, "utf-8");
5044
+ identical = a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
5045
+ } catch {
5046
+ identical = false;
5047
+ }
5048
+ }
5049
+ return { name, templatePath, targetPath, exists, identical };
5050
+ });
5051
+ }
5052
+ async function mcpSetupCommand(options = {}) {
5053
+ const targetDir = resolveTargetDir(options);
5054
+ const templateDir = resolveDefaultTemplateDir();
5055
+ logger.header("Install OneX MCP context files");
5056
+ logger.log(`Target: ${targetDir}`);
5057
+ logger.log("");
5058
+ if (!isThemeDir(targetDir)) {
5059
+ logger.error(
5060
+ `${targetDir} does not look like an OneX theme (no theme.config.ts found).`
5061
+ );
5062
+ logger.log("Run this command from the root of your theme project.");
5063
+ process.exitCode = 1;
5064
+ return;
5065
+ }
5066
+ const statuses = inspectFiles(templateDir, targetDir);
5067
+ const missing = statuses.filter((s) => !s.exists);
5068
+ if (missing.length === 0) {
5069
+ logger.success("All AI context files are already present.");
5070
+ logger.log("Run `onexthm mcp upgrade` to refresh them to the latest version.");
5071
+ return;
5072
+ }
5073
+ logger.log(`Will install ${missing.length} file(s):`);
5074
+ for (const s of missing) logger.log(` + ${s.name}`);
5075
+ logger.log("");
5076
+ if (!options.yes) {
5077
+ const { confirm } = await inquirer.prompt([
5078
+ {
5079
+ type: "confirm",
5080
+ name: "confirm",
5081
+ message: "Proceed?",
5082
+ default: true
5083
+ }
5084
+ ]);
5085
+ if (!confirm) {
5086
+ logger.log("Cancelled.");
5087
+ return;
5088
+ }
5089
+ }
5090
+ for (const s of missing) {
5091
+ if (!fs.existsSync(s.templatePath)) {
5092
+ logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5093
+ continue;
5094
+ }
5095
+ await fs.copy(s.templatePath, s.targetPath);
5096
+ logger.success(` \u2713 ${s.name}`);
5097
+ }
5098
+ logger.log("");
5099
+ logger.success("Done. Restart your AI client to pick up the new MCP server.");
5100
+ logger.log("");
5101
+ logger.log("Tip: if your theme uses the Figma MCP, edit .mcp.json and");
5102
+ logger.log("replace __FIGMA_API_KEY__ with your Figma personal access token.");
5103
+ }
5104
+ async function mcpUpgradeCommand(options = {}) {
5105
+ const targetDir = resolveTargetDir(options);
5106
+ const templateDir = resolveDefaultTemplateDir();
5107
+ logger.header("Upgrade OneX MCP context files");
5108
+ logger.log(`Target: ${targetDir}`);
5109
+ logger.log("");
5110
+ if (!isThemeDir(targetDir)) {
5111
+ logger.error(
5112
+ `${targetDir} does not look like an OneX theme (no theme.config.ts found).`
5113
+ );
5114
+ process.exitCode = 1;
5115
+ return;
5116
+ }
5117
+ const statuses = inspectFiles(templateDir, targetDir);
5118
+ const toUpgrade = statuses.filter(
5119
+ (s) => !s.identical && s.name !== ".mcp.json"
5120
+ );
5121
+ const mcpJsonStatus = statuses.find((s) => s.name === ".mcp.json");
5122
+ if (mcpJsonStatus && !mcpJsonStatus.exists) {
5123
+ logger.warning(
5124
+ ".mcp.json is missing. Run `onexthm mcp setup` to install it."
5125
+ );
5126
+ }
5127
+ if (toUpgrade.length === 0) {
5128
+ logger.success("All AI context files are already up to date.");
5129
+ return;
5130
+ }
5131
+ logger.log("Files to update:");
5132
+ for (const s of toUpgrade) {
5133
+ const tag = !s.exists ? "+ new" : "~ changed";
5134
+ logger.log(` ${tag} ${s.name}`);
5135
+ }
5136
+ logger.log("");
5137
+ logger.log("(.mcp.json is never auto-upgraded \u2014 edit by hand if needed.)");
5138
+ logger.log("");
5139
+ if (!options.yes) {
5140
+ const { confirm } = await inquirer.prompt([
5141
+ {
5142
+ type: "confirm",
5143
+ name: "confirm",
5144
+ message: "Overwrite the file(s) above?",
5145
+ default: true
5146
+ }
5147
+ ]);
5148
+ if (!confirm) {
5149
+ logger.log("Cancelled.");
5150
+ return;
5151
+ }
5152
+ }
5153
+ for (const s of toUpgrade) {
5154
+ if (!fs.existsSync(s.templatePath)) {
5155
+ logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5156
+ continue;
5157
+ }
5158
+ await fs.copy(s.templatePath, s.targetPath, { overwrite: true });
5159
+ logger.success(` \u2713 ${s.name}`);
5160
+ }
5161
+ logger.log("");
5162
+ logger.success("Done. Restart your AI client to pick up the new context.");
5163
+ }
5164
+ async function mcpDoctorCommand(options = {}) {
5165
+ const targetDir = resolveTargetDir(options);
5166
+ const templateDir = resolveDefaultTemplateDir();
5167
+ logger.header("OneX MCP doctor");
5168
+ logger.log(`Target: ${targetDir}`);
5169
+ logger.log("");
5170
+ if (!isThemeDir(targetDir)) {
5171
+ logger.error("Not an OneX theme directory (no theme.config.ts).");
5172
+ process.exitCode = 1;
5173
+ return;
5174
+ }
5175
+ logger.success("theme.config.ts present");
5176
+ const mcpJsonPath = path9.join(targetDir, ".mcp.json");
5177
+ if (!fs.existsSync(mcpJsonPath)) {
5178
+ logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
5179
+ } else {
5180
+ try {
5181
+ const mcpJson = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
5182
+ const servers = mcpJson?.mcpServers ?? {};
5183
+ if (servers.onexthm) {
5184
+ logger.success(".mcp.json registers `onexthm`");
5185
+ } else {
5186
+ logger.error(".mcp.json does not register `onexthm`");
5187
+ }
5188
+ if (servers.figma) {
5189
+ const arg = (servers.figma.args ?? []).join(" ");
5190
+ if (arg.includes("__FIGMA_API_KEY__")) {
5191
+ logger.warning("figma server uses placeholder API key \u2014 replace __FIGMA_API_KEY__");
5192
+ } else {
5193
+ logger.success(".mcp.json registers `figma`");
5194
+ }
5195
+ }
5196
+ } catch (err) {
5197
+ logger.error(`.mcp.json could not be parsed: ${err.message}`);
5198
+ }
5199
+ }
5200
+ const statuses = inspectFiles(templateDir, targetDir).filter(
5201
+ (s) => s.name !== ".mcp.json"
5202
+ );
5203
+ for (const s of statuses) {
5204
+ if (!s.exists) {
5205
+ logger.warning(`${s.name} missing`);
5206
+ } else if (!s.identical) {
5207
+ logger.warning(`${s.name} is out of date \u2014 run \`onexthm mcp upgrade\``);
5208
+ } else {
5209
+ logger.success(`${s.name} up to date`);
5210
+ }
5211
+ }
5212
+ const registryPath = path9.join(targetDir, "sections-registry.ts");
5213
+ if (fs.existsSync(registryPath)) {
5214
+ logger.success("sections-registry.ts present");
5215
+ } else {
5216
+ logger.warning("sections-registry.ts missing \u2014 section tools won't auto-register");
5217
+ }
5218
+ }
5219
+
5276
5220
  // src/cli.ts
5277
5221
  dotenv.config({
5278
5222
  path: path9.join(process.cwd(), ".env.local"),
@@ -5323,33 +5267,25 @@ program.command("deploy").description("Upload theme package to API server").opti
5323
5267
  "-e, --environment <env>",
5324
5268
  "Environment (production, staging, development)"
5325
5269
  ).action(deployCommand);
5326
- program.command("upload").description("Upload compiled theme to S3 bucket").option("-t, --theme <theme>", "Theme to upload").option("-b, --bucket <name>", "S3 bucket name").option("-v, --version <version>", "Theme version").option(
5327
- "-e, --environment <env>",
5328
- "Environment (staging|production)",
5329
- "staging"
5330
- ).option("--dry-run", "Show what would be uploaded without uploading").option("--skip-source", "Skip uploading source.zip").option("--source-dir <dir>", "Source directory path").action(uploadCommand);
5331
- program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
5270
+ program.command("upload").description("[deprecated] use `onexthm publish` instead").option("-t, --theme <theme>", "[deprecated] ignored").option("-b, --bucket <name>", "[deprecated] ignored").option("-v, --version <version>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("--dry-run", "[deprecated] ignored").option("--skip-source", "[deprecated] ignored").option("--source-dir <dir>", "[deprecated] ignored").action(uploadCommand);
5271
+ program.command("download").description("Download a published theme via the website-api").option("-t, --theme-id <id>", "Theme ID to download").option(
5332
5272
  "-v, --version <version>",
5333
5273
  "Theme version (default: latest)",
5334
5274
  "latest"
5335
- ).option("-b, --bucket <name>", "S3 bucket name").option(
5336
- "-e, --environment <env>",
5337
- "Environment (staging|production)",
5338
- "staging"
5339
- ).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5340
- program.command("clone").description("Clone theme source code from S3").argument("<theme-name>", "Theme to clone").option(
5275
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5276
+ program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5341
5277
  "-v, --version <version>",
5342
5278
  "Theme version (default: latest)",
5343
5279
  "latest"
5344
- ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
5345
- "-e, --environment <env>",
5346
- "Environment (staging|production)",
5347
- "staging"
5348
- ).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5280
+ ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5349
5281
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5350
5282
  program.command("login").description("Login to OneX platform").action(loginCommand);
5351
5283
  program.command("logout").description("Logout from OneX platform").action(logoutCommand);
5352
5284
  program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
5285
+ var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
5286
+ mcpCmd.command("setup").description("Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme").option("-y, --yes", "Skip confirmation prompts").action(mcpSetupCommand);
5287
+ mcpCmd.command("upgrade").description("Refresh AI-context files to the latest version from the bundled template").option("-y, --yes", "Skip confirmation prompts").action(mcpUpgradeCommand);
5288
+ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme directory").action(mcpDoctorCommand);
5353
5289
  program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
5354
5290
  "--bump <type>",
5355
5291
  "Auto-bump version before publish (patch|minor|major)"