supermind-claude 2.0.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/.env.example +5 -0
- package/README.md +77 -0
- package/airis/docker-compose.yml +76 -0
- package/airis/mcp-config.json +43 -0
- package/cli/commands/approve.js +72 -0
- package/cli/commands/doctor.js +101 -0
- package/cli/commands/install.js +85 -0
- package/cli/commands/uninstall.js +75 -0
- package/cli/commands/update.js +52 -0
- package/cli/index.js +69 -0
- package/cli/lib/hooks.js +66 -0
- package/cli/lib/logger.js +38 -0
- package/cli/lib/mcp.js +112 -0
- package/cli/lib/platform.js +31 -0
- package/cli/lib/plugins.js +19 -0
- package/cli/lib/settings.js +158 -0
- package/cli/lib/skills.js +70 -0
- package/cli/lib/templates.js +24 -0
- package/hooks/bash-permissions.js +430 -0
- package/hooks/cost-tracker.js +26 -0
- package/hooks/session-end.js +82 -0
- package/hooks/session-start.js +162 -0
- package/hooks/statusline-command.js +234 -0
- package/package.json +26 -0
- package/skills/supermind/SKILL.md +13 -0
- package/skills/supermind-init/SKILL.md +174 -0
- package/skills/supermind-init/architecture-template.md +48 -0
- package/skills/supermind-init/design-template.md +43 -0
- package/skills/supermind-living-docs/SKILL.md +55 -0
- package/templates/CLAUDE.md +100 -0
package/cli/lib/hooks.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { PATHS, ensureDir, getPackageRoot } = require('./platform');
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
|
|
8
|
+
function getHookFiles() {
|
|
9
|
+
const hooksSource = path.join(getPackageRoot(), 'hooks');
|
|
10
|
+
return fs.readdirSync(hooksSource).filter(f => f.endsWith('.js'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function installHooks() {
|
|
14
|
+
ensureDir(PATHS.hooksDir);
|
|
15
|
+
const hooksSource = path.join(getPackageRoot(), 'hooks');
|
|
16
|
+
const files = getHookFiles();
|
|
17
|
+
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
fs.copyFileSync(path.join(hooksSource, file), path.join(PATHS.hooksDir, file));
|
|
20
|
+
logger.success(file);
|
|
21
|
+
}
|
|
22
|
+
return files;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getHookSettings() {
|
|
26
|
+
const hooksDir = PATHS.hooksDir;
|
|
27
|
+
return {
|
|
28
|
+
hooks: {
|
|
29
|
+
PreToolUse: [{
|
|
30
|
+
matcher: 'Bash',
|
|
31
|
+
hooks: [{ type: 'command', command: `node "${path.join(hooksDir, 'bash-permissions.js')}"`, timeout: 5 }],
|
|
32
|
+
}],
|
|
33
|
+
SessionStart: [{
|
|
34
|
+
hooks: [{ type: 'command', command: `node "${path.join(hooksDir, 'session-start.js')}"`, statusMessage: 'Loading session context...' }],
|
|
35
|
+
}],
|
|
36
|
+
Stop: [{
|
|
37
|
+
hooks: [
|
|
38
|
+
{ type: 'command', command: `node "${path.join(hooksDir, 'session-end.js')}"`, async: true },
|
|
39
|
+
{ type: 'command', command: `node "${path.join(hooksDir, 'cost-tracker.js')}"`, async: true },
|
|
40
|
+
],
|
|
41
|
+
}],
|
|
42
|
+
},
|
|
43
|
+
statusLine: {
|
|
44
|
+
type: 'command',
|
|
45
|
+
command: `node "${path.join(hooksDir, 'statusline-command.js')}"`,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fallback list if package source is unavailable
|
|
51
|
+
const KNOWN_HOOKS = ['bash-permissions.js', 'session-start.js', 'session-end.js', 'cost-tracker.js', 'statusline-command.js'];
|
|
52
|
+
|
|
53
|
+
function removeHooks() {
|
|
54
|
+
if (!fs.existsSync(PATHS.hooksDir)) return;
|
|
55
|
+
let files;
|
|
56
|
+
try { files = getHookFiles(); } catch { files = KNOWN_HOOKS; }
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const target = path.join(PATHS.hooksDir, file);
|
|
59
|
+
if (fs.existsSync(target)) {
|
|
60
|
+
fs.unlinkSync(target);
|
|
61
|
+
logger.success(`Removed ${file}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { installHooks, getHookSettings, removeHooks, getHookFiles };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { version } = require('../../package.json');
|
|
4
|
+
|
|
5
|
+
const GREEN = '\x1b[32m';
|
|
6
|
+
const YELLOW = '\x1b[33m';
|
|
7
|
+
const RED = '\x1b[31m';
|
|
8
|
+
const DIM = '\x1b[2m';
|
|
9
|
+
const BOLD = '\x1b[1m';
|
|
10
|
+
const CYAN = '\x1b[36m';
|
|
11
|
+
const R = '\x1b[0m';
|
|
12
|
+
|
|
13
|
+
function banner() {
|
|
14
|
+
console.log(`\n${CYAN}${BOLD} \u26a1 Supermind${R} ${DIM}v${version}${R}`);
|
|
15
|
+
console.log(`${DIM} Complete Claude Code setup${R}\n`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function step(n, total, message) {
|
|
19
|
+
console.log(`${DIM}[${n}/${total}]${R} ${message}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function success(message) {
|
|
23
|
+
console.log(` ${GREEN}\u2713${R} ${message}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function warn(message) {
|
|
27
|
+
console.log(` ${YELLOW}\u26a0${R} ${message}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function error(message) {
|
|
31
|
+
console.log(` ${RED}\u2717${R} ${message}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function info(message) {
|
|
35
|
+
console.log(` ${DIM}${message}${R}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { banner, step, success, warn, error, info };
|
package/cli/lib/mcp.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const { PATHS, ensureDir, getPackageRoot } = require('./platform');
|
|
8
|
+
const logger = require('./logger');
|
|
9
|
+
|
|
10
|
+
function prompt(question) {
|
|
11
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer.trim()); }));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isDockerAvailable() {
|
|
16
|
+
try {
|
|
17
|
+
execSync('docker compose version', { stdio: 'pipe', timeout: 5000 });
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getDirectMcpConfig() {
|
|
25
|
+
const configPath = path.join(getPackageRoot(), 'airis', 'mcp-config.json');
|
|
26
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function setupDocker() {
|
|
30
|
+
const airisSource = path.join(getPackageRoot(), 'airis');
|
|
31
|
+
ensureDir(PATHS.airisDir);
|
|
32
|
+
for (const file of ['docker-compose.yml', 'mcp-config.json']) {
|
|
33
|
+
const src = path.join(airisSource, file);
|
|
34
|
+
if (fs.existsSync(src)) {
|
|
35
|
+
fs.copyFileSync(src, path.join(PATHS.airisDir, file));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
logger.success('AIRIS config copied');
|
|
39
|
+
|
|
40
|
+
if (isDockerAvailable()) {
|
|
41
|
+
logger.info('Starting AIRIS gateway...');
|
|
42
|
+
try {
|
|
43
|
+
execSync('docker compose up -d', { cwd: PATHS.airisDir, stdio: 'pipe', timeout: 60000 });
|
|
44
|
+
logger.success('AIRIS gateway started');
|
|
45
|
+
} catch {
|
|
46
|
+
logger.warn('Could not start AIRIS gateway — start manually with: docker compose up -d');
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
logger.warn('Docker not found — install Docker and run: docker compose up -d');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function promptApiKeys(flags) {
|
|
54
|
+
const keys = {};
|
|
55
|
+
if (flags.nonInteractive) return keys;
|
|
56
|
+
|
|
57
|
+
const tavily = await prompt(' Tavily API key (press Enter to skip): ');
|
|
58
|
+
if (tavily) keys.TAVILY_API_KEY = tavily;
|
|
59
|
+
|
|
60
|
+
return keys;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function setupDirect(apiKeys) {
|
|
64
|
+
const config = getDirectMcpConfig();
|
|
65
|
+
const servers = config.mcpServers || {};
|
|
66
|
+
|
|
67
|
+
// Inject API keys into server configs that need them
|
|
68
|
+
if (apiKeys.TAVILY_API_KEY && servers.tavily) {
|
|
69
|
+
servers.tavily.env = { TAVILY_API_KEY: apiKeys.TAVILY_API_KEY };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Remove servers that require missing API keys
|
|
73
|
+
if (!apiKeys.TAVILY_API_KEY && servers.tavily?.env?.TAVILY_API_KEY?.startsWith('$')) {
|
|
74
|
+
logger.warn('Skipping Tavily (no API key)');
|
|
75
|
+
delete servers.tavily;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return servers;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function setupMcp(flags) {
|
|
82
|
+
// Determine mode from flags or prompt
|
|
83
|
+
let mode = flags.mcp;
|
|
84
|
+
if (!mode && !flags.nonInteractive) {
|
|
85
|
+
console.log('\n MCP Server Setup:');
|
|
86
|
+
console.log(' 1) Docker (AIRIS gateway — single endpoint, recommended)');
|
|
87
|
+
console.log(' 2) Direct (individual servers via npx/uvx)');
|
|
88
|
+
console.log(' 3) Skip\n');
|
|
89
|
+
const answer = await prompt(' Choose [1/2/3]: ');
|
|
90
|
+
mode = { '1': 'docker', '2': 'direct', '3': 'skip' }[answer] || 'skip';
|
|
91
|
+
}
|
|
92
|
+
if (!mode || mode === 'skip') {
|
|
93
|
+
logger.info('Skipping MCP setup');
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (mode === 'docker') {
|
|
98
|
+
await setupDocker();
|
|
99
|
+
return {}; // Docker mode uses AIRIS, not settings.json mcpServers
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (mode === 'direct') {
|
|
103
|
+
const apiKeys = await promptApiKeys(flags);
|
|
104
|
+
const servers = setupDirect(apiKeys);
|
|
105
|
+
logger.success(`Configured ${Object.keys(servers).length} MCP servers`);
|
|
106
|
+
return { mcpServers: servers };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { setupMcp };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const home = os.homedir();
|
|
8
|
+
|
|
9
|
+
const PATHS = {
|
|
10
|
+
claudeHome: path.join(home, '.claude'),
|
|
11
|
+
settings: path.join(home, '.claude', 'settings.json'),
|
|
12
|
+
settingsBackup: path.join(home, '.claude', 'settings.json.backup'),
|
|
13
|
+
hooksDir: path.join(home, '.claude', 'hooks'),
|
|
14
|
+
skillsDir: path.join(home, '.claude', 'skills'),
|
|
15
|
+
templatesDir: path.join(home, '.claude', 'templates'),
|
|
16
|
+
sessionsDir: path.join(home, '.claude', 'sessions'),
|
|
17
|
+
versionFile: path.join(home, '.claude', '.supermind-version'),
|
|
18
|
+
legacyHooksJson: path.join(home, '.claude', 'hooks.json'),
|
|
19
|
+
airisDir: path.join(home, '.claude', 'airis'),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function ensureDir(dirPath) {
|
|
23
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Note: assumes platform.js is at cli/lib/platform.js (two levels deep from package root)
|
|
27
|
+
function getPackageRoot() {
|
|
28
|
+
return path.resolve(__dirname, '..', '..');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { PATHS, ensureDir, getPackageRoot };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function getPluginDefaults() {
|
|
4
|
+
return {
|
|
5
|
+
enabledPlugins: {
|
|
6
|
+
'superpowers@claude-plugins-official': true,
|
|
7
|
+
'claude-md-management@claude-plugins-official': true,
|
|
8
|
+
'frontend-design@claude-plugins-official': true,
|
|
9
|
+
'ui-ux-pro-max@ui-ux-pro-max-skill': true,
|
|
10
|
+
},
|
|
11
|
+
extraKnownMarketplaces: {
|
|
12
|
+
'ui-ux-pro-max-skill': {
|
|
13
|
+
source: { source: 'github', repo: 'nextlevelbuilder/ui-ux-pro-max-skill' },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { getPluginDefaults };
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { PATHS } = require('./platform');
|
|
5
|
+
const logger = require('./logger');
|
|
6
|
+
|
|
7
|
+
// Known Supermind hook filenames — used to identify owned entries
|
|
8
|
+
const SUPERMIND_HOOKS = [
|
|
9
|
+
'bash-permissions.js', 'session-start.js', 'session-end.js',
|
|
10
|
+
'cost-tracker.js', 'statusline-command.js',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const SUPERMIND_PLUGINS = [
|
|
14
|
+
'superpowers@claude-plugins-official',
|
|
15
|
+
'claude-md-management@claude-plugins-official',
|
|
16
|
+
'frontend-design@claude-plugins-official',
|
|
17
|
+
'ui-ux-pro-max@ui-ux-pro-max-skill',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function readSettings() {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(fs.readFileSync(PATHS.settings, 'utf-8'));
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function writeSettings(settings) {
|
|
29
|
+
fs.writeFileSync(PATHS.settings, JSON.stringify(settings, null, 2) + '\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function backupSettings() {
|
|
33
|
+
if (fs.existsSync(PATHS.settings) && !fs.existsSync(PATHS.settingsBackup)) {
|
|
34
|
+
fs.copyFileSync(PATHS.settings, PATHS.settingsBackup);
|
|
35
|
+
logger.info('Backed up existing settings.json');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isSupermindHookEntry(entry) {
|
|
40
|
+
const cmd = entry?.command || '';
|
|
41
|
+
return SUPERMIND_HOOKS.some(h => cmd.includes(h));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Merge hook arrays: keep user entries, upsert Supermind entries by filename match
|
|
45
|
+
function mergeHookArray(existing, incoming) {
|
|
46
|
+
const result = (existing || []).filter(e => !isSupermindHookEntry(e));
|
|
47
|
+
for (const entry of incoming) {
|
|
48
|
+
result.push(entry);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Merge objects: add missing keys, preserve existing
|
|
54
|
+
function mergeObjects(existing, incoming) {
|
|
55
|
+
const result = { ...existing };
|
|
56
|
+
for (const [key, val] of Object.entries(incoming)) {
|
|
57
|
+
if (!(key in result)) result[key] = val;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Merge hook event configs: each event has a { matcher?, hooks: [] } structure
|
|
63
|
+
function mergeHookEvents(existing, incoming) {
|
|
64
|
+
const result = { ...existing };
|
|
65
|
+
for (const [event, configs] of Object.entries(incoming)) {
|
|
66
|
+
if (!result[event]) {
|
|
67
|
+
result[event] = configs;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Merge each config entry's hooks array
|
|
71
|
+
const existingConfigs = Array.isArray(result[event]) ? result[event] : [result[event]];
|
|
72
|
+
const incomingConfigs = Array.isArray(configs) ? configs : [configs];
|
|
73
|
+
|
|
74
|
+
for (const inConfig of incomingConfigs) {
|
|
75
|
+
const matcher = inConfig.matcher || null;
|
|
76
|
+
const existingIdx = existingConfigs.findIndex(c => (c.matcher || null) === matcher);
|
|
77
|
+
if (existingIdx >= 0) {
|
|
78
|
+
existingConfigs[existingIdx] = {
|
|
79
|
+
...existingConfigs[existingIdx],
|
|
80
|
+
hooks: mergeHookArray(existingConfigs[existingIdx].hooks, inConfig.hooks),
|
|
81
|
+
};
|
|
82
|
+
} else {
|
|
83
|
+
existingConfigs.push(inConfig);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
result[event] = existingConfigs;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function mergeSettings(existing, defaults) {
|
|
92
|
+
const result = { ...existing };
|
|
93
|
+
|
|
94
|
+
// Scalars: set only if absent
|
|
95
|
+
for (const key of ['alwaysThinkingEnabled', 'effortLevel', 'voiceEnabled', 'skipDangerousModePermissionPrompt']) {
|
|
96
|
+
if (!(key in result) && key in defaults) result[key] = defaults[key];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Supermind-owned (always overwrite)
|
|
100
|
+
if (defaults.statusLine) result.statusLine = defaults.statusLine;
|
|
101
|
+
|
|
102
|
+
// Objects: recursive merge
|
|
103
|
+
if (defaults.permissions) result.permissions = mergeObjects(result.permissions || {}, defaults.permissions);
|
|
104
|
+
if (defaults.enabledPlugins) result.enabledPlugins = mergeObjects(result.enabledPlugins || {}, defaults.enabledPlugins);
|
|
105
|
+
if (defaults.extraKnownMarketplaces) result.extraKnownMarketplaces = mergeObjects(result.extraKnownMarketplaces || {}, defaults.extraKnownMarketplaces);
|
|
106
|
+
|
|
107
|
+
// Hooks: special merge
|
|
108
|
+
if (defaults.hooks) result.hooks = mergeHookEvents(result.hooks || {}, defaults.hooks);
|
|
109
|
+
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Remove Supermind entries from settings (for uninstall)
|
|
114
|
+
function removeSupermindEntries(settings) {
|
|
115
|
+
const result = { ...settings };
|
|
116
|
+
|
|
117
|
+
// Remove statusLine
|
|
118
|
+
delete result.statusLine;
|
|
119
|
+
|
|
120
|
+
// Remove Supermind plugins
|
|
121
|
+
if (result.enabledPlugins) {
|
|
122
|
+
for (const id of SUPERMIND_PLUGINS) {
|
|
123
|
+
delete result.enabledPlugins[id];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Remove Supermind marketplace entries
|
|
128
|
+
if (result.extraKnownMarketplaces) {
|
|
129
|
+
delete result.extraKnownMarketplaces['ui-ux-pro-max-skill'];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Remove Supermind hooks from each event
|
|
133
|
+
if (result.hooks) {
|
|
134
|
+
for (const [event, configs] of Object.entries(result.hooks)) {
|
|
135
|
+
const configArr = Array.isArray(configs) ? configs : [configs];
|
|
136
|
+
for (const config of configArr) {
|
|
137
|
+
if (config.hooks) {
|
|
138
|
+
config.hooks = config.hooks.filter(h => !isSupermindHookEntry(h));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Clean up empty configs
|
|
142
|
+
result.hooks[event] = configArr.filter(c => c.hooks?.length > 0);
|
|
143
|
+
if (result.hooks[event].length === 0) delete result.hooks[event];
|
|
144
|
+
}
|
|
145
|
+
if (Object.keys(result.hooks).length === 0) delete result.hooks;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Note: do NOT delete scalars like alwaysThinkingEnabled/effortLevel —
|
|
149
|
+
// the user may have set these independently. Supermind only sets them if absent.
|
|
150
|
+
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
readSettings, writeSettings, backupSettings,
|
|
156
|
+
mergeSettings, removeSupermindEntries,
|
|
157
|
+
SUPERMIND_HOOKS, SUPERMIND_PLUGINS,
|
|
158
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { PATHS, ensureDir, getPackageRoot } = require('./platform');
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
|
|
8
|
+
function getSkillDirs() {
|
|
9
|
+
const skillsSource = path.join(getPackageRoot(), 'skills');
|
|
10
|
+
return fs.readdirSync(skillsSource).filter(f =>
|
|
11
|
+
fs.statSync(path.join(skillsSource, f)).isDirectory()
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function copyDirRecursive(src, dest) {
|
|
16
|
+
ensureDir(dest);
|
|
17
|
+
for (const entry of fs.readdirSync(src)) {
|
|
18
|
+
const srcPath = path.join(src, entry);
|
|
19
|
+
const destPath = path.join(dest, entry);
|
|
20
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
21
|
+
copyDirRecursive(srcPath, destPath);
|
|
22
|
+
} else {
|
|
23
|
+
fs.copyFileSync(srcPath, destPath);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function installSkills() {
|
|
29
|
+
ensureDir(PATHS.skillsDir);
|
|
30
|
+
const skillsSource = path.join(getPackageRoot(), 'skills');
|
|
31
|
+
const dirs = getSkillDirs();
|
|
32
|
+
|
|
33
|
+
for (const dir of dirs) {
|
|
34
|
+
copyDirRecursive(path.join(skillsSource, dir), path.join(PATHS.skillsDir, dir));
|
|
35
|
+
logger.success(dir);
|
|
36
|
+
}
|
|
37
|
+
return dirs;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Fallback list if package source is unavailable
|
|
41
|
+
const KNOWN_SKILLS = ['supermind', 'supermind-init', 'supermind-living-docs'];
|
|
42
|
+
|
|
43
|
+
function removeSkills() {
|
|
44
|
+
let dirs;
|
|
45
|
+
try { dirs = getSkillDirs(); } catch { dirs = KNOWN_SKILLS; }
|
|
46
|
+
for (const dir of dirs) {
|
|
47
|
+
const target = path.join(PATHS.skillsDir, dir);
|
|
48
|
+
if (fs.existsSync(target)) {
|
|
49
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
50
|
+
logger.success(`Removed ${dir}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Remove legacy skill paths from previous versions
|
|
56
|
+
function removeLegacySkills() {
|
|
57
|
+
const legacyPaths = [
|
|
58
|
+
path.join(PATHS.skillsDir, 'supermind', 'init'),
|
|
59
|
+
path.join(PATHS.skillsDir, 'supermind', 'living-docs'),
|
|
60
|
+
path.join(PATHS.skillsDir, 'sm'),
|
|
61
|
+
];
|
|
62
|
+
for (const p of legacyPaths) {
|
|
63
|
+
if (fs.existsSync(p)) {
|
|
64
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
65
|
+
logger.info(`Cleaned up legacy path: ${path.basename(p)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { installSkills, removeSkills, removeLegacySkills, getSkillDirs };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { PATHS, ensureDir, getPackageRoot } = require('./platform');
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
|
|
8
|
+
function installTemplates() {
|
|
9
|
+
ensureDir(PATHS.templatesDir);
|
|
10
|
+
const src = path.join(getPackageRoot(), 'templates', 'CLAUDE.md');
|
|
11
|
+
const dest = path.join(PATHS.templatesDir, 'CLAUDE.md');
|
|
12
|
+
fs.copyFileSync(src, dest);
|
|
13
|
+
logger.success('CLAUDE.md template');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function removeTemplates() {
|
|
17
|
+
const dest = path.join(PATHS.templatesDir, 'CLAUDE.md');
|
|
18
|
+
if (fs.existsSync(dest)) {
|
|
19
|
+
fs.unlinkSync(dest);
|
|
20
|
+
logger.success('Removed CLAUDE.md template');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { installTemplates, removeTemplates };
|