@nocobase/cli 2.1.0-beta.20 → 2.1.0-beta.21

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 (42) hide show
  1. package/README.md +4 -4
  2. package/README.zh-CN.md +2 -2
  3. package/bin/run.js +15 -0
  4. package/dist/commands/db/shared.js +19 -5
  5. package/dist/commands/dev.js +8 -1
  6. package/dist/commands/down.js +10 -6
  7. package/dist/commands/env/add.js +14 -34
  8. package/dist/commands/env/auth.js +6 -13
  9. package/dist/commands/env/list.js +10 -15
  10. package/dist/commands/env/remove.js +4 -10
  11. package/dist/commands/env/update.js +7 -13
  12. package/dist/commands/env/use.js +5 -13
  13. package/dist/commands/init.js +190 -62
  14. package/dist/commands/install.js +65 -26
  15. package/dist/commands/logs.js +8 -1
  16. package/dist/commands/pm/list.js +8 -1
  17. package/dist/commands/ps.js +18 -15
  18. package/dist/commands/self/check.js +1 -1
  19. package/dist/commands/self/update.js +13 -3
  20. package/dist/commands/skills/check.js +11 -5
  21. package/dist/commands/skills/index.js +1 -1
  22. package/dist/commands/skills/install.js +20 -7
  23. package/dist/commands/skills/update.js +20 -7
  24. package/dist/commands/start.js +8 -1
  25. package/dist/commands/stop.js +8 -1
  26. package/dist/commands/upgrade.js +12 -1
  27. package/dist/lib/api-client.js +3 -2
  28. package/dist/lib/app-runtime.js +16 -5
  29. package/dist/lib/auth-store.js +159 -43
  30. package/dist/lib/bootstrap.js +13 -12
  31. package/dist/lib/cli-home.js +33 -2
  32. package/dist/lib/env-auth.js +3 -3
  33. package/dist/lib/generated-command.js +10 -2
  34. package/dist/lib/http-request.js +49 -0
  35. package/dist/lib/resource-command.js +10 -2
  36. package/dist/lib/runtime-generator.js +1 -1
  37. package/dist/lib/self-manager.js +1 -1
  38. package/dist/lib/skills-manager.js +140 -73
  39. package/dist/lib/startup-update.js +203 -0
  40. package/dist/locale/en-US.json +4 -1
  41. package/dist/locale/zh-CN.json +4 -1
  42. package/package.json +2 -2
@@ -16,6 +16,7 @@ import path from 'node:path';
16
16
  import { exit } from 'node:process';
17
17
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
18
18
  import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
19
+ import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath, } from '../lib/cli-home.js';
19
20
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
20
21
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
21
22
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
@@ -53,7 +54,6 @@ const DEFAULT_INSTALL_ROOT_USERNAME = 'nocobase';
53
54
  const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
54
55
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
55
56
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
56
- const CONFIG_SCOPE = 'project';
57
57
  const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
58
58
  const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
59
59
  const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
