fnos-cli 0.1.0 → 0.2.0

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.
package/README.md CHANGED
@@ -59,7 +59,27 @@ fnos login -e nas-9.timandes.net:5666 -u SystemMonitor -p yourpassword
59
59
 
60
60
  登录成功后,凭证会保存在 `~/.fnos/settings.json` 文件中,后续命令无需重复输入。
61
61
 
62
- ### 2. 使用命令
62
+ ### 2. 使用命令行凭证参数
63
+
64
+ 除了使用 `login` 保存凭证外,您还可以直接在命令中提供连接参数:
65
+
66
+ ```bash
67
+ fnos <command> -e <endpoint> -u <username> -p <password>
68
+ ```
69
+
70
+ 例如:
71
+
72
+ ```bash
73
+ fnos resmon.cpu -e nas-9.timandes.net:5666 -u SystemMonitor -p yourpassword
74
+ ```
75
+
76
+ **注意事项**:
77
+ - 三个参数(`-e`、`-u`、`-p`)必须同时提供,不能只提供部分参数
78
+ - 使用命令行参数提供的凭证**不会**保存到配置文件中,适合临时使用
79
+ - 如果未提供这三个参数,会自动使用已保存的凭证(需要先执行 `login`)
80
+ - 这种方式适合自动化脚本或临时连接到不同服务器
81
+
82
+ ### 3. 使用命令
63
83
 
64
84
  登录后即可执行各种命令:
65
85
 
@@ -120,6 +140,38 @@ fnos resmon.cpu -v > output.json 2>log.txt
120
140
  fnos resmon.cpu 2>/dev/null
