stigmergy 1.0.94 → 1.0.95

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 (50) hide show
  1. package/bin/stigmergy +26 -12
  2. package/docs/HASH_TABLE.md +83 -0
  3. package/docs/WEATHER_PROCESSOR_API.md +230 -0
  4. package/docs/best_practices.md +135 -0
  5. package/docs/development_guidelines.md +392 -0
  6. package/docs/requirements_specification.md +148 -0
  7. package/docs/system_design.md +314 -0
  8. package/docs/tdd_implementation_plan.md +384 -0
  9. package/docs/test_report.md +49 -0
  10. package/examples/calculator-example.js +72 -0
  11. package/examples/json-validation-example.js +64 -0
  12. package/examples/rest-client-example.js +52 -0
  13. package/package.json +21 -17
  14. package/scripts/post-deployment-config.js +9 -2
  15. package/src/auth.js +171 -0
  16. package/src/auth_command.js +195 -0
  17. package/src/calculator.js +220 -0
  18. package/src/core/cli_help_analyzer.js +625 -562
  19. package/src/core/cli_parameter_handler.js +121 -0
  20. package/src/core/cli_tools.js +89 -0
  21. package/src/core/error_handler.js +307 -0
  22. package/src/core/memory_manager.js +76 -0
  23. package/src/core/smart_router.js +133 -0
  24. package/src/deploy.js +50 -0
  25. package/src/main_english.js +642 -719
  26. package/src/main_fixed.js +1035 -0
  27. package/src/utils.js +529 -0
  28. package/src/weatherProcessor.js +205 -0
  29. package/test/calculator.test.js +215 -0
  30. package/test/collision-test.js +26 -0
  31. package/test/csv-processing-test.js +36 -0
  32. package/test/e2e/claude-cli-test.js +128 -0
  33. package/test/e2e/collaboration-test.js +75 -0
  34. package/test/e2e/comprehensive-test.js +431 -0
  35. package/test/e2e/error-handling-test.js +90 -0
  36. package/test/e2e/individual-tool-test.js +143 -0
  37. package/test/e2e/other-cli-test.js +130 -0
  38. package/test/e2e/qoder-cli-test.js +128 -0
  39. package/test/e2e/run-e2e-tests.js +73 -0
  40. package/test/e2e/test-data.js +88 -0
  41. package/test/e2e/test-utils.js +222 -0
  42. package/test/hash-table-demo.js +33 -0
  43. package/test/hash-table-test.js +26 -0
  44. package/test/json-validation-test.js +164 -0
  45. package/test/rest-client-test.js +56 -0
  46. package/test/unit/calculator-full.test.js +191 -0
  47. package/test/unit/calculator-simple.test.js +96 -0
  48. package/test/unit/calculator.test.js +97 -0
  49. package/test/unit/cli_parameter_handler.test.js +116 -0
  50. package/test/weather-processor.test.js +104 -0
