@nocobase/cli 2.1.0-alpha.40 → 2.1.0-alpha.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +20 -8
  2. package/README.zh-CN.md +20 -8
  3. package/bin/run.js +9 -37
  4. package/dist/commands/app/destroy.js +225 -0
  5. package/dist/commands/app/down.js +24 -254
  6. package/dist/commands/app/restart.js +12 -5
  7. package/dist/commands/app/shared.js +122 -0
  8. package/dist/commands/app/start.js +41 -59
  9. package/dist/commands/app/stop.js +72 -26
  10. package/dist/commands/app/upgrade.js +312 -429
  11. package/dist/commands/config/delete.js +8 -1
  12. package/dist/commands/config/get.js +5 -0
  13. package/dist/commands/config/set.js +10 -1
  14. package/dist/commands/db/ps.js +5 -18
  15. package/dist/commands/db/start.js +23 -8
  16. package/dist/commands/env/add.js +66 -6
  17. package/dist/commands/env/auth.js +86 -27
  18. package/dist/commands/env/info.js +52 -8
  19. package/dist/commands/env/list.js +6 -7
  20. package/dist/commands/env/shared.js +41 -3
  21. package/dist/commands/env/status.js +10 -7
  22. package/dist/commands/init.js +214 -136
  23. package/dist/commands/install.js +364 -294
  24. package/dist/commands/license/plugins/shared.js +9 -3
  25. package/dist/commands/license/plugins/sync.js +54 -25
  26. package/dist/commands/self/update.js +58 -1
  27. package/dist/commands/source/dev.js +5 -5
  28. package/dist/commands/source/download.js +29 -25
  29. package/dist/generated/command-registry.js +3 -2
  30. package/dist/lib/api-client.js +6 -0
  31. package/dist/lib/api-command-compat.js +641 -0
  32. package/dist/lib/app-health.js +31 -18
  33. package/dist/lib/app-managed-resources.js +1 -1
  34. package/dist/lib/app-runtime.js +2 -2
  35. package/dist/lib/auth-store.js +56 -19
  36. package/dist/lib/backup.js +3 -3
  37. package/dist/lib/bootstrap.js +14 -8
  38. package/dist/lib/cli-config.js +137 -4
  39. package/dist/lib/cli-entry-error.js +44 -0
  40. package/dist/lib/cli-locale.js +19 -7
  41. package/dist/lib/db-connection-check.js +61 -0
  42. package/dist/lib/docker-log-stream.js +45 -0
  43. package/dist/lib/env-auth.js +98 -11
  44. package/dist/lib/env-config.js +3 -2
  45. package/dist/lib/env-guard.js +1 -1
  46. package/dist/lib/generated-command.js +17 -0
  47. package/dist/lib/prompt-validators.js +23 -5
  48. package/dist/lib/prompt-web-ui.js +143 -19
  49. package/dist/lib/run-npm.js +133 -23
  50. package/dist/lib/skills-manager.js +80 -4
  51. package/dist/lib/source-publish.js +4 -1
  52. package/dist/lib/startup-update.js +74 -50
  53. package/dist/lib/ui.js +4 -1
  54. package/dist/locale/en-US.json +92 -5
  55. package/dist/locale/zh-CN.json +92 -5
  56. package/package.json +34 -2
@@ -17,6 +17,72 @@ import { commandOutput, commandOutputViaFile, run } from './run-npm.js';
17
17
  export const NOCOBASE_SKILLS_SOURCE = 'nocobase/skills';
18
18
  export const NOCOBASE_SKILLS_PACKAGE_NAME = '@nocobase/skills';
19
19
  const NOCOBASE_SKILLS_NAME_PREFIX = 'nocobase-';
