@nocobase/cli 2.2.0-alpha.10 → 2.2.0-alpha.12

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.
@@ -27,17 +27,14 @@ import { omitKeys, pickKeys } from "../lib/object-utils.js";
27
27
  import { ENV_CONFIG_SCHEMA_VERSION } from '../lib/env-config.js';
28
28
  import { printInfo, printStage, printVerbose, printWarning } from '../lib/ui.js';
29
29
  import { persistHookScript } from '../lib/hook-script.js';
30
+ import { ensureManagedEnvFileDefaults } from '../lib/managed-env-file.js';
30
31
  import Download from "./download.js";
31
32
  import EnvAdd from "./env/add.js";
32
33
  import Install, { defaultDbPortForDialect } from "./install.js";
33
34
  const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
34
35
  const DEFAULT_INIT_APP_NAME = 'local';
35
- const DEFAULT_INIT_PORTAL_TYPE = 'no-code';
36
- const DEFAULT_INIT_PORTAL_NAME = 'admin';
37
- const DEFAULT_INIT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
38
36
  const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
39
37
  const INIT_SETUP_MODES = ['install-new', 'manage-local', 'connect-remote'];
40
- const INIT_PORTAL_TYPES = ['no-code', 'ai'];
41
38
  const INIT_ENV_ADD_FLAG_NAMES = [
42
39
  'locale',
43
40
  'default-api-base-url',
@@ -84,9 +81,6 @@ function isInstallNewSetupMode(values) {
84
81
  function isInstallLikeSetupMode(values) {
85
82
  return !isRemoteSetupMode(values);
86
83
  }
87
- function isAiMode(values) {
88
- return String(values.portalType ?? DEFAULT_INIT_PORTAL_TYPE).trim() === 'ai';
89
- }
90
84
  function remoteConnectionOnly(def) {
91
85
  return withExtraHidden(def, (values) => !isRemoteSetupMode(values));
92
86
  }
@@ -394,42 +388,6 @@ Prompt modes:
394
388
  devDependencies: installLikeDownloadExecutionOnly(Download.prompts.devDependencies),
395
389
  build: installLikeDownloadExecutionOnly(Download.prompts.build),
396
390
  buildDts: installLikeDownloadExecutionOnly(Download.prompts.buildDts),
397
- portalType: installNewOnly({
398
- type: 'select',
399
- variant: 'radio',
400
- message: initText('prompts.portalType.message'),
401
- options: [
402
- {
403
- value: 'no-code',
404
- label: initText('prompts.portalType.noCodeLabel'),
405
- hint: initText('prompts.portalType.noCodeHint'),
406
- },
407
- {
408
- value: 'ai',
409
- label: initText('prompts.portalType.aiLabel'),
410
- hint: initText('prompts.portalType.aiHint'),
411
- },
412
- ],
413
- initialValue: DEFAULT_INIT_PORTAL_TYPE,
414
- yesInitialValue: DEFAULT_INIT_PORTAL_TYPE,
415
- required: true,
416
- }),
417
- portalName: installNewOnly({
418
- type: 'text',
419
- message: initText('prompts.portalName.message'),
420
- placeholder: DEFAULT_INIT_PORTAL_NAME,
421
- initialValue: DEFAULT_INIT_PORTAL_NAME,
422
- yesInitialValue: DEFAULT_INIT_PORTAL_NAME,
423
- required: true,
424
- }),
425
- portalTemplate: installNewOnly({
426
- type: 'text',
427
- message: initText('prompts.portalTemplate.message'),
428
- placeholder: DEFAULT_INIT_PORTAL_TEMPLATE,
429
- yesInitialValue: DEFAULT_INIT_PORTAL_TEMPLATE,
430
- hidden: (values) => !isAiMode(values),
431
- required: true,
432
- }),
433
391
  dbDialect: installLikeOnly(Install.dbPrompts.dbDialect),
434
392
  builtinDb: installLikeOnly(Install.dbPrompts.builtinDb),
435
393
  builtinDbImage: installLikeOnly(Install.dbPrompts.builtinDbImage),
@@ -455,13 +413,6 @@ Prompt modes:
455
413
  ...Init.prompts,
456
414
  installApiBaseUrl: createInstallConnectionApiBaseUrlPrompt(options.defaultApiHost),
457
415
  };
458
- const defaultPortalTemplate = String(options.defaultPortalTemplate ?? '').trim();
459
- if (defaultPortalTemplate) {
460
- prompts.portalTemplate = {
461
- ...prompts.portalTemplate,
462
- yesInitialValue: defaultPortalTemplate,
463
- };
464
- }
465
416
  if (flags['skip-auth']) {
466
417
  const accessTokenPrompt = {
467
418
  ...EnvAdd.prompts.accessToken,
@@ -511,16 +462,6 @@ Prompt modes:
511
462
  description: 'Setup mode: install a new app, manage a local app by reusing its database, or connect a remote app',
512
463
  options: [...INIT_SETUP_MODES],
513
464
  }),
514
- 'portal-type': Flags.string({
515
- description: 'Initial portal type: no-code or ai',
516
- options: [...INIT_PORTAL_TYPES],
517
- }),
518
- 'portal-name': Flags.string({
519
- description: 'Initial portal name',
520
- }),
521
- 'portal-template': Flags.string({
522
- description: 'Initial portal template npm package or local path when --portal-type ai is used',
523
- }),
524
465
  ui: Flags.boolean({
525
466
  description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
526
467
  default: false,
@@ -638,8 +579,7 @@ Prompt modes:
638
579
  const dynamicInitialValues = await Init.buildDynamicInitialValuesForInstall(normalizedFlags, presetValues);
639
580
  const defaultUiHost = await resolveDefaultUiHost();
640
581
  const defaultApiHost = await resolveDefaultApiHost();
641
- const defaultPortalTemplate = String(dynamicInitialValues.portalTemplate ?? '').trim();
642
- const promptCatalog = this.buildPromptCatalog(normalizedFlags, { defaultApiHost, defaultPortalTemplate });
582
+ const promptCatalog = this.buildPromptCatalog(normalizedFlags, { defaultApiHost });
643
583
  if (useBrowserUi) {
644
584
  presetValues = await runPromptCatalogWebUI({
645
585
  stages: Init.buildWebUiStages(promptCatalog),
@@ -668,7 +608,7 @@ Prompt modes:
668
608
  ? { setupMode: normalizeInitSetupMode(presetValues.hasNocobase) }
669
609
  : {}),
670
610
  },
671
- yesInitialValues: pickKeys(dynamicInitialValues, ['portalTemplate']),
611
+ yesInitialValues: {},
672
612
  values: presetValues,
673
613
  yes: normalizedFlags.yes || useBrowserUi || !interactive,
674
614
  hooks: {
@@ -741,8 +681,7 @@ Prompt modes:
741
681
  }
742
682
  static async buildDynamicInitialValuesForInstall(flags, presetValues) {
743
683
  const out = {};
744
- const shouldResolveAppInitialValues = !Object.prototype.hasOwnProperty.call(presetValues, 'appPort') ||
745
- !Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate');
684
+ const shouldResolveAppInitialValues = !Object.prototype.hasOwnProperty.call(presetValues, 'appPort');
746
685
  if (shouldResolveAppInitialValues) {
747
686
  const appInitialValues = await Install.buildAppPromptInitialValues({
748
687
  envName: String(presetValues.appName ?? '').trim(),
@@ -751,20 +690,12 @@ Prompt modes:
751
690
  'app-path': flags['app-path'] ?? '',
752
691
  'app-root-path': flags['app-root-path'] ?? '',
753
692
  'storage-path': flags['storage-path'] ?? '',
754
- 'portal-template': flags['portal-template'] ??
755
- (Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate')
756
- ? String(presetValues.portalTemplate ?? '')
757
- : undefined),
758
693
  },
759
694
  warnOnPortFallback: false,
760
695
  });
761
696
  if (appInitialValues.appPort !== undefined && !Object.prototype.hasOwnProperty.call(presetValues, 'appPort')) {
762
697
  out.appPort = appInitialValues.appPort;
763
698
  }
764
- if (appInitialValues.portalTemplate !== undefined &&
765
- !Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate')) {
766
- out.portalTemplate = appInitialValues.portalTemplate;
767
- }
768
699
  }
769
700
  const downloadSeed = { ...presetValues };
770
701
  if (flags.yes && !Object.prototype.hasOwnProperty.call(downloadSeed, 'source')) {
@@ -843,14 +774,6 @@ Prompt modes:
843
774
  buildDts: c.buildDts,
844
775
  },
845
776
  },
846
- {
847
- sectionTitle: initText('webUi.portalType.title'),
848
- sectionDescription: initText('webUi.portalType.description'),
849
- catalog: {
850
- portalName: c.portalName,
851
- portalType: c.portalType,
852
- },
853
- },
854
777
  {
855
778
  sectionTitle: initText('webUi.configureDatabase.title'),
856
779
  sectionDescription: initText('webUi.configureDatabase.description'),
@@ -951,15 +874,6 @@ Prompt modes:
951
874
  if (flags['app-public-path'] !== undefined && String(flags['app-public-path']).trim() !== '') {
952
875
  preset.appPublicPath = String(flags['app-public-path']).trim();
953
876
  }
954
- if (flags['portal-type'] !== undefined && String(flags['portal-type']).trim() !== '') {
955
- preset.portalType = String(flags['portal-type']).trim();
956
- }
957
- if (flags['portal-name'] !== undefined && String(flags['portal-name']).trim() !== '') {
958
- preset.portalName = String(flags['portal-name']).trim();
959
- }
960
- if (flags['portal-template'] !== undefined && String(flags['portal-template']).trim() !== '') {
961
- preset.portalTemplate = String(flags['portal-template']).trim();
962
- }
963
877
  if (flags['root-username'] !== undefined) {
964
878
  preset.rootUsername = String(flags['root-username'] ?? '').trim();
965
879
  }
@@ -1097,9 +1011,6 @@ Prompt modes:
1097
1011
  const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
1098
1012
  const appPort = String(results.appPort ?? '').trim();
1099
1013
  const appPublicPath = String(results.appPublicPath ?? '').trim();
1100
- const portalType = String(results.portalType ?? '').trim();
1101
- const portalName = String(results.portalName ?? '').trim();
1102
- const portalTemplate = String(results.portalTemplate ?? '').trim();
1103
1014
  const source = String(results.source ?? '').trim();
1104
1015
  const version = resolveInitDownloadVersion(results);
1105
1016
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
@@ -1126,7 +1037,8 @@ Prompt modes:
1126
1037
  const dbSchema = String(results.dbSchema ?? '').trim();
1127
1038
  const dbTablePrefix = String(results.dbTablePrefix ?? '').trim();
1128
1039
  const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
1129
- const authType = String(results.authType ?? '').trim() || 'oauth';
1040
+ const authTypeInput = String(results.authType ?? '').trim();
1041
+ const authType = authTypeInput === 'basic' || authTypeInput === 'token' || authTypeInput === 'oauth' ? authTypeInput : 'oauth';
1130
1042
  const authUsername = authType === 'basic' ? String(results.username ?? results.rootUsername ?? '').trim() : '';
1131
1043
  const accessToken = String(results.accessToken ?? '');
1132
1044
  const skipDownload = results.skipDownload === true;
@@ -1147,7 +1059,7 @@ Prompt modes:
1147
1059
  : Boolean(results.builtinDb);
1148
1060
  results.appKey = appKey;
1149
1061
  results.timeZone = timeZone;
1150
- await upsertEnv(envName, {
1062
+ const savedEnvConfig = {
1151
1063
  schemaVersion: ENV_CONFIG_SCHEMA_VERSION,
1152
1064
  ...(source === 'docker'
1153
1065
  ? { kind: 'docker' }
@@ -1172,9 +1084,6 @@ Prompt modes:
1172
1084
  ...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
1173
1085
  ...(appPort ? { appPort } : {}),
1174
1086
  ...(appPublicPath ? { appPublicPath } : {}),
1175
- ...(portalType && portalType !== DEFAULT_INIT_PORTAL_TYPE ? { portalType } : {}),
1176
- ...(portalName ? { portalName } : {}),
1177
- ...(portalTemplate ? { portalTemplate } : {}),
1178
1087
  ...(appKey ? { appKey } : {}),
1179
1088
  ...(timeZone ? { timezone: timeZone } : {}),
1180
1089
  ...(!skipDownload && results.devDependencies !== undefined
@@ -1195,7 +1104,11 @@ Prompt modes:
1195
1104
  ...(results.dbUnderscored !== undefined ? { dbUnderscored: Boolean(results.dbUnderscored) } : {}),
1196
1105
  setupState: 'prepared',
1197
1106
  ...(String(results.lang ?? '').trim() ? { lang: String(results.lang ?? '').trim() } : {}),
1198
- }, { scope: resolveDefaultConfigScope() });
1107
+ };
1108
+ await upsertEnv(envName, savedEnvConfig, { scope: resolveDefaultConfigScope() });
1109
+ if (source === 'docker' || appPath) {
1110
+ await ensureManagedEnvFileDefaults(envName, savedEnvConfig);
1111
+ }
1199
1112
  }
1200
1113
  buildEnvAddArgv(results) {
1201
1114
  const argv = [String(results.appName ?? DEFAULT_INIT_APP_NAME)];
@@ -1299,18 +1212,6 @@ Prompt modes:
1299
1212
  if (appPublicPath) {
1300
1213
  argv.push('--app-public-path', appPublicPath);
1301
1214
  }
1302
- const portalType = String(results.portalType ?? '').trim();
1303
- if (portalType && portalType !== DEFAULT_INIT_PORTAL_TYPE) {
1304
- argv.push('--portal-type', portalType);
1305
- }
1306
- const portalName = String(results.portalName ?? '').trim();
1307
- if (portalName) {
1308
- argv.push('--portal-name', portalName);
1309
- }
1310
- const portalTemplate = String(results.portalTemplate ?? '').trim();
1311
- if (portalTemplate) {
1312
- argv.push('--portal-template', portalTemplate);
1313
- }
1314
1215
  if (flags.force) {
1315
1216
  argv.push('--force');
1316
1217
  }
@@ -1508,15 +1409,6 @@ Prompt modes:
1508
1409
  delete normalized.rootPassword;
1509
1410
  delete normalized.rootNickname;
1510
1411
  }
1511
- const portalType = normalizeConnectionString(normalized.portalType) || DEFAULT_INIT_PORTAL_TYPE;
1512
- normalized.portalType = portalType;
1513
- normalized.portalName = normalizeConnectionString(normalized.portalName) || DEFAULT_INIT_PORTAL_NAME;
1514
- if (portalType === 'ai') {
1515
- normalized.portalTemplate = normalizeConnectionString(normalized.portalTemplate);
1516
- }
1517
- else {
1518
- delete normalized.portalTemplate;
1519
- }
1520
1412
  delete normalized.installApiBaseUrl;
1521
1413
  delete normalized.installAuthType;
1522
1414
  delete normalized.installUsername;
@@ -29,6 +29,7 @@ import { buildStoredEnvConfig } from '../lib/env-config.js';
29
29
  import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
30
30
  import { startDockerLogFollower } from '../lib/docker-log-stream.js';
31
31
  import { buildInitAppEnvVarsFromConfig } from '../lib/managed-init-env.js';
32
+ import { ensureManagedEnvFileDefaults } from '../lib/managed-env-file.js';
32
33
  import { buildHookContext, persistHookScript, resolveHookScriptPath, runHookScriptHook, } from '../lib/hook-script.js';
33
34
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
34
35
  import Download from './download.js';
@@ -53,8 +54,8 @@ const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
53
54
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
54
55
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
55
56
  const DEFAULT_INSTALL_API_HOST = '127.0.0.1';
56
- const DEFAULT_INSTALL_PORTAL_TYPE = 'no-code';
57
- const DEFAULT_INSTALL_PORTAL_NAME = 'admin';
57
+ const DEFAULT_INSTALL_PORTAL_TYPE = 'ai';
58
+ const DEFAULT_INSTALL_PORTAL_NAME = 'main';
58
59
  const DEFAULT_INSTALL_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
59
60
  const INSTALL_PORTAL_TYPES = ['no-code', 'ai'];
60
61
  function toOptionalPromptString(value) {
@@ -2316,9 +2317,13 @@ export default class Install extends Command {
2316
2317
  }
2317
2318
  async saveInstalledEnv(params) {
2318
2319
  const defaultApiHost = await resolveDefaultApiHost();
2319
- await upsertEnv(params.envName, Install.buildSavedEnvConfig(params, { defaultApiHost }), {
2320
+ const savedEnvConfig = Install.buildSavedEnvConfig(params, { defaultApiHost });
2321
+ await upsertEnv(params.envName, savedEnvConfig, {
2320
2322
  scope: resolveDefaultConfigScope(),
2321
2323
  });
2324
+ if (params.ensureEnvFileDefaults !== false) {
2325
+ await ensureManagedEnvFileDefaults(params.envName, savedEnvConfig);
2326
+ }
2322
2327
  await setCurrentEnv(params.envName, { scope: resolveDefaultConfigScope() });
2323
2328
  }
2324
2329
  async syncInstalledEnvConnection(params) {
@@ -2660,6 +2665,7 @@ export default class Install extends Command {
2660
2665
  dbResults,
2661
2666
  rootResults,
2662
2667
  envAddResults,
2668
+ ensureEnvFileDefaults: false,
2663
2669
  });
2664
2670
  if (!parsed['skip-save-env-log']) {
2665
2671
  printInfo(`Saved env config for "${envName}".`);
@@ -16,7 +16,7 @@ import { printInfo, printSuccess } from '../../lib/ui.js';
16
16
  const DEFAULT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
17
17
  const portalCreateText = (key, values, fallback) => translateCli(`commands.portalCreate.${key}`, values, { fallback });
18
18
  export default class PortalCreate extends Command {
19
- static summary = 'Create a local portal from a template';
19
+ static summary = 'Create a local AI portal from a template';
20
20
  static examples = [
21
21
  '<%= config.bin %> <%= command.id %> customer',
22
22
  '<%= config.bin %> <%= command.id %> customer --template @nocobase/portal-template-default',
@@ -26,7 +26,7 @@ export default class PortalCreate extends Command {
26
26
  static args = {
27
27
  portal: Args.string({
28
28
  required: true,
29
- description: 'Portal name',
29
+ description: 'AI Portal name',
30
30
  }),
31
31
  };
32
32
  static flags = {
@@ -76,7 +76,8 @@ export default class PortalCreate extends Command {
76
76
  if (!confirmed) {
77
77
  return;
78
78
  }
79
- const env = await getEnv(flags.env, { scope: resolveDefaultConfigScope() });
79
+ const scope = resolveDefaultConfigScope();
80
+ const env = await getEnv(flags.env, { scope });
80
81
  if (!env) {
81
82
  this.error(flags.env
82
83
  ? portalCreateText('errors.envNotConfigured', { envName: flags.env }, `Env "${flags.env}" is not configured. Run \`nb env add ${flags.env} --api-base-url <url>\` first.`)
@@ -199,6 +199,13 @@ async function createMultipartBody(flags, operation) {
199
199
  if (value === undefined) {
200
200
  continue;
201
201
  }
202
+ if (Array.isArray(value)) {
203
+ for (const item of value) {
204
+ formData.append(parameter.name, typeof item === 'object' ? JSON.stringify(item) : String(item));
205
+ }
206
+ hasValues = hasValues || value.length > 0;
207
+ continue;
208
+ }
202
209
  formData.append(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
203
210
  hasValues = true;
204
211
  }
@@ -32,12 +32,12 @@ function buildDeviceVerificationPathFromApiBaseUrl(apiBaseUrl) {
32
32
  const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
33
33
  if (subappMatch) {
34
34
  const publicPath = (subappMatch[1] || '').replace(/\/+$/, '');
35
- return `${publicPath}/apps/${subappMatch[2]}/idpOAuth/device`;
35
+ return `${publicPath}/settings/apps/${subappMatch[2]}/idpOAuth/device`;
36
36
  }
37
37
  const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
38
38
  if (appMatch) {
39
39
  const publicPath = (appMatch[1] || '').replace(/\/+$/, '');
40
- return `${publicPath}/idpOAuth/device`;
40
+ return `${publicPath}/settings/idpOAuth/device`;
41
41
  }
42
42
  return undefined;
43
43
  }
@@ -20,6 +20,9 @@ const DEFAULT_API_BASE_PATH = '/api/';
20
20
  const DEFAULT_WS_PATH = '/ws';
21
21
  const DEFAULT_PLUGIN_STATICS_PATH = '/static/plugins/';
22
22
  const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
23
+ const SETTINGS_CLIENT_PREFIX = 'settings';
24
+ const DEFAULT_APP_CLIENT_ENTRY_MODE = 'legacy-default';
25
+ const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only']);
23
26
  const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
24
27
  const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
25
28
  const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
@@ -67,7 +70,15 @@ function normalizeModernClientPrefix(value) {
67
70
  const segment = String(value || '')
68
71
  .trim()
69
72
  .replace(/^\/+|\/+$/g, '');
70
- return segment || DEFAULT_MODERN_CLIENT_PREFIX;
73
+ const normalized = segment || DEFAULT_MODERN_CLIENT_PREFIX;
74
+ if (normalized === SETTINGS_CLIENT_PREFIX) {
75
+ throw new Error('APP_MODERN_CLIENT_PREFIX "settings" is reserved for the standalone Settings application.');
76
+ }
77
+ return normalized;
78
+ }
79
+ function normalizeAppClientEntryMode(value) {
80
+ const normalized = String(value || '').trim();
81
+ return APP_CLIENT_ENTRY_MODES.has(normalized) ? normalized : DEFAULT_APP_CLIENT_ENTRY_MODE;
71
82
  }
72
83
  function normalizeApiBasePath(value = DEFAULT_API_BASE_PATH) {
73
84
  return resolveAppPublicPath(value);
@@ -275,6 +286,7 @@ export async function loadEnvProxySettings(runtime, options) {
275
286
  wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
276
287
  pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
277
288
  modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
289
+ appClientEntryMode: normalizeAppClientEntryMode(envValues.APP_CLIENT_ENTRY_MODE),
278
290
  cdnBaseUrl: trimValue(options?.cdnBaseUrl) ??
279
291
  trimValue(runtime.env.envVars?.CDN_BASE_URL) ??
280
292
  trimValue(envValues.CDN_BASE_URL),
@@ -299,7 +311,8 @@ function createManualProxyEnvSettings(input) {
299
311
  pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
300
312
  trailingSlash: true,
301
313
  }),
302
- modernClientPrefix: DEFAULT_MODERN_CLIENT_PREFIX,
314
+ modernClientPrefix: normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX),
315
+ appClientEntryMode: normalizeAppClientEntryMode(process.env.APP_CLIENT_ENTRY_MODE),
303
316
  cdnBaseUrl: trimValue(input.cdnBaseUrl),
304
317
  apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
305
318
  apiClientStorageType: DEFAULT_API_CLIENT_STORAGE_TYPE,
@@ -491,6 +504,9 @@ function buildNginxManagedConfigBlock(context) {
491
504
  const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
492
505
  const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
493
506
  const fileAccessPath = `${context.appPublicPath}files/`;
507
+ const settingsAssetsPath = `${context.appPublicPath}settings/assets/`;
508
+ const settingsAssetsRoot = joinRuntimePath(context.distRootDir, `${context.activeVersion}/settings/assets`);
509
+ const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/|$)`;
494
510
  const isRootMounted = context.appPublicPath === '/';
495
511
  const appPublicPathRedirectBlock = isRootMounted
496
512
  ? ''
@@ -563,6 +579,17 @@ function buildNginxManagedConfigBlock(context) {
563
579
  ` return 302 ${context.v2PublicPath}$is_args$args;`,
564
580
  ' }',
565
581
  '',
582
+ ` location ^~ ${settingsAssetsPath} {`,
583
+ ` alias ${settingsAssetsRoot}/;`,
584
+ ` include ${context.snippetsDir}/dist-location.conf;`,
585
+ ' }',
586
+ '',
587
+ ` location ~ ${settingsRoutePattern} {`,
588
+ ` root ${context.publicDir};`,
589
+ ` try_files $uri /index-settings.html =404;`,
590
+ ` include ${context.snippetsDir}/spa-location.conf;`,
591
+ ' }',
592
+ '',
566
593
  ` location ^~ ${context.v2PublicPath} {`,
567
594
  ` alias ${context.publicDir}/;`,
568
595
  ` try_files $uri /index-v2.html =404;`,
@@ -640,8 +667,9 @@ function buildNginxPortalLocationBlock(context) {
640
667
  function buildNginxRuntimeConfig(context, variant) {
641
668
  return {
642
669
  __webpack_public_path__: context.cdnBaseUrl,
643
- __nocobase_public_path__: variant === 'v1' ? context.appPublicPath : context.v2PublicPath,
644
- ...(variant === 'v2' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
670
+ __nocobase_public_path__: variant === 'v2' ? context.v2PublicPath : context.appPublicPath,
671
+ ...(variant !== 'v1' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
672
+ __nocobase_app_client_entry_mode__: context.appClientEntryMode,
645
673
  __nocobase_api_base_url__: context.apiBasePath,
646
674
  __nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
647
675
  __nocobase_api_client_storage_type__: context.apiClientStorageType,
@@ -692,7 +720,9 @@ async function buildEnvProxyNginxRenderContext(source, options) {
692
720
  esmCdnSuffix: source.settings.esmCdnSuffix,
693
721
  indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
694
722
  indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
723
+ indexSettingsPath: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }), options),
695
724
  modernClientPrefix: source.settings.modernClientPrefix,
725
+ appClientEntryMode: source.settings.appClientEntryMode,
696
726
  proxyHost,
697
727
  snippetsDir: mappedSnippetsDir,
698
728
  storageDir: mappedStorageDir,
@@ -811,16 +841,20 @@ async function buildNginxBundleFromSource(source, options) {
811
841
  const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
812
842
  const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
813
843
  const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
814
- const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
844
+ const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
845
+ const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
815
846
  readFile(sourceIndexV1Path, 'utf8'),
816
847
  readFile(sourceIndexV2Path, 'utf8'),
848
+ readFile(sourceIndexSettingsPath, 'utf8'),
817
849
  ]);
818
850
  const v1RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v1'));
819
851
  const v2RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v2'));
852
+ const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'settings'));
820
853
  const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
821
854
  const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
822
855
  const indexV1AssetPublicPath = context.cdnBaseUrl;
823
856
  const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
857
+ const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
824
858
  const appConfigIncludePath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir('nginx', { scope: options?.scope }), '*', resolveEnvProxyFileSpec('nginx').appFilename), options);
825
859
  const managedConfigBlock = buildNginxManagedConfigBlock(context);
826
860
  const templateValues = {
@@ -844,6 +878,7 @@ async function buildNginxBundleFromSource(source, options) {
844
878
  appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
845
879
  indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
846
880
  indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
881
+ indexSettingsPath: resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
847
882
  mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
848
883
  snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
849
884
  appPublicPath: context.appPublicPath,
@@ -861,6 +896,7 @@ async function buildNginxBundleFromSource(source, options) {
861
896
  }),
862
897
  indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
863
898
  indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
899
+ indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
864
900
  };
865
901
  }
866
902
  export async function buildEnvProxyCaddyBundle(runtime, options) {
@@ -877,21 +913,26 @@ async function buildCaddyBundleFromSource(source, options) {
877
913
  const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
878
914
  const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
879
915
  const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
880
- const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
916
+ const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
917
+ const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
881
918
  readFile(sourceIndexV1Path, 'utf8'),
882
919
  readFile(sourceIndexV2Path, 'utf8'),
920
+ readFile(sourceIndexSettingsPath, 'utf8'),
883
921
  ]);
884
922
  const v1RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v1'));
885
923
  const v2RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v2'));
924
+ const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'settings'));
886
925
  const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
887
926
  const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
888
927
  const indexV1AssetPublicPath = context.cdnBaseUrl;
889
928
  const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
929
+ const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
890
930
  const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
891
931
  const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
892
932
  const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
893
933
  const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
894
934
  const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
935
+ activeVersion: context.activeVersion,
895
936
  appPublicPath: context.appPublicPath,
896
937
  apiBasePath: context.apiBasePath,
897
938
  apiPort: context.apiPort,
@@ -912,6 +953,7 @@ async function buildCaddyBundleFromSource(source, options) {
912
953
  appConfigPath,
913
954
  indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
914
955
  indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
956
+ indexSettingsPath: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
915
957
  mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
916
958
  appPublicPath: context.appPublicPath,
917
959
  apiBasePath: context.apiBasePath,
@@ -925,6 +967,7 @@ async function buildCaddyBundleFromSource(source, options) {
925
967
  mainConfigContent: await buildEnvProxyMainConfig({ provider: 'caddy', scope: options?.scope }),
926
968
  indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
927
969
  indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
970
+ indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
928
971
  };
929
972
  }
930
973
  async function pathExists(candidate) {
@@ -1179,6 +1222,9 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
1179
1222
  const uploadsPath = `${context.appPublicPath}storage/uploads/`;
1180
1223
  const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
1181
1224
  const distPathMatcher = toCaddyPathMatcher(context.distPath);
1225
+ const settingsAssetsPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}settings/assets/`);
1226
+ const settingsAssetsRoot = joinRuntimePath(context.distClientRoot, `${context.activeVersion}/settings/assets`);
1227
+ const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/.*)?$`;
1182
1228
  const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
1183
1229
  const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
1184
1230
  const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
@@ -1236,6 +1282,12 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
1236
1282
  ' file_server',
1237
1283
  ' }',
1238
1284
  '',
1285
+ ` handle_path ${settingsAssetsPathMatcher} {`,
1286
+ ` root * ${settingsAssetsRoot}`,
1287
+ ' header Cache-Control "public, max-age=31536000, immutable"',
1288
+ ' file_server',
1289
+ ' }',
1290
+ '',
1239
1291
  ' @oauth path_regexp oauth ^/\\.well-known/oauth-authorization-server/(.+)$',
1240
1292
  ' handle @oauth {',
1241
1293
  ' rewrite * /{re.oauth.1}/.well-known/oauth-authorization-server',
@@ -1269,6 +1321,15 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
1269
1321
  ` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
1270
1322
  ' }',
1271
1323
  '',
1324
+ ` @settingsRoute path_regexp settingsRoute ${settingsRoutePattern}`,
1325
+ ' handle @settingsRoute {',
1326
+ ` root * ${publicDir}`,
1327
+ ' header Cache-Control "no-store, no-cache, must-revalidate"',
1328
+ ' header X-Robots-Tag "noindex, nofollow"',
1329
+ ' try_files {path} /index-settings.html',
1330
+ ' file_server',
1331
+ ' }',
1332
+ '',
1272
1333
  ' # Keep the v2 SPA route above the fallback SPA route.',
1273
1334
  ` handle_path ${toCaddyPathMatcher(context.v2PublicPath)} {`,
1274
1335
  ` root * ${publicDir}`,
@@ -1341,6 +1402,7 @@ async function buildEnvProxyRenderState(runtime, options) {
1341
1402
  : await mapProxyPathFromCliRoot(distClientRoot, options);
1342
1403
  const provider = resolveProxyProviderName(options?.provider);
1343
1404
  const templateContext = {
1405
+ activeVersion: runtimeVersion,
1344
1406
  appPublicPath: settings.appPublicPath,
1345
1407
  apiBasePath: settings.apiBasePath,
1346
1408
  apiPort,
@@ -6,11 +6,17 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { readFile } from 'node:fs/promises';
9
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
10
  import path from 'node:path';
11
+ import { resolveEnvKind } from './auth-store.js';
11
12
  import { resolveConfiguredEnvPath } from './cli-home.js';
12
- import { resolveDockerEnvFileArg } from "./docker-env-file.js";
13
+ import { resolveDockerEnvFileArg, resolveDockerEnvFilePath } from "./docker-env-file.js";
13
14
  import { resolveConfiguredAppPath } from './env-paths.js';
15
+ export const DEFAULT_MANAGED_ENV_FILE_VALUES = {
16
+ APP_DISCOVERY_ADAPTER: 'local',
17
+ APP_PROCESS_ADAPTER: 'local',
18
+ APP_CLIENT_ENTRY_MODE: 'modern-only',
19
+ };
14
20
  function trimValue(value) {
15
21
  const text = String(value ?? '').trim();
16
22
  return text || undefined;
@@ -68,6 +74,56 @@ export function resolveManagedLocalEnvFilePath(runtime) {
68
74
  }
69
75
  return normalizeEnvFilePath(path.join(runtime.projectRoot, '.env'));
70
76
  }
77
+ export function resolveManagedEnvFilePathFromConfig(envName, config) {
78
+ const kind = config?.kind ?? resolveEnvKind(config);
79
+ if (kind === 'docker') {
80
+ const filePath = resolveDockerEnvFilePath(envName, config);
81
+ return filePath ? normalizeEnvFilePath(filePath) : undefined;
82
+ }
83
+ if (kind !== 'local') {
84
+ return undefined;
85
+ }
86
+ const explicitEnvFile = trimValue(config?.envFile);
87
+ if (explicitEnvFile) {
88
+ return normalizeEnvFilePath(resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile);
89
+ }
90
+ const configuredAppPath = resolveConfiguredAppPath(config);
91
+ if (configuredAppPath) {
92
+ return normalizeEnvFilePath(path.join(configuredAppPath, '.env'));
93
+ }
94
+ const configuredAppRootPath = trimValue(config?.appRootPath);
95
+ if (configuredAppRootPath) {
96
+ const appRootPath = resolveConfiguredEnvPath(configuredAppRootPath) ?? configuredAppRootPath;
97
+ return normalizeEnvFilePath(path.basename(appRootPath) === 'source' ? path.resolve(appRootPath, '..', '.env') : path.join(appRootPath, '.env'));
98
+ }
99
+ return undefined;
100
+ }
101
+ export async function ensureManagedEnvFileDefaults(envName, config, defaults = DEFAULT_MANAGED_ENV_FILE_VALUES) {
102
+ const envFilePath = resolveManagedEnvFilePathFromConfig(envName, config);
103
+ if (!envFilePath) {
104
+ return undefined;
105
+ }
106
+ let content = '';
107
+ try {
108
+ content = await readFile(envFilePath, 'utf8');
109
+ }
110
+ catch (error) {
111
+ const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
112
+ if (code !== 'ENOENT') {
113
+ throw error;
114
+ }
115
+ }
116
+ const existing = parseSimpleEnvFile(content);
117
+ const missingEntries = Object.entries(defaults).filter(([key, value]) => trimValue(value) && !existing[key]);
118
+ if (missingEntries.length === 0) {
119
+ return envFilePath;
120
+ }
121
+ const separator = content && !content.endsWith('\n') ? '\n' : '';
122
+ const nextContent = `${content}${separator}${missingEntries.map(([key, value]) => `${key}=${value}`).join('\n')}\n`;
123
+ await mkdir(path.dirname(envFilePath), { recursive: true });
124
+ await writeFile(envFilePath, nextContent, 'utf8');
125
+ return envFilePath;
126
+ }
71
127
  export async function resolveManagedRuntimeEnvFilePath(runtime) {
72
128
  if (runtime.kind === 'local') {
73
129
  return resolveManagedLocalEnvFilePath(runtime);
@@ -1,6 +1,15 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
1
9
  import path from 'node:path';
2
10
  export function toKebabCase(value) {
3
11
  return value
12
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
4
13
  .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
5
14
  .replace(/[^a-zA-Z0-9]+/g, '-')
6
15
  .replace(/-+/g, '-')
@@ -79,6 +79,7 @@ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeCon
79
79
  writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
80
80
  writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
81
81
  writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
82
+ writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
82
83
  writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
83
84
  ]);
84
85
  return {
@@ -100,6 +101,7 @@ export async function writeManualCaddyProxyBundle(input, appEntryOptions, runtim
100
101
  writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
101
102
  writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
102
103
  writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
104
+ writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
103
105
  writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
104
106
  ]);
105
107
  return {
@@ -107,6 +107,7 @@ async function writeResolvedNginxProxyBundle(bundle, appEntryOptions, options) {
107
107
  writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
108
108
  writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
109
109
  writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
110
+ writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
110
111
  writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
111
112
  syncEnvProxyNginxSnippets(),
112
113
  ]);
@@ -202,6 +202,117 @@
202
202
  }
203
203
  }
204
204
  },
205
+ "ai": {
206
+ "name": "ai",
207
+ "description": "Discover LLM providers and manage LLM services and AI employees.",
208
+ "include": true,
209
+ "resources": {
210
+ "includes": ["ai", "llmServices", "aiEmployees"],
211
+ "excludes": [],
212
+ "overrides": {
213
+ "ai": {
214
+ "name": "llm-providers",
215
+ "description": "Discover providers and models and test unsaved LLM settings.",
216
+ "topLevel": false,
217
+ "operations": {
218
+ "includes": [
219
+ "ai:listLLMProviders",
220
+ "ai:listProviderModels",
221
+ "ai:testFlight",
222
+ "ai:listModels",
223
+ "ai:listLLMServices"
224
+ ]
225
+ }
226
+ },
227
+ "llmServices": {
228
+ "name": "llm-services",
229
+ "description": "Manage saved LLM service configurations.",
230
+ "topLevel": false,
231
+ "operations": {
232
+ "includes": [
233
+ "llmServices:list",
234
+ "llmServices:get",
235
+ "llmServices:create",
236
+ "llmServices:update",
237
+ "llmServices:destroy"
238
+ ]
239
+ }
240
+ },
241
+ "aiEmployees": {
242
+ "name": "employees",
243
+ "description": "Manage AI employees.",
244
+ "topLevel": false,
245
+ "operations": {
246
+ "includes": [
247
+ "aiEmployees:list",
248
+ "aiEmployees:get",
249
+ "aiEmployees:create",
250
+ "aiEmployees:update",
251
+ "aiEmployees:destroy"
252
+ ]
253
+ }
254
+ }
255
+ }
256
+ }
257
+ },
258
+ "kb": {
259
+ "name": "kb",
260
+ "description": "Manage vector databases, knowledge bases, documents, vectorization, and retrieval tests.",
261
+ "include": true,
262
+ "resources": {
263
+ "includes": ["aiVectorDatabases", "aiKnowledgeBase", "aiKnowledgeBaseDocs"],
264
+ "excludes": [],
265
+ "overrides": {
266
+ "aiVectorDatabases": {
267
+ "name": "vector-databases",
268
+ "description": "Manage vector database connections.",
269
+ "topLevel": false,
270
+ "operations": {
271
+ "includes": [
272
+ "aiVectorDatabases:listProviders",
273
+ "aiVectorDatabases:testConnection",
274
+ "aiVectorDatabases:list",
275
+ "aiVectorDatabases:get",
276
+ "aiVectorDatabases:create",
277
+ "aiVectorDatabases:update",
278
+ "aiVectorDatabases:destroy"
279
+ ]
280
+ }
281
+ },
282
+ "aiKnowledgeBase": {
283
+ "name": "kb",
284
+ "segments": ["kb"],
285
+ "description": "Manage knowledge bases and retrieval tests.",
286
+ "topLevel": true,
287
+ "operations": {
288
+ "includes": [
289
+ "aiKnowledgeBase:list",
290
+ "aiKnowledgeBase:get",
291
+ "aiKnowledgeBase:create",
292
+ "aiKnowledgeBase:update",
293
+ "aiKnowledgeBase:destroy",
294
+ "aiKnowledgeBase:runHitTest",
295
+ "aiKnowledgeBase:listExternalVectorStoreProviders"
296
+ ]
297
+ }
298
+ },
299
+ "aiKnowledgeBaseDocs": {
300
+ "name": "documents",
301
+ "description": "Manage knowledge base documents and vectorization.",
302
+ "topLevel": false,
303
+ "operations": {
304
+ "includes": [
305
+ "aiKnowledgeBaseDocs:list",
306
+ "aiKnowledgeBaseDocs:get",
307
+ "aiKnowledgeBaseDocs:upload",
308
+ "aiKnowledgeBaseDocs:vectorization",
309
+ "aiKnowledgeBaseDocs:destroy"
310
+ ]
311
+ }
312
+ }
313
+ }
314
+ }
315
+ },
205
316
  "flow-engine": {
206
317
  "name": "flow-engine",
207
318
  "description": "Manage flow surface composition, configuration, layout, and mutation APIs.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.2.0-alpha.10",
3
+ "version": "2.2.0-alpha.12",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -147,5 +147,5 @@
147
147
  "type": "git",
148
148
  "url": "git+https://github.com/nocobase/nocobase.git"
149
149
  },
150
- "gitHead": "b5a3f131bb0a4a2fbfd96852896bccadc933d834"
150
+ "gitHead": "96ae69754a6398de63037ead3a4aad5e5d0ab918"
151
151
  }