@mindexec/cli 0.2.371 → 0.2.372

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/package.json +1 -1
  2. package/server.js +448 -5
  3. package/wwwroot/assets/{AdminDashboardPage-3j_fLtJI.js → AdminDashboardPage-Cz9oqmp1.js} +1 -1
  4. package/wwwroot/assets/{AppSidebar-NgcRUfE7.js → AppSidebar-CCyfl-_8.js} +2 -2
  5. package/wwwroot/assets/{AuthPages-Cnj1SLJh.js → AuthPages-CkWEDGgC.js} +1 -1
  6. package/wwwroot/assets/{CodePage-Do4K07Ge.js → CodePage-D0CxncU0.js} +1 -1
  7. package/wwwroot/assets/{CompanyCorePage-iD32axX4.js → CompanyCorePage-C8AAF9MJ.js} +1 -1
  8. package/wwwroot/assets/{ExecutionModePage-BHn-i9eu.js → ExecutionModePage-DM4G1xkT.js} +1 -1
  9. package/wwwroot/assets/{MindCanvas-0T8dFIuQ.css → MindCanvas-D7mLWp5-.css} +1 -1
  10. package/wwwroot/assets/MindCanvas-g37obZMq.js +48 -0
  11. package/wwwroot/assets/{PlanMasterPage-B9XXbERQ.js → PlanMasterPage-axrFePG0.js} +4 -4
  12. package/wwwroot/assets/{PricingPage-Cgbt5ZmJ.js → PricingPage-BwayR6ga.js} +1 -1
  13. package/wwwroot/assets/{ToolPages-DLme_QLb.js → ToolPages-CTVDo24Z.js} +5 -5
  14. package/wwwroot/assets/{YouTubeSearchPage-BSQIhWnY.js → YouTubeSearchPage-BWA45eoM.js} +2 -2
  15. package/wwwroot/assets/canvas-runtime-CddXXjp9.js +44 -0
  16. package/wwwroot/assets/{index-D32iV1LN.js → index-Bc6exjGV.js} +2 -2
  17. package/wwwroot/assets/{storage-C8yTm9Oa.js → storage-BPqBi6qs.js} +1 -1
  18. package/wwwroot/assets/{supabaseAuthAdapter-C1dNnpk8.js → supabaseAuthAdapter-bSjZe3In.js} +1 -1
  19. package/wwwroot/index.html +2 -2
  20. package/wwwroot/assets/MindCanvas-uwqW1baW.js +0 -48
  21. package/wwwroot/assets/canvas-runtime-CcSvL_l6.js +0 -44
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.371",
3
+ "version": "0.2.372",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
package/server.js CHANGED
@@ -2198,10 +2198,11 @@ const PROTECTED_BRIDGE_ROUTES = [
2198
2198
  { method: 'GET', exact: '/api/concept/embed/readiness' },
2199
2199
  { method: 'POST', exact: '/api/concept/embed' },
2200
2200
  { method: 'POST', prefix: '/api/browser/act' },
2201
- { method: 'GET', prefix: '/api/company-core/' },
2201
+ { method: 'GET', prefix: '/api/company-core/' },
2202
2202
  { method: 'POST', prefix: '/api/company-core/' },
2203
2203
  { method: 'PUT', prefix: '/api/company-core/' },
2204
2204
  { method: 'DELETE', prefix: '/api/company-core/' },
2205
+ { method: 'GET', prefix: '/api/model-catalog' },
2205
2206
  { method: 'GET', prefix: '/api/codex/' },
2206
2207
  { method: 'POST', prefix: '/api/codex/' },
2207
2208
  { method: 'GET', prefix: '/api/shell/' },
@@ -3231,14 +3232,78 @@ function extractCodexOutputPath(command) {
3231
3232
  return match[2] || match[3] || match[4] || null;
3232
3233
  }
3233
3234
 
3234
- const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
3235
- const CODEX_MODEL_CATALOG_TTL_MS = 15 * 60 * 1000;
3236
- let cachedCodexConfig = null;
3237
- let cachedCodexConfigMtimeMs = -1;
3235
+ const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
3236
+ const CODEX_MODEL_CATALOG_TTL_MS = 15 * 60 * 1000;
3237
+ const EXTERNAL_MODEL_CATALOG_TTL_MS = 60 * 1000;
3238
+ let cachedCodexConfig = null;
3239
+ let cachedCodexConfigMtimeMs = -1;
3238
3240
  let cachedCodexModelCatalog = null;
3239
3241
  let cachedCodexModelCatalogExpiresAt = 0;
3242
+ let cachedBridgeModelCatalog = null;
3243
+ let cachedBridgeModelCatalogExpiresAt = 0;
3240
3244
  let lastCodexExecRuntime = null;
3241
3245
  let codexRuntime = null;
3246
+
3247
+ const FALLBACK_CODEX_TEXT_MODELS = [
3248
+ { id: 'gpt-5.5', displayName: 'GPT-5.5' },
3249
+ { id: 'gpt-5.4', displayName: 'GPT-5.4' },
3250
+ { id: 'gpt-5.4-mini', displayName: 'GPT-5.4 mini' },
3251
+ { id: 'gpt-5.3-codex-spark', displayName: 'GPT-5.3 Codex Spark' }
3252
+ ];
3253
+
3254
+ const FALLBACK_OPENROUTER_TEXT_MODELS = [
3255
+ { id: 'openrouter/auto', displayName: 'Auto Router', isFallback: true },
3256
+ { id: 'openai/gpt-chat-latest', displayName: 'GPT Chat Latest', isFallback: true },
3257
+ { id: 'anthropic/claude-sonnet-4.5', displayName: 'Claude Sonnet 4.5', supportsReasoning: true, isFallback: true },
3258
+ { id: 'google/gemini-2.5-pro', displayName: 'Gemini 2.5 Pro', supportsReasoning: true, isFallback: true },
3259
+ { id: 'google/gemini-2.5-flash', displayName: 'Gemini 2.5 Flash', isFallback: true }
3260
+ ];
3261
+
3262
+ const FALLBACK_IMAGE_MODELS = [
3263
+ {
3264
+ id: 'gpt-image-2',
3265
+ label: 'GPT Image 2',
3266
+ displayName: 'GPT Image 2',
3267
+ provider: 'openai',
3268
+ mode: 'image',
3269
+ supportedSizes: ['custom'],
3270
+ vendorPricing: { averageUsd: 0.02, source: 'fallback' },
3271
+ isFallback: true
3272
+ },
3273
+ {
3274
+ id: 'Tongyi-MAI/Z-Image-Turbo:free',
3275
+ label: 'Z-Image Turbo',
3276
+ displayName: 'Z-Image Turbo',
3277
+ provider: 'imagerouter',
3278
+ mode: 'image',
3279
+ supportedSizes: ['custom'],
3280
+ vendorPricing: { averageUsd: 0.02, source: 'fallback' },
3281
+ isFallback: true
3282
+ }
3283
+ ];
3284
+
3285
+ const FALLBACK_VIDEO_MODELS = [
3286
+ {
3287
+ id: 'google/veo-2',
3288
+ label: 'Google Veo 2',
3289
+ displayName: 'Google Veo 2',
3290
+ provider: 'imagerouter',
3291
+ mode: 'video',
3292
+ supportedSizes: ['1280x720', '720x1280'],
3293
+ vendorPricing: { averageUsd: 0.25, maxUsd: 0.25, source: 'fallback' },
3294
+ isFallback: true
3295
+ },
3296
+ {
3297
+ id: 'runway/gen-3',
3298
+ label: 'Runway Gen-3',
3299
+ displayName: 'Runway Gen-3',
3300
+ provider: 'imagerouter',
3301
+ mode: 'video',
3302
+ supportedSizes: ['1280x720', '720x1280'],
3303
+ vendorPricing: { averageUsd: 0.20, maxUsd: 0.20, source: 'fallback' },
3304
+ isFallback: true
3305
+ }
3306
+ ];
3242
3307
 
3243
3308
  function readCodexConfig(options = {}) {
3244
3309
  const forceRefresh = Boolean(options.forceRefresh);
@@ -3409,6 +3474,371 @@ async function getCodexModelCatalog(options = {}) {
3409
3474
  }
3410
3475
  }
3411
3476
 
3477
+ function getPlainString(value) {
3478
+ return String(value ?? '').trim();
3479
+ }
3480
+
3481
+ function getCatalogString(item, propertyName) {
3482
+ if (!item || typeof item !== 'object') {
3483
+ return '';
3484
+ }
3485
+
3486
+ return getPlainString(item[propertyName]);
3487
+ }
3488
+
3489
+ function getCatalogNumber(item, propertyName) {
3490
+ if (!item || typeof item !== 'object') {
3491
+ return null;
3492
+ }
3493
+
3494
+ const value = Number(item[propertyName]);
3495
+ return Number.isFinite(value) ? value : null;
3496
+ }
3497
+
3498
+ function getCatalogStringArray(item, propertyName) {
3499
+ if (!item || typeof item !== 'object' || !Array.isArray(item[propertyName])) {
3500
+ return [];
3501
+ }
3502
+
3503
+ return [...new Set(item[propertyName]
3504
+ .map(entry => getPlainString(entry))
3505
+ .filter(Boolean))];
3506
+ }
3507
+
3508
+ function getNestedCatalogStringArray(item, objectPropertyName, arrayPropertyName) {
3509
+ if (!item || typeof item !== 'object') {
3510
+ return [];
3511
+ }
3512
+
3513
+ return getCatalogStringArray(item[objectPropertyName], arrayPropertyName);
3514
+ }
3515
+
3516
+ function getNestedCatalogBoolean(item, objectPropertyName, propertyName) {
3517
+ if (!item || typeof item !== 'object') {
3518
+ return false;
3519
+ }
3520
+
3521
+ const parent = item[objectPropertyName];
3522
+ if (!parent || typeof parent !== 'object') {
3523
+ return false;
3524
+ }
3525
+
3526
+ const value = parent[propertyName];
3527
+ return value === true || /^(1|true|yes|on)$/i.test(String(value ?? ''));
3528
+ }
3529
+
3530
+ function getCatalogDecimal(item, propertyName) {
3531
+ const value = getCatalogNumber(item, propertyName);
3532
+ return value === null ? undefined : value;
3533
+ }
3534
+
3535
+ function getOpenRouterPricing(item) {
3536
+ const pricing = item?.pricing;
3537
+ if (!pricing || typeof pricing !== 'object') {
3538
+ return null;
3539
+ }
3540
+
3541
+ const promptUsdPerToken = getCatalogDecimal(pricing, 'prompt');
3542
+ const completionUsdPerToken = getCatalogDecimal(pricing, 'completion');
3543
+ const requestUsd = getCatalogDecimal(pricing, 'request');
3544
+ if (promptUsdPerToken === undefined && completionUsdPerToken === undefined && requestUsd === undefined) {
3545
+ return null;
3546
+ }
3547
+
3548
+ return {
3549
+ promptUsdPerToken,
3550
+ completionUsdPerToken,
3551
+ requestUsd,
3552
+ source: 'openrouter',
3553
+ fetchedAt: new Date().toISOString()
3554
+ };
3555
+ }
3556
+
3557
+ function getImageRouterPricing(item) {
3558
+ const price = item?.price;
3559
+ if (!price || typeof price !== 'object') {
3560
+ return null;
3561
+ }
3562
+
3563
+ const minUsd = getCatalogDecimal(price, 'min');
3564
+ const averageUsd = getCatalogDecimal(price, 'average');
3565
+ const maxUsd = getCatalogDecimal(price, 'max');
3566
+ if (minUsd === undefined && averageUsd === undefined && maxUsd === undefined) {
3567
+ return null;
3568
+ }
3569
+
3570
+ return {
3571
+ minUsd,
3572
+ averageUsd,
3573
+ maxUsd,
3574
+ source: 'imagerouter',
3575
+ fetchedAt: new Date().toISOString()
3576
+ };
3577
+ }
3578
+
3579
+ function buildCatalogDisplayLabel(name, id) {
3580
+ const label = getPlainString(name);
3581
+ const modelId = getPlainString(id);
3582
+ if (!label || label.toLowerCase() === modelId.toLowerCase()) {
3583
+ return modelId;
3584
+ }
3585
+
3586
+ return `${label} (${modelId})`;
3587
+ }
3588
+
3589
+ function containsCatalogString(item, propertyName, expected) {
3590
+ return getCatalogStringArray(item, propertyName)
3591
+ .some(value => value.toLowerCase() === String(expected).toLowerCase());
3592
+ }
3593
+
3594
+ function containsAnyCatalogString(item, propertyName, expectedValues) {
3595
+ const expected = new Set(expectedValues.map(value => String(value).toLowerCase()));
3596
+ return getCatalogStringArray(item, propertyName)
3597
+ .some(value => expected.has(value.toLowerCase()));
3598
+ }
3599
+
3600
+ function hasCatalogMediaModalityInElement(item) {
3601
+ const modality = getCatalogString(item, 'modality').toLowerCase();
3602
+ return containsAnyCatalogString(item, 'input_modalities', ['image', 'video', 'audio'])
3603
+ || containsAnyCatalogString(item, 'output_modalities', ['image', 'video', 'audio'])
3604
+ || /image|video|audio/i.test(modality);
3605
+ }
3606
+
3607
+ function supportsOpenRouterTextOnlyOutput(item) {
3608
+ const architecture = item?.architecture && typeof item.architecture === 'object'
3609
+ ? item.architecture
3610
+ : null;
3611
+ const supportsText = containsCatalogString(item, 'output_modalities', 'text')
3612
+ || (architecture && containsCatalogString(architecture, 'output_modalities', 'text'));
3613
+ const hasMedia = hasCatalogMediaModalityInElement(item)
3614
+ || (architecture && hasCatalogMediaModalityInElement(architecture));
3615
+ return supportsText && !hasMedia;
3616
+ }
3617
+
3618
+ function normalizeOpenRouterModel(item) {
3619
+ const id = getCatalogString(item, 'id');
3620
+ if (!id || !supportsOpenRouterTextOnlyOutput(item)) {
3621
+ return null;
3622
+ }
3623
+
3624
+ const name = getCatalogString(item, 'name');
3625
+ const supportedParameters = getCatalogStringArray(item, 'supported_parameters');
3626
+ return {
3627
+ id,
3628
+ label: buildCatalogDisplayLabel(name, id),
3629
+ displayName: name || id,
3630
+ provider: 'openrouter',
3631
+ mode: 'text',
3632
+ supportsReasoning: supportedParameters.some(value => value.toLowerCase() === 'reasoning'),
3633
+ vendorPricing: getOpenRouterPricing(item),
3634
+ source: 'openrouter'
3635
+ };
3636
+ }
3637
+
3638
+ async function fetchJsonWithTimeout(url, timeoutMs = 10000) {
3639
+ const controller = new AbortController();
3640
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
3641
+ try {
3642
+ const response = await fetch(url, {
3643
+ headers: {
3644
+ Accept: 'application/json',
3645
+ 'Cache-Control': 'no-store',
3646
+ Pragma: 'no-cache'
3647
+ },
3648
+ signal: controller.signal
3649
+ });
3650
+ if (!response.ok) {
3651
+ throw new Error(`HTTP ${response.status}`);
3652
+ }
3653
+ return await response.json();
3654
+ } finally {
3655
+ clearTimeout(timeout);
3656
+ }
3657
+ }
3658
+
3659
+ async function getOpenRouterTextModelCatalog() {
3660
+ const payload = await fetchJsonWithTimeout('https://openrouter.ai/api/v1/models', 10000);
3661
+ const items = Array.isArray(payload?.data) ? payload.data : [];
3662
+ const options = items
3663
+ .map(normalizeOpenRouterModel)
3664
+ .filter(Boolean)
3665
+ .sort((a, b) => String(a.label).localeCompare(String(b.label)));
3666
+ return options.length > 0
3667
+ ? options
3668
+ : FALLBACK_OPENROUTER_TEXT_MODELS.map(option => ({
3669
+ id: option.id,
3670
+ label: option.displayName,
3671
+ displayName: option.displayName,
3672
+ provider: 'openrouter',
3673
+ mode: 'text',
3674
+ supportsReasoning: option.supportsReasoning === true,
3675
+ isFallback: true,
3676
+ source: 'fallback'
3677
+ }));
3678
+ }
3679
+
3680
+ function normalizeImageRouterModel(item, outputType) {
3681
+ const id = getCatalogString(item, 'id');
3682
+ if (!id) {
3683
+ return null;
3684
+ }
3685
+
3686
+ const displayName = getCatalogString(item, 'name') || id;
3687
+ const supportedSizes = getNestedCatalogStringArray(item, 'inputs', 'size');
3688
+ const supportedSeconds = getNestedCatalogStringArray(item, 'inputs', 'seconds');
3689
+ return {
3690
+ id,
3691
+ label: displayName,
3692
+ displayName,
3693
+ provider: 'imagerouter',
3694
+ mode: outputType,
3695
+ supportedSizes: supportedSizes.length > 0 ? supportedSizes : (outputType === 'image' ? ['custom'] : []),
3696
+ supportedSeconds,
3697
+ capabilities: {
3698
+ supportsTextToImage: outputType === 'image',
3699
+ supportsImageToImage: getNestedCatalogBoolean(item, 'inputs', 'image'),
3700
+ supportsInpaint: getNestedCatalogBoolean(item, 'inputs', 'mask'),
3701
+ supportsSizeSelection: supportedSizes.length > 0,
3702
+ supportedSizes,
3703
+ supportedSeconds
3704
+ },
3705
+ vendorPricing: getImageRouterPricing(item),
3706
+ source: 'imagerouter'
3707
+ };
3708
+ }
3709
+
3710
+ async function getImageRouterModelCatalog(outputType) {
3711
+ const endpoint = new URL('https://api.imagerouter.io/v2/models');
3712
+ endpoint.searchParams.set('outputType', outputType);
3713
+ endpoint.searchParams.set('limit', '200');
3714
+ endpoint.searchParams.set('fields', 'name,price,inputs');
3715
+
3716
+ const payload = await fetchJsonWithTimeout(endpoint.toString(), 10000);
3717
+ const items = Array.isArray(payload)
3718
+ ? payload
3719
+ : Array.isArray(payload?.data)
3720
+ ? payload.data
3721
+ : [];
3722
+ const options = items
3723
+ .map(item => normalizeImageRouterModel(item, outputType))
3724
+ .filter(Boolean)
3725
+ .sort((a, b) => String(a.label).localeCompare(String(b.label)));
3726
+
3727
+ if (options.length > 0) {
3728
+ return options;
3729
+ }
3730
+
3731
+ return (outputType === 'image' ? FALLBACK_IMAGE_MODELS : FALLBACK_VIDEO_MODELS)
3732
+ .map(option => ({ ...option, source: 'fallback' }));
3733
+ }
3734
+
3735
+ function normalizeCodexCatalogOption(model) {
3736
+ if (!model || typeof model !== 'object') {
3737
+ return null;
3738
+ }
3739
+
3740
+ const id = getCatalogString(model, 'id');
3741
+ if (!id) {
3742
+ return null;
3743
+ }
3744
+
3745
+ const displayName = getCatalogString(model, 'displayName') || id;
3746
+ return {
3747
+ id,
3748
+ label: `OpenAI Codex - ${displayName}`,
3749
+ displayName,
3750
+ description: getCatalogString(model, 'description'),
3751
+ provider: 'codex',
3752
+ mode: 'text',
3753
+ supportsReasoning: true,
3754
+ source: 'codex debug models',
3755
+ priority: getCatalogNumber(model, 'priority') ?? 999,
3756
+ defaultReasoningLevel: getCatalogString(model, 'defaultReasoningLevel'),
3757
+ supportedReasoningLevels: getCatalogStringArray(model, 'supportedReasoningLevels'),
3758
+ supportedInApi: model.supportedInApi === true
3759
+ };
3760
+ }
3761
+
3762
+ async function getBridgeModelCatalog(options = {}) {
3763
+ const now = Date.now();
3764
+ if (!options.forceRefresh && cachedBridgeModelCatalog && cachedBridgeModelCatalogExpiresAt > now) {
3765
+ return cachedBridgeModelCatalog;
3766
+ }
3767
+
3768
+ const errors = [];
3769
+ const codexCatalog = await getCodexModelCatalog({ forceRefresh: options.forceRefresh });
3770
+ if (codexCatalog?.error) {
3771
+ errors.push(`codex: ${codexCatalog.error}`);
3772
+ }
3773
+
3774
+ let codexTextModels = (codexCatalog?.models || [])
3775
+ .map(normalizeCodexCatalogOption)
3776
+ .filter(Boolean)
3777
+ .sort((a, b) => (a.priority ?? 999) - (b.priority ?? 999) || a.id.localeCompare(b.id));
3778
+ if (codexTextModels.length === 0) {
3779
+ codexTextModels = FALLBACK_CODEX_TEXT_MODELS.map((model, index) => ({
3780
+ id: model.id,
3781
+ label: `OpenAI Codex - ${model.displayName}`,
3782
+ displayName: model.displayName,
3783
+ provider: 'codex',
3784
+ mode: 'text',
3785
+ supportsReasoning: true,
3786
+ priority: index,
3787
+ isFallback: true,
3788
+ source: 'fallback'
3789
+ }));
3790
+ }
3791
+
3792
+ const [openRouterResult, imageResult, videoResult] = await Promise.allSettled([
3793
+ getOpenRouterTextModelCatalog(),
3794
+ getImageRouterModelCatalog('image'),
3795
+ getImageRouterModelCatalog('video')
3796
+ ]);
3797
+
3798
+ const openRouterTextModels = openRouterResult.status === 'fulfilled'
3799
+ ? openRouterResult.value
3800
+ : FALLBACK_OPENROUTER_TEXT_MODELS.map(option => ({
3801
+ id: option.id,
3802
+ label: option.displayName,
3803
+ displayName: option.displayName,
3804
+ provider: 'openrouter',
3805
+ mode: 'text',
3806
+ supportsReasoning: option.supportsReasoning === true,
3807
+ isFallback: true,
3808
+ source: 'fallback'
3809
+ }));
3810
+ if (openRouterResult.status === 'rejected') {
3811
+ errors.push(`openrouter: ${openRouterResult.reason?.message || String(openRouterResult.reason)}`);
3812
+ }
3813
+
3814
+ const imageModels = imageResult.status === 'fulfilled'
3815
+ ? imageResult.value
3816
+ : FALLBACK_IMAGE_MODELS.map(option => ({ ...option, source: 'fallback' }));
3817
+ if (imageResult.status === 'rejected') {
3818
+ errors.push(`imagerouter:image: ${imageResult.reason?.message || String(imageResult.reason)}`);
3819
+ }
3820
+
3821
+ const videoModels = videoResult.status === 'fulfilled'
3822
+ ? videoResult.value
3823
+ : FALLBACK_VIDEO_MODELS.map(option => ({ ...option, source: 'fallback' }));
3824
+ if (videoResult.status === 'rejected') {
3825
+ errors.push(`imagerouter:video: ${videoResult.reason?.message || String(videoResult.reason)}`);
3826
+ }
3827
+
3828
+ const updatedAt = new Date().toISOString();
3829
+ cachedBridgeModelCatalog = {
3830
+ success: true,
3831
+ textModels: [...codexTextModels, ...openRouterTextModels],
3832
+ imageModels,
3833
+ videoModels,
3834
+ source: 'localbridge-model-catalog',
3835
+ updatedAt,
3836
+ errors
3837
+ };
3838
+ cachedBridgeModelCatalogExpiresAt = now + EXTERNAL_MODEL_CATALOG_TTL_MS;
3839
+ return cachedBridgeModelCatalog;
3840
+ }
3841
+
3412
3842
  function getCodexRuntime() {
3413
3843
  if (!codexRuntime) {
3414
3844
  codexRuntime = createCodexRuntime({
@@ -13601,6 +14031,19 @@ app.get('/api/codex/capabilities', async (req, res) => {
13601
14031
  }
13602
14032
  });
13603
14033
 
14034
+ app.get('/api/model-catalog', async (req, res) => {
14035
+ try {
14036
+ const forceRefresh = /^(1|true|yes|on)$/i.test(String(req.query?.refresh || ''));
14037
+ res.json(await getBridgeModelCatalog({ forceRefresh }));
14038
+ } catch (err) {
14039
+ console.error('[ModelCatalog] Error:', err);
14040
+ res.status(500).json({
14041
+ success: false,
14042
+ error: err?.message || String(err)
14043
+ });
14044
+ }
14045
+ });
14046
+
13604
14047
  app.post('/api/codex/thread/start', async (req, res) => {
13605
14048
  try {
13606
14049
  res.json(await getCodexRuntime().startThread(req.body || {}));