20
+ const SKILLS_LIST_TIMEOUT_MS = 5000;
21
+ const SKILLS_NPM_VIEW_TIMEOUT_MS = 3000;
22
+ const SKILLS_PACK_TIMEOUT_MS = 30000;
23
+ const SKILLS_ADD_TIMEOUT_MS = 20000;
24
+ const NPM_REGISTRY_UNAVAILABLE_PATTERNS = [
25
+ 'enotfound',
26
+ 'eai_again',
27
+ 'etimedout',
28
+ 'esockettimedout',
29
+ 'econnreset',
30
+ 'econnrefused',
31
+ 'ehostunreach',
32
+ 'enetunreach',
33
+ 'socket hang up',
34
+ 'getaddrinfo',
35
+ 'fetch failed',
36
+ 'network request to',
37
+ 'self_signed_cert',
38
+ 'depth_zero_self_signed_cert',
39
+ 'unable_to_verify_leaf_signature',
40
+ 'cert_has_expired',
41
+ 'timed out after',
42
+ ];
43
+ function collectErrorMessages(error) {
44
+ const messages = [];
45
+ const queue = [error];
46
+ const seen = new Set();
47
+ while (queue.length > 0) {
48
+ const current = queue.shift();
49
+ if (current === undefined || current === null || seen.has(current)) {
50
+ continue;
51
+ }
52
+ seen.add(current);
53
+ if (current instanceof Error) {
54
+ if (current.message) {
55
+ messages.push(current.message);
56
+ }
57
+ const cause = current.cause;
58
+ if (cause !== undefined) {
59
+ queue.push(cause);
60
+ }
61
+ continue;
62
+ }
63
+ if (typeof current === 'string') {
64
+ messages.push(current);
65
+ continue;
66
+ }
67
+ if (typeof current === 'object') {
68
+ if ('message' in current && typeof current.message === 'string') {
69
+ messages.push(current.message);
70
+ }
71
+ if ('cause' in current) {
72
+ queue.push(current.cause);
73
+ }
74
+ continue;
75
+ }
76
+ messages.push(String(current));
77
+ }
78
+ return messages;
79
+ }
80
+ export function isNpmRegistryUnavailable(error) {
81
+ return collectErrorMessages(error).some((message) => {
82
+ const normalized = message.toLowerCase();
83
+ return NPM_REGISTRY_UNAVAILABLE_PATTERNS.some((pattern) => normalized.includes(pattern));
84
+ });
85
+ }
20
86
  function normalizePath(value) {
21
87
  return path.resolve(value);
22
88
  }
@@ -61,6 +127,12 @@ async function readManagedSkillsState(workspaceRoot) {
61
127
  return undefined;
62
128
  }
63
129
  }
130
+ export async function readInstalledManagedSkillsVersion(options = {}) {
131
+ const globalRoot = resolveSkillsRoot(options);
132
+ const state = await readManagedSkillsState(globalRoot);
133
+ const installedVersion = String(state?.installedVersion ?? state?.installedRef ?? '').trim();
134
+ return installedVersion || undefined;
135
+ }
64
136
  async function writeManagedSkillsState(workspaceRoot, state) {
65
137
  const filePath = getManagedSkillsStateFile(workspaceRoot);
66
138
  await fsp.mkdir(path.dirname(filePath), { recursive: true });
@@ -72,6 +144,7 @@ export async function listGlobalSkills(options = {}) {
72
144
  const output = await (options.commandOutputFn ?? commandOutputViaFile)('npx', ['-y', 'skills', 'list', '-g', '--json'], {
73
145
  cwd: globalRoot,
74
146
  errorName: 'skills list',
147
+ timeoutMs: SKILLS_LIST_TIMEOUT_MS,
75
148
  });
76
149
  const parsed = JSON.parse(output);
77
150
  return Array.isArray(parsed) ? parsed : [];
@@ -95,6 +168,7 @@ async function readPublishedSkillsVersion(options = {}) {
95
168
  const output = await (options.commandOutputFn ?? commandOutput)('npm', ['view', NOCOBASE_SKILLS_PACKAGE_NAME, 'version', '--json'], {
96
169
  cwd: globalRoot,
97
170
  errorName: 'npm view',
171
+ timeoutMs: SKILLS_NPM_VIEW_TIMEOUT_MS,
98
172
  });
99
173
  const parsed = JSON.parse(output);
100
174
  const version = String(parsed ?? '').trim();
@@ -187,6 +261,7 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
187
261
  cwd: packRoot,
188
262
  stdio: options.verbose ? 'inherit' : 'ignore',
189
263
  errorName: 'npm pack',
264
+ timeoutMs: SKILLS_PACK_TIMEOUT_MS,
190
265
  });
191
266
  const tarballPath = await resolvePackedSkillsTarball(packRoot);
192
267
  await extractPackedSkillsTarball(tarballPath, cacheRoot, targetVersion);
@@ -276,6 +351,7 @@ async function reinstallManagedSkills(globalRoot, options = {}, targetVersion) {
276
351
  cwd: globalRoot,
277
352
  stdio: options.verbose ? 'inherit' : 'ignore',
278
353
  errorName: 'skills add',
354
+ timeoutMs: SKILLS_ADD_TIMEOUT_MS,
279
355
  });
