stigmergy 1.2.8 → 1.2.11
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 +40 -6
- package/STIGMERGY.md +10 -0
- package/package.json +19 -5
- package/scripts/preuninstall.js +10 -0
- package/src/adapters/claude/install_claude_integration.js +21 -21
- package/src/adapters/codebuddy/install_codebuddy_integration.js +54 -51
- package/src/adapters/codex/install_codex_integration.js +27 -28
- package/src/adapters/gemini/install_gemini_integration.js +60 -60
- package/src/adapters/iflow/install_iflow_integration.js +72 -72
- package/src/adapters/qoder/install_qoder_integration.js +64 -64
- package/src/adapters/qwen/install_qwen_integration.js +7 -7
- package/src/cli/router.js +581 -175
- package/src/commands/skill-bridge.js +39 -0
- package/src/commands/skill-handler.js +150 -0
- package/src/commands/skill.js +127 -0
- package/src/core/cli_path_detector.js +710 -0
- package/src/core/cli_tools.js +72 -1
- package/src/core/coordination/nodejs/AdapterManager.js +29 -1
- package/src/core/directory_permission_manager.js +568 -0
- package/src/core/enhanced_cli_installer.js +609 -0
- package/src/core/installer.js +232 -88
- package/src/core/multilingual/language-pattern-manager.js +78 -50
- package/src/core/persistent_shell_configurator.js +468 -0
- package/src/core/skills/StigmergySkillManager.js +357 -0
- package/src/core/skills/__tests__/SkillInstaller.test.js +275 -0
- package/src/core/skills/__tests__/SkillParser.test.js +202 -0
- package/src/core/skills/__tests__/SkillReader.test.js +189 -0
- package/src/core/skills/cli-command-test.js +201 -0
- package/src/core/skills/comprehensive-e2e-test.js +473 -0
- package/src/core/skills/e2e-test.js +267 -0
- package/src/core/skills/embedded-openskills/SkillInstaller.js +438 -0
- package/src/core/skills/embedded-openskills/SkillParser.js +123 -0
- package/src/core/skills/embedded-openskills/SkillReader.js +143 -0
- package/src/core/skills/integration-test.js +248 -0
- package/src/core/skills/package.json +6 -0
- package/src/core/skills/regression-test.js +285 -0
- package/src/core/skills/run-all-tests.js +129 -0
- package/src/core/skills/sync-test.js +210 -0
- package/src/core/skills/test-runner.js +242 -0
- package/src/utils/helpers.js +3 -20
- package/src/auth.js +0 -173
- package/src/auth_command.js +0 -208
- package/src/calculator.js +0 -313
- package/src/core/enhanced_installer.js +0 -479
- package/src/core/enhanced_uninstaller.js +0 -638
- package/src/data_encryption.js +0 -143
- package/src/data_structures.js +0 -440
- package/src/deploy.js +0 -55
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Test Runner - TDD Verification
|
|
3
|
+
* No dependency on Jest, runs tests directly
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SkillParser } from './embedded-openskills/SkillParser.js';
|
|
7
|
+
import { SkillReader } from './embedded-openskills/SkillReader.js';
|
|
8
|
+
import { SkillInstaller } from './embedded-openskills/SkillInstaller.js';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import assert from 'assert';
|
|
13
|
+
|
|
14
|
+
class TestRunner {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.passed = 0;
|
|
17
|
+
this.failed = 0;
|
|
18
|
+
this.tests = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async test(name, fn) {
|
|
22
|
+
try {
|
|
23
|
+
await fn();
|
|
24
|
+
this.passed++;
|
|
25
|
+
console.log(`[OK] ${name}`);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
this.failed++;
|
|
28
|
+
console.error(`[X] ${name}`);
|
|
29
|
+
console.error(` Error: ${err.message}`);
|
|
30
|
+
if (err.stack) {
|
|
31
|
+
console.error(` ${err.stack.split('\n')[1]}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
summary() {
|
|
37
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
38
|
+
console.log(`Total: ${this.passed + this.failed} tests`);
|
|
39
|
+
console.log(`[OK] Passed: ${this.passed}`);
|
|
40
|
+
console.log(`[X] Failed: ${this.failed}`);
|
|
41
|
+
console.log('='.repeat(60));
|
|
42
|
+
return this.failed === 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runTests() {
|
|
47
|
+
const runner = new TestRunner();
|
|
48
|
+
let tempDir;
|
|
49
|
+
|
|
50
|
+
console.log('[LIST] Running TDD tests...\n');
|
|
51
|
+
|
|
52
|
+
// ===== SkillParser Tests =====
|
|
53
|
+
console.log('[LIST] SkillParser Tests\n');
|
|
54
|
+
|
|
55
|
+
await runner.test('parseMetadata - Parse valid YAML', () => {
|
|
56
|
+
const parser = new SkillParser();
|
|
57
|
+
const content = `---
|
|
58
|
+
name: test-skill
|
|
59
|
+
description: A test skill
|
|
60
|
+
version: 1.0.0
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
# Content`;
|
|
64
|
+
const result = parser.parseMetadata(content);
|
|
65
|
+
assert.strictEqual(result.name, 'test-skill');
|
|
66
|
+
assert.strictEqual(result.description, 'A test skill');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await runner.test('parseMetadata - Handle arrays', () => {
|
|
70
|
+
const parser = new SkillParser();
|
|
71
|
+
const content = `---
|
|
72
|
+
name: skill
|
|
73
|
+
allowed-tools:
|
|
74
|
+
- bash
|
|
75
|
+
- text_editor
|
|
76
|
+
---`;
|
|
77
|
+
const result = parser.parseMetadata(content);
|
|
78
|
+
assert.deepStrictEqual(result['allowed-tools'], ['bash', 'text_editor']);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await runner.test('extractContent - Extract body content', () => {
|
|
82
|
+
const parser = new SkillParser();
|
|
83
|
+
const content = `---
|
|
84
|
+
name: test
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
# Instructions`;
|
|
88
|
+
const result = parser.extractContent(content);
|
|
89
|
+
assert(result.includes('# Instructions'));
|
|
90
|
+
assert(!result.includes('---'));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await runner.test('validateSkill - Detect missing name', () => {
|
|
94
|
+
const parser = new SkillParser();
|
|
95
|
+
const content = `---
|
|
96
|
+
description: No name
|
|
97
|
+
---`;
|
|
98
|
+
const result = parser.validateSkill(content);
|
|
99
|
+
assert.strictEqual(result.valid, false);
|
|
100
|
+
assert(result.errors.some(e => e.includes('name')));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await runner.test('validateSkill - Detect invalid name format', () => {
|
|
104
|
+
const parser = new SkillParser();
|
|
105
|
+
const content = `---
|
|
106
|
+
name: Invalid_Name
|
|
107
|
+
description: Test
|
|
108
|
+
---`;
|
|
109
|
+
const result = parser.validateSkill(content);
|
|
110
|
+
assert.strictEqual(result.valid, false);
|
|
111
|
+
assert(result.errors.some(e => e.includes('lowercase and hyphens')));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ===== SkillReader Tests =====
|
|
115
|
+
console.log('\n[LIST] SkillReader Tests\n');
|
|
116
|
+
|
|
117
|
+
// Setup test environment
|
|
118
|
+
tempDir = path.join(os.tmpdir(), `test-skills-${Date.now()}`);
|
|
119
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
await runner.test('findSkill - Find existing skill', async () => {
|
|
122
|
+
const skillName = 'test-skill';
|
|
123
|
+
const skillDir = path.join(tempDir, skillName);
|
|
124
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
125
|
+
await fs.writeFile(
|
|
126
|
+
path.join(skillDir, 'SKILL.md'),
|
|
127
|
+
'---\nname: test-skill\n---\n# Test'
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const reader = new SkillReader([tempDir]);
|
|
131
|
+
const result = await reader.findSkill(skillName);
|
|
132
|
+
|
|
133
|
+
assert.notStrictEqual(result, null);
|
|
134
|
+
assert.strictEqual(result.name, skillName);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await runner.test('findSkill - Non-existent skill returns null', async () => {
|
|
138
|
+
const reader = new SkillReader([tempDir]);
|
|
139
|
+
const result = await reader.findSkill('non-existent');
|
|
140
|
+
assert.strictEqual(result, null);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await runner.test('readSkill - Read skill content', async () => {
|
|
144
|
+
const skillName = 'readable-skill';
|
|
145
|
+
const skillDir = path.join(tempDir, skillName);
|
|
146
|
+
await fs.mkdir(skillDir);
|
|
147
|
+
const content = '---\nname: readable-skill\n---\n# Content';
|
|
148
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), content);
|
|
149
|
+
|
|
150
|
+
const reader = new SkillReader([tempDir]);
|
|
151
|
+
const result = await reader.readSkill(skillName);
|
|
152
|
+
|
|
153
|
+
assert.strictEqual(result.name, skillName);
|
|
154
|
+
assert.strictEqual(result.content, content);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await runner.test('listSkills - List all skills', async () => {
|
|
158
|
+
const skills = ['skill-1', 'skill-2'];
|
|
159
|
+
for (const name of skills) {
|
|
160
|
+
const dir = path.join(tempDir, name);
|
|
161
|
+
await fs.mkdir(dir);
|
|
162
|
+
await fs.writeFile(
|
|
163
|
+
path.join(dir, 'SKILL.md'),
|
|
164
|
+
`---\nname: ${name}\ndescription: ${name}\n---\n`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const reader = new SkillReader([tempDir]);
|
|
169
|
+
const result = await reader.listSkills();
|
|
170
|
+
|
|
171
|
+
assert(result.length >= skills.length);
|
|
172
|
+
const names = result.map(s => s.name);
|
|
173
|
+
skills.forEach(name => assert(names.includes(name)));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ===== SkillInstaller Tests =====
|
|
177
|
+
console.log('\n[LIST] SkillInstaller Tests\n');
|
|
178
|
+
|
|
179
|
+
await runner.test('parseGitHubUrl - Parse standard URL', () => {
|
|
180
|
+
const installer = new SkillInstaller();
|
|
181
|
+
const result = installer.parseGitHubUrl('https://github.com/owner/repo');
|
|
182
|
+
assert.strictEqual(result.owner, 'owner');
|
|
183
|
+
assert.strictEqual(result.repo, 'repo');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await runner.test('parseGitHubUrl - Parse shorthand format', () => {
|
|
187
|
+
const installer = new SkillInstaller();
|
|
188
|
+
const result = installer.parseGitHubUrl('owner/repo');
|
|
189
|
+
assert.strictEqual(result.owner, 'owner');
|
|
190
|
+
assert.strictEqual(result.repo, 'repo');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await runner.test('parseGitHubUrl - Remove .git suffix', () => {
|
|
194
|
+
const installer = new SkillInstaller();
|
|
195
|
+
const result = installer.parseGitHubUrl('owner/repo.git');
|
|
196
|
+
assert.strictEqual(result.repo, 'repo');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await runner.test('scanSkills - Scan skill directory', async () => {
|
|
200
|
+
const scanDir = path.join(tempDir, 'scan-test');
|
|
201
|
+
await fs.mkdir(scanDir);
|
|
202
|
+
|
|
203
|
+
const skill1 = path.join(scanDir, 'skill-a');
|
|
204
|
+
await fs.mkdir(skill1);
|
|
205
|
+
await fs.writeFile(
|
|
206
|
+
path.join(skill1, 'SKILL.md'),
|
|
207
|
+
'---\nname: skill-a\n---\n'
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const installer = new SkillInstaller();
|
|
211
|
+
const skills = await installer.scanSkills(scanDir);
|
|
212
|
+
|
|
213
|
+
assert(skills.length >= 1);
|
|
214
|
+
assert(skills.some(s => s.name === 'skill-a'));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await runner.test('calculateSize - Calculate directory size', async () => {
|
|
218
|
+
const sizeDir = path.join(tempDir, 'size-test');
|
|
219
|
+
await fs.mkdir(sizeDir);
|
|
220
|
+
await fs.writeFile(path.join(sizeDir, 'file.txt'), 'x'.repeat(100));
|
|
221
|
+
|
|
222
|
+
const installer = new SkillInstaller();
|
|
223
|
+
const size = await installer.calculateSize(sizeDir);
|
|
224
|
+
|
|
225
|
+
assert.strictEqual(size, 100);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Cleanup
|
|
229
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
230
|
+
|
|
231
|
+
return runner.summary();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Run tests
|
|
235
|
+
runTests()
|
|
236
|
+
.then(success => {
|
|
237
|
+
process.exit(success ? 0 : 1);
|
|
238
|
+
})
|
|
239
|
+
.catch(err => {
|
|
240
|
+
console.error('[X] Test execution failed:', err);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
});
|
package/src/utils/helpers.js
CHANGED
|
@@ -1,32 +1,15 @@
|
|
|
1
1
|
const fs = require('fs/promises');
|
|
2
2
|
const fsSync = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const { UserAuthenticator } = require('../auth');
|
|
5
4
|
|
|
6
5
|
function maxOfTwo(a, b) {
|
|
7
6
|
return a > b ? a : b;
|
|
8
7
|
}
|
|
9
8
|
|
|
9
|
+
// Placeholder function - always returns true since no user authentication is needed
|
|
10
|
+
// for a multi-AI CLI collaboration system
|
|
10
11
|
function isAuthenticated() {
|
|
11
|
-
|
|
12
|
-
const authenticator = new UserAuthenticator();
|
|
13
|
-
// Load authentication data
|
|
14
|
-
const authFile = path.join(
|
|
15
|
-
process.env.HOME || process.env.USERPROFILE,
|
|
16
|
-
'.stigmergy',
|
|
17
|
-
'auth.json',
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
if (!fsSync.existsSync(authFile)) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const authData = JSON.parse(fsSync.readFileSync(authFile, 'utf8'));
|
|
25
|
-
return authenticator.validateToken(authData.token);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
console.log(`[AUTH] Authentication check failed: ${error.message}`);
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
12
|
+
return true;
|
|
30
13
|
}
|
|
31
14
|
|
|
32
15
|
module.exports = {
|
package/src/auth.js
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
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
|
|
137
|
-
.pbkdf2Sync(
|
|
138
|
-
password,
|
|
139
|
-
salt,
|
|
140
|
-
100000, // iterations
|
|
141
|
-
32, // key length
|
|
142
|
-
'sha256', // digest
|
|
143
|
-
)
|
|
144
|
-
.toString('hex');
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Helper function to authenticate a user and return a session token.
|
|
150
|
-
*
|
|
151
|
-
* @param {UserAuthenticator} authenticator - The authenticator instance
|
|
152
|
-
* @param {string} username - The user's username
|
|
153
|
-
* @param {string} password - The user's password
|
|
154
|
-
* @returns {[boolean, string]} A tuple containing success status and message/token
|
|
155
|
-
*/
|
|
156
|
-
function authenticateAndGetToken(authenticator, username, password) {
|
|
157
|
-
try {
|
|
158
|
-
const token = authenticator.authenticateUser(username, password);
|
|
159
|
-
return [true, token];
|
|
160
|
-
} catch (error) {
|
|
161
|
-
if (error instanceof AuthenticationError) {
|
|
162
|
-
return [false, error.message];
|
|
163
|
-
} else {
|
|
164
|
-
return [false, `Authentication error: ${error.message}`];
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
module.exports = {
|
|
170
|
-
UserAuthenticator,
|
|
171
|
-
AuthenticationError,
|
|
172
|
-
authenticateAndGetToken,
|
|
173
|
-
};
|
package/src/auth_command.js
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
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(
|
|
119
|
-
authenticator,
|
|
120
|
-
username,
|
|
121
|
-
password,
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
if (success) {
|
|
125
|
-
const token = result;
|
|
126
|
-
saveAuthData(authenticator);
|
|
127
|
-
|
|
128
|
-
// Also save the token to a session file for easy access
|
|
129
|
-
const sessionFile = path.join(
|
|
130
|
-
path.dirname(getAuthDataPath()),
|
|
131
|
-
'session.token',
|
|
132
|
-
);
|
|
133
|
-
fs.writeFileSync(sessionFile, token);
|
|
134
|
-
|
|
135
|
-
console.log('[SUCCESS] Login successful');
|
|
136
|
-
console.log(`Session token: ${token}`);
|
|
137
|
-
} else {
|
|
138
|
-
console.log(`[ERROR] Login failed: ${result}`);
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Handle user logout.
|
|
145
|
-
*/
|
|
146
|
-
function handleLogout() {
|
|
147
|
-
const authenticator = new UserAuthenticator();
|
|
148
|
-
loadAuthData(authenticator);
|
|
149
|
-
|
|
150
|
-
// Read the current session token
|
|
151
|
-
const sessionFile = path.join(
|
|
152
|
-
path.dirname(getAuthDataPath()),
|
|
153
|
-
'session.token',
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
if (!fs.existsSync(sessionFile)) {
|
|
157
|
-
console.log('[ERROR] No active session found');
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const token = fs.readFileSync(sessionFile, 'utf8').trim();
|
|
162
|
-
|
|
163
|
-
const success = authenticator.logout(token);
|
|
164
|
-
if (success) {
|
|
165
|
-
saveAuthData(authenticator);
|
|
166
|
-
fs.unlinkSync(sessionFile); // Remove the session file
|
|
167
|
-
console.log('[SUCCESS] Logged out successfully');
|
|
168
|
-
} else {
|
|
169
|
-
console.log('[ERROR] Logout failed: Invalid session');
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Check if user is authenticated.
|
|
176
|
-
*/
|
|
177
|
-
function handleStatus() {
|
|
178
|
-
const authenticator = new UserAuthenticator();
|
|
179
|
-
loadAuthData(authenticator);
|
|
180
|
-
|
|
181
|
-
// Read the current session token
|
|
182
|
-
const sessionFile = path.join(
|
|
183
|
-
path.dirname(getAuthDataPath()),
|
|
184
|
-
'session.token',
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
if (!fs.existsSync(sessionFile)) {
|
|
188
|
-
console.log('[INFO] No active session');
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const token = fs.readFileSync(sessionFile, 'utf8').trim();
|
|
193
|
-
const username = authenticator.validateSession(token);
|
|
194
|
-
|
|
195
|
-
if (username) {
|
|
196
|
-
console.log(`[INFO] Authenticated as: ${username}`);
|
|
197
|
-
} else {
|
|
198
|
-
console.log('[INFO] Session expired or invalid');
|
|
199
|
-
fs.unlinkSync(sessionFile); // Remove the invalid session file
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
module.exports = {
|
|
204
|
-
handleRegister,
|
|
205
|
-
handleLogin,
|
|
206
|
-
handleLogout,
|
|
207
|
-
handleStatus,
|
|
208
|
-
};
|