@@ -0,0 +1,49 @@
1
+ # Stigmergy CLI - 测试报告
2
+
3
+ ## 概述
4
+ 本次测试验证了Stigmergy CLI系统的核心模块,包括智能路由、CLI帮助分析和内存管理模块。
5
+
6
+ ## 测试结果
7
+
8
+ ### 测试套件执行情况
9
+ - **通过**: 3/3 测试套件
10
+ - **失败**: 0/3 测试套件
11
+ - **总测试数**: 20个测试用例
12
+ - **通过率**: 100%
13
+
14
+ ### 详细测试结果
15
+
16
+ #### 1. SmartRouter 测试套件
17
+ - **总测试数**: 11个测试用例
18
+ - **通过**: 11/11
19
+ - **测试内容**:
20
+ - 构造函数测试
21
+ - 关键字提取功能测试
22
+ - 智能路由决策测试
23
+
24
+ #### 2. CLIHelpAnalyzer 测试套件
25
+ - **总测试数**: 6个测试用例
26
+ - **通过**: 6/6
27
+ - **测试内容**:
28
+ - 构造函数测试
29
+ - 初始化功能测试
30
+ - 缓存过期检查测试
31
+ - 文件存在性检查测试
32
+
33
+ #### 3. MemoryManager 测试套件
34
+ - **总测试数**: 3个测试用例
35
+ - **通过**: 3/3
36
+ - **测试内容**:
37
+ - 构造函数测试
38
+ - 全局内存获取测试
39
+ - 内存文件读写测试
40
+
41
+ ## 代码覆盖率
42
+ 当前测试覆盖率尚未达到预设阈值:
43
+ - **语句覆盖率**: 7.51% (目标: 80%)
44
+ - **分支覆盖率**: 7.84% (目标: 75%)
45
+ - **函数覆盖率**: 12.39% (目标: 85%)
46
+ - **行覆盖率**: 7.6% (目标: 80%)
47
+
48
+ ## 结论
49
+ 所有单元测试均已通过,验证了核心模块的基本功能。下一步需要增加更多测试用例以提高代码覆盖率。
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Example usage of the Calculator class
3
+ */
4
+
5
+ const Calculator = require('../src/calculator');
6
+
7
+ // Create a new calculator instance
8
+ const calc = new Calculator();
9
+
10
+ // Basic operations
11
+ console.log('=== Basic Operations ===');
12
+ console.log(`Addition: 5 + 3 = ${calc.add(5, 3)}`);
13
+ console.log(`Subtraction: 10 - 4 = ${calc.subtract(10, 4)}`);
14
+ console.log(`Multiplication: 6 * 7 = ${calc.multiply(6, 7)}`);
15
+ console.log(`Division: 15 / 3 = ${calc.divide(15, 3)}`);
16
+ console.log(`Power: 2^8 = ${calc.power(2, 8)}`);
17
+ console.log(`Square Root: √64 = ${calc.sqrt(64)}`);
18
+ console.log(`Factorial: 5! = ${calc.factorial(5)}`);
19
+ console.log(`Percentage: 25 is what percent of 200 = ${calc.percentage(25, 200)}%`);
20
+
21
+ // Chained calculations
22
+ console.log('\n=== Chained Calculations ===');
23
+ const result1 = calc.chain(10)
24
+ .add(5)
25
+ .multiply(2)
26
+ .subtract(10)
27
+ .divide(4)
28
+ .equals();
29
+
30
+ console.log(`((10 + 5) * 2 - 10) / 4 = ${result1}`);
31
+
32
+ // More complex chained calculation
33
+ const result2 = calc.chain(2)
34
+ .power(3)
35
+ .add(1)
36
+ .multiply(2)
37
+ .sqrt()
38
+ .equals();
39
+
40
+ console.log(`√(((2^3) + 1) * 2) = ${result2}`);
41
+
42
+ // Getting intermediate values
43
+ console.log('\n=== Intermediate Values ===');
44
+ const chain = calc.chain(100)
45
+ .subtract(20) // 80
46
+ .divide(4) // 20
47
+ .add(5); // 25
48
+
49
+ console.log(`Intermediate result: ${chain.value()}`);
50
+
51
+ const finalResult = chain.multiply(2).equals(); // 50
52
+ console.log(`Final result: ${finalResult}`);
53
+
54
+ // Error handling examples
55
+ console.log('\n=== Error Handling ===');
56
+ try {
57
+ calc.divide(10, 0);
58
+ } catch (error) {
59
+ console.log(`Error caught: ${error.message}`);
60
+ }
61
+
62
+ try {
63
+ calc.sqrt(-1);
64
+ } catch (error) {
65
+ console.log(`Error caught: ${error.message}`);
66
+ }
67
+
68
+ try {
69
+ calc.chain(10).divide(0);
70
+ } catch (error) {
71
+ console.log(`Error caught: ${error.message}`);
72
+ }
@@ -0,0 +1,64 @@
1
+ // Example usage of parseAndValidateJSON function
2
+
3
+ const { parseAndValidateJSON } = require('../src/utils');
4
+
5
+ // Example 1: Basic JSON parsing
6
+ console.log('=== Example 1: Basic JSON Parsing ===');
7
+ try {
8
+ const jsonData = '{"name": "Alice", "age": 25, "city": "New York"}';
9
+ const parsed = parseAndValidateJSON(jsonData);
10
+ console.log('Parsed data:', parsed);
11
+ } catch (error) {
12
+ console.error('Error:', error.message);
13
+ }
14
+
15
+ // Example 2: JSON parsing with schema validation
16
+ console.log('\n=== Example 2: JSON Parsing with Schema Validation ===');
17
+ try {
18
+ const jsonData = '{"id": 123, "name": "Bob", "email": "bob@example.com", "active": true}';
19
+
20
+ // Define a schema for validation
21
+ const userSchema = {
22
+ required: ['id', 'name', 'email'],
23
+ properties: {
24
+ id: { type: 'number' },
25
+ name: { type: 'string' },
26
+ email: { type: 'string' },
27
+ active: { type: 'boolean' }
28
+ }
29
+ };
30
+
31
+ const validatedData = parseAndValidateJSON(jsonData, userSchema);
32
+ console.log('Validated data:', validatedData);
33
+ } catch (error) {
34
+ console.error('Error:', error.message);
35
+ }
36
+
37
+ // Example 3: Handling invalid JSON
38
+ console.log('\n=== Example 3: Handling Invalid JSON ===');
39
+ try {
40
+ const invalidJson = '{"name": "Charlie", "age":}';
41
+ const result = parseAndValidateJSON(invalidJson);
42
+ console.log('Result:', result);
43
+ } catch (error) {
44
+ console.error('Error:', error.message);
45
+ }
46
+
47
+ // Example 4: Handling schema validation errors
48
+ console.log('\n=== Example 4: Handling Schema Validation Errors ===');
49
+ try {
50
+ const jsonData = '{"name": "David", "age": "thirty"}'; // age should be a number
51
+
52
+ const schema = {
53
+ required: ['name', 'age'],
54
+ properties: {
55
+ name: { type: 'string' },
56
+ age: { type: 'number' }
57
+ }
58
+ };
59
+
60
+ const result = parseAndValidateJSON(jsonData, schema);
61
+ console.log('Result:', result);
62
+ } catch (error) {
63
+ console.error('Error:', error.message);
64
+ }
@@ -0,0 +1,52 @@
1
+ // Example usage of the RESTClient class
2
+
3
+ const { RESTClient } = require('../src/utils');
4
+
5
+ // Create a new REST client instance
6
+ const client = new RESTClient('https://jsonplaceholder.typicode.com');
7
+
8
+ // Example 1: GET request
9
+ async function getPost() {
10
+ try {
11
+ const response = await client.get('/posts/1');
12
+ console.log('Post Title:', response.data.title);
13
+ } catch (error) {
14
+ console.error('Error fetching post:', error.message);
15
+ }
16
+ }
17
+
18
+ // Example 2: POST request
19
+ async function createPost() {
20
+ try {
21
+ const newPost = {
22
+ title: 'My New Post',
23
+ body: 'This is the content of my new post',
24
+ userId: 1
25
+ };
26
+
27
+ const response = await client.post('/posts', newPost);
28
+ console.log('Created Post ID:', response.data.id);
29
+ } catch (error) {
30
+ console.error('Error creating post:', error.message);
31
+ }
32
+ }
33
+
34
+ // Example 3: Using custom headers
35
+ async function getRequestWithHeaders() {
36
+ try {
37
+ const clientWithAuth = new RESTClient('https://jsonplaceholder.typicode.com', {
38
+ 'Authorization': 'Bearer your-token-here',
39
+ 'X-Custom-Header': 'custom-value'
40
+ });
41
+
42
+ const response = await clientWithAuth.get('/users/1');
43
+ console.log('User Name:', response.data.name);
44
+ } catch (error) {
45
+ console.error('Error fetching user:', error.message);
46
+ }
47
+ }
48
+
49
+ // Run the examples
50
+ getPost();
51
+ createPost();
52
+ getRequestWithHeaders();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stigmergy",
3
- "version": "1.0.94",
3
+ "version": "1.0.95",
4
4
  "description": "Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System",
