@trops/dash-core 0.1.129 → 0.1.131

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.
@@ -3,31 +3,31 @@
3
3
  var require$$0$1 = require('electron');
4
4
  var require$$1 = require('electron-store');
5
5
  var require$$1$1 = require('path');
6
- var require$$2 = require('fs');
6
+ var require$$0$2 = require('fs');
7
7
  var require$$5$1 = require('objects-to-csv');
8
8
  var require$$1$2 = require('readline');
9
- var require$$2$1 = require('xtreamer');
9
+ var require$$2 = require('xtreamer');
10
10
  var require$$3 = require('xml2js');
11
11
  var require$$4 = require('JSONStream');
12
12
  var require$$5 = require('stream');
13
13
  var require$$6 = require('csv-parser');
14
- var require$$0$2 = require('image-downloader');
15
- var require$$2$2 = require('get-pixels');
14
+ var require$$0$3 = require('image-downloader');
15
+ var require$$2$1 = require('get-pixels');
16
16
  var require$$3$1 = require('extract-colors');
17
17
  var require$$8 = require('https');
18
- var require$$0$3 = require('@modelcontextprotocol/sdk/client/index.js');
18
+ var require$$0$4 = require('@modelcontextprotocol/sdk/client/index.js');
19
19
  var require$$1$3 = require('@modelcontextprotocol/sdk/client/stdio.js');
20
- var require$$2$3 = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
20
+ var require$$2$2 = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
21
21
  var require$$5$2 = require('child_process');
22
- var require$$2$4 = require('algoliasearch');
22
+ var require$$2$3 = require('algoliasearch');
23
23
  var require$$3$2 = require('node:path');
24
- var require$$0$4 = require('openai');
24
+ var require$$0$5 = require('openai');
25
25
  require('live-plugin-manager');
26
- var require$$0$5 = require('@anthropic-ai/sdk');
26
+ var require$$0$6 = require('@anthropic-ai/sdk');
27
27
  var require$$3$3 = require('adm-zip');
28
- var require$$2$6 = require('os');
28
+ var require$$2$5 = require('os');
29
29
  var require$$4$1 = require('url');
30
- var require$$2$5 = require('vm');
30
+ var require$$2$4 = require('vm');
31
31
 