@@ -137,6 +137,11 @@ function argvHasToken(argv, tokens) {
137
137
  function isInstallDbDialect(value) {
138
138
  return INSTALL_DB_DIALECTS.includes(value);
139
139
  }
140
+ function downloadVersionPromptValue(version) {
141
+ return version === 'latest' || version === 'beta' || version === 'alpha'
142
+ ? version
143
+ : 'other';
144
+ }
140
145
  function supportsBuiltinDbDialect(value) {
141
146
  const dialect = String(value ?? '').trim();
142
147
  return Object.prototype.hasOwnProperty.call(DEFAULT_INSTALL_BUILTIN_DB_IMAGES, dialect);
@@ -303,6 +308,7 @@ export default class Install extends Command {
303
308
  required: false,
304
309
  }),
305
310
  'builtin-db': Flags.boolean({
311
+ allowNo: true,
306
312
  description: 'Create and connect a CLI-managed built-in database for the app',
307
313
  default: false,
308
314
  }),
@@ -569,7 +575,7 @@ export default class Install extends Command {
569
575
  if (argvHasToken(argv, ['--fetch-source'])) {
570
576
  preset.fetchSource = flags['fetch-source'];
571
577
  }
572
- if (argvHasToken(argv, ['--builtin-db'])) {
578
+ if (argvHasToken(argv, ['--builtin-db', '--no-builtin-db'])) {
573
579
  preset.builtinDb = flags['builtin-db'];
574
580
  }
575
581
  if (flags['db-dialect'] !== undefined) {
@@ -588,6 +594,7 @@ export default class Install extends Command {
588
594
  const v = String(flags['db-host'] ?? '').trim();
589
595
  if (v) {
590
596
  preset.dbHost = v;
597
+ preset.builtinDb = false;
591
598
  }
592
599
  }
593
600
  if (flags['db-port'] !== undefined) {
@@ -682,7 +689,14 @@ export default class Install extends Command {
682
689
  };
683
690
  const downloadPreset = {
684
691
  ...(source ? { source } : {}),
685
- ...(downloadVersion ? { version: downloadVersion } : {}),
692
+ ...(downloadVersion
693
+ ? {
694
+ version: downloadVersionPromptValue(downloadVersion),
695
+ ...(downloadVersionPromptValue(downloadVersion) === 'other'
696
+ ? { otherVersion: downloadVersion }
697
+ : {}),
698
+ }
699
+ : {}),
686
700
  ...(dockerRegistry ? { dockerRegistry } : {}),
687
701
  ...(dockerPlatform ? { dockerPlatform } : {}),
688
702
  ...(gitUrl ? { gitUrl } : {}),
@@ -746,7 +760,7 @@ export default class Install extends Command {
746
760
  if (!parsed.resume) {
747
761
  return undefined;
748
762
  }
749
- const env = await getEnv(parsed.env, { scope: CONFIG_SCOPE });
763
+ const env = await getEnv(parsed.env, { scope: resolveDefaultConfigScope() });
750
764
  if (!env) {
751
765
  throw new Error(formatMissingManagedAppEnvMessage(parsed.env));
752
766
  }
@@ -861,7 +875,11 @@ export default class Install extends Command {
861
875
  preset.source = String(flags.source).trim();
862
876
  }
863
877
  if (flags.version !== undefined) {
864
- preset.version = String(flags.version).trim() || 'latest';
878
+ const version = String(flags.version).trim() || 'latest';
879
+ preset.version = downloadVersionPromptValue(version);
880
+ if (preset.version === 'other') {
881
+ preset.otherVersion = version;
882
+ }
865
883
  }
866
884
  if (flags['docker-registry'] !== undefined) {
867
885
  const value = String(flags['docker-registry'] ?? '').trim();
@@ -891,7 +909,10 @@ export default class Install extends Command {
891
909
  preset.npmRegistry =
892
910
  typeof flags['npm-registry'] === 'string' ? flags['npm-registry'] : '';
893
911
  }
894
- if (argvHasToken(argv, ['--replace', '-r'])) {
912
+ if (flags.resume && !argvHasToken(argv, ['--replace', '-r'])) {
913
+ preset.replace = true;
914
+ }
915
+ else if (argvHasToken(argv, ['--replace', '-r'])) {
895
916
  preset.replace = flags.replace;
896
917
  }
897
918
  if (argvHasToken(argv, ['--dev-dependencies', '--no-dev-dependencies', '-D'])) {
@@ -933,7 +954,7 @@ export default class Install extends Command {
933
954
  : Install.defaultWorkspaceName();
934
955
  }
935
956
  static async ensureWorkspaceName() {
936
- return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: CONFIG_SCOPE });
957
+ return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: resolveDefaultConfigScope() });
937
958
  }
938
959
  static buildBuiltinDbNetworkName(envName, workspaceName) {
939
960
  return Install.buildBuiltinDbResourcePrefix(envName, workspaceName);
@@ -982,9 +1003,11 @@ export default class Install extends Command {
982
1003
  && dbHostInput !== 'localhost'
983
1004
  ? dbHostInput
984
1005
  : containerName);
1006
+ const storagePath = resolveConfiguredEnvPath(params.storagePath)
1007
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
985
1008
  if (dbDialect === 'postgres') {
986
1009
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
987
- const dataDir = path.resolve(params.storagePath, 'db', 'postgres');
1010
+ const dataDir = path.resolve(storagePath, 'db', 'postgres');
988
1011
  const args = [
989
1012
  'run',
990
1013
  '-d',
@@ -1028,7 +1051,7 @@ export default class Install extends Command {
1028
1051
  }
1029
1052
  if (dbDialect === 'mysql') {
1030
1053
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1031
- const dataDir = path.resolve(params.storagePath, 'db', 'mysql');
1054
+ const dataDir = path.resolve(storagePath, 'db', 'mysql');
1032
1055
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1033
1056
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1034
1057
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1074,7 +1097,7 @@ export default class Install extends Command {
1074
1097
  }
1075
1098
  if (dbDialect === 'mariadb') {
1076
1099
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1077
- const dataDir = path.resolve(params.storagePath, 'db', 'mariadb');
1100
+ const dataDir = path.resolve(storagePath, 'db', 'mariadb');
1078
1101
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1079
1102
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1080
1103
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1120,7 +1143,7 @@ export default class Install extends Command {
1120
1143
  }
1121
1144
  if (dbDialect === 'kingbase') {
1122
1145
  const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1123
- const dataDir = path.resolve(params.storagePath, 'db', 'kingbase');
1146
+ const dataDir = path.resolve(storagePath, 'db', 'kingbase');
1124
1147
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1125
1148
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1126
1149
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
@@ -1269,12 +1292,12 @@ export default class Install extends Command {
1269
1292
  });
1270
1293
  p.log.step(`Preparing built-in ${plan.dbDialect} database`);
1271
1294
  await this.ensureDockerNetwork(plan.networkName);
1272
- await this.removeDockerContainerIfForced({
1295
+ const existingContainerKept = await this.removeDockerContainerIfForced({
1273
1296
  containerName: plan.containerName,
1274
1297
  displayName: `built-in ${plan.dbDialect} container`,
1275
1298
  force: params.force,
1276
1299
  });
1277
- if (Install.shouldPublishBuiltinDbPort(params.downloadResults.source)) {
1300
+ if (!existingContainerKept && Install.shouldPublishBuiltinDbPort(params.downloadResults.source)) {
1278
1301
  const portError = await validateAvailableTcpPort(plan.dbPort);
1279
1302
  if (portError) {
1280
1303
  throw new Error(`Built-in ${plan.dbDialect} needs host port ${plan.dbPort}, but ${portError}`);
@@ -1291,8 +1314,9 @@ export default class Install extends Command {
1291
1314
  || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1292
1315
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || 'latest';
1293
1316
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1294
- const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
1295
- || defaultInstallStoragePath(params.envName));
1317
+ const storagePath = resolveConfiguredEnvPath(String(params.appResults.storagePath ?? '').trim()
1318
+ || defaultInstallStoragePath(params.envName))
1319
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1296
1320
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
1297
1321
  const dbHost = String(params.dbResults.dbHost ?? DEFAULT_INSTALL_DB_HOST).trim() || DEFAULT_INSTALL_DB_HOST;
1298
1322
  const dbPort = String(params.dbResults.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
@@ -1387,12 +1411,16 @@ export default class Install extends Command {
1387
1411
  }
1388
1412
  static buildDownloadArgvFromResults(results, options) {
1389
1413
  const argv = ['-y', '--no-intro'];
1414
+ const source = String(results.source ?? '').trim();
1390
1415
  if (options?.verbose) {
1391
1416
  argv.push('--verbose');
1392
1417
  }
1393
1418
  Install.pushDownloadArgIfValue(argv, '--source', results.source);
1394
- Install.pushDownloadArgIfValue(argv, '--version', results.version);
1395
- Install.pushDownloadArgIfValue(argv, '--output-dir', results.outputDir);
1419
+ Install.pushDownloadArgIfValue(argv, '--version', downloadResultsValue(results, 'version'));
1420
+ Install.pushDownloadArgIfValue(argv, '--output-dir', source === 'npm' || source === 'git'
1421
+ ? (resolveConfiguredEnvPath(results.outputDir)
1422
+ ?? resolveConfiguredEnvPath(String(results.outputDir ?? '').trim() || defaultInstallAppRootPath(results.env)))
1423
+ : results.outputDir);
1396
1424
  Install.pushDownloadArgIfValue(argv, '--git-url', results.gitUrl);
1397
1425
  Install.pushDownloadArgIfValue(argv, '--docker-registry', results.dockerRegistry);
1398
1426
  Install.pushDownloadArgIfValue(argv, '--docker-platform', results.dockerPlatform);
@@ -1422,7 +1450,12 @@ export default class Install extends Command {
1422
1450
  const outputDir = String(params.downloadResults.outputDir ?? '').trim()
1423
1451
  || String(params.appResults.appRootPath ?? '').trim()
1424
1452
  || defaultInstallAppRootPath(params.envName);
1425
- return path.resolve(process.cwd(), outputDir);
1453
+ return resolveConfiguredEnvPath(outputDir) ?? resolveEnvRelativePath(defaultInstallAppRootPath(params.envName));
1454
+ }
1455
+ static resolveLocalProjectConfigPath(params) {
1456
+ return (String(params.downloadResults.outputDir ?? '').trim()
1457
+ || String(params.appResults.appRootPath ?? '').trim()
1458
+ || defaultInstallAppRootPath(params.envName));
1426
1459
  }
1427
1460
  commandStdio(verbose) {
1428
1461
  return verbose ? 'inherit' : 'ignore';
@@ -1442,18 +1475,24 @@ export default class Install extends Command {
1442
1475
  downloadResults: params.downloadResults,
1443
1476
  verbose: params.verbose,
1444
1477
  });
1445
- const projectRoot = Install.resolveLocalProjectRoot({
1478
+ const downloadedProjectRoot = Install.resolveLocalProjectRoot({
1446
1479
  envName: params.envName,
1447
1480
  appResults: params.appResults,
1448
1481
  downloadResults: params.downloadResults,
1449
1482
  downloadCommandResult: result,
1450
1483
  });
1451
- params.appResults.appRootPath = projectRoot;
1452
- return projectRoot;
1484
+ params.appResults.appRootPath = Install.resolveLocalProjectConfigPath({
1485
+ envName: params.envName,
1486
+ appResults: params.appResults,
1487
+ downloadResults: params.downloadResults,
1488
+ });
1489
+ return downloadedProjectRoot;
1453
1490
  }
1454
1491
  static buildLocalAppEnvVars(params) {
1455
- const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
1456
- || defaultInstallStoragePath(params.envName));
1492
+ const configuredStoragePath = String(params.appResults.storagePath ?? '').trim()
1493
+ || defaultInstallStoragePath(params.envName);
1494
+ const storagePath = resolveConfiguredEnvPath(configuredStoragePath)
1495
+ ?? resolveEnvRelativePath(defaultInstallStoragePath(params.envName));
1457
1496
  const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim()
1458
1497
  || 'postgres';
1459
1498
  const appKey = crypto.randomBytes(32).toString('hex');
@@ -1646,8 +1685,6 @@ export default class Install extends Command {
1646
1685
  const argv = [
1647
1686
  params.envName,
1648
1687
  '--no-intro',
1649
- '--scope',
1650
- CONFIG_SCOPE,
1651
1688
  '--api-base-url',
1652
1689
  apiBaseUrl,
1653
1690
  '--auth-type',
@@ -1760,7 +1797,6 @@ export default class Install extends Command {
1760
1797
  },
1761
1798
  values: {
1762
1799
  name: envName,
1763
- scope: 'project',
1764
1800
  ...(resumePreset?.envAddPreset ?? {}),
1765
1801
  },
1766
1802
  yes,
@@ -1884,5 +1920,8 @@ export default class Install extends Command {
1884
1920
  }
1885
1921
  }
1886
1922
  function downloadResultsValue(downloadResults, key) {
1923
+ if (key === 'version' && String(downloadResults.version ?? '').trim() === 'other') {
1924
+ return downloadResults.otherVersion;
1925
+ }
1887
1926
  return downloadResults[key];
1888
1927
  }
@@ -49,13 +49,20 @@ export default class Logs extends Command {
49
49
  if (!runtime) {
50
50
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
51
51
  }
52
- if (runtime.kind === 'remote') {
52
+ if (runtime.kind === 'http') {
53
53
  this.error([
54
54
  `Can't show runtime logs for "${runtime.envName}" from this machine.`,
55
55
  'This env only has an API connection, so there is no saved local app or Docker container to read logs from.',
56
56
  'Connect it to a local checkout or reinstall it with npm, git, or Docker if you want CLI-managed logs.',
57
57
  ].join('\n'));
58
58
  }
59
+ if (runtime.kind === 'ssh') {
60
+ this.error([
61
+ `Can't show runtime logs for "${runtime.envName}" yet.`,
62
+ 'SSH env support is reserved but not implemented yet.',
63
+ 'Use a local or Docker env for CLI-managed logs for now.',
64
+ ].join('\n'));
65
+ }
59
66
  const tail = String(flags.tail ?? 100);
60
67
  const follow = flags.follow !== false;
61
68
  printInfo(follow
@@ -11,7 +11,7 @@ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runDockerN
11
11
  export default class PmList extends Command {
12
12
  static args = {};
13
13
  static summary = 'List plugins for the selected env';
14
- static description = 'List installed plugins in the selected env (npm/git runs locally, Docker runs inside the saved app container, remote envs fall back to the API)';
14
+ static description = 'List installed plugins in the selected env (npm/git runs locally, Docker runs inside the saved app container, HTTP envs fall back to the API)';
15
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> -e local',
@@ -49,6 +49,13 @@ export default class PmList extends Command {
49
49
  }
50
50
  return;
51
51
  }
52
+ if (runtime.kind === 'ssh') {
53
+ this.error([
54
+ `Can't list plugins for "${runtime.envName}" yet.`,
55
+ 'SSH env support is reserved but not implemented yet.',
56
+ 'Use a local, Docker, or HTTP env for plugin inspection right now.',
57
+ ].join('\n'));
58
+ }
52
59
  await this.config.runCommand('api:pm:list', ['--mode=summary']);
53
60
  }
54
61
  }
@@ -10,12 +10,15 @@ import { Command, Flags } from '@oclif/core';
10
10
  import { buildDockerDbContainerName, dockerContainerExists, dockerContainerIsRunning, formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../lib/app-runtime.js';
11
11
  import { listEnvs } from '../lib/auth-store.js';
12
12
  import { renderTable } from '../lib/ui.js';
13
+ function resolveApiBaseUrl(config) {
14
+ return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
15
+ }
13
16
  function appUrl(runtime) {
14
17
  const port = String(runtime.env.config.appPort ?? '').trim();
15
18
  if (port) {
16
19
  return `http://127.0.0.1:${port}`;
17
20
  }
18
- const baseUrl = String(runtime.env.config.baseUrl ?? '').trim();
21
+ const baseUrl = resolveApiBaseUrl(runtime.env.config);
19
22
  return baseUrl.replace(/\/api\/?$/, '');
20
23
  }
21
24
  async function isLocalAppHealthy(runtime) {
@@ -47,30 +50,30 @@ async function dockerStatus(containerName) {
47
50
  }
48
51
  async function dbStatus(runtime) {
49
52
  if (!runtime.env.config.builtinDb) {
50
- return runtime.kind === 'remote' ? 'external' : '-';
53
+ return runtime.kind === 'http' ? 'external' : '-';
51
54
  }
52
- if (runtime.kind === 'remote') {
55
+ if (runtime.kind === 'http') {
53
56
  return 'external';
54
57
  }
58
+ if (runtime.kind === 'ssh') {
59
+ return '-';
60
+ }
55
61
  const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
56
62
  const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.workspaceName);
57
63
  return await dockerStatus(containerName);
58
64
  }
59
- async function appStatus(runtime) {
60
- if (runtime.kind === 'remote') {
61
- return 'remote';
65
+ async function runtimeStatus(runtime) {
66
+ if (runtime.kind === 'http') {
67
+ return 'http';
68
+ }
69
+ if (runtime.kind === 'ssh') {
70
+ return 'ssh';
62
71
  }
63
72
  if (runtime.kind === 'docker') {
64
73
  return await dockerStatus(runtime.containerName);
65
74
  }
66
75
  return await isLocalAppHealthy(runtime) ? 'running' : 'stopped';
67
76
  }
68
- function sourceLabel(runtime) {
69
- if (runtime.kind === 'remote') {
70
- return 'remote';
71
- }
72
- return runtime.source;
73
- }
74
77
  export default class Ps extends Command {
75
78
  static description = 'Show NocoBase runtime status for configured envs without starting or stopping anything.';
76
79
  static examples = [
@@ -105,12 +108,12 @@ export default class Ps extends Command {
105
108
  }
106
109
  rows.push([
107
110
  runtime.envName,
108
- sourceLabel(runtime),
109
- await appStatus(runtime),
111
+ runtime.kind,
112
+ await runtimeStatus(runtime),
110
113
  await dbStatus(runtime),
111
114
  appUrl(runtime),
112
115
  ]);
113
116
  }
114
- this.log(renderTable(['Env', 'Source', 'App', 'Database', 'URL'], rows));
117
+ this.log(renderTable(['Env', 'Kind', 'Status', 'Database', 'URL'], rows));
115
118
  }
116
119
  }
@@ -59,7 +59,7 @@ export default class SelfCheck extends Command {
59
59
  ['Update available', status.updateAvailable ? 'yes' : 'no'],
60
60
  ]));
61
61
  if (status.updateAvailable && status.updatable) {
62
- printInfo('Run `nb self update` to update the CLI.');
62
+ printInfo('Run `nb self update`.');
63
63
  }
64
64
  else if (status.updateAvailable && status.updateBlockedReason) {
65
65
  printInfo(status.updateBlockedReason);
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
- import { confirmAction } from '../../lib/ui.js';
10
+ import { confirmAction, setVerboseMode } from '../../lib/ui.js';
11
11
  import { formatSelfUpdateUnavailableMessage, formatUnsupportedSelfUpdateMessage, inspectSelfStatus, updateSelf, } from '../../lib/self-manager.js';
12
12
  export default class SelfUpdate extends Command {
13
13
  static summary = 'Update the globally installed NocoBase CLI';
@@ -32,9 +32,14 @@ export default class SelfUpdate extends Command {
32
32
  description: 'Output the result as JSON',
33
33
  default: false,
34
34
  }),
35
+ verbose: Flags.boolean({
36
+ description: 'Show detailed update output',
37
+ default: false,
38
+ }),
35
39
  };
36
40
  async run() {
37
41
  const { flags } = await this.parse(SelfUpdate);
42
+ setVerboseMode(flags.verbose);
38
43
  const status = await inspectSelfStatus({
39
44
  channel: flags.channel,
40
45
  });
@@ -53,6 +58,7 @@ export default class SelfUpdate extends Command {
53
58
  }
54
59
  const result = await updateSelf({
55
60
  channel: flags.channel,
61
+ verbose: flags.verbose,
56
62
  });
57
63
  if (flags.json) {
58
64
  this.log(JSON.stringify({
@@ -68,9 +74,13 @@ export default class SelfUpdate extends Command {
68
74
  return;
69
75
  }
70
76
  if (result.action === 'noop') {
71
- this.log(`NocoBase CLI is already up to date at ${result.status.currentVersion}.`);
77
+ this.log(flags.verbose
78
+ ? `NocoBase CLI is already up to date at ${result.status.currentVersion}.`
79
+ : `NocoBase CLI is up to date: ${result.status.currentVersion}.`);
72
80
  return;
73
81
  }
74
- this.log(`Updated NocoBase CLI from ${result.status.currentVersion} using ${result.packageSpec}${result.targetVersion ? ` (latest ${result.status.channel} resolves to ${result.targetVersion})` : ''}.`);
82
+ this.log(flags.verbose
83
+ ? `Updated NocoBase CLI from ${result.status.currentVersion} using ${result.packageSpec}${result.targetVersion ? ` (latest ${result.status.channel} resolves to ${result.targetVersion})` : ''}.`
84
+ : `Updated NocoBase CLI: ${result.status.currentVersion} -> ${result.targetVersion}.`);
75
85
  }
76
86
  }
@@ -10,8 +10,8 @@ import { Command, Flags } from '@oclif/core';
10
10
  import { inspectSkillsStatus } from '../../lib/skills-manager.js';
11
11
  import { printInfo, renderTable } from '../../lib/ui.js';
12
12
  export default class SkillsCheck extends Command {
13
- static summary = 'Check the NocoBase AI coding skills installed for this workspace';
14
- static description = 'Inspect the current workspace for NocoBase AI coding skills and report whether they are managed by the CLI and whether an update is available.';
13
+ static summary = 'Check the globally installed NocoBase AI coding skills';
14
+ static description = 'Inspect the global NocoBase AI coding skills and report whether they are managed by the CLI and whether an update is available.';
15
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> --json',
@@ -29,11 +29,15 @@ export default class SkillsCheck extends Command {
29
29
  this.log(JSON.stringify({
30
30
  ok: true,
31
31
  kind: 'skills',
32
+ globalRoot: status.globalRoot,
32
33
  workspaceRoot: status.workspaceRoot,
33
34
  installed: status.installed,
34
35
  managedByNb: status.managedByNb,
35
36
  sourcePackage: status.sourcePackage,
37
+ npmPackageName: status.npmPackageName,
36
38
  installedSkillNames: status.installedSkillNames,
39
+ installedVersion: status.installedVersion,
40
+ latestVersion: status.latestVersion,
37
41
  installedRef: status.installedRef,
38
42
  latestRef: status.latestRef,
39
43
  updateAvailable: status.updateAvailable,
@@ -43,18 +47,20 @@ export default class SkillsCheck extends Command {
43
47
  return;
44
48
  }
45
49
  this.log(renderTable(['Field', 'Value'], [
46
- ['Workspace', status.workspaceRoot],
50
+ ['Skills home', status.globalRoot],
47
51
  ['Installed', status.installed ? 'yes' : 'no'],
48
52
  ['Managed by nb', status.managedByNb ? 'yes' : 'no'],
49
53
  ['Installed skills', status.installedSkillNames.length ? status.installedSkillNames.join(', ') : '(none)'],
54
+ ['Installed version', status.installedVersion ?? '(unknown)'],
55
+ ['Latest version', status.latestVersion ?? '(unknown)'],
50
56
  ['Update available', status.updateAvailable === null ? 'unknown' : status.updateAvailable ? 'yes' : 'no'],
51
57
  ]));
52
58
  if (!status.installed) {
53
- printInfo('Run `nb skills install` to install the NocoBase AI coding skills for this workspace.');
59
+ printInfo('Run `nb skills install` to install the NocoBase AI coding skills globally.');
54
60
  return;
55
61
  }
56
62
  if (status.updateAvailable) {
57
- printInfo('Run `nb skills update` to refresh the NocoBase AI coding skills for this workspace.');
63
+ printInfo('Run `nb skills update` to refresh the global NocoBase AI coding skills.');
58
64
  }
59
65
  if (status.registryError) {
60
66
  printInfo(`Update check warning: ${status.registryError}`);
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, loadHelpClass } from '@oclif/core';
10
10
  export default class Skills extends Command {
11
- static summary = 'Inspect or synchronize NocoBase AI coding skills for this workspace';
11
+ static summary = 'Inspect or synchronize global NocoBase AI coding skills';
12
12
  async run() {
13
13
  await this.parse(Skills);
14
14
  const Help = await loadHelpClass(this.config);
@@ -7,11 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
- import { confirmAction } from '../../lib/ui.js';
10
+ import { confirmAction, setVerboseMode } from '../../lib/ui.js';
11
11
  import { installNocoBaseSkills } from '../../lib/skills-manager.js';
12
12
  export default class SkillsInstall extends Command {
13
- static summary = 'Install the NocoBase AI coding skills for this workspace';
14
- static description = 'Install the NocoBase AI coding skills for the current workspace. If they are already installed, this command does not update them.';
13
+ static summary = 'Install the NocoBase AI coding skills globally';
14
+ static description = 'Install the NocoBase AI coding skills globally. If they are already installed, this command does not update them.';
15
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> --yes',
@@ -27,32 +27,45 @@ export default class SkillsInstall extends Command {
27
27
  description: 'Output the result as JSON',
28
28
  default: false,
29
29
  }),
30
+ verbose: Flags.boolean({
31
+ description: 'Show detailed install output',
32
+ default: false,
33
+ }),
30
34
  };
31
35
  async run() {
32
36
  const { flags } = await this.parse(SkillsInstall);
37
+ setVerboseMode(flags.verbose);
33
38
  if (!flags.yes) {
34
- const confirmed = await confirmAction('Install the NocoBase AI coding skills for this workspace?', { defaultValue: true });
39
+ const confirmed = await confirmAction('Install the NocoBase AI coding skills globally?', { defaultValue: true });
35
40
  if (!confirmed) {
36
41
  this.log('Skipped skills install.');
37
42
  return;
38
43
  }
39
44
  }
40
- const result = await installNocoBaseSkills();
45
+ const result = await installNocoBaseSkills({
46
+ verbose: flags.verbose,
47
+ });
41
48
  if (flags.json) {
42
49
  this.log(JSON.stringify({
43
50
  ok: true,
44
51
  kind: 'skills',
45
52
  action: result.action,
53
+ globalRoot: result.status.globalRoot,
46
54
  workspaceRoot: result.status.workspaceRoot,
47
55
  installedSkillNames: result.status.installedSkillNames,
56
+ installedVersion: result.status.installedVersion,
48
57
  installedRef: result.status.installedRef,
49
58
  }, null, 2));
50
59
  return;
51
60
  }
52
61
  if (result.action === 'noop') {
53
- this.log('NocoBase AI coding skills are already installed for this workspace. Run `nb skills update` to refresh them.');
62
+ this.log(flags.verbose
63
+ ? 'NocoBase AI coding skills are already installed globally. Run `nb skills update` to refresh them.'
64
+ : 'NocoBase AI coding skills are already installed globally.');
54
65
  return;
55
66
  }
56
- this.log('Installed the NocoBase AI coding skills for this workspace.');
67
+ this.log(flags.verbose
68
+ ? 'Installed the NocoBase AI coding skills globally.'
69
+ : 'Installed NocoBase AI coding skills globally.');
57
70
  }
58
71
  }
@@ -7,11 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
- import { confirmAction } from '../../lib/ui.js';
10
+ import { confirmAction, setVerboseMode } from '../../lib/ui.js';
11
11
  import { updateNocoBaseSkills } from '../../lib/skills-manager.js';
12
12
  export default class SkillsUpdate extends Command {
13
- static summary = 'Update the NocoBase AI coding skills for this workspace';
14
- static description = 'Refresh the NocoBase AI coding skills for the current workspace. This command only updates an existing nocobase/skills install.';
13
+ static summary = 'Update the globally installed NocoBase AI coding skills';
14
+ static description = 'Refresh the globally installed NocoBase AI coding skills. This command only updates an existing @nocobase/skills install.';
15
15
  static examples = [
16
16
  '<%= config.bin %> <%= command.id %>',
17
17
  '<%= config.bin %> <%= command.id %> --yes',
@@ -27,32 +27,45 @@ export default class SkillsUpdate extends Command {
27
27
  description: 'Output the result as JSON',
28
28
  default: false,
29
29
  }),
30
+ verbose: Flags.boolean({
31
+ description: 'Show detailed update output',
32
+ default: false,
33
+ }),
30
34
  };
31
35
  async run() {
32
36
  const { flags } = await this.parse(SkillsUpdate);
37
+ setVerboseMode(flags.verbose);
33
38
  if (!flags.yes) {
34
- const confirmed = await confirmAction('Update the NocoBase AI coding skills for this workspace?', { defaultValue: true });
39
+ const confirmed = await confirmAction('Update the globally installed NocoBase AI coding skills?', { defaultValue: true });
35
40
  if (!confirmed) {
36
41
  this.log('Skipped skills update.');
37
42
  return;
38
43
  }
39
44
  }
40
- const result = await updateNocoBaseSkills();
45
+ const result = await updateNocoBaseSkills({
46
+ verbose: flags.verbose,
47
+ });
41
48
  if (flags.json) {
42
49
  this.log(JSON.stringify({
43
50
  ok: true,
44
51
  kind: 'skills',
45
52
  action: result.action,
53
+ globalRoot: result.status.globalRoot,
46
54
  workspaceRoot: result.status.workspaceRoot,
47
55
  installedSkillNames: result.status.installedSkillNames,
56
+ installedVersion: result.status.installedVersion,
48
57
  installedRef: result.status.installedRef,
49
58
  }, null, 2));
50
59
  return;
51
60
  }
52
61
  if (result.action === 'noop') {
53
- this.log('NocoBase AI coding skills are already up to date for this workspace.');
62
+ this.log(flags.verbose
63
+ ? 'NocoBase AI coding skills are already up to date globally.'
64
+ : 'NocoBase AI coding skills are up to date.');
54
65
  return;
55
66
  }
56
- this.log('Updated the NocoBase AI coding skills for this workspace.');
67
+ this.log(flags.verbose
68
+ ? 'Updated the global NocoBase AI coding skills.'
69
+ : 'Updated NocoBase AI coding skills globally.');
57
70
  }
58
71
  }
@@ -111,13 +111,20 @@ export default class Start extends Command {
111
111
  if (!runtime) {
112
112
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
113
113
  }
114
- if (runtime.kind === 'remote') {
114
+ if (runtime.kind === 'http') {
115
115
  this.error([
116
116
  `Can't start "${runtime.envName}" from this machine.`,
117
117
  'This env only has an API connection, so there is no saved local app or Docker runtime to launch here.',
118
118
  'Connect it to a local checkout or reinstall it with npm, git, or Docker if you want CLI-managed start and stop.',
119
119
  ].join('\n'));
120
120
  }
121
+ if (runtime.kind === 'ssh') {
122
+ this.error([
123
+ `Can't start "${runtime.envName}" yet.`,
124
+ 'SSH env support is reserved but not implemented yet.',
125
+ 'Use a local or Docker env if you need CLI-managed start and stop right now.',
126
+ ].join('\n'));
127
+ }
121
128
  if (runtime.kind === 'docker') {
122
129
  const unsupportedFlags = [
123
130
  flags.quickstart ? '--quickstart' : undefined,