5
5
  "main": "src/main_english.js",
6
6
  "bin": {
@@ -8,9 +8,12 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node src/main_english.js",
11
- "test": "node test/real-test.js",
12
- "test:all": "node test/test-all-clis.js",
13
- "test:cross-cli": "node test/cross-cli-real-test.js",
11
+ "test": "jest",
12
+ "test:unit": "jest tests/unit",
13
+ "test:integration": "jest tests/integration",
14
+ "test:e2e": "jest tests/e2e",
15
+ "coverage": "jest --coverage",
16
+ "test:watch": "jest --watch",
14
17
  "build": "node scripts/build.js",
15
18
  "deploy": "node src/deploy.js",
16
19
  "status": "node src/index.js status",
@@ -69,29 +72,30 @@
69
72
  "node": ">=16.0.0"
70
73
  },
71
74
  "dependencies": {
75
+ "chalk": "^4.1.2",
76
+ "child_process": "^1.0.2",
77
+ "chokidar": "^3.5.3",
72
78
  "commander": "^12.0.0",
79
+ "crypto": "^1.0.1",
80
+ "events": "^3.3.0",
81
+ "figures": "^3.2.0",
82
+ "fs-extra": "^11.1.1",
83
+ "glob": "^10.3.10",
73
84
  "inquirer": "^8.2.6",
74
- "chalk": "^4.1.2",
75
85
  "js-yaml": "^4.1.0",
76
86
  "node-fetch": "^2.6.7",
77
- "child_process": "^1.0.2",
78
- "fs-extra": "^11.1.1",
79
- "path": "^0.12.7",
87
+ "ora": "^5.4.1",
80
88
  "os": "^0.1.2",
81
- "crypto": "^1.0.1",
82
- "events": "^3.3.0",
83
- "util": "^0.12.5",
89
+ "path": "^0.12.7",
84
90
  "semver": "^7.5.4",
85
- "glob": "^10.3.10",
86
- "chokidar": "^3.5.3",
87
91
  "table": "^6.8.1",
88
- "figures": "^3.2.0",
89
- "ora": "^5.4.1"
92
+ "util": "^0.12.5"
90
93
  },
