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.
Files changed (48) hide show
  1. package/README.md +40 -6
  2. package/STIGMERGY.md +10 -0
  3. package/package.json +19 -5
  4. package/scripts/preuninstall.js +10 -0
  5. package/src/adapters/claude/install_claude_integration.js +21 -21
  6. package/src/adapters/codebuddy/install_codebuddy_integration.js +54 -51
  7. package/src/adapters/codex/install_codex_integration.js +27 -28
  8. package/src/adapters/gemini/install_gemini_integration.js +60 -60
  9. package/src/adapters/iflow/install_iflow_integration.js +72 -72
  10. package/src/adapters/qoder/install_qoder_integration.js +64 -64
  11. package/src/adapters/qwen/install_qwen_integration.js +7 -7
  12. package/src/cli/router.js +581 -175
  13. package/src/commands/skill-bridge.js +39 -0
  14. package/src/commands/skill-handler.js +150 -0
  15. package/src/commands/skill.js +127 -0
  16. package/src/core/cli_path_detector.js +710 -0
  17. package/src/core/cli_tools.js +72 -1
  18. package/src/core/coordination/nodejs/AdapterManager.js +29 -1
  19. package/src/core/directory_permission_manager.js +568 -0
  20. package/src/core/enhanced_cli_installer.js +609 -0
  21. package/src/core/installer.js +232 -88
  22. package/src/core/multilingual/language-pattern-manager.js +78 -50
  23. package/src/core/persistent_shell_configurator.js +468 -0
  24. package/src/core/skills/StigmergySkillManager.js +357 -0
  25. package/src/core/skills/__tests__/SkillInstaller.test.js +275 -0
  26. package/src/core/skills/__tests__/SkillParser.test.js +202 -0
  27. package/src/core/skills/__tests__/SkillReader.test.js +189 -0
  28. package/src/core/skills/cli-command-test.js +201 -0
  29. package/src/core/skills/comprehensive-e2e-test.js +473 -0
  30. package/src/core/skills/e2e-test.js +267 -0
  31. package/src/core/skills/embedded-openskills/SkillInstaller.js +438 -0
  32. package/src/core/skills/embedded-openskills/SkillParser.js +123 -0
  33. package/src/core/skills/embedded-openskills/SkillReader.js +143 -0
  34. package/src/core/skills/integration-test.js +248 -0
  35. package/src/core/skills/package.json +6 -0
  36. package/src/core/skills/regression-test.js +285 -0
  37. package/src/core/skills/run-all-tests.js +129 -0
  38. package/src/core/skills/sync-test.js +210 -0
  39. package/src/core/skills/test-runner.js +242 -0
  40. package/src/utils/helpers.js +3 -20
  41. package/src/auth.js +0 -173
  42. package/src/auth_command.js +0 -208
  43. package/src/calculator.js +0 -313
  44. package/src/core/enhanced_installer.js +0 -479
  45. package/src/core/enhanced_uninstaller.js +0 -638
  46. package/src/data_encryption.js +0 -143
  47. package/src/data_structures.js +0 -440
  48. 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
+ });
@@ -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
- try {
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
- };
@@ -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
- };