@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.js CHANGED
@@ -20,7 +20,6 @@ var inquirer = require('inquirer');
20
20
  var archiver = require('archiver');
21
21
  var FormData = require('form-data');
22
22
  var fetch2 = require('node-fetch');
23
- var clientS3 = require('@aws-sdk/client-s3');
24
23
  var AdmZip = require('adm-zip');
25
24
  var chokidar = require('chokidar');
26
25
  var http = require('http');
@@ -2717,12 +2716,12 @@ async function validateCommand(options) {
2717
2716
  }
2718
2717
  themeToValidate = options.theme;
2719
2718
  } else {
2720
- const isThemeDir = [
2719
+ const isThemeDir2 = [
2721
2720
  "theme.config.ts",
2722
2721
  "bundle-entry.ts",
2723
2722
  "manifest.ts"
2724
2723
  ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
2725
- if (isThemeDir) {
2724
+ if (isThemeDir2) {
2726
2725
  themeToValidate = path9__default.default.basename(process.cwd());
2727
2726
  logger.info(`Validating current theme: ${themeToValidate}`);
2728
2727
  } else {
@@ -3114,12 +3113,12 @@ async function buildCommand(options) {
3114
3113
  process.exit(1);
3115
3114
  }
3116
3115
  } else {
3117
- const isThemeDir = [
3116
+ const isThemeDir2 = [
3118
3117
  "theme.config.ts",
3119
3118
  "bundle-entry.ts",
3120
3119
  "manifest.ts"
3121
3120
  ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
3122
- if (isThemeDir) {
3121
+ if (isThemeDir2) {
3123
3122
  themePath = process.cwd();
3124
3123
  themeName = path9__default.default.basename(themePath);
3125
3124
  logger.info(`Building current theme: ${themeName}`);
@@ -3250,12 +3249,12 @@ async function packageCommand(options) {
3250
3249
  process.exit(1);
3251
3250
  }
3252
3251
  } else {
3253
- const isThemeDir = [
3252
+ const isThemeDir2 = [
3254
3253
  "theme.config.ts",
3255
3254
  "bundle-entry.ts",
3256
3255
  "manifest.ts"
3257
3256
  ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
3258
- if (isThemeDir) {
3257
+ if (isThemeDir2) {
3259
3258
  themePath = process.cwd();
3260
3259
  themeName = path9__default.default.basename(themePath);
3261
3260
  logger.info(`Packaging current theme: ${themeName}`);
@@ -3486,343 +3485,94 @@ async function deployCommand(options) {
3486
3485
 
3487
3486
  // src/commands/upload.ts
3488
3487
  init_logger();
3489
- function getS3Client() {
3490
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3491
- if (adapterMode === "vps") {
3492
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3493
- const secure = process.env.MINIO_SECURE === "true";
3494
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3495
- return new clientS3.S3Client({
3496
- endpoint: endpointUrl,
3497
- region: "us-east-1",
3498
- credentials: {
3499
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3500
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3501
- },
3502
- forcePathStyle: true
3503
- });
3504
- }
3505
- if (adapterMode === "local") {
3506
- return new clientS3.S3Client({
3507
- endpoint: "http://localhost:4569",
3508
- region: "ap-southeast-1",
3509
- credentials: {
3510
- accessKeyId: "S3RVER",
3511
- secretAccessKey: "S3RVER"
3512
- },
3513
- forcePathStyle: true
3514
- });
3515
- }
3516
- return new clientS3.S3Client({
3517
- region: process.env.AWS_REGION || "ap-southeast-1"
3518
- });
3519
- }
3520
- function getBucketName(env) {
3521
- if (process.env.BUCKET_NAME) {
3522
- return process.env.BUCKET_NAME;
3523
- }
3524
- const environment = env || process.env.ENVIRONMENT || "staging";
3525
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3526
- }
3527
- async function findCompiledThemeDir(themeId, version2) {
3528
- const searchPaths = [path9__default.default.resolve(process.cwd(), "dist")];
3529
- for (const dir of searchPaths) {
3530
- if (await fs__default.default.pathExists(dir)) {
3531
- const hasManifest = await fs__default.default.pathExists(path9__default.default.join(dir, "manifest.json"));
3532
- const hasThemeEntry = await fs__default.default.pathExists(path9__default.default.join(dir, "bundle-entry.js")) || await fs__default.default.pathExists(path9__default.default.join(dir, "theme.config.js")) || await fs__default.default.pathExists(path9__default.default.join(dir, "index.js"));
3533
- if (hasManifest || hasThemeEntry) {
3534
- return dir;
3535
- }
3536
- }
3537
- }
3538
- return null;
3539
- }
3540
- async function readManifest() {
3541
- const manifestTsPath = path9__default.default.resolve(process.cwd(), "manifest.ts");
3542
- if (await fs__default.default.pathExists(manifestTsPath)) {
3543
- try {
3544
- const module = await import(manifestTsPath);
3545
- return module.default || module;
3546
- } catch (error) {
3547
- logger.warning("Failed to import manifest.ts, trying package.json");
3548
- }
3549
- }
3550
- const packageJsonPath = path9__default.default.resolve(process.cwd(), "package.json");
3551
- if (await fs__default.default.pathExists(packageJsonPath)) {
3552
- const pkg = await fs__default.default.readJson(packageJsonPath);
3553
- return {
3554
- themeId: pkg.name?.replace("@onex-themes/", "") || "unknown",
3555
- version: pkg.version || "1.0.0"
3556
- };
3557
- }
3558
- throw new Error(
3559
- "No manifest.ts or package.json found. Are you in a theme directory?"
3488
+ async function uploadCommand(_options) {
3489
+ logger.header("Upload Theme to S3 \u2014 DEPRECATED");
3490
+ console.log();
3491
+ console.log(
3492
+ chalk4__default.default.yellow.bold(
3493
+ "`onexthm upload` is deprecated and no longer functional."
3494
+ )
3560
3495
  );
3561
- }
3562
- async function createZipFromDir(sourceDir, outputPath, excludePatterns = []) {
3563
- return new Promise((resolve, reject) => {
3564
- const output = fs__default.default.createWriteStream(outputPath);
3565
- const archive = archiver__default.default("zip", { zlib: { level: 6 } });
3566
- output.on("close", () => resolve());
3567
- archive.on("error", (err) => reject(err));
3568
- archive.pipe(output);
3569
- archive.glob("**/*", {
3570
- cwd: sourceDir,
3571
- dot: true,
3572
- ignore: excludePatterns
3573
- });
3574
- archive.finalize();
3575
- });
3576
- }
3577
- async function findSourceDir(themeId, explicitDir) {
3578
- if (explicitDir) {
3579
- if (await fs__default.default.pathExists(explicitDir)) return explicitDir;
3580
- return null;
3581
- }
3582
- const searchPaths = [
3583
- process.cwd(),
3584
- path9__default.default.resolve(process.cwd(), `../../themes/${themeId}`),
3585
- path9__default.default.resolve(process.cwd(), `../themes/${themeId}`)
3586
- ];
3587
- const markers = ["theme.config.ts", "bundle-entry.ts"];
3588
- for (const dir of searchPaths) {
3589
- for (const marker of markers) {
3590
- if (await fs__default.default.pathExists(path9__default.default.join(dir, marker))) {
3591
- return dir;
3592
- }
3593
- }
3594
- }
3595
- return null;
3596
- }
3597
- async function updateLatestPointer(s3Client, bucket, themeId, version2) {
3598
- const latestData = {
3599
- version: version2,
3600
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
3601
- };
3602
- await s3Client.send(
3603
- new clientS3.PutObjectCommand({
3604
- Bucket: bucket,
3605
- Key: `themes/${themeId}/latest.json`,
3606
- Body: JSON.stringify(latestData, null, 2),
3607
- ContentType: "application/json"
3608
- })
3496
+ console.log();
3497
+ console.log(
3498
+ "The platform no longer exposes themes via direct S3 access, so this"
3609
3499
  );
3610
- }
3611
- async function uploadCommand(options) {
3612
- logger.header("Upload Theme to S3");
3613
- const spinner = ora__default.default("Preparing theme upload...").start();
3614
- try {
3615
- let themeId;
3616
- let version2;
3617
- if (options.theme) {
3618
- themeId = options.theme;
3619
- version2 = options.version || "1.0.0";
3620
- } else {
3621
- const manifest = await readManifest();
3622
- themeId = manifest.themeId;
3623
- version2 = options.version || manifest.version || "1.0.0";
3624
- }
3625
- spinner.text = `Found theme: ${themeId}@${version2}`;
3626
- const bucket = options.bucket || getBucketName(options.environment);
3627
- const s3Client = getS3Client();
3628
- const compiledDir = await findCompiledThemeDir(themeId, version2);
3629
- if (!compiledDir) {
3630
- spinner.fail(
3631
- chalk4__default.default.red(
3632
- `Compiled theme not found for ${themeId}@${version2}. Run 'onexthm build' first.`
3633
- )
3634
- );
3635
- logger.info(chalk4__default.default.gray(`Expected location:
3636
- - ./dist/`));
3637
- process.exit(1);
3638
- }
3639
- spinner.succeed(`Found compiled theme at: ${compiledDir}`);
3640
- spinner.start("Creating bundle.zip...");
3641
- const tmpDir = os__default.default.tmpdir();
3642
- const bundleZipPath = path9__default.default.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
3643
- await createZipFromDir(compiledDir, bundleZipPath);
3644
- const bundleZipBuffer = await fs__default.default.readFile(bundleZipPath);
3645
- const bundleSizeMB = (bundleZipBuffer.length / 1024 / 1024).toFixed(2);
3646
- spinner.succeed(`Created bundle.zip (${bundleSizeMB} MB)`);
3647
- if (options.dryRun) {
3648
- spinner.info(chalk4__default.default.yellow("Dry run mode \u2014 no files will be uploaded"));
3649
- console.log();
3650
- console.log(chalk4__default.default.gray(` bundle.zip: ${bundleSizeMB} MB`));
3651
- console.log(
3652
- chalk4__default.default.cyan(
3653
- ` S3 path: s3://${bucket}/themes/${themeId}/${version2}/bundle.zip`
3654
- )
3655
- );
3656
- if (!options.skipSource) {
3657
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
3658
- if (sourceDir) {
3659
- console.log(chalk4__default.default.gray(` source dir: ${sourceDir}`));
3660
- console.log(
3661
- chalk4__default.default.cyan(
3662
- ` S3 path: s3://${bucket}/themes/${themeId}/${version2}/source.zip`
3663
- )
3664
- );
3665
- } else {
3666
- console.log(
3667
- chalk4__default.default.yellow(" source dir: not found (source.zip will be skipped)")
3668
- );
3669
- }
3670
- }
3671
- console.log();
3672
- await fs__default.default.remove(bundleZipPath);
3673
- return;
3674
- }
3675
- spinner.start("Uploading bundle.zip to S3...");
3676
- const bundleS3Key = `themes/${themeId}/${version2}/bundle.zip`;
3677
- await s3Client.send(
3678
- new clientS3.PutObjectCommand({
3679
- Bucket: bucket,
3680
- Key: bundleS3Key,
3681
- Body: bundleZipBuffer,
3682
- ContentType: "application/zip"
3683
- })
3684
- );
3685
- spinner.succeed(
3686
- `Uploaded bundle.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${bundleS3Key}`)}`
3687
- );
3688
- await fs__default.default.remove(bundleZipPath);
3689
- let sourceUploaded = false;
3690
- if (!options.skipSource) {
3691
- spinner.start("Looking for source directory...");
3692
- const sourceDir = await findSourceDir(themeId, options.sourceDir);
3693
- if (sourceDir) {
3694
- spinner.succeed(`Found source at: ${sourceDir}`);
3695
- spinner.start("Creating source.zip...");
3696
- const sourceZipPath = path9__default.default.join(
3697
- tmpDir,
3698
- `${themeId}-${version2}-source.zip`
3699
- );
3700
- await createZipFromDir(sourceDir, sourceZipPath, [
3701
- "node_modules/**",
3702
- "dist/**",
3703
- ".git/**",
3704
- "*.zip",
3705
- ".next/**",
3706
- ".turbo/**"
3707
- ]);
3708
- const sourceZipBuffer = await fs__default.default.readFile(sourceZipPath);
3709
- const sourceSizeMB = (sourceZipBuffer.length / 1024 / 1024).toFixed(2);
3710
- spinner.succeed(`Created source.zip (${sourceSizeMB} MB)`);
3711
- spinner.start("Uploading source.zip to S3...");
3712
- const sourceS3Key = `themes/${themeId}/${version2}/source.zip`;
3713
- await s3Client.send(
3714
- new clientS3.PutObjectCommand({
3715
- Bucket: bucket,
3716
- Key: sourceS3Key,
3717
- Body: sourceZipBuffer,
3718
- ContentType: "application/zip"
3719
- })
3720
- );
3721
- spinner.succeed(
3722
- `Uploaded source.zip ${chalk4__default.default.gray(`\u2192 s3://${bucket}/${sourceS3Key}`)}`
3723
- );
3724
- await fs__default.default.remove(sourceZipPath);
3725
- sourceUploaded = true;
3726
- } else {
3727
- spinner.warn(
3728
- chalk4__default.default.yellow("Source directory not found \u2014 skipping source.zip")
3729
- );
3730
- }
3731
- }
3732
- spinner.start("Updating latest.json pointer...");
3733
- await updateLatestPointer(s3Client, bucket, themeId, version2);
3734
- spinner.succeed("Updated latest.json pointer");
3735
- console.log();
3736
- logger.success(chalk4__default.default.green.bold("Theme uploaded successfully!"));
3737
- console.log();
3738
- console.log(
3739
- chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${version2}`)
3740
- );
3741
- console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
3742
- console.log(
3743
- chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(`bundle.zip${sourceUploaded ? " + source.zip" : ""}`)
3744
- );
3745
- console.log(
3746
- chalk4__default.default.cyan(" Path: ") + chalk4__default.default.gray(`s3://${bucket}/themes/${themeId}/${version2}/`)
3747
- );
3748
- console.log();
3749
- } catch (error) {
3750
- spinner.fail(chalk4__default.default.red(`Upload failed: ${error.message}`));
3751
- logger.error(error.stack || error.message);
3752
- process.exit(1);
3753
- }
3500
+ console.log(
3501
+ "command can no longer reach the bucket. Use `onexthm publish` instead:"
3502
+ );
3503
+ console.log();
3504
+ console.log(chalk4__default.default.cyan(" cd themes/your-theme"));
3505
+ console.log(chalk4__default.default.cyan(" onexthm login # one-time, refreshes JWT"));
3506
+ console.log(
3507
+ chalk4__default.default.cyan(" onexthm publish # builds + uploads + confirms")
3508
+ );
3509
+ console.log();
3510
+ console.log(
3511
+ "`publish` does everything this command did (build, version bump,"
3512
+ );
3513
+ console.log(
3514
+ "bundle + source upload) plus content-hashed asset upload, security"
3515
+ );
3516
+ console.log("scanning, and atomic version registration in one step.");
3517
+ console.log();
3518
+ process.exit(1);
3754
3519
  }
3755
3520
 
3756
3521
  // src/commands/download.ts
3757
3522
  init_logger();
3758
- function getS3Client2() {
3759
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3760
- if (adapterMode === "vps") {
3761
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3762
- const secure = process.env.MINIO_SECURE === "true";
3763
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3764
- return new clientS3.S3Client({
3765
- endpoint: endpointUrl,
3766
- region: "us-east-1",
3767
- credentials: {
3768
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3769
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3770
- },
3771
- forcePathStyle: true
3772
- });
3523
+ function unwrapEnvelope(raw) {
3524
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3525
+ return raw.body;
3773
3526
  }
3774
- if (adapterMode === "local") {
3775
- return new clientS3.S3Client({
3776
- endpoint: "http://localhost:4569",
3777
- region: "ap-southeast-1",
3778
- credentials: {
3779
- accessKeyId: "S3RVER",
3780
- secretAccessKey: "S3RVER"
3781
- },
3782
- forcePathStyle: true
3783
- });
3784
- }
3785
- return new clientS3.S3Client({
3786
- region: process.env.AWS_REGION || "ap-southeast-1"
3787
- });
3527
+ return raw;
3788
3528
  }
3789
- function getBucketName2(env) {
3790
- if (process.env.BUCKET_NAME) {
3791
- return process.env.BUCKET_NAME;
3529
+ async function resolveLatestVersion(apiUrl, themeId) {
3530
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3531
+ let response;
3532
+ try {
3533
+ response = await fetch(url, { cache: "no-store" });
3534
+ } catch (err) {
3535
+ throw new Error(
3536
+ `Network error contacting ${url}: ${err instanceof Error ? err.message : "unknown"}`
3537
+ );
3792
3538
  }
3793
- const environment = env || process.env.ENVIRONMENT || "staging";
3794
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
3795
- }
3796
- async function streamToString(stream) {
3797
- const chunks = [];
3798
- for await (const chunk of stream) {
3799
- chunks.push(Buffer.from(chunk));
3539
+ if (!response.ok) {
3540
+ throw new Error(
3541
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3542
+ );
3800
3543
  }
3801
- return Buffer.concat(chunks).toString("utf-8");
3802
- }
3803
- async function streamToBuffer(stream) {
3804
- const chunks = [];
3805
- for await (const chunk of stream) {
3806
- chunks.push(Buffer.from(chunk));
3544
+ const raw = await response.json();
3545
+ const data = unwrapEnvelope(raw);
3546
+ const latest = data?.latest_version;
3547
+ if (typeof latest !== "string" || latest.length === 0) {
3548
+ throw new Error(
3549
+ `Theme "${themeId}" has no published versions yet (no latest_version in response)`
3550
+ );
3807
3551
  }
3808
- return Buffer.concat(chunks);
3552
+ return latest;
3809
3553
  }
3810
- async function resolveLatestVersion(s3Client, bucket, themeId) {
3811
- try {
3812
- const response = await s3Client.send(
3813
- new clientS3.GetObjectCommand({
3814
- Bucket: bucket,
3815
- Key: `themes/${themeId}/latest.json`
3816
- })
3817
- );
3818
- const body = await streamToString(response.Body);
3819
- const data = JSON.parse(body);
3820
- return data.version;
3821
- } catch (error) {
3554
+ async function downloadBundleZip(apiUrl, themeId, version2) {
3555
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/${encodeURIComponent(version2)}/download`;
3556
+ const response = await fetch(url);
3557
+ if (!response.ok) {
3822
3558
  throw new Error(
3823
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3559
+ `Bundle download failed for "${themeId}@${version2}" (HTTP ${response.status})`
3824
3560
  );
3825
3561
  }
3562
+ const contentType = response.headers.get("content-type") || "";
3563
+ if (contentType.includes("application/json")) {
3564
+ const raw = await response.json();
3565
+ const envelope = raw && typeof raw === "object" && "statusCode" in raw ? raw : { body: raw };
3566
+ const body = envelope.body;
3567
+ if (typeof body !== "string") {
3568
+ throw new Error(
3569
+ "Unexpected /download response shape: expected base64 string in body"
3570
+ );
3571
+ }
3572
+ return Buffer.from(body, "base64");
3573
+ }
3574
+ const arrayBuffer = await response.arrayBuffer();
3575
+ return Buffer.from(arrayBuffer);
3826
3576
  }
3827
3577
  async function createCompatibilityFiles(outputDir, manifest) {
3828
3578
  const entryFile = manifest.output?.entry || "bundle-entry.js";
@@ -3846,47 +3596,58 @@ export * from './bundle-entry.js';
3846
3596
  const pkgJsonPath = path9__default.default.join(outputDir, "package.json");
3847
3597
  await fs__default.default.writeFile(pkgJsonPath, '{\n "type": "module"\n}\n', "utf-8");
3848
3598
  }
3849
- function showDownloadFailureHelp(themeId, bucket) {
3599
+ function showDownloadFailureHelp(themeId, apiUrl) {
3850
3600
  console.log();
3851
3601
  logger.error(chalk4__default.default.red.bold("Theme download failed"));
3852
3602
  console.log();
3853
3603
  console.log(chalk4__default.default.yellow("Possible reasons:"));
3854
- console.log(chalk4__default.default.gray(" 1. Theme not uploaded to S3 yet"));
3855
- console.log(chalk4__default.default.gray(" 2. AWS credentials not configured correctly"));
3856
- console.log(chalk4__default.default.gray(" 3. Bucket name or region is incorrect"));
3857
- console.log(chalk4__default.default.gray(" 4. bundle.zip is missing or corrupted"));
3604
+ console.log(
3605
+ chalk4__default.default.gray(" 1. Theme has not been published yet (run `onexthm publish`)")
3606
+ );
3607
+ console.log(
3608
+ chalk4__default.default.gray(
3609
+ " 2. Theme ID is wrong (typo in --theme-id or THEME_ID env var)"
3610
+ )
3611
+ );
3612
+ console.log(
3613
+ chalk4__default.default.gray(" 3. API base URL is wrong or the website-api is unreachable")
3614
+ );
3858
3615
  console.log();
3859
3616
  console.log(chalk4__default.default.cyan.bold("To fix this:"));
3860
3617
  console.log();
3861
- console.log(chalk4__default.default.white("1. Compile and upload the theme:"));
3862
- console.log(chalk4__default.default.gray(` cd themes/${themeId}`));
3863
- console.log(chalk4__default.default.gray(" pnpm build"));
3864
- console.log(chalk4__default.default.gray(" onexthm upload"));
3865
- console.log();
3866
- console.log(chalk4__default.default.white("2. Verify AWS credentials are set:"));
3618
+ console.log(chalk4__default.default.white("1. Verify the theme is published:"));
3867
3619
  console.log(
3868
- chalk4__default.default.gray(" - Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY")
3620
+ chalk4__default.default.gray(
3621
+ ` curl -s ${apiUrl}/website-api/themes/${themeId}/versions | jq .latest_version`
3622
+ )
3869
3623
  );
3870
- console.log(chalk4__default.default.gray(" - Or use AWS_PROFILE=your-profile"));
3871
- console.log(chalk4__default.default.gray(" - Set AWS_REGION (e.g., ap-southeast-1)"));
3872
3624
  console.log();
3873
- console.log(chalk4__default.default.white("3. Check bucket configuration:"));
3874
- console.log(chalk4__default.default.gray(` Current bucket: ${bucket}`));
3625
+ console.log(chalk4__default.default.white("2. Check API URL configuration:"));
3626
+ console.log(chalk4__default.default.gray(` Current API URL: ${apiUrl}`));
3875
3627
  console.log(
3876
- chalk4__default.default.gray(" Set BUCKET_NAME environment variable if different")
3628
+ chalk4__default.default.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
3877
3629
  );
3878
3630
  console.log();
3879
- console.log(chalk4__default.default.white("4. Verify theme exists in S3:"));
3880
- console.log(chalk4__default.default.gray(` aws s3 ls s3://${bucket}/themes/${themeId}/`));
3631
+ console.log(chalk4__default.default.white("3. Pin a specific version (CI/production):"));
3632
+ console.log(
3633
+ chalk4__default.default.gray(` THEME_VERSION=1.2.3 onexthm download --theme-id ${themeId}`)
3634
+ );
3881
3635
  console.log();
3882
3636
  }
3883
3637
  async function downloadCommand(options) {
3884
- logger.header("Download Theme from S3");
3638
+ logger.header("Download Theme");
3885
3639
  const spinner = ora__default.default("Initializing download...").start();
3640
+ if (options.bucket || options.environment) {
3641
+ spinner.stop();
3642
+ logger.warning(
3643
+ "--bucket and --environment are deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
3644
+ );
3645
+ spinner.start();
3646
+ }
3647
+ const apiUrl = getApiUrl();
3886
3648
  try {
3887
3649
  const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
3888
- const version2 = options.version || process.env.THEME_VERSION || "latest";
3889
- const bucket = options.bucket || getBucketName2(options.environment);
3650
+ const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
3890
3651
  const outputDir = options.output || "./active-theme";
3891
3652
  if (!themeId) {
3892
3653
  spinner.fail(
@@ -3896,12 +3657,10 @@ async function downloadCommand(options) {
3896
3657
  );
3897
3658
  process.exit(1);
3898
3659
  }
3899
- spinner.text = `Downloading ${themeId}@${version2} from ${bucket}...`;
3900
- const s3Client = getS3Client2();
3901
- let resolvedVersion = version2;
3902
- if (version2 === "latest") {
3903
- spinner.text = "Resolving latest version...";
3904
- resolvedVersion = await resolveLatestVersion(s3Client, bucket, themeId);
3660
+ spinner.text = `Resolving ${themeId}@${requestedVersion}...`;
3661
+ let resolvedVersion = requestedVersion;
3662
+ if (requestedVersion === "latest") {
3663
+ resolvedVersion = await resolveLatestVersion(apiUrl, themeId);
3905
3664
  spinner.succeed(
3906
3665
  `Resolved latest version: ${chalk4__default.default.cyan(resolvedVersion)}`
3907
3666
  );
@@ -3911,24 +3670,19 @@ async function downloadCommand(options) {
3911
3670
  chalk4__default.default.yellow(
3912
3671
  `
3913
3672
  Warning: Resolved "latest" to ${resolvedVersion} in CI environment.
3914
- For production builds, pin to a specific version:
3673
+ For reproducible builds, pin to a specific version:
3915
3674
  THEME_VERSION=${resolvedVersion}
3916
3675
  `
3917
3676
  )
3918
3677
  );
3919
3678
  }
3679
+ } else {
3680
+ spinner.succeed(`Using version: ${chalk4__default.default.cyan(resolvedVersion)}`);
3920
3681
  }
3921
3682
  spinner.start(
3922
3683
  `Downloading bundle.zip for ${themeId}@${resolvedVersion}...`
3923
3684
  );
3924
- const s3Key = `themes/${themeId}/${resolvedVersion}/bundle.zip`;
3925
- const response = await s3Client.send(
3926
- new clientS3.GetObjectCommand({
3927
- Bucket: bucket,
3928
- Key: s3Key
3929
- })
3930
- );
3931
- const zipBuffer = await streamToBuffer(response.Body);
3685
+ const zipBuffer = await downloadBundleZip(apiUrl, themeId, resolvedVersion);
3932
3686
  const sizeMB = (zipBuffer.length / 1024 / 1024).toFixed(2);
3933
3687
  spinner.succeed(`Downloaded bundle.zip (${sizeMB} MB)`);
3934
3688
  spinner.start("Extracting bundle...");
@@ -3947,7 +3701,7 @@ async function downloadCommand(options) {
3947
3701
  console.log(
3948
3702
  chalk4__default.default.cyan(" Theme: ") + chalk4__default.default.white(`${themeId}@${resolvedVersion}`)
3949
3703
  );
3950
- console.log(chalk4__default.default.cyan(" Bucket: ") + chalk4__default.default.white(bucket));
3704
+ console.log(chalk4__default.default.cyan(" Source: ") + chalk4__default.default.white(apiUrl));
3951
3705
  console.log(chalk4__default.default.cyan(" Output: ") + chalk4__default.default.white(outputDir));
3952
3706
  console.log(chalk4__default.default.cyan(" Files: ") + chalk4__default.default.white(entries.length));
3953
3707
  if (manifest.counts) {
@@ -3959,82 +3713,70 @@ async function downloadCommand(options) {
3959
3713
  } catch (error) {
3960
3714
  spinner.fail(chalk4__default.default.red("Download failed"));
3961
3715
  logger.error(error.message);
3962
- const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || "unknown";
3963
- const bucket = options.bucket || getBucketName2(options.environment);
3964
- showDownloadFailureHelp(themeId, bucket);
3716
+ const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID || "unknown";
3717
+ showDownloadFailureHelp(themeId, apiUrl);
3965
3718
  process.exit(1);
3966
3719
  }
3967
3720
  }
3968
3721
 
3969
3722
  // src/commands/clone.ts
3970
3723
  init_logger();
3971
- function getS3Client3() {
3972
- const adapterMode = (process.env.ADAPTER_MODE || "aws").trim().toLowerCase();
3973
- if (adapterMode === "vps") {
3974
- const endpoint = process.env.MINIO_ENDPOINT || "localhost:9000";
3975
- const secure = process.env.MINIO_SECURE === "true";
3976
- const endpointUrl = endpoint.startsWith("http") ? endpoint : `${secure ? "https" : "http"}://${endpoint}`;
3977
- return new clientS3.S3Client({
3978
- endpoint: endpointUrl,
3979
- region: "us-east-1",
3980
- credentials: {
3981
- accessKeyId: process.env.MINIO_ACCESS_KEY || "minioadmin",
3982
- secretAccessKey: process.env.MINIO_SECRET_KEY || "minioadmin"
3983
- },
3984
- forcePathStyle: true
3985
- });
3986
- }
3987
- if (adapterMode === "local") {
3988
- return new clientS3.S3Client({
3989
- endpoint: "http://localhost:4569",
3990
- region: "ap-southeast-1",
3991
- credentials: {
3992
- accessKeyId: "S3RVER",
3993
- secretAccessKey: "S3RVER"
3994
- },
3995
- forcePathStyle: true
3996
- });
3724
+ function unwrapEnvelope2(raw) {
3725
+ if (raw && typeof raw === "object" && "statusCode" in raw && "body" in raw) {
3726
+ return raw.body;
3997
3727
  }
3998
- return new clientS3.S3Client({
3999
- region: process.env.AWS_REGION || "ap-southeast-1"
4000
- });
3728
+ return raw;
4001
3729
  }
4002
- function getBucketName3(env) {
4003
- if (process.env.BUCKET_NAME) {
4004
- return process.env.BUCKET_NAME;
3730
+ async function resolveLatestVersion2(apiUrl, themeId) {
3731
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`;
3732
+ const response = await fetch(url, { cache: "no-store" });
3733
+ if (!response.ok) {
3734
+ throw new Error(
3735
+ `Version lookup failed for "${themeId}" (HTTP ${response.status})`
3736
+ );
4005
3737
  }
4006
- const environment = env || process.env.ENVIRONMENT || "staging";
4007
- return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
4008
- }
4009
- async function streamToString2(stream) {
4010
- const chunks = [];
4011
- for await (const chunk of stream) {
4012
- chunks.push(Buffer.from(chunk));
3738
+ const raw = await response.json();
3739
+ const data = unwrapEnvelope2(raw);
3740
+ const latest = data?.latest_version;
3741
+ if (typeof latest !== "string" || latest.length === 0) {
3742
+ throw new Error(`Theme "${themeId}" has no published versions yet`);
4013
3743
  }
4014
- return Buffer.concat(chunks).toString("utf-8");
3744
+ return latest;
4015
3745
  }
4016
- async function streamToBuffer2(stream) {
4017
- const chunks = [];
4018
- for await (const chunk of stream) {
4019
- chunks.push(Buffer.from(chunk));
4020
- }
4021
- return Buffer.concat(chunks);
4022
- }
4023
- async function resolveLatestVersion2(s3Client, bucket, themeId) {
4024
- try {
4025
- const response = await s3Client.send(
4026
- new clientS3.GetObjectCommand({
4027
- Bucket: bucket,
4028
- Key: `themes/${themeId}/latest.json`
4029
- })
3746
+ async function fetchSourceZip(apiUrl, themeId, version2) {
3747
+ const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
3748
+ const response = await authenticatedFetch(url, {
3749
+ method: "GET"
3750
+ });
3751
+ if (!response.ok) {
3752
+ if (response.status === 404) {
3753
+ throw new Error(
3754
+ `Source not found for ${themeId}@${version2}. The theme may not have been published with source upload enabled.`
3755
+ );
3756
+ }
3757
+ if (response.status === 401 || response.status === 403) {
3758
+ throw new Error(
3759
+ `Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
3760
+ );
3761
+ }
3762
+ throw new Error(
3763
+ `Source URL request failed for "${themeId}@${version2}" (HTTP ${response.status})`
4030
3764
  );
4031
- const body = await streamToString2(response.Body);
4032
- return JSON.parse(body).version;
4033
- } catch (error) {
3765
+ }
3766
+ const raw = await response.json();
3767
+ const data = unwrapEnvelope2(raw);
3768
+ const presignedUrl = data?.download_url;
3769
+ if (typeof presignedUrl !== "string" || presignedUrl.length === 0) {
3770
+ throw new Error("Unexpected /source response shape: missing download_url");
3771
+ }
3772
+ const zipResponse = await fetch(presignedUrl);
3773
+ if (!zipResponse.ok) {
4034
3774
  throw new Error(
4035
- `Failed to resolve latest version for theme "${themeId}": ${error.message}`
3775
+ `Presigned source download failed (HTTP ${zipResponse.status})`
4036
3776
  );
4037
3777
  }
3778
+ const arrayBuffer = await zipResponse.arrayBuffer();
3779
+ return Buffer.from(arrayBuffer);
4038
3780
  }
4039
3781
  function runInstall(cwd) {
4040
3782
  return new Promise((resolve) => {
@@ -4048,8 +3790,8 @@ function runInstall(cwd) {
4048
3790
  });
4049
3791
  }
4050
3792
  async function promptThemeName(originalName) {
4051
- const { default: inquirer7 } = await import('inquirer');
4052
- const { themeName } = await inquirer7.prompt([
3793
+ const { default: inquirer8 } = await import('inquirer');
3794
+ const { themeName } = await inquirer8.prompt([
4053
3795
  {
4054
3796
  type: "input",
4055
3797
  name: "themeName",
@@ -4133,15 +3875,24 @@ async function renameTheme(themeDir, oldName, newName) {
4133
3875
  }
4134
3876
  async function cloneCommand(themeName, options) {
4135
3877
  logger.header("Clone Theme Source");
3878
+ if (options.bucket || options.environment) {
3879
+ logger.warning(
3880
+ "--bucket and --environment are deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
3881
+ );
3882
+ }
3883
+ const tokens = await getValidTokens();
3884
+ if (!tokens) {
3885
+ logger.error("Not logged in. Run: onexthm login");
3886
+ process.exit(1);
3887
+ }
4136
3888
  let newName = options.name;
4137
3889
  if (!newName) {
4138
3890
  newName = await promptThemeName(themeName);
4139
3891
  }
4140
3892
  const spinner = ora__default.default("Initializing clone...").start();
4141
3893
  try {
4142
- const bucket = options.bucket || getBucketName3(options.environment);
3894
+ const apiUrl = getApiUrl();
4143
3895
  const outputDir = options.output || path9__default.default.resolve(process.cwd(), newName);
4144
- const s3Client = getS3Client3();
4145
3896
  if (await fs__default.default.pathExists(outputDir)) {
4146
3897
  spinner.fail(chalk4__default.default.red(`Directory already exists: ${outputDir}`));
4147
3898
  logger.info(
@@ -4154,28 +3905,20 @@ async function cloneCommand(themeName, options) {
4154
3905
  let version2 = options.version || "latest";
4155
3906
  if (version2 === "latest") {
4156
3907
  spinner.text = "Resolving latest version...";
4157
- version2 = await resolveLatestVersion2(s3Client, bucket, themeName);
3908
+ version2 = await resolveLatestVersion2(apiUrl, themeName);
4158
3909
  spinner.succeed(`Resolved latest version: ${chalk4__default.default.cyan(version2)}`);
4159
3910
  }
4160
3911
  spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
4161
- const s3Key = `themes/${themeName}/${version2}/source.zip`;
4162
3912
  let zipBuffer;
4163
3913
  try {
4164
- const response = await s3Client.send(
4165
- new clientS3.GetObjectCommand({
4166
- Bucket: bucket,
4167
- Key: s3Key
4168
- })
4169
- );
4170
- zipBuffer = await streamToBuffer2(response.Body);
3914
+ zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
4171
3915
  } catch (error) {
4172
- spinner.fail(chalk4__default.default.red(`Source not found: s3://${bucket}/${s3Key}`));
3916
+ spinner.fail(chalk4__default.default.red(error.message));
4173
3917
  console.log();
4174
3918
  console.log(
4175
- chalk4__default.default.yellow("The theme source may not have been uploaded yet.")
4176
- );
4177
- console.log(
4178
- chalk4__default.default.gray(`Upload source with: onexthm upload --theme ${themeName}`)
3919
+ chalk4__default.default.gray(
3920
+ `Verify the theme is published: curl -s ${apiUrl}/website-api/themes/${themeName}/versions`
3921
+ )
4179
3922
  );
4180
3923
  console.log();
4181
3924
  process.exit(1);
@@ -4358,10 +4101,7 @@ function createDevServer(options) {
4358
4101
  return;
4359
4102
  }
4360
4103
  if (segments.length > 1) {
4361
- const fallbackPath = path9__default.default.join(
4362
- assetsBase,
4363
- segments.slice(1).join("/")
4364
- );
4104
+ const fallbackPath = path9__default.default.join(assetsBase, segments.slice(1).join("/"));
4365
4105
  if (fallbackPath.startsWith(assetsBase) && fs3__default.default.existsSync(fallbackPath)) {
4366
4106
  serveFile(res, fallbackPath);
4367
4107
  return;
@@ -4528,12 +4268,12 @@ async function devCommand(options) {
4528
4268
  process.exit(1);
4529
4269
  }
4530
4270
  } else {
4531
- const isThemeDir = [
4271
+ const isThemeDir2 = [
4532
4272
  "theme.config.ts",
4533
4273
  "bundle-entry.ts",
4534
4274
  "manifest.ts"
4535
4275
  ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
4536
- if (isThemeDir) {
4276
+ if (isThemeDir2) {
4537
4277
  themePath = process.cwd();
4538
4278
  themeName = path9__default.default.basename(themePath);
4539
4279
  } else {
@@ -4961,12 +4701,12 @@ async function publishCommand(options) {
4961
4701
  if (options.theme) {
4962
4702
  themePath = path9__default.default.resolve(options.theme);
4963
4703
  } else {
4964
- const isThemeDir = [
4704
+ const isThemeDir2 = [
4965
4705
  "theme.config.ts",
4966
4706
  "bundle-entry.ts",
4967
4707
  "manifest.ts"
4968
4708
  ].some((f) => fs__default.default.existsSync(path9__default.default.join(process.cwd(), f)));
4969
- if (isThemeDir) {
4709
+ if (isThemeDir2) {
4970
4710
  themePath = process.cwd();
4971
4711
  } else {
4972
4712
  logger.error(
@@ -5299,11 +5039,11 @@ Or use the --bump flag:
5299
5039
  logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
5300
5040
  }
5301
5041
  async function createZip(sourceDir, outputPath, exclude) {
5302
- const archiver3 = (await import('archiver')).default;
5042
+ const archiver2 = (await import('archiver')).default;
5303
5043
  const { createWriteStream } = await import('fs');
5304
5044
  return new Promise((resolve, reject) => {
5305
5045
  const output = createWriteStream(outputPath);
5306
- const archive = archiver3("zip", { zlib: { level: 9 } });
5046
+ const archive = archiver2("zip", { zlib: { level: 9 } });
5307
5047
  output.on("close", resolve);
5308
5048
  archive.on("error", reject);
5309
5049
  archive.pipe(output);
@@ -5316,6 +5056,210 @@ async function createZip(sourceDir, outputPath, exclude) {
5316
5056
  });
5317
5057
  }
5318
5058
 
5059
+ // src/commands/mcp.ts
5060
+ init_logger();
5061
+ var AI_CONTEXT_FILES = [
5062
+ "CLAUDE.md",
5063
+ "AGENTS.md",
5064
+ ".cursorrules",
5065
+ "THEME_REFERENCE.md",
5066
+ ".mcp.json"
5067
+ ];
5068
+ function resolveTargetDir(opts) {
5069
+ return path9__default.default.resolve(opts.cwd ?? process.cwd());
5070
+ }
5071
+ function resolveDefaultTemplateDir() {
5072
+ return path9__default.default.join(getTemplatesDir(), "default");
5073
+ }
5074
+ function isThemeDir(dir) {
5075
+ return fs__default.default.existsSync(path9__default.default.join(dir, "theme.config.ts")) || fs__default.default.existsSync(path9__default.default.join(dir, "theme.config.js"));
5076
+ }
5077
+ function inspectFiles(templateDir, targetDir) {
5078
+ return AI_CONTEXT_FILES.map((name) => {
5079
+ const templatePath = path9__default.default.join(templateDir, name);
5080
+ const targetPath = path9__default.default.join(targetDir, name);
5081
+ const exists = fs__default.default.existsSync(targetPath);
5082
+ let identical = false;
5083
+ if (exists && fs__default.default.existsSync(templatePath)) {
5084
+ try {
5085
+ const a = fs__default.default.readFileSync(templatePath, "utf-8");
5086
+ const b = fs__default.default.readFileSync(targetPath, "utf-8");
5087
+ identical = a.replace(/\r\n/g, "\n") === b.replace(/\r\n/g, "\n");
5088
+ } catch {
5089
+ identical = false;
5090
+ }
5091
+ }
5092
+ return { name, templatePath, targetPath, exists, identical };
5093
+ });
5094
+ }
5095
+ async function mcpSetupCommand(options = {}) {
5096
+ const targetDir = resolveTargetDir(options);
5097
+ const templateDir = resolveDefaultTemplateDir();
5098
+ logger.header("Install OneX MCP context files");
5099
+ logger.log(`Target: ${targetDir}`);
5100
+ logger.log("");
5101
+ if (!isThemeDir(targetDir)) {
5102
+ logger.error(
5103
+ `${targetDir} does not look like an OneX theme (no theme.config.ts found).`
5104
+ );
5105
+ logger.log("Run this command from the root of your theme project.");
5106
+ process.exitCode = 1;
5107
+ return;
5108
+ }
5109
+ const statuses = inspectFiles(templateDir, targetDir);
5110
+ const missing = statuses.filter((s) => !s.exists);
5111
+ if (missing.length === 0) {
5112
+ logger.success("All AI context files are already present.");
5113
+ logger.log("Run `onexthm mcp upgrade` to refresh them to the latest version.");
5114
+ return;
5115
+ }
5116
+ logger.log(`Will install ${missing.length} file(s):`);
5117
+ for (const s of missing) logger.log(` + ${s.name}`);
5118
+ logger.log("");
5119
+ if (!options.yes) {
5120
+ const { confirm } = await inquirer__default.default.prompt([
5121
+ {
5122
+ type: "confirm",
5123
+ name: "confirm",
5124
+ message: "Proceed?",
5125
+ default: true
5126
+ }
5127
+ ]);
5128
+ if (!confirm) {
5129
+ logger.log("Cancelled.");
5130
+ return;
5131
+ }
5132
+ }
5133
+ for (const s of missing) {
5134
+ if (!fs__default.default.existsSync(s.templatePath)) {
5135
+ logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5136
+ continue;
5137
+ }
5138
+ await fs__default.default.copy(s.templatePath, s.targetPath);
5139
+ logger.success(` \u2713 ${s.name}`);
5140
+ }
5141
+ logger.log("");
5142
+ logger.success("Done. Restart your AI client to pick up the new MCP server.");
5143
+ logger.log("");
5144
+ logger.log("Tip: if your theme uses the Figma MCP, edit .mcp.json and");
5145
+ logger.log("replace __FIGMA_API_KEY__ with your Figma personal access token.");
5146
+ }
5147
+ async function mcpUpgradeCommand(options = {}) {
5148
+ const targetDir = resolveTargetDir(options);
5149
+ const templateDir = resolveDefaultTemplateDir();
5150
+ logger.header("Upgrade OneX MCP context files");
5151
+ logger.log(`Target: ${targetDir}`);
5152
+ logger.log("");
5153
+ if (!isThemeDir(targetDir)) {
5154
+ logger.error(
5155
+ `${targetDir} does not look like an OneX theme (no theme.config.ts found).`
5156
+ );
5157
+ process.exitCode = 1;
5158
+ return;
5159
+ }
5160
+ const statuses = inspectFiles(templateDir, targetDir);
5161
+ const toUpgrade = statuses.filter(
5162
+ (s) => !s.identical && s.name !== ".mcp.json"
5163
+ );
5164
+ const mcpJsonStatus = statuses.find((s) => s.name === ".mcp.json");
5165
+ if (mcpJsonStatus && !mcpJsonStatus.exists) {
5166
+ logger.warning(
5167
+ ".mcp.json is missing. Run `onexthm mcp setup` to install it."
5168
+ );
5169
+ }
5170
+ if (toUpgrade.length === 0) {
5171
+ logger.success("All AI context files are already up to date.");
5172
+ return;
5173
+ }
5174
+ logger.log("Files to update:");
5175
+ for (const s of toUpgrade) {
5176
+ const tag = !s.exists ? "+ new" : "~ changed";
5177
+ logger.log(` ${tag} ${s.name}`);
5178
+ }
5179
+ logger.log("");
5180
+ logger.log("(.mcp.json is never auto-upgraded \u2014 edit by hand if needed.)");
5181
+ logger.log("");
5182
+ if (!options.yes) {
5183
+ const { confirm } = await inquirer__default.default.prompt([
5184
+ {
5185
+ type: "confirm",
5186
+ name: "confirm",
5187
+ message: "Overwrite the file(s) above?",
5188
+ default: true
5189
+ }
5190
+ ]);
5191
+ if (!confirm) {
5192
+ logger.log("Cancelled.");
5193
+ return;
5194
+ }
5195
+ }
5196
+ for (const s of toUpgrade) {
5197
+ if (!fs__default.default.existsSync(s.templatePath)) {
5198
+ logger.warning(` ! ${s.name} not in template \u2014 skipped`);
5199
+ continue;
5200
+ }
5201
+ await fs__default.default.copy(s.templatePath, s.targetPath, { overwrite: true });
5202
+ logger.success(` \u2713 ${s.name}`);
5203
+ }
5204
+ logger.log("");
5205
+ logger.success("Done. Restart your AI client to pick up the new context.");
5206
+ }
5207
+ async function mcpDoctorCommand(options = {}) {
5208
+ const targetDir = resolveTargetDir(options);
5209
+ const templateDir = resolveDefaultTemplateDir();
5210
+ logger.header("OneX MCP doctor");
5211
+ logger.log(`Target: ${targetDir}`);
5212
+ logger.log("");
5213
+ if (!isThemeDir(targetDir)) {
5214
+ logger.error("Not an OneX theme directory (no theme.config.ts).");
5215
+ process.exitCode = 1;
5216
+ return;
5217
+ }
5218
+ logger.success("theme.config.ts present");
5219
+ const mcpJsonPath = path9__default.default.join(targetDir, ".mcp.json");
5220
+ if (!fs__default.default.existsSync(mcpJsonPath)) {
5221
+ logger.error(".mcp.json missing \u2014 run `onexthm mcp setup`");
5222
+ } else {
5223
+ try {
5224
+ const mcpJson = JSON.parse(fs__default.default.readFileSync(mcpJsonPath, "utf-8"));
5225
+ const servers = mcpJson?.mcpServers ?? {};
5226
+ if (servers.onexthm) {
5227
+ logger.success(".mcp.json registers `onexthm`");
5228
+ } else {
5229
+ logger.error(".mcp.json does not register `onexthm`");
5230
+ }
5231
+ if (servers.figma) {
5232
+ const arg = (servers.figma.args ?? []).join(" ");
5233
+ if (arg.includes("__FIGMA_API_KEY__")) {
5234
+ logger.warning("figma server uses placeholder API key \u2014 replace __FIGMA_API_KEY__");
5235
+ } else {
5236
+ logger.success(".mcp.json registers `figma`");
5237
+ }
5238
+ }
5239
+ } catch (err) {
5240
+ logger.error(`.mcp.json could not be parsed: ${err.message}`);
5241
+ }
5242
+ }
5243
+ const statuses = inspectFiles(templateDir, targetDir).filter(
5244
+ (s) => s.name !== ".mcp.json"
5245
+ );
5246
+ for (const s of statuses) {
5247
+ if (!s.exists) {
5248
+ logger.warning(`${s.name} missing`);
5249
+ } else if (!s.identical) {
5250
+ logger.warning(`${s.name} is out of date \u2014 run \`onexthm mcp upgrade\``);
5251
+ } else {
5252
+ logger.success(`${s.name} up to date`);
5253
+ }
5254
+ }
5255
+ const registryPath = path9__default.default.join(targetDir, "sections-registry.ts");
5256
+ if (fs__default.default.existsSync(registryPath)) {
5257
+ logger.success("sections-registry.ts present");
5258
+ } else {
5259
+ logger.warning("sections-registry.ts missing \u2014 section tools won't auto-register");
5260
+ }
5261
+ }
5262
+
5319
5263
  // src/cli.ts
5320
5264
  dotenv__default.default.config({
5321
5265
  path: path9__default.default.join(process.cwd(), ".env.local"),
@@ -5366,33 +5310,25 @@ program.command("deploy").description("Upload theme package to API server").opti
5366
5310
  "-e, --environment <env>",
5367
5311
  "Environment (production, staging, development)"
5368
5312
  ).action(deployCommand);
5369
- 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(
5370
- "-e, --environment <env>",
5371
- "Environment (staging|production)",
5372
- "staging"
5373
- ).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);
5374
- program.command("download").description("Download theme from S3 bucket").option("-t, --theme-id <id>", "Theme ID to download").option(
5313
+ 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);
5314
+ program.command("download").description("Download a published theme via the website-api").option("-t, --theme-id <id>", "Theme ID to download").option(
5375
5315
  "-v, --version <version>",
5376
5316
  "Theme version (default: latest)",
5377
5317
  "latest"
5378
- ).option("-b, --bucket <name>", "S3 bucket name").option(
5379
- "-e, --environment <env>",
5380
- "Environment (staging|production)",
5381
- "staging"
5382
- ).option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5383
- program.command("clone").description("Clone theme source code from S3").argument("<theme-name>", "Theme to clone").option(
5318
+ ).option("-b, --bucket <name>", "[deprecated] ignored").option("-e, --environment <env>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
5319
+ program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
5384
5320
  "-v, --version <version>",
5385
5321
  "Theme version (default: latest)",
5386
5322
  "latest"
5387
- ).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option("-b, --bucket <name>", "S3 bucket name").option(
5388
- "-e, --environment <env>",
5389
- "Environment (staging|production)",
5390
- "staging"
5391
- ).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
5323
+ ).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);
5392
5324
  program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
5393
5325
  program.command("login").description("Login to OneX platform").action(loginCommand);
5394
5326
  program.command("logout").description("Logout from OneX platform").action(logoutCommand);
5395
5327
  program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
5328
+ var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
5329
+ mcpCmd.command("setup").description("Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme").option("-y, --yes", "Skip confirmation prompts").action(mcpSetupCommand);
5330
+ mcpCmd.command("upgrade").description("Refresh AI-context files to the latest version from the bundled template").option("-y, --yes", "Skip confirmation prompts").action(mcpUpgradeCommand);
5331
+ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme directory").action(mcpDoctorCommand);
5396
5332
  program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
5397
5333
  "--bump <type>",
5398
5334
  "Auto-bump version before publish (patch|minor|major)"