121
141
  ```
122
142
 
143
+ ### 认证参数
144
+
145
+ 所有命令(`login` 和 `logout` 除外)都支持以下可选的认证参数:
146
+
147
+ | 参数 | 说明 |
148
+ |------|------|
149
+ | `-e, --endpoint <endpoint>` | 服务器端点(例如:nas-9.timandes.net:5666) |
150
+ | `-u, --username <username>` | 用户名 |
151
+ | `-p, --password <password>` | 密码 |
152
+
153
+ **使用规则**:
154
+
155
+ 1. **三个参数必须同时提供**:如果提供任何一个参数,必须同时提供另外两个参数
156
+ 2. **临时使用不保存**:通过命令行参数提供的凭证不会被保存到配置文件
157
+ 3. **优先使用命令行参数**:如果同时提供了命令行参数和配置文件中有保存的凭证,优先使用命令行参数
158
+ 4. **回退到配置文件**:如果未提供这三个参数,会自动使用 `login` 保存的凭证
159
+
160
+ **示例**:
161
+
162
+ ```bash
163
+ # 使用命令行参数(临时使用,不保存)
164
+ fnos resmon.cpu -e nas-9.timandes.net:5666 -u SystemMonitor -p yourpassword
165
+
166
+ # 错误示例:只提供部分参数
167
+ fnos resmon.cpu -e nas-9.timandes.net:5666
168
+ # Error: When using -e/--endpoint, you must also provide -u/--username and -p/--password.
169
+
170
+ # 使用已保存的凭证(需要先执行 login)
171
+ fnos login -e nas-9.timandes.net:5666 -u SystemMonitor -p yourpassword
172
+ fnos resmon.cpu # 不需要再提供参数
173
+ ```
174
+
123
175
  ### 资源监控命令
124
176
 
125
177
  | 命令 | 说明 |
package/package.json CHANGED
@@ -1,3 +1 @@
1
- {
2
- "name": "fnos-cli",
3
- "version": "0.1.0", "description": "CLI client for 飞牛 fnOS system", "main": "src/index.js", "bin": {"fnos": "./bin/fnos"}, "scripts": {"start": "node src/index.js", "test": "mocha test/**/*.test.js"}, "keywords": ["fnos", "cli", "nas"], "author": "", "license": "Apache-2.0", "dependencies": {"commander": "^11.1.0", "fnos": "^0.2.0", "winston": "^3.19.0"}, "devDependencies": {"chai": "^5.3.3", "mocha": "^10.8.2"}}
1
+ {"name": "fnos-cli", "version": "0.2.0", "description": "CLI client for 飞牛 fnOS system", "main": "src/index.js", "bin": {"fnos": "./bin/fnos"}, "scripts": {"start": "node src/index.js", "test": "mocha test/**/*.test.js"}, "keywords": ["fnos", "cli", "nas"], "author": "", "license": "Apache-2.0", "dependencies": {"commander": "^11.1.0", "fnos": "^0.2.0", "winston": "^3.19.0"}, "devDependencies": {"chai": "^5.3.3", "mocha": "^10.8.2"}}
@@ -23,7 +23,7 @@ function registerLoginCommand(program) {
23
23
  const { endpoint, username, password } = options;
24
24
 
25
25
  logger.info('Logging in...');
26
- const client = await createClient({ endpoint, username, password, timeout: 60 });
26
+ const client = await createClient({ endpoint, username, password, timeout: 60, saveCredentials: true });
27
27
  logger.info('Login successful! Credentials saved.');
28
28
  client.close();
29
29
  process.exit(0);
@@ -44,8 +44,9 @@ function registerLogoutCommand(program) {
44
44
  .description('Logout and clear saved credentials')
45
45
  .action(() => {
46
46
  try {
47
- if (settings.exists()) {
48
- settings.clear();
47
+ const credentials = settings.getCredentials();
48
+ if (credentials && (credentials.endpoint || credentials.username)) {
49
+ settings.clearCredentials();
49
50
  console.log('Logged out successfully. Credentials cleared.');
50
51
  } else {
51
52
  console.log('No saved credentials found.');
@@ -6,6 +6,7 @@ const { COMMAND_MAPPING } = require('../constants');
6
6
  const { createClient, getSDKInstance } = require('../utils/client');
7
7
  const { formatOutput } = require('../utils/formatter');
8
8
  const { logger } = require('../utils/logger');
9
+ const { resolveCredentials } = require('../utils/auth-helper');
9
10
 
10
11
  /**
11
12
  * Register all dynamic commands based on COMMAND_MAPPING
@@ -21,6 +22,11 @@ function registerCommands(program) {
21
22
  const cmd = program.command(fullCommandName)
22
23
  .description(commandConfig.description);
23
24
 
25
+ // Add authentication parameters
26
+ cmd.option('-e, --endpoint <endpoint>', 'Server endpoint (e.g., nas-9.timandes.net:5666)');
27
+ cmd.option('-u, --username <username>', 'Username');
28
+ cmd.option('-p, --password <password>', 'Password');
29
+
24
30
  // Add parameters
25
31
  if (commandConfig.params) {
26
32
  commandConfig.params.forEach(param => {
@@ -51,8 +57,23 @@ function registerCommands(program) {
51
57
  }
52
58
  }
53
59
 
54
- // Create client (will use saved credentials from settings)
60
+ // Resolve credentials (command line or settings file)
61
+ let credentials;
62
+ try {
63
+ credentials = resolveCredentials(options, program);
64
+ } catch (error) {
65
+ if (error.message === 'PARTIAL_CREDENTIALS') {
66
+ // Display friendly error message
67
+ console.error('Error: When using -e/--endpoint, you must also provide -u/--username and -p/--password.');
68
+ console.error('Usage: fnos %s -e <endpoint> -u <username> -p <password>', fullCommandName);
69
+ process.exit(1);
70
+ }
71
+ throw error;
72
+ }
73
+
74
+ // Create client with resolved credentials
55
75
  const client = await createClient({
76
+ credentials,
56
77
  timeout: 60
57
78
  });
58
79
 
package/src/index.js CHANGED
@@ -16,7 +16,7 @@ const program = new Command();
16
16
  program
17
17
  .name('fnos')
18
18
  .description('CLI client for 飞牛 fnOS system')
19
- .version('1.0.0');
19
+ .version('0.2.0');
20
20
 
21
21
  // Global options
22
22
  program
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Authentication helper for credential validation and resolution
3
+ */
4
+
5
+ const settings = require('./settings');
6
+
7
+ /**
8
+ * Check if partial credentials are provided
9
+ * @param {Object} options - Command line options
10
+ * @returns {boolean} - True if at least one but not all three parameters are provided
11
+ */
12
+ function hasPartialCredentials(options) {
13
+ const { endpoint, username, password } = options;
14
+ const provided = [endpoint, username, password].filter(Boolean);
15
+ return provided.length > 0 && provided.length < 3;
16
+ }
17
+
18
+ /**
19
+ * Validate command line credentials completeness
20
+ * @param {Object} options - Command line options
21
+ * @returns {Object} - { isValid: boolean, hasCredentials: boolean }
22
+ * @throws {Error} - If partial credentials are provided
23
+ */
24
+ function validateCommandLineCredentials(options) {
25
+ if (hasPartialCredentials(options)) {
26
+ throw new Error('PARTIAL_CREDENTIALS');
27
+ }
28
+
29
+ const hasCredentials = options.endpoint && options.username && options.password;
30
+ return { isValid: true, hasCredentials };
31
+ }
32
+
33
+ /**
34
+ * Resolve and return the final credentials to use
35
+ * @param {Object} options - Command line options
36
+ * @returns {Object} - Credentials object
37
+ * @throws {Error} - If no valid credentials are found or partial credentials are provided
38
+ */
39
+ function resolveCredentials(options) {
40
+ const validation = validateCommandLineCredentials(options);
41
+
42
+ if (validation.hasCredentials) {
43
+ // Use command line credentials (do NOT save to settings)
44
+ return {
45
+ endpoint: options.endpoint,
46
+ username: options.username,
47
+ password: options.password,
48
+ source: 'command_line'
49
+ };
50
+ }
51
+
52
+ // Read from settings file
53
+ const credentials = settings.getCredentials();
54
+ if (!credentials || !credentials.endpoint || !credentials.username || !credentials.password) {
55
+ throw new Error('No valid credentials found. Please use "fnos login" or provide -e/-u/-p options.');
56
+ }
57
+
58
+ return {
59
+ ...credentials,
60
+ source: 'settings_file'
61
+ };
62
+ }
63
+
64
+ module.exports = {
65
+ hasPartialCredentials,
66
+ validateCommandLineCredentials,
67
+ resolveCredentials
68
+ };
@@ -9,16 +9,23 @@ const { logger } = require('./logger');
9
9
  /**
10
10
  * Create authenticated FnosClient
11
11
  * @param {Object} options - Options object
12
- * @param {string} options.endpoint - Server endpoint
13
- * @param {string} options.username - Username
14
- * @param {string} options.password - Password
15
- * @param {number} options.timeout - Connection timeout in seconds
12
+ * @param {Object} [options.credentials] - Direct credentials object (endpoint, username, password, etc.)
13
+ * @param {string} [options.endpoint] - Server endpoint (legacy)
14
+ * @param {string} [options.username] - Username (legacy)
15
+ * @param {string} [options.password] - Password (legacy)
16
+ * @param {number} [options.timeout] - Connection timeout in seconds
17
+ * @param {boolean} [options.saveCredentials] - Whether to save credentials to settings (default: false)
16
18
  * @returns {Promise<FnosClient>} Authenticated client
17
19
  */
18
20
  async function createClient(options = {}) {
19
- const { endpoint, username, password, timeout = 60 } = options;
21
+ const { credentials, endpoint, username, password, timeout = 60, saveCredentials = false } = options;
20
22
 
21
- // Get credentials from settings if not provided
23
+ // Use provided credentials object
24
+ if (credentials) {
25
+ return await createClientWithCredentials(credentials, timeout, saveCredentials);
26
+ }
27
+
28
+ // Legacy support: build credentials from individual parameters
22
29
  const savedCredentials = settings.getCredentials();
23
30
  const finalEndpoint = endpoint || savedCredentials?.endpoint;
24
31
  const finalUsername = username || savedCredentials?.username;
@@ -28,41 +35,56 @@ async function createClient(options = {}) {
28
35
  throw new Error('Missing credentials. Please run "fnos login" first or provide -e, -u, -p parameters.');
29
36
  }
30
37
 
38
+ return await createClientWithCredentials(
39
+ {
40
+ endpoint: finalEndpoint,
41
+ username: finalUsername,
42
+ password: finalPassword,
43
+ token: savedCredentials?.token,
44
+ longToken: savedCredentials?.longToken,
45
+ secret: savedCredentials?.secret
46
+ },
47
+ timeout,
48
+ saveCredentials
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Create authenticated client with credentials
54
+ * @param {Object} credentials - Credentials object
55
+ * @param {string} credentials.endpoint - Server endpoint
56
+ * @param {string} credentials.username - Username
57
+ * @param {string} credentials.password - Password
58
+ * @param {number} timeout - Connection timeout in seconds
59
+ * @param {boolean} saveToSettings - Whether to save credentials to settings
60
+ * @returns {Promise<FnosClient>} Authenticated client
61
+ */
62
+ async function createClientWithCredentials(credentials, timeout, saveToSettings) {
63
+ const { endpoint, username, password } = credentials;
64
+
31
65
  // Create client
32
66
  const client = new FnosClient();
33
- logger.info(`Connecting to ${finalEndpoint}...`);
67
+ logger.info(`Connecting to ${endpoint}...`);
34
68
 
35
69
  // Connect
36
- await client.connect(finalEndpoint, timeout * 1000);
70
+ await client.connect(endpoint, timeout * 1000);
37
71
  logger.info('Connected successfully');
38
72
 
39
- // Try to login with token first
40
- // if (savedCredentials?.token && savedCredentials?.secret && !endpoint && !username && !password) {
41
- // try {
42
- // logger.info('Logging in with saved token...');
43
- // await client.loginViaToken(savedCredentials.token, savedCredentials.longToken, savedCredentials.secret);
44
- // logger.info('Logged in successfully with token');
45
- // return client;
46
- // } catch (error) {
47
- // logger.warn('Token login failed, falling back to password login:', error.message);
48
- // }
49
- // }
50
-
51
73
  // Login with password
52
- if (!finalPassword) {
74
+ if (!password) {
53
75
  throw new Error('Password required. Please run "fnos login" first or provide -p parameter.');
54
76
  }
55
77
 
56
78
  logger.info('Logging in...');
57
- const loginResult = await client.login(finalUsername, finalPassword);
79
+ const loginResult = await client.login(username, password);
58
80
  logger.info('Logged in successfully');
59
81
 
60
- // Save credentials if they were provided via command line
61
- if (endpoint || username || password) {
82
+ // Save credentials only if explicitly requested (e.g., from login command)
83
+ if (saveToSettings) {
62
84
  settings.saveCredentials({
63
- endpoint: finalEndpoint,
64
- username: finalUsername,
65
- password: finalPassword,
85
+ endpoint,
86
+ username,
87
+ password,
66
88
  token: loginResult.token,
67
89
  longToken: loginResult.longToken,
68
90
  secret: loginResult.secret
@@ -63,6 +63,23 @@ class Settings {
63
63
  }
64
64
  }
65
65
 
66
+ /**
67
+ * Clear only authentication credentials, preserving other settings
68
+ */
69
+ clearCredentials() {
70
+ const existingSettings = this.load();
71
+ if (!existingSettings) {
72
+ return;
73
+ }
74
+
75
+ // Remove only auth-related fields, keep other settings
76
+ const authFields = ['endpoint', 'username', 'password', 'token', 'longToken', 'secret'];
77
+ authFields.forEach(field => delete existingSettings[field]);
78
+
79
+ // Always save the file (even if empty), to preserve the settings.json file
80
+ this.save(existingSettings);
81
+ }
82
+
66
83
  /**
67
84
  * Check if settings file exists
68
85
  * @returns {boolean} True if settings file exists