32
32
  function getDefaultExportFromCjs (x) {
33
33
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -139,6 +139,10 @@ const THEME_DELETE$1 = "theme-delete";
139
139
  const THEME_DELETE_COMPLETE = "theme-delete-complete";
140
140
  const THEME_DELETE_ERROR = "theme-delete-error";
141
141
 
142
+ const THEME_PUBLISH$1 = "theme-publish";
143
+ const THEME_INSTALL_FROM_REGISTRY$1 = "theme-install-from-registry";
144
+ const THEME_PUBLISH_PREVIEW$1 = "theme-publish-preview";
145
+
142
146
  var themeEvents$1 = {
143
147
  THEME_LIST: THEME_LIST$1,
144
148
  THEME_LIST_COMPLETE,
@@ -149,6 +153,9 @@ var themeEvents$1 = {
149
153
  THEME_DELETE: THEME_DELETE$1,
150
154
  THEME_DELETE_COMPLETE,
151
155
  THEME_DELETE_ERROR,
156
+ THEME_PUBLISH: THEME_PUBLISH$1,
157
+ THEME_INSTALL_FROM_REGISTRY: THEME_INSTALL_FROM_REGISTRY$1,
158
+ THEME_PUBLISH_PREVIEW: THEME_PUBLISH_PREVIEW$1,
152
159
  };
153
160
 
154
161
  const DATA_JSON_TO_CSV_FILE$1 = "data-json-to-csv-file";
@@ -424,6 +431,7 @@ const REGISTRY_SEARCH = "registry:search";
424
431
  const REGISTRY_GET_PACKAGE = "registry:get-package";
425
432
  const REGISTRY_CHECK_UPDATES = "registry:check-updates";
426
433
  const REGISTRY_SEARCH_DASHBOARDS = "registry:search-dashboards";
434
+ const REGISTRY_SEARCH_THEMES = "registry:search-themes";
427
435
 
428
436
  var registryEvents$1 = {
429
437
  REGISTRY_FETCH_INDEX,
@@ -431,6 +439,7 @@ var registryEvents$1 = {
431
439
  REGISTRY_GET_PACKAGE,
432
440
  REGISTRY_CHECK_UPDATES,
433
441
  REGISTRY_SEARCH_DASHBOARDS,
442
+ REGISTRY_SEARCH_THEMES,
434
443
  };
435
444
 
436
445
  /**
@@ -785,14 +794,14 @@ var events$8 = {
785
794
  * Open a dialog window for choosing files
786
795
  */
787
796
 
788
- const { dialog: dialog$1 } = require$$0$1;
797
+ const { dialog: dialog$2 } = require$$0$1;
789
798
  const events$7 = events$8;
790
799
 
791
800
  const showDialog$1 = async (win, message, allowFile, extensions = ["*"]) => {
792
801
  const properties =
793
802
  allowFile === true ? ["openFile"] : ["openDirectory", "createDirectory"];
794
803
  const filters = allowFile === true ? [{ name: "Data", extensions }] : [];
795
- const result = await dialog$1.showOpenDialog({ properties, filters });
804
+ const result = await dialog$2.showOpenDialog({ properties, filters });
796
805
  if (result.canceled || !result.filePaths[0]) return null;
797
806
  return result.filePaths[0];
798
807
  };
@@ -870,7 +879,7 @@ var secureStoreController$1 = {
870
879
  getData: getData$1,
871
880
  };
872
881
 
873
- const path$g = require$$1$1;
882
+ const path$h = require$$1$1;
874
883
  const {
875
884
  readFileSync,
876
885
  writeFileSync: writeFileSync$4,
@@ -883,12 +892,12 @@ const {
883
892
  unlinkSync,
884
893
  readdirSync,
885
894
  lstatSync,
886
- } = require$$2;
895
+ } = require$$0$2;
887
896
 
888
897
  function ensureDirectoryExistence$2(filePath) {
889
898
  try {
890
899
  // isDirectory
891
- var dirname = path$g.dirname(filePath);
900
+ var dirname = path$h.dirname(filePath);
892
901
  // check if the directory exists...return true
893
902
  // if not, we can pass in the dirname as the filepath
894
903
  // and check each directory recursively.
@@ -1003,7 +1012,7 @@ function removeFilesFromDirectory(directory, excludeFiles = []) {
1003
1012
 
1004
1013
  for (const file of files) {
1005
1014
  if (!excludeFiles.includes(file)) {
1006
- unlinkSync(path$g.join(directory, file), (err) => {
1015
+ unlinkSync(path$h.join(directory, file), (err) => {
1007
1016
  if (err) throw err;
1008
1017
  });
1009
1018
  }
@@ -1020,9 +1029,9 @@ var file = {
1020
1029
  checkDirectory: checkDirectory$1,
1021
1030
  };
1022
1031
 
1023
- const { app: app$9 } = require$$0$1;
1024
- const path$f = require$$1$1;
1025
- const { writeFileSync: writeFileSync$3 } = require$$2;
1032
+ const { app: app$a } = require$$0$1;
1033
+ const path$g = require$$1$1;
1034
+ const { writeFileSync: writeFileSync$3 } = require$$0$2;
1026
1035
  const { getFileContents: getFileContents$7 } = file;
1027
1036
 
1028
1037
  const configFilename$5 = "workspaces.json";
@@ -1068,8 +1077,8 @@ const workspaceController$1 = {
1068
1077
  saveWorkspaceForApplication: (win, appId, workspaceObject) => {
1069
1078
  try {
1070
1079
  // filename to the pages file (live pages)
1071
- const filename = path$f.join(
1072
- app$9.getPath("userData"),
1080
+ const filename = path$g.join(
1081
+ app$a.getPath("userData"),
1073
1082
  appName$7,
1074
1083
  appId,
1075
1084
  configFilename$5,
@@ -1117,8 +1126,8 @@ const workspaceController$1 = {
1117
1126
  saveMenuItemsForApplication: (win, appId, menuItems) => {
1118
1127
  try {
1119
1128
  // filename to the workspaces file
1120
- const filename = path$f.join(
1121
- app$9.getPath("userData"),
1129
+ const filename = path$g.join(
1130
+ app$a.getPath("userData"),
1122
1131
  appName$7,
1123
1132
  appId,
1124
1133
  configFilename$5,
@@ -1166,8 +1175,8 @@ const workspaceController$1 = {
1166
1175
  */
1167
1176
  deleteWorkspaceForApplication: (win, appId, workspaceId) => {
1168
1177
  try {
1169
- const filename = path$f.join(
1170
- app$9.getPath("userData"),
1178
+ const filename = path$g.join(
1179
+ app$a.getPath("userData"),
1171
1180
  appName$7,
1172
1181
  appId,
1173
1182
  configFilename$5,
@@ -1200,8 +1209,8 @@ const workspaceController$1 = {
1200
1209
 
1201
1210
  listWorkspacesForApplication: (win, appId) => {
1202
1211
  try {
1203
- const filename = path$f.join(
1204
- app$9.getPath("userData"),
1212
+ const filename = path$g.join(
1213
+ app$a.getPath("userData"),
1205
1214
  appName$7,
1206
1215
  appId,
1207
1216
  configFilename$5,
@@ -1228,8 +1237,8 @@ const workspaceController$1 = {
1228
1237
 
1229
1238
  listMenuItemsForApplication: (win, appId) => {
1230
1239
  try {
1231
- const filename = path$f.join(
1232
- app$9.getPath("userData"),
1240
+ const filename = path$g.join(
1241
+ app$a.getPath("userData"),
1233
1242
  appName$7,
1234
1243
  appId,
1235
1244
  configFilename$5,
@@ -1272,15 +1281,15 @@ const workspaceController$1 = {
1272
1281
 
1273
1282
  var workspaceController_1 = workspaceController$1;
1274
1283
 
1275
- const { app: app$8 } = require$$0$1;
1276
- const path$e = require$$1$1;
1277
- const { writeFileSync: writeFileSync$2 } = require$$2;
1284
+ const { app: app$9 } = require$$0$1;
1285
+ const path$f = require$$1$1;
1286
+ const { writeFileSync: writeFileSync$2 } = require$$0$2;
1278
1287
  const { getFileContents: getFileContents$6 } = file;
1279
1288
 
1280
1289
  const configFilename$4 = "themes.json";
1281
1290
  const appName$6 = "Dashboard";
1282
1291
 
1283
- const themeController$1 = {
1292
+ const themeController$2 = {
1284
1293
  /**
1285
1294
  * saveTheme
1286
1295
  * Create a new Theme that can be used in the application
@@ -1293,8 +1302,8 @@ const themeController$1 = {
1293
1302
  saveThemeForApplication: (win, appId, name, obj) => {
1294
1303
  try {
1295
1304
  // filename to the pages file (live pages)
1296
- const filename = path$e.join(
1297
- app$8.getPath("userData"),
1305
+ const filename = path$f.join(
1306
+ app$9.getPath("userData"),
1298
1307
  appName$6,
1299
1308
  appId,
1300
1309
  configFilename$4,
@@ -1339,8 +1348,8 @@ const themeController$1 = {
1339
1348
  */
1340
1349
  listThemesForApplication: (win, appId) => {
1341
1350
  try {
1342
- const filename = path$e.join(
1343
- app$8.getPath("userData"),
1351
+ const filename = path$f.join(
1352
+ app$9.getPath("userData"),
1344
1353
  appName$6,
1345
1354
  appId,
1346
1355
  configFilename$4,
@@ -1381,8 +1390,8 @@ const themeController$1 = {
1381
1390
  */
1382
1391
  deleteThemeForApplication: (win, appId, themeKey) => {
1383
1392
  try {
1384
- const filename = path$e.join(
1385
- app$8.getPath("userData"),
1393
+ const filename = path$f.join(
1394
+ app$9.getPath("userData"),
1386
1395
  appName$6,
1387
1396
  appId,
1388
1397
  configFilename$4,
@@ -1411,7 +1420,7 @@ const themeController$1 = {
1411
1420
  },
1412
1421
  };
1413
1422
 
1414
- var themeController_1 = themeController$1;
1423
+ var themeController_1 = themeController$2;
1415
1424
 
1416
1425
  /**
1417
1426
  * Utils/tranaform
@@ -1421,15 +1430,15 @@ var themeController_1 = themeController$1;
1421
1430
  * - CSV -> JSON
1422
1431
  */
1423
1432
 
1424
- var fs$9 = require$$2;
1433
+ var fs$9 = require$$0$2;
1425
1434
  var readline = require$$1$2;
1426
- const xtreamer = require$$2$1;
1435
+ const xtreamer = require$$2;
1427
1436
  var xmlParser = require$$3;
1428
1437
  var JSONStream$1 = require$$4;
1429
1438
  const stream = require$$5;
1430
1439
  var csv = require$$6;
1431
- const path$d = require$$1$1;
1432
- const { app: app$7 } = require$$0$1;
1440
+ const path$e = require$$1$1;
1441
+ const { app: app$8 } = require$$0$1;
1433
1442
  const { ensureDirectoryExistence: ensureDirectoryExistence$1 } = file;
1434
1443
 
1435
1444
  const TRANSFORM_APP_NAME = "Dashboard";
@@ -1717,18 +1726,18 @@ let Transform$1 = class Transform {
1717
1726
  }
1718
1727
 
1719
1728
  // Validate file paths are within app data directory
1720
- const appDataDir = path$d.join(app$7.getPath("userData"), TRANSFORM_APP_NAME);
1721
- const resolvedFilepath = path$d.resolve(filepath);
1722
- const resolvedOutFilepath = path$d.resolve(outFilepath);
1729
+ const appDataDir = path$e.join(app$8.getPath("userData"), TRANSFORM_APP_NAME);
1730
+ const resolvedFilepath = path$e.resolve(filepath);
1731
+ const resolvedOutFilepath = path$e.resolve(outFilepath);
1723
1732
 
1724
- if (!resolvedFilepath.startsWith(appDataDir + path$d.sep)) {
1733
+ if (!resolvedFilepath.startsWith(appDataDir + path$e.sep)) {
1725
1734
  return reject(
1726
1735
  new Error(
1727
1736
  "Input file path must be within the application data directory",
1728
1737
  ),
1729
1738
  );
1730
1739
  }
1731
- if (!resolvedOutFilepath.startsWith(appDataDir + path$d.sep)) {
1740
+ if (!resolvedOutFilepath.startsWith(appDataDir + path$e.sep)) {
1732
1741
  return reject(
1733
1742
  new Error(
1734
1743
  "Output file path must be within the application data directory",
@@ -3542,9 +3551,9 @@ var ntc_1 = ntc$1;
3542
3551
  * - extractColorsFromImageURL
3543
3552
  */
3544
3553
 
3545
- const download = require$$0$2;
3546
- const getPixels = require$$2$2;
3547
- const { extractColors } = require$$3$1;
3554
+ const download = require$$0$3;
3555
+ const getPixels = require$$2$1;
3556
+ const { extractColors: extractColors$1 } = require$$3$1;
3548
3557
  const ntc = ntc_1;
3549
3558
 
3550
3559
  async function extractColorsFromImageURL$2(url, toDirectory) {
@@ -3565,7 +3574,7 @@ async function extractColorsFromImageURL$2(url, toDirectory) {
3565
3574
  const data = [...pixels.data];
3566
3575
  const [width, height] = pixels.shape;
3567
3576
 
3568
- extractColors({ data, width, height })
3577
+ extractColors$1({ data, width, height })
3569
3578
  .then((result) => {
3570
3579
  console.log(result);
3571
3580
  const n = new ntc();
@@ -3586,9 +3595,9 @@ async function extractColorsFromImageURL$2(url, toDirectory) {
3586
3595
 
3587
3596
  var color = { extractColorsFromImageURL: extractColorsFromImageURL$2 };
3588
3597
 
3589
- const { app: app$6 } = require$$0$1;
3590
- var fs$8 = require$$2;
3591
- const path$c = require$$1$1;
3598
+ const { app: app$7 } = require$$0$1;
3599
+ var fs$8 = require$$0$2;
3600
+ const path$d = require$$1$1;
3592
3601
  const events$5 = events$8;
3593
3602
  const { getFileContents: getFileContents$5, writeToFile: writeToFile$2 } = file;
3594
3603
 
@@ -3611,8 +3620,8 @@ const dataController$1 = {
3611
3620
  convertJsonToCsvFile: (win, appId, jsonObject, toFilename = "test.csv") => {
3612
3621
  try {
3613
3622
  // filename to the pages file (live pages)
3614
- const filename = path$c.join(
3615
- app$6.getPath("userData"),
3623
+ const filename = path$d.join(
3624
+ app$7.getPath("userData"),
3616
3625
  appName$5,
3617
3626
  appId,
3618
3627
  "data",
@@ -3739,9 +3748,9 @@ const dataController$1 = {
3739
3748
  }
3740
3749
 
3741
3750
  // Validate toFilepath is within the app data directory
3742
- const appDataDir = path$c.join(app$6.getPath("userData"), appName$5);
3743
- const resolvedFilepath = path$c.resolve(toFilepath);
3744
- if (!resolvedFilepath.startsWith(appDataDir + path$c.sep)) {
3751
+ const appDataDir = path$d.join(app$7.getPath("userData"), appName$5);
3752
+ const resolvedFilepath = path$d.resolve(toFilepath);
3753
+ if (!resolvedFilepath.startsWith(appDataDir + path$d.sep)) {
3745
3754
  throw new Error(
3746
3755
  "File path must be within the application data directory",
3747
3756
  );
@@ -3888,8 +3897,8 @@ const dataController$1 = {
3888
3897
  try {
3889
3898
  if (data) {
3890
3899
  // filename to the pages file (live pages)
3891
- const toFilename = path$c.join(
3892
- app$6.getPath("userData"),
3900
+ const toFilename = path$d.join(
3901
+ app$7.getPath("userData"),
3893
3902
  appName$5,
3894
3903
  "data",
3895
3904
  filename,
@@ -3970,8 +3979,8 @@ const dataController$1 = {
3970
3979
  try {
3971
3980
  if (filename) {
3972
3981
  // filename to the pages file (live pages)
3973
- const fromFilename = path$c.join(
3974
- app$6.getPath("userData"),
3982
+ const fromFilename = path$d.join(
3983
+ app$7.getPath("userData"),
3975
3984
  appName$5,
3976
3985
  "data",
3977
3986
  filename,
@@ -4048,8 +4057,8 @@ const dataController$1 = {
4048
4057
  try {
4049
4058
  console.log(url);
4050
4059
  const fileExtension = ".jpg";
4051
- const filename = path$c.join(
4052
- app$6.getPath("userData"),
4060
+ const filename = path$d.join(
4061
+ app$7.getPath("userData"),
4053
4062
  appName$5,
4054
4063
  "@algolia/dash-electron",
4055
4064
  "data",
@@ -4073,9 +4082,9 @@ var dataController_1 = dataController$1;
4073
4082
  * settingsController
4074
4083
  */
4075
4084
 
4076
- const { app: app$5 } = require$$0$1;
4077
- const path$b = require$$1$1;
4078
- const fs$7 = require$$2;
4085
+ const { app: app$6 } = require$$0$1;
4086
+ const path$c = require$$1$1;
4087
+ const fs$7 = require$$0$2;
4079
4088
  const { getFileContents: getFileContents$4, writeToFile: writeToFile$1 } = file;
4080
4089
 
4081
4090
  const configFilename$3 = "settings.json";
@@ -4089,8 +4098,8 @@ function copyDirectory(source, destination) {
4089
4098
 
4090
4099
  const files = fs$7.readdirSync(source);
4091
4100
  for (const file of files) {
4092
- const srcPath = path$b.join(source, file);
4093
- const destPath = path$b.join(destination, file);
4101
+ const srcPath = path$c.join(source, file);
4102
+ const destPath = path$c.join(destination, file);
4094
4103
  const stat = fs$7.lstatSync(srcPath);
4095
4104
 
4096
4105
  // Skip symlinks to prevent following links to sensitive files
@@ -4118,8 +4127,8 @@ const settingsController$1 = {
4118
4127
  try {
4119
4128
  if (data) {
4120
4129
  // <appId>/settings.json
4121
- const filename = path$b.join(
4122
- app$5.getPath("userData"),
4130
+ const filename = path$c.join(
4131
+ app$6.getPath("userData"),
4123
4132
  appName$4,
4124
4133
  configFilename$3,
4125
4134
  );
@@ -4154,8 +4163,8 @@ const settingsController$1 = {
4154
4163
  getSettingsForApplication: (win) => {
4155
4164
  try {
4156
4165
  // <appId>/settings.json
4157
- const filename = path$b.join(
4158
- app$5.getPath("userData"),
4166
+ const filename = path$c.join(
4167
+ app$6.getPath("userData"),
4159
4168
  appName$4,
4160
4169
  configFilename$3,
4161
4170
  );
@@ -4185,15 +4194,15 @@ const settingsController$1 = {
4185
4194
  */
4186
4195
  getDataDirectory: (win) => {
4187
4196
  try {
4188
- const settingsPath = path$b.join(
4189
- app$5.getPath("userData"),
4197
+ const settingsPath = path$c.join(
4198
+ app$6.getPath("userData"),
4190
4199
  appName$4,
4191
4200
  configFilename$3,
4192
4201
  );
4193
4202
  const settings = getFileContents$4(settingsPath, {});
4194
4203
  const userDataDir =
4195
4204
  settings.userDataDirectory ||
4196
- path$b.join(app$5.getPath("userData"), appName$4);
4205
+ path$c.join(app$6.getPath("userData"), appName$4);
4197
4206
 
4198
4207
  console.log("[settingsController] Data directory retrieved successfully");
4199
4208
  // Return the data for ipcMain.handle() - modern promise-based approach
@@ -4230,8 +4239,8 @@ const settingsController$1 = {
4230
4239
  }
4231
4240
 
4232
4241
  // Update settings
4233
- const settingsPath = path$b.join(
4234
- app$5.getPath("userData"),
4242
+ const settingsPath = path$c.join(
4243
+ app$6.getPath("userData"),
4235
4244
  appName$4,
4236
4245
  configFilename$3,
4237
4246
  );
@@ -4264,20 +4273,20 @@ const settingsController$1 = {
4264
4273
  migrateDataDirectory: (win, oldPath, newPath) => {
4265
4274
  try {
4266
4275
  // Resolve paths to prevent traversal
4267
- const resolvedOldPath = path$b.resolve(oldPath);
4268
- const resolvedNewPath = path$b.resolve(newPath);
4276
+ const resolvedOldPath = path$c.resolve(oldPath);
4277
+ const resolvedNewPath = path$c.resolve(newPath);
4269
4278
 
4270
4279
  // Validate oldPath is the current configured data directory
4271
- const settingsCheckPath = path$b.join(
4272
- app$5.getPath("userData"),
4280
+ const settingsCheckPath = path$c.join(
4281
+ app$6.getPath("userData"),
4273
4282
  appName$4,
4274
4283
  configFilename$3,
4275
4284
  );
4276
4285
  const currentSettings = getFileContents$4(settingsCheckPath, {});
4277
4286
  const currentDataDir =
4278
4287
  currentSettings.userDataDirectory ||
4279
- path$b.join(app$5.getPath("userData"), appName$4);
4280
- if (resolvedOldPath !== path$b.resolve(currentDataDir)) {
4288
+ path$c.join(app$6.getPath("userData"), appName$4);
4289
+ if (resolvedOldPath !== path$c.resolve(currentDataDir)) {
4281
4290
  throw new Error("Source path must be the current data directory");
4282
4291
  }
4283
4292
 
@@ -4313,8 +4322,8 @@ const settingsController$1 = {
4313
4322
  copyDirectory(resolvedOldPath, resolvedNewPath);
4314
4323
 
4315
4324
  // Update settings to use new path
4316
- const settingsPath = path$b.join(
4317
- app$5.getPath("userData"),
4325
+ const settingsPath = path$c.join(
4326
+ app$6.getPath("userData"),
4318
4327
  appName$4,
4319
4328
  configFilename$3,
4320
4329
  );
@@ -4598,7 +4607,7 @@ function requireProviderController () {
4598
4607
  hasRequiredProviderController = 1;
4599
4608
  const { app, safeStorage } = require$$0$1;
4600
4609
  const path = require$$1$1;
4601
- const { writeFileSync, readFileSync, existsSync } = require$$2;
4610
+ const { writeFileSync, readFileSync, existsSync } = require$$0$2;
4602
4611
  const {
4603
4612
  ensureDirectoryExistence,
4604
4613
  getFileContents,
@@ -4927,9 +4936,9 @@ function requireProviderController () {
4927
4936
  return providerController_1;
4928
4937
  }
4929
4938
 
4930
- const { app: app$4 } = require$$0$1;
4931
- const path$a = require$$1$1;
4932
- const { writeFileSync: writeFileSync$1 } = require$$2;
4939
+ const { app: app$5 } = require$$0$1;
4940
+ const path$b = require$$1$1;
4941
+ const { writeFileSync: writeFileSync$1 } = require$$0$2;
4933
4942
  const events$4 = events$8;
4934
4943
  const { getFileContents: getFileContents$3 } = file;
4935
4944
 
@@ -4948,8 +4957,8 @@ const layoutController$1 = {
4948
4957
  saveLayoutForApplication: (win, appId, layoutObject) => {
4949
4958
  try {
4950
4959
  // filename to the pages file (live pages)
4951
- const filename = path$a.join(
4952
- app$4.getPath("userData"),
4960
+ const filename = path$b.join(
4961
+ app$5.getPath("userData"),
4953
4962
  appName$3,
4954
4963
  appId,
4955
4964
  configFilename$2,
@@ -4981,8 +4990,8 @@ const layoutController$1 = {
4981
4990
  */
4982
4991
  listLayoutsForApplication: (win, appId) => {
4983
4992
  try {
4984
- const filename = path$a.join(
4985
- app$4.getPath("userData"),
4993
+ const filename = path$b.join(
4994
+ app$5.getPath("userData"),
4986
4995
  appName$3,
4987
4996
  appId,
4988
4997
  configFilename$2,
@@ -5017,15 +5026,15 @@ var mcpController$3 = {exports: {}};
5017
5026
  * Uses @modelcontextprotocol/sdk for protocol handling.
5018
5027
  */
5019
5028
 
5020
- const { Client } = require$$0$3;
5029
+ const { Client } = require$$0$4;
5021
5030
  const {
5022
5031
  StdioClientTransport,
5023
5032
  } = require$$1$3;
5024
5033
  const {
5025
5034
  StreamableHTTPClientTransport,
5026
- } = require$$2$3;
5027
- const path$9 = require$$1$1;
5028
- const fs$6 = require$$2;
5035
+ } = require$$2$2;
5036
+ const path$a = require$$1$1;
5037
+ const fs$6 = require$$0$2;
5029
5038
 
5030
5039
  /**
5031
5040
  * Cached shell PATH result (resolved once, reused for all spawns).
@@ -5441,7 +5450,7 @@ const mcpController$2 = {
5441
5450
  }
5442
5451
 
5443
5452
  // Interpolate {{MCP_DIR}} in args to resolve local MCP server scripts
5444
- const mcpDir = path$9.join(__dirname, "..", "mcp");
5453
+ const mcpDir = path$a.join(__dirname, "..", "mcp");
5445
5454
  for (let i = 0; i < args.length; i++) {
5446
5455
  if (
5447
5456
  typeof args[i] === "string" &&
@@ -5816,7 +5825,7 @@ const mcpController$2 = {
5816
5825
  */
5817
5826
  getCatalog: (win) => {
5818
5827
  try {
5819
- const catalogPath = path$9.join(
5828
+ const catalogPath = path$a.join(
5820
5829
  __dirname,
5821
5830
  "..",
5822
5831
  "mcp",
@@ -5928,7 +5937,7 @@ const mcpController$2 = {
5928
5937
  }
5929
5938
 
5930
5939
  // Interpolate {{MCP_DIR}} in authCommand args (same as startServer)
5931
- const mcpDir = path$9.join(__dirname, "..", "mcp");
5940
+ const mcpDir = path$a.join(__dirname, "..", "mcp");
5932
5941
  const resolvedArgs = (authCommand.args || []).map((arg) =>
5933
5942
  typeof arg === "string" && arg.includes("{{MCP_DIR}}")
5934
5943
  ? arg.replace(/\{\{MCP_DIR\}\}/g, mcpDir)
@@ -6024,8 +6033,8 @@ var mcpControllerExports = mcpController$3.exports;
6024
6033
  * - Support two-level browsing: packages (bundles) and widgets within packages
6025
6034
  */
6026
6035
 
6027
- const path$8 = require$$1$1;
6028
- const fs$5 = require$$2;
6036
+ const path$9 = require$$1$1;
6037
+ const fs$5 = require$$0$2;
6029
6038
 
6030
6039
  // Default registry API base URL
6031
6040
  const DEFAULT_REGISTRY_API_URL = "https://main.d919rwhuzp7rj.amplifyapp.com";
@@ -6040,7 +6049,7 @@ let cacheTimestamp = 0;
6040
6049
  * Get the local test registry path for dev mode
6041
6050
  */
6042
6051
  function getTestRegistryPath() {
6043
- return path$8.join(__dirname, "..", "registry", "test-registry-index.json");
6052
+ return path$9.join(__dirname, "..", "registry", "test-registry-index.json");
6044
6053
  }
6045
6054
 
6046
6055
  /**
@@ -6326,18 +6335,31 @@ async function searchDashboards(query = "", filters = {}) {
6326
6335
  return searchRegistry$1(query, { ...filters, type: "dashboard" });
6327
6336
  }
6328
6337
 
6329
- var registryController$1 = {
6338
+ /**
6339
+ * Search the registry for theme packages only.
6340
+ * Convenience wrapper around searchRegistry with type: "theme".
6341
+ *
6342
+ * @param {string} query - Search query string
6343
+ * @param {Object} filters - Optional filters (category, author, tag)
6344
+ * @returns {Promise<Object>} { packages: [...], totalWidgets: number }
6345
+ */
6346
+ async function searchThemes(query = "", filters = {}) {
6347
+ return searchRegistry$1(query, { ...filters, type: "theme" });
6348
+ }
6349
+
6350
+ var registryController$2 = {
6330
6351
  fetchRegistryIndex,
6331
6352
  searchRegistry: searchRegistry$1,
6332
6353
  searchDashboards,
6354
+ searchThemes,
6333
6355
  getPackage: getPackage$1,
6334
6356
  checkUpdates,
6335
6357
  };
6336
6358
 
6337
- var fs$4 = require$$2;
6359
+ var fs$4 = require$$0$2;
6338
6360
  var JSONStream = require$$4;
6339
- const algoliasearch$1 = require$$2$4;
6340
- const path$7 = require$$3$2;
6361
+ const algoliasearch$1 = require$$2$3;
6362
+ const path$8 = require$$3$2;
6341
6363
  const { ensureDirectoryExistence, checkDirectory } = file;
6342
6364
 
6343
6365
  let AlgoliaIndex$1 = class AlgoliaIndex {
@@ -6444,7 +6466,7 @@ let AlgoliaIndex$1 = class AlgoliaIndex {
6444
6466
  if (err) reject(err);
6445
6467
  if (files) {
6446
6468
  files.forEach((file) => {
6447
- fs$4.unlinkSync(path$7.join(directoryPath, file));
6469
+ fs$4.unlinkSync(path$8.join(directoryPath, file));
6448
6470
  });
6449
6471
  resolve();
6450
6472
  }
@@ -6467,7 +6489,7 @@ let AlgoliaIndex$1 = class AlgoliaIndex {
6467
6489
  let results = [];
6468
6490
  for (const fileIndex in files) {
6469
6491
  // for each file lets read the file and then push to algolia
6470
- const pathToBatch = path$7.join(batchFilepath, files[fileIndex]);
6492
+ const pathToBatch = path$8.join(batchFilepath, files[fileIndex]);
6471
6493
  const fileContents = await this.readFile(pathToBatch);
6472
6494
  if (fileContents) {
6473
6495
  if ("data" in fileContents && "filepath" in fileContents) {
@@ -6639,10 +6661,10 @@ var algolia = AlgoliaIndex$1;
6639
6661
  * the controller methods as seen below.
6640
6662
  */
6641
6663
 
6642
- const algoliasearch = require$$2$4;
6664
+ const algoliasearch = require$$2$3;
6643
6665
  const events$3 = events$8;
6644
6666
  const AlgoliaIndex = algolia;
6645
- var fs$3 = require$$2;
6667
+ var fs$3 = require$$0$2;
6646
6668
 
6647
6669
  const algoliaController$1 = {
6648
6670
  /**
@@ -6880,7 +6902,7 @@ const algoliaController$1 = {
6880
6902
 
6881
6903
  var algoliaController_1 = algoliaController$1;
6882
6904
 
6883
- const OpenAI = require$$0$4;
6905
+ const OpenAI = require$$0$5;
6884
6906
  const events$2 = events$8;
6885
6907
 
6886
6908
  const openaiController$1 = {
@@ -6921,9 +6943,9 @@ const openaiController$1 = {
6921
6943
 
6922
6944
  var openaiController_1 = openaiController$1;
6923
6945
 
6924
- const { app: app$3 } = require$$0$1;
6925
- const path$6 = require$$1$1;
6926
- const { writeFileSync } = require$$2;
6946
+ const { app: app$4 } = require$$0$1;
6947
+ const path$7 = require$$1$1;
6948
+ const { writeFileSync } = require$$0$2;
6927
6949
  const { getFileContents: getFileContents$2 } = file;
6928
6950
 
6929
6951
  const configFilename$1 = "menuItems.json";
@@ -6933,8 +6955,8 @@ const menuItemsController$1 = {
6933
6955
  saveMenuItemForApplication: (win, appId, menuItem) => {
6934
6956
  try {
6935
6957
  // filename to the pages file (live pages)
6936
- const filename = path$6.join(
6937
- app$3.getPath("userData"),
6958
+ const filename = path$7.join(
6959
+ app$4.getPath("userData"),
6938
6960
  appName$2,
6939
6961
  appId,
6940
6962
  configFilename$1,
@@ -6969,8 +6991,8 @@ const menuItemsController$1 = {
6969
6991
 
6970
6992
  listMenuItemsForApplication: (win, appId) => {
6971
6993
  try {
6972
- const filename = path$6.join(
6973
- app$3.getPath("userData"),
6994
+ const filename = path$7.join(
6995
+ app$4.getPath("userData"),
6974
6996
  appName$2,
6975
6997
  appId,
6976
6998
  configFilename$1,
@@ -6995,14 +7017,14 @@ const menuItemsController$1 = {
6995
7017
 
6996
7018
  var menuItemsController_1 = menuItemsController$1;
6997
7019
 
6998
- const path$5 = require$$1$1;
6999
- const { app: app$2 } = require$$0$1;
7020
+ const path$6 = require$$1$1;
7021
+ const { app: app$3 } = require$$0$1;
7000
7022
 
7001
7023
  const pluginController$1 = {
7002
7024
  install: (win, packageName, filepath) => {
7003
7025
  try {
7004
- const rootPath = path$5.join(
7005
- app$2.getPath("userData"),
7026
+ const rootPath = path$6.join(
7027
+ app$3.getPath("userData"),
7006
7028
  "plugins",
7007
7029
  packageName,
7008
7030
  );
@@ -7457,7 +7479,7 @@ var cliController_1 = cliController$2;
7457
7479
  * per-request, receiving the full messages array each time.
7458
7480
  */
7459
7481
 
7460
- const Anthropic = require$$0$5;
7482
+ const Anthropic = require$$0$6;
7461
7483
  const mcpController$1 = mcpControllerExports;
7462
7484
  const cliController$1 = cliController_1;
7463
7485
  const {
@@ -8897,8 +8919,8 @@ var dynamicWidgetLoader$2 = {exports: {}};
8897
8919
  * Runs in the Electron main process at widget install time.
8898
8920
  */
8899
8921
 
8900
- const fs$2 = require$$2;
8901
- const path$4 = require$$1$1;
8922
+ const fs$2 = require$$0$2;
8923
+ const path$5 = require$$1$1;
8902
8924
 
8903
8925
  /**
8904
8926
  * Find the widgets/ directory, handling nested ZIP extraction.
@@ -8913,13 +8935,13 @@ const path$4 = require$$1$1;
8913
8935
  * @returns {string|null} Path to the widgets/ directory, or null
8914
8936
  */
8915
8937
  function findWidgetsDir$1(widgetPath) {
8916
- const direct = path$4.join(widgetPath, "widgets");
8938
+ const direct = path$5.join(widgetPath, "widgets");
8917
8939
  if (fs$2.existsSync(direct)) {
8918
8940
  return direct;
8919
8941
  }
8920
8942
 
8921
8943
  // Check configs/ directory (used by packageZip.js for distributed widgets)
8922
- const configs = path$4.join(widgetPath, "configs");
8944
+ const configs = path$5.join(widgetPath, "configs");
8923
8945
  if (fs$2.existsSync(configs)) {
8924
8946
  return configs;
8925
8947
  }
@@ -8936,7 +8958,7 @@ function findWidgetsDir$1(widgetPath) {
8936
8958
  );
8937
8959
 
8938
8960
  for (const subdir of subdirs) {
8939
- const nested = path$4.join(widgetPath, subdir.name, "widgets");
8961
+ const nested = path$5.join(widgetPath, subdir.name, "widgets");
8940
8962
  if (fs$2.existsSync(nested)) {
8941
8963
  console.log(`[WidgetCompiler] Found nested widgets/ at ${nested}`);
8942
8964
  return nested;
@@ -8985,14 +9007,14 @@ async function compileWidget(widgetPath) {
8985
9007
  // Compute relative path from the entry file (in widgetPath) to widgetsDir,
8986
9008
  // since widgetsDir may be nested (e.g., ./weather-widget/widgets/).
8987
9009
  const relWidgetsDir =
8988
- "./" + path$4.relative(widgetPath, widgetsDir).split(path$4.sep).join("/");
9010
+ "./" + path$5.relative(widgetPath, widgetsDir).split(path$5.sep).join("/");
8989
9011
  const imports = [];
8990
9012
  const exportParts = [];
8991
9013
 
8992
9014
  for (const dashFile of dashFiles) {
8993
9015
  const componentName = dashFile.replace(".dash.js", "");
8994
9016
  const componentFile = `${componentName}.js`;
8995
- const componentFilePath = path$4.join(widgetsDir, componentFile);
9017
+ const componentFilePath = path$5.join(widgetsDir, componentFile);
8996
9018
  const hasComponent = fs$2.existsSync(componentFilePath);
8997
9019
 
8998
9020
  // Import the config (always)
@@ -9019,9 +9041,9 @@ async function compileWidget(widgetPath) {
9019
9041
  const entryContent = [...imports, "", ...exportParts, ""].join("\n");
9020
9042
 
9021
9043
  // Write temporary entry file in the widget root
9022
- const entryPath = path$4.join(widgetPath, "__compile_entry.js");
9023
- const distDir = path$4.join(widgetPath, "dist");
9024
- const outPath = path$4.join(distDir, "index.cjs.js");
9044
+ const entryPath = path$5.join(widgetPath, "__compile_entry.js");
9045
+ const distDir = path$5.join(widgetPath, "dist");
9046
+ const outPath = path$5.join(distDir, "index.cjs.js");
9025
9047
 
9026
9048
  try {
9027
9049
  // Ensure dist/ directory exists
@@ -9097,9 +9119,9 @@ var widgetCompiler$1 = { compileWidget, findWidgetsDir: findWidgetsDir$1 };
9097
9119
  * Integrates with ComponentManager for automatic registration
9098
9120
  */
9099
9121
 
9100
- const fs$1 = require$$2;
9101
- const path$3 = require$$1$1;
9102
- const vm = require$$2$5;
9122
+ const fs$1 = require$$0$2;
9123
+ const path$4 = require$$1$1;
9124
+ const vm = require$$2$4;
9103
9125
  const { findWidgetsDir } = widgetCompiler$1;
9104
9126
 
9105
9127
  class DynamicWidgetLoader {
@@ -9139,9 +9161,9 @@ class DynamicWidgetLoader {
9139
9161
  );
9140
9162
 
9141
9163
  const widgetsDir =
9142
- findWidgetsDir(widgetPath) || path$3.join(widgetPath, "widgets");
9143
- const componentPath = path$3.join(widgetsDir, `${componentName}.js`);
9144
- const configPath = path$3.join(widgetsDir, `${componentName}.dash.js`);
9164
+ findWidgetsDir(widgetPath) || path$4.join(widgetPath, "widgets");
9165
+ const componentPath = path$4.join(widgetsDir, `${componentName}.js`);
9166
+ const configPath = path$4.join(widgetsDir, `${componentName}.dash.js`);
9145
9167
 
9146
9168
  if (!fs$1.existsSync(componentPath)) {
9147
9169
  throw new Error(`Component file not found: ${componentPath}`);
@@ -9300,9 +9322,9 @@ var dynamicWidgetLoaderExports = dynamicWidgetLoader$2.exports;
9300
9322
  */
9301
9323
 
9302
9324
  (function (module) {
9303
- const fs = require$$2;
9325
+ const fs = require$$0$2;
9304
9326
  const path = require$$1$1;
9305
- const os = require$$2$6;
9327
+ const os = require$$2$5;
9306
9328
  const AdmZip = require$$3$3;
9307
9329
  const { fileURLToPath } = require$$4$1;
9308
9330
  const { app, ipcMain, BrowserWindow } = require$$0$1;
@@ -10381,7 +10403,7 @@ function getStoredToken$1() {
10381
10403
  *
10382
10404
  * @returns {Object} { authenticated: boolean, userId?: string }
10383
10405
  */
10384
- function getAuthStatus() {
10406
+ function getAuthStatus$1() {
10385
10407
  const stored = getStoredToken$1();
10386
10408
  if (!stored) {
10387
10409
  return { authenticated: false };
@@ -10573,7 +10595,7 @@ var registryAuthController$1 = {
10573
10595
  initiateDeviceFlow: initiateDeviceFlow$1,
10574
10596
  pollForToken: pollForToken$1,
10575
10597
  getStoredToken: getStoredToken$1,
10576
- getAuthStatus,
10598
+ getAuthStatus: getAuthStatus$1,
10577
10599
  getRegistryProfile: getRegistryProfile$1,
10578
10600
  updateRegistryProfile: updateRegistryProfile$1,
10579
10601
  getRegistryPackages: getRegistryPackages$1,
@@ -10589,8 +10611,8 @@ var registryAuthController$1 = {
10589
10611
  * Handles publishing packages and generating registry URLs.
10590
10612
  */
10591
10613
 
10592
- const fs = require$$2;
10593
- const path$2 = require$$1$1;
10614
+ const fs = require$$0$2;
10615
+ const path$3 = require$$1$1;
10594
10616
  const { getStoredToken } = registryAuthController$1;
10595
10617
 
10596
10618
  const REGISTRY_BASE_URL =
@@ -10622,7 +10644,7 @@ async function publishToRegistry$1(zipPath, manifest) {
10622
10644
  formData.append(
10623
10645
  "file",
10624
10646
  new Blob([zipBuffer], { type: "application/zip" }),
10625
- path$2.basename(zipPath),
10647
+ path$3.basename(zipPath),
10626
10648
  );
10627
10649
  formData.append("manifest", JSON.stringify(manifest));
10628
10650
 
@@ -10673,7 +10695,7 @@ function getRegistryUrl$1(scope, name) {
10673
10695
  return `${REGISTRY_BASE_URL}/package/${scope}/${name}`;
10674
10696
  }
10675
10697
 
10676
- var registryApiController$1 = {
10698
+ var registryApiController$2 = {
10677
10699
  publishToRegistry: publishToRegistry$1,
10678
10700
  getRegistryUrl: getRegistryUrl$1,
10679
10701
  REGISTRY_BASE_URL,
@@ -10694,9 +10716,9 @@ var registryApiController$1 = {
10694
10716
  * applies event wiring. (Import is implemented in DASH-13.)
10695
10717
  */
10696
10718
 
10697
- const { app: app$1, dialog } = require$$0$1;
10698
- const path$1 = require$$1$1;
10699
- const AdmZip = require$$3$3;
10719
+ const { app: app$2, dialog: dialog$1 } = require$$0$1;
10720
+ const path$2 = require$$1$1;
10721
+ const AdmZip$1 = require$$3$3;
10700
10722
  const { getFileContents: getFileContents$1 } = file;
10701
10723
  const {
10702
10724
  validateDashboardConfig,
@@ -10710,7 +10732,7 @@ const {
10710
10732
  buildProviderRequirements,
10711
10733
  applyEventWiringToLayout,
10712
10734
  } = dashboardConfigUtils$1;
10713
- const { searchRegistry, getPackage } = registryController$1;
10735
+ const { searchRegistry, getPackage } = registryController$2;
10714
10736
 
10715
10737
  const configFilename = "workspaces.json";
10716
10738
  const appName$1 = "Dashboard";
@@ -10736,8 +10758,8 @@ async function exportDashboardConfig$1(
10736
10758
  ) {
10737
10759
  try {
10738
10760
  // 1. Read workspace from workspaces.json
10739
- const filename = path$1.join(
10740
- app$1.getPath("userData"),
10761
+ const filename = path$2.join(
10762
+ app$2.getPath("userData"),
10741
10763
  appName$1,
10742
10764
  appId,
10743
10765
  configFilename,
@@ -10802,10 +10824,10 @@ async function exportDashboardConfig$1(
10802
10824
  .replace(/\s+/g, "-")
10803
10825
  .toLowerCase();
10804
10826
 
10805
- const { canceled, filePath } = await dialog.showSaveDialog(win, {
10827
+ const { canceled, filePath } = await dialog$1.showSaveDialog(win, {
10806
10828
  title: "Export Dashboard as ZIP",
10807
- defaultPath: path$1.join(
10808
- app$1.getPath("desktop"),
10829
+ defaultPath: path$2.join(
10830
+ app$2.getPath("desktop"),
10809
10831
  `dashboard-${sanitizedName}.zip`,
10810
10832
  ),
10811
10833
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
@@ -10816,7 +10838,7 @@ async function exportDashboardConfig$1(
10816
10838
  }
10817
10839
 
10818
10840
  // 6. Create ZIP with the config
10819
- const zip = new AdmZip();
10841
+ const zip = new AdmZip$1();
10820
10842
  const configJson = JSON.stringify(dashboardConfig, null, 2);
10821
10843
  zip.addFile(
10822
10844
  `${sanitizedName}.dashboard.json`,
@@ -10856,7 +10878,7 @@ async function exportDashboardConfig$1(
10856
10878
  */
10857
10879
  async function selectDashboardFile$1(win) {
10858
10880
  try {
10859
- const { canceled, filePaths } = await dialog.showOpenDialog(win, {
10881
+ const { canceled, filePaths } = await dialog$1.showOpenDialog(win, {
10860
10882
  title: "Import Dashboard Configuration",
10861
10883
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
10862
10884
  properties: ["openFile"],
@@ -10869,8 +10891,8 @@ async function selectDashboardFile$1(win) {
10869
10891
  const zipPath = filePaths[0];
10870
10892
 
10871
10893
  // Extract and validate
10872
- const zip = new AdmZip(zipPath);
10873
- const tempDir = path$1.join(app$1.getPath("temp"), "dash-import");
10894
+ const zip = new AdmZip$1(zipPath);
10895
+ const tempDir = path$2.join(app$2.getPath("temp"), "dash-import");
10874
10896
  const { validateZipEntries } = widgetRegistryExports;
10875
10897
  validateZipEntries(zip, tempDir);
10876
10898
 
@@ -10963,7 +10985,7 @@ async function importDashboardConfig$1(
10963
10985
  zipPath = options.filePath;
10964
10986
  } else {
10965
10987
  // Show file picker
10966
- const { canceled, filePaths } = await dialog.showOpenDialog(win, {
10988
+ const { canceled, filePaths } = await dialog$1.showOpenDialog(win, {
10967
10989
  title: "Import Dashboard Configuration",
10968
10990
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
10969
10991
  properties: ["openFile"],
@@ -10977,10 +10999,10 @@ async function importDashboardConfig$1(
10977
10999
  }
10978
11000
 
10979
11001
  // 2. Extract and validate .dashboard.json from ZIP
10980
- const zip = new AdmZip(zipPath);
11002
+ const zip = new AdmZip$1(zipPath);
10981
11003
 
10982
11004
  // Validate ZIP entries for path traversal
10983
- const tempDir = path$1.join(app$1.getPath("temp"), "dash-import");
11005
+ const tempDir = path$2.join(app$2.getPath("temp"), "dash-import");
10984
11006
  const { validateZipEntries } = widgetRegistryExports;
10985
11007
  validateZipEntries(zip, tempDir);
10986
11008
 
@@ -11263,10 +11285,10 @@ async function installDashboardFromRegistry$1(
11263
11285
  }
11264
11286
 
11265
11287
  const buffer = await response.arrayBuffer();
11266
- const zip = new AdmZip(Buffer.from(buffer));
11288
+ const zip = new AdmZip$1(Buffer.from(buffer));
11267
11289
 
11268
11290
  // 3. Validate ZIP entries
11269
- const tempDir = path$1.join(app$1.getPath("temp"), "dash-registry-import");
11291
+ const tempDir = path$2.join(app$2.getPath("temp"), "dash-registry-import");
11270
11292
  const { validateZipEntries } = widgetRegistryExports;
11271
11293
  validateZipEntries(zip, tempDir);
11272
11294
 
@@ -11341,7 +11363,7 @@ async function checkCompatibility$1(dashboardWidgets, widgetRegistry = null) {
11341
11363
  const {
11342
11364
  checkDashboardCompatibility,
11343
11365
  } = dashboardConfigUtils$1;
11344
- const { fetchRegistryIndex } = registryController$1;
11366
+ const { fetchRegistryIndex } = registryController$2;
11345
11367
 
11346
11368
  const installedWidgets = widgetRegistry ? widgetRegistry.getWidgets() : [];
11347
11369
 
@@ -11398,8 +11420,8 @@ async function prepareDashboardForPublish$1(
11398
11420
  } = dashboardConfigUtils$1;
11399
11421
 
11400
11422
  // 1. Read workspace
11401
- const filename = path$1.join(
11402
- app$1.getPath("userData"),
11423
+ const filename = path$2.join(
11424
+ app$2.getPath("userData"),
11403
11425
  appName$1,
11404
11426
  appId,
11405
11427
  configFilename,
@@ -11470,7 +11492,7 @@ async function prepareDashboardForPublish$1(
11470
11492
  }
11471
11493
 
11472
11494
  // 5. Check which widgets exist in the registry (soft warning, not blocking)
11473
- const { fetchRegistryIndex } = registryController$1;
11495
+ const { fetchRegistryIndex } = registryController$2;
11474
11496
  let registryPackages = [];
11475
11497
  let registryCheckFailed = false;
11476
11498
  try {
@@ -11511,10 +11533,10 @@ async function prepareDashboardForPublish$1(
11511
11533
 
11512
11534
  // 7. Show save dialog for the publish package
11513
11535
  const sanitizedName = manifest.name;
11514
- const { canceled, filePath } = await dialog.showSaveDialog(win, {
11536
+ const { canceled, filePath } = await dialog$1.showSaveDialog(win, {
11515
11537
  title: "Save Dashboard Package for Registry",
11516
- defaultPath: path$1.join(
11517
- app$1.getPath("desktop"),
11538
+ defaultPath: path$2.join(
11539
+ app$2.getPath("desktop"),
11518
11540
  `dashboard-${sanitizedName}-v${manifest.version}.zip`,
11519
11541
  ),
11520
11542
  filters: [{ name: "ZIP Archive", extensions: ["zip"] }],
@@ -11525,7 +11547,7 @@ async function prepareDashboardForPublish$1(
11525
11547
  }
11526
11548
 
11527
11549
  // 8. Create ZIP with manifest and dashboard config
11528
- const zip = new AdmZip();
11550
+ const zip = new AdmZip$1();
11529
11551
  zip.addFile(
11530
11552
  "manifest.json",
11531
11553
  Buffer.from(JSON.stringify(manifest, null, 2), "utf-8"),
@@ -11544,7 +11566,7 @@ async function prepareDashboardForPublish$1(
11544
11566
  let registrySubmission = null;
11545
11567
  try {
11546
11568
  const { getAuthStatus } = registryAuthController$1;
11547
- const { publishToRegistry } = registryApiController$1;
11569
+ const { publishToRegistry } = registryApiController$2;
11548
11570
  const authStatus = getAuthStatus();
11549
11571
 
11550
11572
  if (authStatus.authenticated) {
@@ -11604,7 +11626,7 @@ async function getDashboardPreview$1(packageName, widgetRegistry = null) {
11604
11626
  buildDashboardPreview,
11605
11627
  checkDashboardCompatibility,
11606
11628
  } = dashboardConfigUtils$1;
11607
- const { getPackage, fetchRegistryIndex } = registryController$1;
11629
+ const { getPackage, fetchRegistryIndex } = registryController$2;
11608
11630
 
11609
11631
  const pkg = await getPackage(packageName);
11610
11632
  if (!pkg) {
@@ -11649,11 +11671,11 @@ async function getDashboardPreview$1(packageName, widgetRegistry = null) {
11649
11671
  */
11650
11672
  async function checkDashboardUpdatesForApp$1(appId) {
11651
11673
  const { checkDashboardUpdates } = dashboardConfigUtils$1;
11652
- const { fetchRegistryIndex } = registryController$1;
11674
+ const { fetchRegistryIndex } = registryController$2;
11653
11675
 
11654
11676
  try {
11655
- const filename = path$1.join(
11656
- app$1.getPath("userData"),
11677
+ const filename = path$2.join(
11678
+ app$2.getPath("userData"),
11657
11679
  appName$1,
11658
11680
  appId,
11659
11681
  configFilename,
@@ -11723,8 +11745,8 @@ function getProviderSetupManifest$1(appId, requiredProviders = []) {
11723
11745
  */
11724
11746
  function getDashboardPublishPreview$1(appId, workspaceId, widgetRegistry = null) {
11725
11747
  try {
11726
- const filename = path$1.join(
11727
- app$1.getPath("userData"),
11748
+ const filename = path$2.join(
11749
+ app$2.getPath("userData"),
11728
11750
  appName$1,
11729
11751
  appId,
11730
11752
  configFilename,
@@ -12016,6 +12038,348 @@ const notificationController$2 = {
12016
12038
 
12017
12039
  var notificationController_1 = notificationController$2;
12018
12040
 
12041
+ /**
12042
+ * themeRegistryController.js
12043
+ *
12044
+ * Handles publishing themes to and installing themes from the Dash registry.
12045
+ * Mirrors dashboardConfigController patterns for ZIP creation, manifest generation,
12046
+ * and registry interaction.
12047
+ */
12048
+ const path$1 = require$$1$1;
12049
+ const { app: app$1, dialog } = require$$0$1;
12050
+ const AdmZip = require$$3$3;
12051
+
12052
+ const themeController$1 = themeController_1;
12053
+ const registryController$1 = registryController$2;
12054
+ const registryApiController$1 = registryApiController$2;
12055
+ const { getAuthStatus } = registryAuthController$1;
12056
+
12057
+ /**
12058
+ * Sanitize a name for use as a filename (lowercase, hyphens only).
12059
+ */
12060
+ function sanitizeName(name) {
12061
+ return (name || "theme")
12062
+ .toLowerCase()
12063
+ .replace(/[^a-z0-9]+/g, "-")
12064
+ .replace(/^-|-$/g, "");
12065
+ }
12066
+
12067
+ /**
12068
+ * Generate a registry manifest for a theme package.
12069
+ *
12070
+ * @param {Object} themeData - The raw theme object
12071
+ * @param {string} themeKey - The theme key/name
12072
+ * @param {Object} options - Publish options { authorName, description, tags, scope }
12073
+ * @returns {Object} Registry manifest
12074
+ */
12075
+ function generateThemeRegistryManifest(themeData, themeKey, options = {}) {
12076
+ const sanitizedName = sanitizeName(themeKey);
12077
+ const colors = extractColors(themeData);
12078
+
12079
+ return {
12080
+ scope: options.scope || "",
12081
+ name: sanitizedName,
12082
+ displayName: themeKey,
12083
+ author: options.authorName || "",
12084
+ description: options.description || "",
12085
+ version: "1.0.0",
12086
+ type: "theme",
12087
+ category: "general",
12088
+ tags: options.tags || [],
12089
+ icon: "palette",
12090
+ colors,
12091
+ appOrigin: options.appOrigin || "",
12092
+ publishedAt: new Date().toISOString(),
12093
+ };
12094
+ }
12095
+
12096
+ /**
12097
+ * Extract primary/secondary/tertiary/neutral colors from a theme object.
12098
+ * Theme objects store colors in various structures; this normalizes them.
12099
+ */
12100
+ function extractColors(themeData) {
12101
+ const colors = {
12102
+ primary: "",
12103
+ secondary: "",
12104
+ tertiary: "",
12105
+ neutral: "",
12106
+ };
12107
+
12108
+ if (!themeData) return colors;
12109
+
12110
+ // Direct color fields
12111
+ if (themeData.primary) colors.primary = themeData.primary;
12112
+ if (themeData.secondary) colors.secondary = themeData.secondary;
12113
+ if (themeData.tertiary) colors.tertiary = themeData.tertiary;
12114
+ if (themeData.neutral) colors.neutral = themeData.neutral;
12115
+
12116
+ // Nested under "colors" key
12117
+ if (themeData.colors) {
12118
+ if (themeData.colors.primary) colors.primary = themeData.colors.primary;
12119
+ if (themeData.colors.secondary)
12120
+ colors.secondary = themeData.colors.secondary;
12121
+ if (themeData.colors.tertiary) colors.tertiary = themeData.colors.tertiary;
12122
+ if (themeData.colors.neutral) colors.neutral = themeData.colors.neutral;
12123
+ }
12124
+
12125
+ return colors;
12126
+ }
12127
+
12128
+ /**
12129
+ * Prepare a theme for publishing to the registry.
12130
+ *
12131
+ * Reads the theme from themes.json, generates a manifest, creates a ZIP,
12132
+ * and publishes via the registry API.
12133
+ *
12134
+ * @param {BrowserWindow} win - The sender window
12135
+ * @param {string} appId - Application identifier
12136
+ * @param {string} themeKey - Key of the theme to publish
12137
+ * @param {Object} options - { authorName, description, tags }
12138
+ * @returns {Object} Result with success, manifest, registryResult
12139
+ */
12140
+ async function prepareThemeForPublish$1(win, appId, themeKey, options = {}) {
12141
+ try {
12142
+ // Read the theme data
12143
+ const themesResult = themeController$1.listThemesForApplication(win, appId);
12144
+ if (themesResult.error) {
12145
+ return { success: false, error: "Failed to read themes: " + themesResult.message };
12146
+ }
12147
+
12148
+ const themeData = themesResult.themes[themeKey];
12149
+ if (!themeData) {
12150
+ return { success: false, error: `Theme "${themeKey}" not found` };
12151
+ }
12152
+
12153
+ // Get auth status for scope
12154
+ const auth = getAuthStatus();
12155
+ const scope = auth.profile?.username || options.scope || "";
12156
+ if (!scope) {
12157
+ return {
12158
+ success: false,
12159
+ error: "Not authenticated with registry",
12160
+ authRequired: true,
12161
+ };
12162
+ }
12163
+
12164
+ // Generate manifest
12165
+ const manifest = generateThemeRegistryManifest(themeData, themeKey, {
12166
+ ...options,
12167
+ scope,
12168
+ appOrigin: appId,
12169
+ });
12170
+
12171
+ // Validate colors
12172
+ if (!manifest.colors.primary || !manifest.colors.secondary || !manifest.colors.tertiary) {
12173
+ return {
12174
+ success: false,
12175
+ error: "Theme must have primary, secondary, and tertiary colors defined",
12176
+ };
12177
+ }
12178
+
12179
+ // Show save dialog
12180
+ const sanitizedName = sanitizeName(themeKey);
12181
+ const defaultFilename = `theme-${sanitizedName}-v${manifest.version}.zip`;
12182
+
12183
+ const saveResult = await dialog.showSaveDialog(win, {
12184
+ title: "Save Theme Package",
12185
+ defaultPath: defaultFilename,
12186
+ filters: [{ name: "ZIP Files", extensions: ["zip"] }],
12187
+ });
12188
+
12189
+ if (saveResult.canceled || !saveResult.filePath) {
12190
+ return { success: false, error: "Save canceled" };
12191
+ }
12192
+
12193
+ const filePath = saveResult.filePath;
12194
+
12195
+ // Create ZIP with manifest.json + {name}.theme.json
12196
+ const zip = new AdmZip();
12197
+ zip.addFile(
12198
+ "manifest.json",
12199
+ Buffer.from(JSON.stringify(manifest, null, 2), "utf-8"),
12200
+ );
12201
+ zip.addFile(
12202
+ `${sanitizedName}.theme.json`,
12203
+ Buffer.from(JSON.stringify(themeData, null, 2), "utf-8"),
12204
+ );
12205
+ zip.writeZip(filePath);
12206
+
12207
+ console.log("[ThemeRegistryController] ZIP created at:", filePath);
12208
+
12209
+ // Attempt to publish to registry
12210
+ let registryResult = null;
12211
+ if (auth.authenticated) {
12212
+ registryResult = await registryApiController$1.publishToRegistry(
12213
+ filePath,
12214
+ manifest,
12215
+ );
12216
+ console.log("[ThemeRegistryController] Registry publish result:", registryResult);
12217
+ }
12218
+
12219
+ return {
12220
+ success: true,
12221
+ manifest,
12222
+ filePath,
12223
+ registryResult,
12224
+ };
12225
+ } catch (err) {
12226
+ console.error("[ThemeRegistryController] Error preparing theme for publish:", err);
12227
+ return { success: false, error: err.message };
12228
+ }
12229
+ }
12230
+
12231
+ /**
12232
+ * Install a theme from the registry.
12233
+ *
12234
+ * Looks up the theme package, downloads the ZIP, extracts the .theme.json,
12235
+ * and saves it via themeController.
12236
+ *
12237
+ * @param {BrowserWindow} win - The sender window
12238
+ * @param {string} appId - Application identifier
12239
+ * @param {string} packageName - Registry package name (e.g., "username/ocean-depth")
12240
+ * @returns {Object} Result with success, themeKey, theme
12241
+ */
12242
+ async function installThemeFromRegistry$1(win, appId, packageName) {
12243
+ try {
12244
+ // Look up the package
12245
+ const pkg = await registryController$1.getPackage(packageName);
12246
+ if (!pkg) {
12247
+ return { success: false, error: `Theme package "${packageName}" not found in registry` };
12248
+ }
12249
+
12250
+ // Resolve download URL
12251
+ let downloadUrl = pkg.downloadUrl;
12252
+ if (!downloadUrl) {
12253
+ return { success: false, error: "Package has no download URL" };
12254
+ }
12255
+
12256
+ // Resolve template variables
12257
+ downloadUrl = downloadUrl
12258
+ .replace("{version}", pkg.version || "1.0.0")
12259
+ .replace("{name}", pkg.name || "");
12260
+
12261
+ // Enforce HTTPS
12262
+ if (!downloadUrl.startsWith("https://")) {
12263
+ return { success: false, error: "Download URL must use HTTPS" };
12264
+ }
12265
+
12266
+ console.log("[ThemeRegistryController] Downloading theme from:", downloadUrl);
12267
+
12268
+ // Download the ZIP
12269
+ const response = await fetch(downloadUrl);
12270
+ if (!response.ok) {
12271
+ return {
12272
+ success: false,
12273
+ error: `Failed to download theme: ${response.status} ${response.statusText}`,
12274
+ };
12275
+ }
12276
+
12277
+ const arrayBuffer = await response.arrayBuffer();
12278
+ const zipBuffer = Buffer.from(arrayBuffer);
12279
+
12280
+ // Extract .theme.json from ZIP
12281
+ const zip = new AdmZip(zipBuffer);
12282
+ const entries = zip.getEntries();
12283
+
12284
+ const themeEntry = entries.find((entry) =>
12285
+ entry.entryName.endsWith(".theme.json"),
12286
+ );
12287
+
12288
+ if (!themeEntry) {
12289
+ return { success: false, error: "ZIP does not contain a .theme.json file" };
12290
+ }
12291
+
12292
+ // Validate entry path (security: prevent path traversal)
12293
+ if (themeEntry.entryName.includes("..") || path$1.isAbsolute(themeEntry.entryName)) {
12294
+ return { success: false, error: "Invalid file path in ZIP" };
12295
+ }
12296
+
12297
+ // Parse theme data
12298
+ const themeJson = themeEntry.getData().toString("utf-8");
12299
+ let themeData;
12300
+ try {
12301
+ themeData = JSON.parse(themeJson);
12302
+ } catch (parseErr) {
12303
+ return { success: false, error: "Invalid JSON in theme file: " + parseErr.message };
12304
+ }
12305
+
12306
+ // Add registry metadata
12307
+ themeData._registryMeta = {
12308
+ source: "registry",
12309
+ packageName,
12310
+ installedAt: new Date().toISOString(),
12311
+ };
12312
+
12313
+ // Determine theme key from package display name or name
12314
+ const themeKey = pkg.displayName || pkg.name;
12315
+
12316
+ // Save via themeController
12317
+ const saveResult = themeController$1.saveThemeForApplication(
12318
+ win,
12319
+ appId,
12320
+ themeKey,
12321
+ themeData,
12322
+ );
12323
+
12324
+ if (saveResult.error) {
12325
+ return { success: false, error: "Failed to save theme: " + saveResult.message };
12326
+ }
12327
+
12328
+ console.log("[ThemeRegistryController] Theme installed:", themeKey);
12329
+
12330
+ return {
12331
+ success: true,
12332
+ themeKey,
12333
+ theme: themeData,
12334
+ themes: saveResult.themes,
12335
+ };
12336
+ } catch (err) {
12337
+ console.error("[ThemeRegistryController] Error installing theme:", err);
12338
+ return { success: false, error: err.message };
12339
+ }
12340
+ }
12341
+
12342
+ /**
12343
+ * Get a preview of theme data for the publish modal.
12344
+ *
12345
+ * @param {string} appId - Application identifier
12346
+ * @param {string} themeKey - Theme key
12347
+ * @returns {Object} Preview data with theme name, colors, etc.
12348
+ */
12349
+ function getThemePublishPreview$1(appId, themeKey) {
12350
+ try {
12351
+ const themesResult = themeController$1.listThemesForApplication(null, appId);
12352
+ if (themesResult.error) {
12353
+ return { success: false, error: "Failed to read themes: " + themesResult.message };
12354
+ }
12355
+
12356
+ const themeData = themesResult.themes[themeKey];
12357
+ if (!themeData) {
12358
+ return { success: false, error: `Theme "${themeKey}" not found` };
12359
+ }
12360
+
12361
+ const colors = extractColors(themeData);
12362
+
12363
+ return {
12364
+ success: true,
12365
+ themeName: themeKey,
12366
+ colors,
12367
+ hasRegistryMeta: !!themeData._registryMeta,
12368
+ };
12369
+ } catch (err) {
12370
+ console.error("[ThemeRegistryController] Error getting preview:", err);
12371
+ return { success: false, error: err.message };
12372
+ }
12373
+ }
12374
+
12375
+ var themeRegistryController$1 = {
12376
+ prepareThemeForPublish: prepareThemeForPublish$1,
12377
+ installThemeFromRegistry: installThemeFromRegistry$1,
12378
+ getThemePublishPreview: getThemePublishPreview$1,
12379
+ generateThemeRegistryManifest,
12380
+ extractColors,
12381
+ };
12382
+
12019
12383
  /**
12020
12384
  * clientFactories.js
12021
12385
  *
@@ -12030,7 +12394,7 @@ const clientCache$1 = requireClientCache();
12030
12394
 
12031
12395
  // --- Algolia ---
12032
12396
  clientCache$1.registerFactory("algolia", (credentials) => {
12033
- const algoliasearch = require$$2$4;
12397
+ const algoliasearch = require$$2$3;
12034
12398
  return algoliasearch(
12035
12399
  credentials.appId,
12036
12400
  credentials.apiKey || credentials.key,
@@ -12039,7 +12403,7 @@ clientCache$1.registerFactory("algolia", (credentials) => {
12039
12403
 
12040
12404
  // --- OpenAI ---
12041
12405
  clientCache$1.registerFactory("openai", (credentials) => {
12042
- const OpenAI = require$$0$4;
12406
+ const OpenAI = require$$0$5;
12043
12407
  return new OpenAI({ apiKey: credentials.apiKey });
12044
12408
  });
12045
12409
 
@@ -12419,7 +12783,7 @@ const {
12419
12783
  const {
12420
12784
  publishToRegistry,
12421
12785
  getRegistryUrl,
12422
- } = registryApiController$1;
12786
+ } = registryApiController$2;
12423
12787
  const {
12424
12788
  getRecentDashboards,
12425
12789
  addRecentDashboard,
@@ -12436,6 +12800,11 @@ const {
12436
12800
  enrichPackagesWithRatings,
12437
12801
  } = dashboardRatingsController;
12438
12802
  const notificationController$1 = notificationController_1;
12803
+ const {
12804
+ prepareThemeForPublish,
12805
+ installThemeFromRegistry,
12806
+ getThemePublishPreview,
12807
+ } = themeRegistryController$1;
12439
12808
 
12440
12809
  var controller = {
12441
12810
  showDialog,
@@ -12511,6 +12880,9 @@ var controller = {
12511
12880
  saveSessionState,
12512
12881
  clearSessionState,
12513
12882
  notificationController: notificationController$1,
12883
+ prepareThemeForPublish,
12884
+ installThemeFromRegistry,
12885
+ getThemePublishPreview,
12514
12886
  };
12515
12887
 
12516
12888
  const { ipcRenderer: ipcRenderer$m } = require$$0$1;
@@ -13505,6 +13877,25 @@ const registryApi$2 = {
13505
13877
  throw error;
13506
13878
  }
13507
13879
  },
13880
+
13881
+ /**
13882
+ * Search the registry for theme packages only
13883
+ * @param {string} query - Search query
13884
+ * @param {Object} filters - Optional filters { category, author, tag }
13885
+ * @returns {Promise<Object>} { packages: [...], totalWidgets: number }
13886
+ */
13887
+ searchThemes: async (query = "", filters = {}) => {
13888
+ try {
13889
+ return await ipcRenderer$d.invoke(
13890
+ "registry:search-themes",
13891
+ query,
13892
+ filters,
13893
+ );
13894
+ } catch (error) {
13895
+ console.error("[RegistryApi] Error searching themes:", error);
13896
+ throw error;
13897
+ }
13898
+ },
13508
13899
  };
13509
13900
 
13510
13901
  var registryApi_1 = registryApi$2;
@@ -13517,7 +13908,14 @@ var registryApi_1 = registryApi$2;
13517
13908
 
13518
13909
  const { ipcRenderer: ipcRenderer$c } = require$$0$1;
13519
13910
 
13520
- const { THEME_LIST, THEME_SAVE, THEME_DELETE } = events$8;
13911
+ const {
13912
+ THEME_LIST,
13913
+ THEME_SAVE,
13914
+ THEME_DELETE,
13915
+ THEME_PUBLISH,
13916
+ THEME_INSTALL_FROM_REGISTRY,
13917
+ THEME_PUBLISH_PREVIEW,
13918
+ } = events$8;
13521
13919
 
13522
13920
  const themeApi$2 = {
13523
13921
  listThemesForApplication: (appId) =>
@@ -13526,6 +13924,12 @@ const themeApi$2 = {
13526
13924
  ipcRenderer$c.invoke(THEME_SAVE, { appId, themeName, themeObject }),
13527
13925
  deleteThemeForApplication: (appId, themeKey) =>
13528
13926
  ipcRenderer$c.invoke(THEME_DELETE, { appId, themeKey }),
13927
+ publishTheme: (appId, themeKey, options) =>
13928
+ ipcRenderer$c.invoke(THEME_PUBLISH, { appId, themeKey, options }),
13929
+ installThemeFromRegistry: (appId, packageName) =>
13930
+ ipcRenderer$c.invoke(THEME_INSTALL_FROM_REGISTRY, { appId, packageName }),
13931
+ getThemePublishPreview: (appId, themeKey) =>
13932
+ ipcRenderer$c.invoke(THEME_PUBLISH_PREVIEW, { appId, themeKey }),
13529
13933
  };
13530
13934
 
13531
13935
  var themeApi_1 = themeApi$2;
@@ -14468,7 +14872,7 @@ const settingsController = settingsController_1;
14468
14872
  const providerController = requireProviderController();
14469
14873
  const layoutController = layoutController_1;
14470
14874
  const mcpController = mcpControllerExports;
14471
- const registryController = registryController$1;
14875
+ const registryController = registryController$2;
14472
14876
  const algoliaController = algoliaController_1;
14473
14877
  const openaiController = openaiController_1;
14474
14878
  const menuItemsController = menuItemsController_1;
@@ -14477,8 +14881,9 @@ const llmController = llmController_1;
14477
14881
  const cliController = cliController_1;
14478
14882
  const dashboardConfigController = dashboardConfigController$1;
14479
14883
  const registryAuthController = registryAuthController$1;
14480
- const registryApiController = registryApiController$1;
14884
+ const registryApiController = registryApiController$2;
14481
14885
  const notificationController = notificationController_1;
14886
+ const themeRegistryController = themeRegistryController$1;
14482
14887
 
14483
14888
  // --- Utils ---
14484
14889
  const clientCache = requireClientCache();
@@ -14547,6 +14952,7 @@ var electron = {
14547
14952
  registryAuthController,
14548
14953
  registryApiController,
14549
14954
  notificationController,
14955
+ themeRegistryController,
14550
14956
 
14551
14957
  // Controller functions (flat) — spread for convenient destructuring
14552
14958
  ...controllers,