91
94
  "devDependencies": {
92
95
  "eslint": "^8.50.0",
93
- "prettier": "^3.0.3",
94
- "nodemon": "^3.0.1"
96
+ "jest": "^30.2.0",
97
+ "nodemon": "^3.0.1",
98
+ "prettier": "^3.0.3"
95
99
  },
96
100
  "config": {
97
101
  "encoding": "ansi",
@@ -63,9 +63,16 @@ class PostDeploymentConfigurer {
63
63
  // Check if an installation script exists
64
64
  async checkInstallScript(toolName) {
65
65
  const tool = CLI_TOOLS[toolName];
66
- if (!tool) return false;
66
+ if (!tool) return { exists: false, path: '' };
67
+
68
+ // Mapping for tool names that don't match their adapter directory names
69
+ const toolNameToAdapterDir = {
70
+ 'qodercli': 'qoder',
71
+ 'qwencode': 'qwen'
72
+ };
73
+ const adapterDirName = toolNameToAdapterDir[toolName] || toolName;
74
+ const scriptPath = path.join(this.stigmergyAssetsDir, adapterDirName, tool.installScript);
67
75
 
68
- const scriptPath = path.join(this.stigmergyAssetsDir, toolName, tool.installScript);
69
76
  try {
70
77
  await fs.access(scriptPath);
71
78
  return { exists: true, path: scriptPath };
package/src/auth.js ADDED
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Authentication module for the Stigmergy CLI system.
3
+ * Provides user authentication functionality including password hashing and token management.
4
+ */
5
+
6
+ const crypto = require('crypto');
7
+
8
+ /**
9
+ * Custom exception for authentication failures.
10
+ */
11
+ class AuthenticationError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = 'AuthenticationError';
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Handles user authentication operations including registration, login, and session management.
20
+ */
21
+ class UserAuthenticator {
22
+ /**
23
+ * Create a new UserAuthenticator instance.
24
+ */
25
+ constructor() {
26
+ // In production, this would be stored in a secure database
27
+ this._users = new Map();
28
+ this._sessions = new Map();
29
+ }
30
+
31
+ /**
32
+ * Register a new user with the provided username and password.
33
+ *
34
+ * @param {string} username - The user's username
35
+ * @param {string} password - The user's password
36
+ * @returns {boolean} True if registration successful, false if username already exists
37
+ * @throws {Error} If username or password is invalid
38
+ */
39
+ registerUser(username, password) {
40
+ if (!username || !password) {
41
+ throw new Error('Username and password cannot be empty');
42
+ }
43
+
44
+ if (username.length < 3) {
45
+ throw new Error('Username must be at least 3 characters long');
46
+ }
47
+
48
+ if (password.length < 8) {
49
+ throw new Error('Password must be at least 8 characters long');
50
+ }
51
+
52
+ if (this._users.has(username)) {
53
+ return false;
54
+ }
55
+
56
+ // Hash the password with a salt
57
+ const salt = crypto.randomBytes(16).toString('hex');
58
+ const passwordHash = this._hashPassword(password, salt);
59
+
60
+ this._users.set(username, {
61
+ passwordHash: passwordHash,
62
+ salt: salt
63
+ });
64
+
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Authenticate a user with the provided credentials.
70
+ *
71
+ * @param {string} username - The user's username
72
+ * @param {string} password - The user's password
73
+ * @returns {string} Session token if authentication is successful
74
+ * @throws {AuthenticationError} If authentication fails
75
+ */
76
+ authenticateUser(username, password) {
77
+ if (!this._users.has(username)) {
78
+ throw new AuthenticationError('Invalid username or password');
79
+ }
80
+
81
+ const userData = this._users.get(username);
82
+ const passwordHash = this._hashPassword(password, userData.salt);
83
+
84
+ if (passwordHash !== userData.passwordHash) {
85
+ throw new AuthenticationError('Invalid username or password');
86
+ }
87
+
88
+ // Generate session token
89
+ const sessionToken = crypto.randomBytes(32).toString('base64url');
90
+ this._sessions.set(sessionToken, {
91
+ username: username,
92
+ createdAt: Date.now()
93
+ });
94
+
95
+ return sessionToken;
96
+ }
97
+
98
+ /**
99
+ * Validate a session token and return the associated username.
100
+ *
101
+ * @param {string} sessionToken - The session token to validate
102
+ * @returns {string|null} Username if session is valid, null otherwise
103
+ */
104
+ validateSession(sessionToken) {
105
+ if (!this._sessions.has(sessionToken)) {
106
+ return null;
107
+ }
108
+
109
+ // In production, you would check session expiration here
110
+ return this._sessions.get(sessionToken).username;
111
+ }
112
+
113
+ /**
114
+ * Invalidate a session token.
115
+ *
116
+ * @param {string} sessionToken - The session token to invalidate
117
+ * @returns {boolean} True if session was invalidated, false if token was not found
118
+ */
119
+ logout(sessionToken) {
120
+ if (this._sessions.has(sessionToken)) {
121
+ this._sessions.delete(sessionToken);
122
+ return true;
123
+ }
124
+ return false;
125
+ }
126
+
127
+ /**
128
+ * Hash a password with the provided salt using PBKDF2.
129
+ *
130
+ * @param {string} password - The password to hash
131
+ * @param {string} salt - The salt to use for hashing
132
+ * @returns {string} The hashed password
133
+ * @private
134
+ */
135
+ _hashPassword(password, salt) {
136
+ return crypto.pbkdf2Sync(
137
+ password,
138
+ salt,
139
+ 100000, // iterations
140
+ 32, // key length
141
+ 'sha256' // digest
142
+ ).toString('hex');
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Helper function to authenticate a user and return a session token.
148
+ *
149
+ * @param {UserAuthenticator} authenticator - The authenticator instance
150
+ * @param {string} username - The user's username
151
+ * @param {string} password - The user's password
152
+ * @returns {[boolean, string]} A tuple containing success status and message/token
153
+ */
154
+ function authenticateAndGetToken(authenticator, username, password) {
155
+ try {
156
+ const token = authenticator.authenticateUser(username, password);
157
+ return [true, token];
158
+ } catch (error) {
159
+ if (error instanceof AuthenticationError) {
160
+ return [false, error.message];
161
+ } else {
162
+ return [false, `Authentication error: ${error.message}`];
163
+ }
164
+ }
165
+ }
166
+
167
+ module.exports = {
168
+ UserAuthenticator,
169
+ AuthenticationError,
170
+ authenticateAndGetToken
171
+ };
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Authentication command handler for the Stigmergy CLI.
3
+ * Provides CLI commands for user registration, login, and session management.
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { UserAuthenticator, authenticateAndGetToken } = require('./auth');
9
+
10
+ /**
11
+ * Get the path to the authentication data file.
12
+ * @returns {string} Path to the auth data file
13
+ */
14
+ function getAuthDataPath() {
15
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
16
+ const configDir = path.join(homeDir, '.stigmergy');
17
+
18
+ // Create config directory if it doesn't exist
19
+ if (!fs.existsSync(configDir)) {
20
+ fs.mkdirSync(configDir, { recursive: true });
21
+ }
22
+
23
+ return path.join(configDir, 'auth.json');
24
+ }
25
+
26
+ /**
27
+ * Load authentication data from file.
28
+ * @param {UserAuthenticator} authenticator - The authenticator instance
29
+ */
30
+ function loadAuthData(authenticator) {
31
+ try {
32
+ const authFile = getAuthDataPath();
33
+ if (fs.existsSync(authFile)) {
34
+ const data = JSON.parse(fs.readFileSync(authFile, 'utf8'));
35
+
36
+ // Load users
37
+ if (data.users) {
38
+ for (const [username, userData] of Object.entries(data.users)) {
39
+ authenticator._users.set(username, userData);
40
+ }
41
+ }
42
+
43
+ // Load sessions
44
+ if (data.sessions) {
45
+ for (const [token, sessionData] of Object.entries(data.sessions)) {
46
+ authenticator._sessions.set(token, sessionData);
47
+ }
48
+ }
49
+ }
50
+ } catch (error) {
51
+ console.warn('[WARN] Could not load authentication data:', error.message);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Save authentication data to file.
57
+ * @param {UserAuthenticator} authenticator - The authenticator instance
58
+ */
59
+ function saveAuthData(authenticator) {
60
+ try {
61
+ const authFile = getAuthDataPath();
62
+
63
+ // Convert Maps to objects for JSON serialization
64
+ const users = {};
65
+ for (const [username, userData] of authenticator._users.entries()) {
66
+ users[username] = userData;
67
+ }
68
+
69
+ const sessions = {};
70
+ for (const [token, sessionData] of authenticator._sessions.entries()) {
71
+ sessions[token] = sessionData;
72
+ }
73
+
74
+ const data = {
75
+ users,
76
+ sessions
77
+ };
78
+
79
+ fs.writeFileSync(authFile, JSON.stringify(data, null, 2));
80
+ } catch (error) {
81
+ console.warn('[WARN] Could not save authentication data:', error.message);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Handle user registration.
87
+ * @param {string} username - The username to register
88
+ * @param {string} password - The password for the user
89
+ */
90
+ function handleRegister(username, password) {
91
+ const authenticator = new UserAuthenticator();
92
+ loadAuthData(authenticator);
93
+
94
+ try {
95
+ const success = authenticator.registerUser(username, password);
96
+ if (success) {
97
+ saveAuthData(authenticator);
98
+ console.log(`[SUCCESS] User '${username}' registered successfully`);
99
+ } else {
100
+ console.log(`[ERROR] Username '${username}' already exists`);
101
+ process.exit(1);
102
+ }
103
+ } catch (error) {
104
+ console.log(`[ERROR] Registration failed: ${error.message}`);
105
+ process.exit(1);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Handle user login.
111
+ * @param {string} username - The username to login
112
+ * @param {string} password - The password for the user
113
+ */
114
+ function handleLogin(username, password) {
115
+ const authenticator = new UserAuthenticator();
116
+ loadAuthData(authenticator);
117
+
118
+ const [success, result] = authenticateAndGetToken(authenticator, username, password);
119
+
120
+ if (success) {
121
+ const token = result;
122
+ saveAuthData(authenticator);
123
+
124
+ // Also save the token to a session file for easy access
125
+ const sessionFile = path.join(path.dirname(getAuthDataPath()), 'session.token');
126
+ fs.writeFileSync(sessionFile, token);
127
+
128
+ console.log(`[SUCCESS] Login successful`);
129
+ console.log(`Session token: ${token}`);
130
+ } else {
131
+ console.log(`[ERROR] Login failed: ${result}`);
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Handle user logout.
138
+ */
139
+ function handleLogout() {
140
+ const authenticator = new UserAuthenticator();
141
+ loadAuthData(authenticator);
142
+
143
+ // Read the current session token
144
+ const sessionFile = path.join(path.dirname(getAuthDataPath()), 'session.token');
145
+
146
+ if (!fs.existsSync(sessionFile)) {
147
+ console.log(`[ERROR] No active session found`);
148
+ process.exit(1);
149
+ }
150
+
151
+ const token = fs.readFileSync(sessionFile, 'utf8').trim();
152
+
153
+ const success = authenticator.logout(token);
154
+ if (success) {
155
+ saveAuthData(authenticator);
156
+ fs.unlinkSync(sessionFile); // Remove the session file
157
+ console.log(`[SUCCESS] Logged out successfully`);
158
+ } else {
159
+ console.log(`[ERROR] Logout failed: Invalid session`);
160
+ process.exit(1);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Check if user is authenticated.
166
+ */
167
+ function handleStatus() {
168
+ const authenticator = new UserAuthenticator();
169
+ loadAuthData(authenticator);
170
+
171
+ // Read the current session token
172
+ const sessionFile = path.join(path.dirname(getAuthDataPath()), 'session.token');
173
+
174
+ if (!fs.existsSync(sessionFile)) {
175
+ console.log(`[INFO] No active session`);
176
+ return;
177
+ }
178
+
179
+ const token = fs.readFileSync(sessionFile, 'utf8').trim();
180
+ const username = authenticator.validateSession(token);
181
+
182
+ if (username) {
183
+ console.log(`[INFO] Authenticated as: ${username}`);
184
+ } else {
185
+ console.log(`[INFO] Session expired or invalid`);
186
+ fs.unlinkSync(sessionFile); // Remove the invalid session file
187
+ }
188
+ }
189
+
190
+ module.exports = {
191
+ handleRegister,
192
+ handleLogin,
193
+ handleLogout,
194
+ handleStatus
195
+ };