280
356
  }
281
357
  finally {
@@ -314,10 +390,10 @@ export async function updateNocoBaseSkills(options = {}) {
314
390
  status,
315
391
  };
316
392
  }
317
- if (status.managedByNb
318
- && status.latestVersion
319
- && status.installedVersion
320
- && compareVersions(status.latestVersion, status.installedVersion) <= 0) {
393
+ if (status.managedByNb &&
394
+ status.latestVersion &&
395
+ status.installedVersion &&
396
+ compareVersions(status.latestVersion, status.installedVersion) <= 0) {
321
397
  return {
322
398
  action: 'noop',
323
399
  reason: 'up-to-date',
@@ -292,6 +292,9 @@ export async function publishSourceSnapshot(params) {
292
292
  if (publishError) {
293
293
  throw publishError;
294
294
  }
295
+ if (!result) {
296
+ throw new Error('Source snapshot publishing finished without a result.');
297
+ }
295
298
  return result;
296
299
  }
297
300
  export function buildSuggestedInitCommand(result) {
@@ -299,7 +302,7 @@ export function buildSuggestedInitCommand(result) {
299
302
  const normalizedRegistry = result.npmRegistry || `http://${host}:${port || DEFAULT_SOURCE_REGISTRY_PORT}`;
300
303
  const suggestedEnv = ['snapshot', sanitizeEnvSegment(result.gitSha)].filter(Boolean).join('');
301
304
  return [
302
- `nb init --env ${suggestedEnv} --yes --source npm`,
305
+ `nb init --ui --env ${suggestedEnv} --yes --source npm`,
303
306
  `--version ${result.version}`,
304
307
  `--npm-registry=${normalizedRegistry}`,
305
308
  ].join(' ');
@@ -10,6 +10,7 @@ import fs from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { confirm } from "./inquirer.js";
13
+ import { DEFAULT_UPDATE_POLICY, getExplicitCliConfigValue, loadCliConfig, } from './cli-config.js';
13
14
  import { inspectSelfInstall, inspectSelfStatus, } from './self-manager.js';
14
15
  import { inspectSkillsStatus } from './skills-manager.js';
15
16
  import { resolveCliHomeDir } from './cli-home.js';
@@ -83,39 +84,53 @@ async function writeCurrentInstallEntry(updater) {
83
84
  const state = await readState();
84
85
  const installBinPath = getCurrentInstallBinPath();
85
86
  const nextEntry = updater(getCurrentInstallEntry(state), state);
87
+ const entries = {
88
+ ...(state.entries ?? {}),
89
+ };
90
+ if (nextEntry?.policy || nextEntry?.lastCheckedDate) {
91
+ entries[installBinPath] = nextEntry;
92
+ }
93
+ else {
94
+ delete entries[installBinPath];
95
+ }
86
96
  await writeState({
87
97
  ...state,
88
- entries: {
89
- ...(state.entries ?? {}),
90
- [installBinPath]: nextEntry,
91
- },
98
+ entries: Object.keys(entries).length ? entries : undefined,
92
99
  });
93
100
  }
94
101
  async function markChecked(now = new Date()) {
95
- await writeCurrentInstallEntry((current, state) => {
102
+ await writeCurrentInstallEntry((current) => {
96
103
  return {
97
- policy: current?.policy ?? 'daily',
104
+ ...current,
98
105
  lastCheckedDate: todayStamp(now),
99
106
  };
100
107
  });
101
108
  }
102
- async function disableStartupUpdateForCurrentInstall() {
103
- await writeCurrentInstallEntry((current, state) => {
104
- return {
105
- policy: 'disabled',
106
- lastCheckedDate: current?.lastCheckedDate
107
- ?? state.lastCheckedDate,
108
- };
109
- });
109
+ function resolveLegacyStartupUpdatePolicy(state) {
110
+ const policy = getCurrentInstallEntry(state)?.policy;
111
+ if (policy === 'disabled') {
112
+ return 'off';
113
+ }
114
+ if (policy === 'daily') {
115
+ return 'prompt';
116
+ }
117
+ return undefined;
110
118
  }
111
- async function enableDailyStartupUpdateForCurrentInstall() {
112
- await writeCurrentInstallEntry((current, state) => {
113
- return {
114
- policy: 'daily',
115
- lastCheckedDate: current?.lastCheckedDate
116
- ?? state.lastCheckedDate,
117
- };
118
- });
119
+ async function resolveStartupUpdatePolicy(state) {
120
+ const config = await loadCliConfig({ scope: 'global' });
121
+ const explicit = getExplicitCliConfigValue(config, 'update.policy');
122
+ return explicit ?? resolveLegacyStartupUpdatePolicy(state) ?? DEFAULT_UPDATE_POLICY;
123
+ }
124
+ export async function clearLegacyStartupUpdatePolicyForCurrentInstall() {
125
+ const state = await readState();
126
+ const current = getCurrentInstallEntry(state);
127
+ if (!current?.policy) {
128
+ return false;
129
+ }
130
+ await writeCurrentInstallEntry(() => ({
131
+ lastCheckedDate: current.lastCheckedDate,
132
+ }));
133
+ return true;
119
134
  }
120
135
  export async function shouldRunStartupUpdateCheck(argv, now = new Date()) {
121
136
  if (process.env[NB_SKIP_STARTUP_UPDATE_ENV] === '1') {
@@ -125,19 +140,14 @@ export async function shouldRunStartupUpdateCheck(argv, now = new Date()) {
125
140
  return false;
126
141
  }
127
142
  const state = await readState();
128
- const currentEntry = getCurrentInstallEntry(state);
129
- if (currentEntry?.policy === 'disabled') {
143
+ const policy = await resolveStartupUpdatePolicy(state);
144
+ if (policy === 'off') {
130
145
  return false;
131
146
  }
132
- if (currentEntry?.policy === 'daily') {
133
- return readCurrentInstallLastCheckedDate(state) !== todayStamp(now);
134
- }
135
147
  const selfInstall = await inspectSelfInstall();
136
148
  if (!shouldEnableStartupUpdateForInstallMethod(selfInstall.installMethod)) {
137
- await disableStartupUpdateForCurrentInstall();
138
149
  return false;
139
150
  }
140
- await enableDailyStartupUpdateForCurrentInstall();
141
151
  return readCurrentInstallLastCheckedDate(state) !== todayStamp(now);
142
152
  }
143
153
  export function shouldEnableStartupUpdateForInstallMethod(installMethod) {
@@ -189,14 +199,13 @@ function buildPromptMessage(selfStatus, skillsStatus) {
189
199
  return lines.join('\n');
190
200
  }
191
201
  function buildUpdateCommands(selfStatus, skillsStatus) {
192
- const commands = [];
193
202
  if (selfStatus.updateAvailable && selfStatus.updatable) {
194
- commands.push('nb self update --yes');
203
+ return [skillsStatus.updateAvailable === true ? 'nb self update --yes --skills' : 'nb self update --yes'];
195
204
  }
196
205
  if (skillsStatus.updateAvailable === true) {
197
- commands.push('nb skills update --yes');
206
+ return ['nb skills update --yes'];
198
207
  }
199
- return commands;
208
+ return [];
200
209
  }
201
210
  function buildNonInteractiveWarning(selfStatus, skillsStatus) {
202
211
  const commands = buildUpdateCommands(selfStatus, skillsStatus);
@@ -233,26 +242,36 @@ function buildDeclinedWarning(selfStatus, skillsStatus) {
233
242
  'You may run into compatibility issues until you update.',
234
243
  ].join(' ');
235
244
  }
236
- async function runStartupUpdates() {
237
- await run('nb', ['self', 'update', '--yes'], {
238
- stdio: 'inherit',
239
- env: {
240
- [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
241
- },
242
- errorName: 'nb self update',
243
- });
244
- await run('nb', ['skills', 'update', '--yes'], {
245
- stdio: 'inherit',
246
- env: {
247
- [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
248
- },
249
- errorName: 'nb skills update',
250
- });
245
+ async function runStartupUpdates(selfStatus, skillsStatus) {
246
+ if (selfStatus.updateAvailable && selfStatus.updatable) {
247
+ const args = ['self', 'update', '--yes'];
248
+ if (skillsStatus.updateAvailable === true) {
249
+ args.push('--skills');
250
+ }
251
+ await run('nb', args, {
252
+ stdio: 'inherit',
253
+ env: {
254
+ [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
255
+ },
256
+ errorName: 'nb self update',
257
+ });
258
+ return;
259
+ }
260
+ if (skillsStatus.updateAvailable === true) {
261
+ await run('nb', ['skills', 'update', '--yes'], {
262
+ stdio: 'inherit',
263
+ env: {
264
+ [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
265
+ },
266
+ errorName: 'nb skills update',
267
+ });
268
+ }
251
269
  }
252
- export async function maybeRunStartupUpdatePrompt(argv) {
270
+ export async function maybeRunStartupUpdate(argv) {
253
271
  if (!(await shouldRunStartupUpdateCheck(argv))) {
254
272
  return { kind: 'skipped' };
255
273
  }
274
+ const policy = await resolveStartupUpdatePolicy(await readState());
256
275
  const selfStatus = await inspectSelfStatus();
257
276
  const skillsStatus = await inspectSkillsStatus();
258
277
  if (!hasPendingUpdates(selfStatus, skillsStatus)) {
@@ -264,6 +283,11 @@ export async function maybeRunStartupUpdatePrompt(argv) {
264
283
  await markChecked();
265
284
  return { kind: 'warned' };
266
285
  }
286
+ if (policy === 'auto') {
287
+ await runStartupUpdates(selfStatus, skillsStatus);
288
+ await markChecked();
289
+ return { kind: 'updated' };
290
+ }
267
291
  let answer = false;
268
292
  try {
269
293
  answer = await confirm({
@@ -279,7 +303,7 @@ export async function maybeRunStartupUpdatePrompt(argv) {
279
303
  await markChecked();
280
304
  return { kind: 'declined' };
281
305
  }
282
- await runStartupUpdates();
306
+ await runStartupUpdates(selfStatus, skillsStatus);
283
307
  await markChecked();
284
308
  return { kind: 'updated' };
285
309
  }
package/dist/lib/ui.js CHANGED
@@ -54,7 +54,10 @@ export function printInfo(message) {
54
54
  console.log(pc.cyan(message));
55
55
  }
56
56
  export function announceTargetEnv(envName) {
57
- printInfo(`Target env: ${envName}`);
57
+ if (process.env.NB_SKIP_TARGET_ENV_LOG === '1') {
58
+ return;
59
+ }
60
+ printInfo(`Using env "${envName}".`);
58
61
  }
59
62
  export function printVerbose(message) {
60
63
  if (!verboseMode) {
@@ -34,6 +34,8 @@
34
34
  "back": "Back",
35
35
  "next": "Next",
36
36
  "submit": "Submit & continue in terminal",
37
+ "showPassword": "Show password",
38
+ "hidePassword": "Hide password",
37
39
  "checking": "Checking...",
38
40
  "sending": "Sending...",
39
41
  "successTitle": "Success",
@@ -73,7 +75,8 @@
73
75
  "timeout": "Timed out connecting to the database at {{host}}:{{port}} after about {{seconds}} seconds.",
74
76
  "authenticationFailed": "Failed to sign in to database \"{{database}}\" with user \"{{user}}\". Check the username and password.",
75
77
  "databaseNotFound": "Database \"{{database}}\" does not exist or is not accessible with the current connection settings.",
76
- "connectionFailed": "Database connection check failed. Details: {{details}}"
78
+ "connectionFailed": "Database connection check failed. Details: {{details}}",
79
+ "lowerCaseTableNamesRequiresUnderscored": "MySQL lower_case_table_names=1 requires DB_UNDERSCORED=true."
77
80
  }
78
81
  },
79
82
  "commands": {
@@ -94,16 +97,28 @@
94
97
  },
95
98
  "authType": {
96
99
  "message": "How would you like to sign in?",
100
+ "basicLabel": "Basic login (username + password)",
101
+ "basicHint": "uses your credentials to fetch a token after save",
97
102
  "oauthLabel": "OAuth (browser login)",
98
103
  "oauthHint": "runs nb env auth after save",
99
104
  "tokenLabel": "API token / API key"
100
105
  },
106
+ "username": {
107
+ "message": "Enter the username for basic login",
108
+ "placeholder": "admin"
109
+ },
110
+ "password": {
111
+ "message": "Enter the password for basic login"
112
+ },
101
113
  "accessToken": {
102
114
  "message": "Enter an API token or API key",
103
115
  "placeholder": "Enter your API token / API key"
104
116
  }
105
117
  }
106
118
  },
119
+ "shared": {
120
+ "missingCommand": "Couldn't run `{{action}}` because the {{displayName}} executable could not be found. Install {{displayName}} or update `nb config set {{configKey}} <path>` and try again."
121
+ },
107
122
  "download": {
108
123
  "failures": {
109
124
  "dependencyInstall": {
@@ -206,6 +221,10 @@
206
221
  }
207
222
  },
208
223
  "install": {
224
+ "messages": {
225
+ "skipDownloadDockerImageMissing": "Cannot continue with `--skip-download` because Docker image \"{{imageRef}}\" is not available locally. Load or pull the image first, or run the command again without `--skip-download`.",
226
+ "skipDownloadLocalAppMissing": "Cannot continue with `--skip-download` because \"{{projectRoot}}\" is missing or does not contain a package.json file. Point `--app-root-path` to an existing NocoBase app, or run the command again without `--skip-download`."
227
+ },
209
228
  "validation": {
210
229
  "builtinDbUnsupported": "Built-in database does not support \"{{dialect}}\" yet. Choose PostgreSQL, MySQL, or MariaDB, or turn off built-in database."
211
230
  },
@@ -229,9 +248,6 @@
229
248
  "message": "Where should uploads and local files be stored?",
230
249
  "placeholder": "./<env>/storage/"
231
250
  },
232
- "fetchSource": {
233
- "message": "Download NocoBase automatically if the app directory is empty?"
234
- },
235
251
  "dbDialect": {
236
252
  "message": "Which database would you like to use?"
237
253
  },
@@ -259,6 +275,17 @@
259
275
  "dbPassword": {
260
276
  "message": "What is the database password?"
261
277
  },
278
+ "dbSchema": {
279
+ "message": "What is the database schema? (PostgreSQL only, optional)",
280
+ "placeholder": "Leave empty to use the default schema"
281
+ },
282
+ "dbTablePrefix": {
283
+ "message": "What table prefix should be used? (optional)",
284
+ "placeholder": "For example: nb_"
285
+ },
286
+ "dbUnderscored": {
287
+ "message": "Use underscored names for database tables and columns?"
288
+ },
262
289
  "rootUsername": {
263
290
  "message": "Choose the initial admin username",
264
291
  "placeholder": "nocobase"
@@ -290,7 +317,8 @@
290
317
  "uiOpening": "A local setup form will open in your browser. That form needs a person to fill it in. If you are using an AI agent, do not stop this process while the CLI waits for the submission.",
291
318
  "uiReady": "Local setup form is ready.",
292
319
  "uiReadyHelp": "If your browser does not open automatically, copy the URL below into your browser to continue. Keep this terminal session running while the CLI waits for the submission.",
293
- "uiOpenBrowserFallback": "We could not open your browser automatically. Copy the URL above into your browser to continue setup, and keep this terminal session running. If you are using an AI agent, do not stop the current process."
320
+ "uiOpenBrowserFallback": "We could not open your browser automatically. Copy the URL above into your browser to continue setup, and keep this terminal session running. If you are using an AI agent, do not stop the current process.",
321
+ "skillsSyncRegistryUnavailable": "Couldn't reach the npm registry to sync NocoBase AI coding skills. Skipping skills install and continuing init. Run `nb skills install` later when registry access is available."
294
322
  },
295
323
  "prompts": {
296
324
  "appName": {
@@ -308,6 +336,9 @@
308
336
  "apiBaseUrl": {
309
337
  "message": "API base URL",
310
338
  "placeholder": "https://demo.example.com/api or https://demo.example.com/api/__app/<subapp>"
339
+ },
340
+ "skipDownload": {
341
+ "message": "Skip downloading NocoBase and reuse existing local app files or Docker images"
311
342
  }
312
343
  },
313
344
  "webUi": {
@@ -339,6 +370,62 @@
339
370
  "description": "Set up the first admin."
340
371
  }
341
372
  }
373
+ },
374
+ "env": {
375
+ "messages": {
376
+ "noEnvsConfigured": "No envs configured.",
377
+ "noEnvsConfiguredHelp": "Run `nb init --ui` to create one first."
378
+ }
342
379
  }
380
+ },
381
+ "apiCommandCompat": {
382
+ "refusalTitle": "Refusing to run `nb api {{commandId}}` because the current CLI version is {{cliVersion}} while the target app version is {{appVersion}}.",
383
+ "refusalTitleMissingVersion": "Refusing to run `nb api {{commandId}}` because the {{versionLabel}} is unavailable.",
384
+ "refusalTitleGeneric": "Refusing to run `nb api {{commandId}}` because the current version combination is not compatible.",
385
+ "currentVersionsLine": "Current versions: {{versions}}.",
386
+ "refusalBodyWithRequirements": "This API command is blocked when used with {{requirements}}.",
387
+ "refusalBody": "This API command may not be compatible with the current CLI and app version combination.",
388
+ "channel": {
389
+ "stable": "stable",
390
+ "beta": "beta",
391
+ "alpha": "alpha",
392
+ "rc": "rc",
393
+ "unknownPrerelease": "unsupported prerelease"
394
+ },
395
+ "requirement": {
396
+ "cli": "CLI version {{condition}}",
397
+ "app": "app version {{condition}}",
398
+ "skills": "NocoBase AI skills version {{condition}}",
399
+ "appChannel": "{{channelLabel}} app version {{condition}}",
400
+ "appUnknownPrereleaseBlocked": "{{channelLabel}} app versions are blocked"
401
+ },
402
+ "currentVersion": {
403
+ "cli": "CLI {{version}}",
404
+ "app": "app {{version}}",
405
+ "appUnavailable": "app unavailable",
406
+ "skills": "NocoBase AI skills {{version}}",
407
+ "skillsUnavailable": "NocoBase AI skills unavailable"
408
+ },
409
+ "versionLabel": {
410
+ "cli": "CLI version",
411
+ "app": "target app version",
412
+ "skills": "installed NocoBase AI skills version"
413
+ },
414
+ "join": {
415
+ "two": "{{left}} and {{right}}",
416
+ "many": "{{head}}, and {{tail}}"
417
+ },
418
+ "continueTitle": "To continue:",
419
+ "continueUpgradeApp": "- upgrade the app to a compatible version",
420
+ "continueUpgradeAppToVersion": "- upgrade the app to {{suggestedUpgradeTarget}}",
421
+ "continueUseSupportedAppVersion": "- use a supported app version: {{supportedTargets}}",
422
+ "continueRefreshAppVersion": "- refresh the target env runtime so the current app version can be detected, then retry",
423
+ "continueUseCompatibleCli": "- or use a compatible CLI version",
424
+ "continueUseCompatibleCliVersion": "- or use a CLI version {{suggestedCliTarget}}",
425
+ "continueUpdateSkillsToVersion": "- install or update the NocoBase AI coding skills to {{suggestedSkillsUpgradeTarget}}. Run `nb skills update --yes` to refresh the managed skills.",
426
+ "continueUseCompatibleSkillsVersion": "- or use a NocoBase AI skills version {{suggestedSkillsTarget}}",
427
+ "continueInstallOrUpdateSkillsToVersion": "- run `nb skills install --yes` or `nb skills update --yes`, and make sure the managed skills version is {{suggestedSkillsUpgradeTarget}} before retrying",
428
+ "continueInstallOrUpdateSkills": "- run `nb skills install --yes` or `nb skills update --yes`, then retry once the managed skills version is available",
429
+ "supportedAppTarget": "{{channelLabel}} {{target}}"
343
430
  }
344
431
  }