zuppaclaude 1.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/LICENSE +21 -0
- package/README.md +487 -0
- package/bin/zuppaclaude.js +147 -0
- package/lib/components/claudehud.js +198 -0
- package/lib/components/claudez.js +205 -0
- package/lib/components/config.js +143 -0
- package/lib/components/index.js +17 -0
- package/lib/components/speckit.js +99 -0
- package/lib/components/superclaude.js +130 -0
- package/lib/index.js +17 -0
- package/lib/installer.js +241 -0
- package/lib/settings.js +243 -0
- package/lib/uninstaller.js +119 -0
- package/lib/utils/index.js +14 -0
- package/lib/utils/logger.js +79 -0
- package/lib/utils/platform.js +217 -0
- package/lib/utils/prompts.js +134 -0
- package/package.json +43 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SuperClaude component installer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { Logger } = require('../utils/logger');
|
|
8
|
+
const { Platform } = require('../utils/platform');
|
|
9
|
+
|
|
10
|
+
const SUPERCLAUDE_REPO = 'https://github.com/SuperClaude-Org/SuperClaude_Framework.git';
|
|
11
|
+
const SUPERCLAUDE_ZIP = 'https://github.com/SuperClaude-Org/SuperClaude_Framework/archive/refs/heads/master.zip';
|
|
12
|
+
|
|
13
|
+
class SuperClaudeInstaller {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.platform = new Platform();
|
|
16
|
+
this.logger = new Logger();
|
|
17
|
+
this.installPath = path.join(this.platform.commandsDir, 'sc');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if SuperClaude is already installed
|
|
22
|
+
*/
|
|
23
|
+
isInstalled() {
|
|
24
|
+
return fs.existsSync(this.installPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Install SuperClaude
|
|
29
|
+
*/
|
|
30
|
+
async install() {
|
|
31
|
+
this.logger.step('Step 2/7: Installing SuperClaude Framework');
|
|
32
|
+
|
|
33
|
+
// Ensure directories exist
|
|
34
|
+
this.platform.ensureDir(this.platform.commandsDir);
|
|
35
|
+
|
|
36
|
+
// Backup existing installation
|
|
37
|
+
if (this.isInstalled()) {
|
|
38
|
+
const backupPath = `${this.installPath}.backup.${Date.now()}`;
|
|
39
|
+
fs.renameSync(this.installPath, backupPath);
|
|
40
|
+
this.logger.info(`Existing installation backed up to: ${backupPath}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Try git clone first
|
|
44
|
+
if (this.platform.commandExists('git')) {
|
|
45
|
+
try {
|
|
46
|
+
this.logger.info('Cloning SuperClaude repository...');
|
|
47
|
+
this.platform.exec(`git clone --depth 1 ${SUPERCLAUDE_REPO} "${this.installPath}"`, { silent: true });
|
|
48
|
+
|
|
49
|
+
// Remove .git folder to save space
|
|
50
|
+
const gitDir = path.join(this.installPath, '.git');
|
|
51
|
+
if (fs.existsSync(gitDir)) {
|
|
52
|
+
fs.rmSync(gitDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.logger.success('SuperClaude installed via git');
|
|
56
|
+
return true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
this.logger.warning('Git clone failed, trying ZIP download...');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fallback to ZIP download
|
|
63
|
+
try {
|
|
64
|
+
const tempZip = path.join(require('os').tmpdir(), 'superclaude.zip');
|
|
65
|
+
const tempDir = path.join(require('os').tmpdir(), 'superclaude-extract');
|
|
66
|
+
|
|
67
|
+
this.logger.info('Downloading SuperClaude ZIP...');
|
|
68
|
+
await this.platform.download(SUPERCLAUDE_ZIP, tempZip);
|
|
69
|
+
|
|
70
|
+
// Extract ZIP
|
|
71
|
+
this.logger.info('Extracting...');
|
|
72
|
+
if (this.platform.isWindows) {
|
|
73
|
+
this.platform.exec(`powershell -Command "Expand-Archive -Path '${tempZip}' -DestinationPath '${tempDir}' -Force"`, { silent: true });
|
|
74
|
+
} else {
|
|
75
|
+
this.platform.exec(`unzip -q -o "${tempZip}" -d "${tempDir}"`, { silent: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Move extracted content to install path
|
|
79
|
+
const extracted = fs.readdirSync(tempDir)[0];
|
|
80
|
+
const extractedPath = path.join(tempDir, extracted);
|
|
81
|
+
|
|
82
|
+
this.platform.ensureDir(path.dirname(this.installPath));
|
|
83
|
+
fs.renameSync(extractedPath, this.installPath);
|
|
84
|
+
|
|
85
|
+
// Cleanup
|
|
86
|
+
fs.rmSync(tempZip, { force: true });
|
|
87
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
88
|
+
|
|
89
|
+
this.logger.success('SuperClaude installed via ZIP');
|
|
90
|
+
return true;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
this.logger.error(`Failed to install SuperClaude: ${error.message}`);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Uninstall SuperClaude
|
|
99
|
+
*/
|
|
100
|
+
uninstall() {
|
|
101
|
+
if (!this.isInstalled()) {
|
|
102
|
+
this.logger.warning('SuperClaude not found');
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
fs.rmSync(this.installPath, { recursive: true });
|
|
108
|
+
this.logger.success('SuperClaude removed');
|
|
109
|
+
return true;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
this.logger.error(`Failed to remove SuperClaude: ${error.message}`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Verify installation
|
|
118
|
+
*/
|
|
119
|
+
verify() {
|
|
120
|
+
if (this.isInstalled()) {
|
|
121
|
+
this.logger.success('SuperClaude: Installed');
|
|
122
|
+
return true;
|
|
123
|
+
} else {
|
|
124
|
+
this.logger.error('SuperClaude: Not installed');
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { SuperClaudeInstaller };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZuppaClaude - Claude Code Enhancement Installer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Installer } = require('./installer');
|
|
6
|
+
const { Uninstaller } = require('./uninstaller');
|
|
7
|
+
const { Settings } = require('./settings');
|
|
8
|
+
const { Logger } = require('./utils/logger');
|
|
9
|
+
const { Platform } = require('./utils/platform');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
Installer,
|
|
13
|
+
Uninstaller,
|
|
14
|
+
Settings,
|
|
15
|
+
Logger,
|
|
16
|
+
Platform
|
|
17
|
+
};
|
package/lib/installer.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZuppaClaude Main Installer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Logger } = require('./utils/logger');
|
|
6
|
+
const { Platform } = require('./utils/platform');
|
|
7
|
+
const { Prompts } = require('./utils/prompts');
|
|
8
|
+
const { Settings } = require('./settings');
|
|
9
|
+
const {
|
|
10
|
+
SuperClaudeInstaller,
|
|
11
|
+
SpecKitInstaller,
|
|
12
|
+
ConfigInstaller,
|
|
13
|
+
ClaudeZInstaller,
|
|
14
|
+
ClaudeHUDInstaller
|
|
15
|
+
} = require('./components');
|
|
16
|
+
|
|
17
|
+
class Installer {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.logger = new Logger();
|
|
20
|
+
this.platform = new Platform();
|
|
21
|
+
this.prompts = new Prompts();
|
|
22
|
+
this.settings = new Settings();
|
|
23
|
+
|
|
24
|
+
// Component installers
|
|
25
|
+
this.superClaude = new SuperClaudeInstaller();
|
|
26
|
+
this.specKit = new SpecKitInstaller();
|
|
27
|
+
this.config = new ConfigInstaller();
|
|
28
|
+
this.claudeZ = new ClaudeZInstaller();
|
|
29
|
+
this.claudeHUD = new ClaudeHUDInstaller();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run the installer
|
|
34
|
+
*/
|
|
35
|
+
async run() {
|
|
36
|
+
this.logger.banner();
|
|
37
|
+
|
|
38
|
+
// Step 1: Check dependencies
|
|
39
|
+
this.logger.step('Step 1/7: Checking Dependencies');
|
|
40
|
+
const depsOk = await this.checkDependencies();
|
|
41
|
+
if (!depsOk) {
|
|
42
|
+
this.logger.error('Dependency check failed. Please install required dependencies.');
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Load existing settings
|
|
47
|
+
const existingSettings = this.settings.load();
|
|
48
|
+
let useExisting = false;
|
|
49
|
+
|
|
50
|
+
if (existingSettings) {
|
|
51
|
+
this.logger.info('Found existing settings from previous installation');
|
|
52
|
+
useExisting = await this.prompts.confirm('Use previous settings?', true);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Step 2: Install SuperClaude
|
|
56
|
+
const scInstalled = await this.superClaude.install();
|
|
57
|
+
|
|
58
|
+
// Step 3: Install Spec Kit
|
|
59
|
+
let installSpecKit = true;
|
|
60
|
+
if (useExisting && existingSettings.specKit !== undefined) {
|
|
61
|
+
installSpecKit = existingSettings.specKit;
|
|
62
|
+
} else {
|
|
63
|
+
installSpecKit = await this.prompts.confirm('Install Spec Kit (specify-cli)?', true);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let skInstalled = false;
|
|
67
|
+
if (installSpecKit) {
|
|
68
|
+
skInstalled = await this.specKit.install();
|
|
69
|
+
} else {
|
|
70
|
+
this.logger.info('Skipping Spec Kit installation');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Step 4: Install Configuration
|
|
74
|
+
const cfgInstalled = await this.config.install();
|
|
75
|
+
|
|
76
|
+
// Step 5: Install Claude-Z (optional)
|
|
77
|
+
let installClaudeZ = false;
|
|
78
|
+
let zaiApiKey = null;
|
|
79
|
+
|
|
80
|
+
if (useExisting && existingSettings.claudeZ) {
|
|
81
|
+
installClaudeZ = true;
|
|
82
|
+
zaiApiKey = this.settings.decodeApiKey(existingSettings.zaiApiKey);
|
|
83
|
+
this.logger.info('Using saved Z.AI API key');
|
|
84
|
+
} else {
|
|
85
|
+
installClaudeZ = await this.prompts.confirm('Install Claude-Z (z.ai backend)?', false);
|
|
86
|
+
if (installClaudeZ) {
|
|
87
|
+
zaiApiKey = await this.prompts.password('Enter your Z.AI API key:');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let czInstalled = false;
|
|
92
|
+
if (installClaudeZ && zaiApiKey) {
|
|
93
|
+
czInstalled = await this.claudeZ.install(zaiApiKey);
|
|
94
|
+
} else {
|
|
95
|
+
this.logger.info('Skipping Claude-Z installation');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Step 6: Install Claude HUD (optional)
|
|
99
|
+
let installClaudeHUD = true;
|
|
100
|
+
if (useExisting && existingSettings.claudeHUD !== undefined) {
|
|
101
|
+
installClaudeHUD = existingSettings.claudeHUD;
|
|
102
|
+
} else {
|
|
103
|
+
installClaudeHUD = await this.prompts.confirm('Install Claude HUD (status display)?', true);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let hudInstalled = false;
|
|
107
|
+
if (installClaudeHUD) {
|
|
108
|
+
hudInstalled = await this.claudeHUD.install();
|
|
109
|
+
} else {
|
|
110
|
+
this.logger.info('Skipping Claude HUD installation');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Step 7: Verification
|
|
114
|
+
this.logger.step('Step 7/7: Verifying Installation');
|
|
115
|
+
console.log('');
|
|
116
|
+
|
|
117
|
+
this.superClaude.verify();
|
|
118
|
+
if (installSpecKit) this.specKit.verify();
|
|
119
|
+
this.config.verify();
|
|
120
|
+
if (installClaudeZ) this.claudeZ.verify();
|
|
121
|
+
if (installClaudeHUD) this.claudeHUD.verify();
|
|
122
|
+
|
|
123
|
+
// Save settings
|
|
124
|
+
const newSettings = {
|
|
125
|
+
specKit: installSpecKit,
|
|
126
|
+
claudeZ: installClaudeZ,
|
|
127
|
+
claudeHUD: installClaudeHUD,
|
|
128
|
+
zaiApiKey: zaiApiKey ? this.settings.encodeApiKey(zaiApiKey) : null,
|
|
129
|
+
installedAt: new Date().toISOString(),
|
|
130
|
+
version: require('../package.json').version
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
this.settings.save(newSettings);
|
|
134
|
+
this.logger.success('Settings saved');
|
|
135
|
+
|
|
136
|
+
// Print summary
|
|
137
|
+
this.printSummary({
|
|
138
|
+
superClaude: scInstalled,
|
|
139
|
+
specKit: skInstalled,
|
|
140
|
+
config: cfgInstalled,
|
|
141
|
+
claudeZ: czInstalled,
|
|
142
|
+
claudeHUD: hudInstalled
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check dependencies
|
|
150
|
+
*/
|
|
151
|
+
async checkDependencies() {
|
|
152
|
+
let allOk = true;
|
|
153
|
+
|
|
154
|
+
// Check Node.js version
|
|
155
|
+
const nodeVersion = process.version;
|
|
156
|
+
const major = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
157
|
+
if (major >= 16) {
|
|
158
|
+
this.logger.success(`Node.js: ${nodeVersion}`);
|
|
159
|
+
} else {
|
|
160
|
+
this.logger.error(`Node.js: ${nodeVersion} (requires 16+)`);
|
|
161
|
+
allOk = false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check Claude Code
|
|
165
|
+
if (this.platform.commandExists('claude')) {
|
|
166
|
+
const version = this.platform.exec('claude --version', { silent: true });
|
|
167
|
+
this.logger.success(`Claude Code: ${version?.trim() || 'installed'}`);
|
|
168
|
+
} else {
|
|
169
|
+
this.logger.error('Claude Code: Not found');
|
|
170
|
+
this.logger.info('Install with: npm install -g @anthropic-ai/claude-code');
|
|
171
|
+
allOk = false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check git (optional but recommended)
|
|
175
|
+
if (this.platform.commandExists('git')) {
|
|
176
|
+
this.logger.success('Git: Available');
|
|
177
|
+
} else {
|
|
178
|
+
this.logger.warning('Git: Not found (will use ZIP fallback)');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check Python (for Spec Kit)
|
|
182
|
+
const pipCmd = this.platform.getPipCommand();
|
|
183
|
+
if (pipCmd) {
|
|
184
|
+
this.logger.success(`Python: Available (${pipCmd.split(' ')[0]})`);
|
|
185
|
+
} else {
|
|
186
|
+
this.logger.warning('Python: Not found (Spec Kit will be skipped)');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('');
|
|
190
|
+
return allOk;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Print installation summary
|
|
195
|
+
*/
|
|
196
|
+
printSummary(results) {
|
|
197
|
+
console.log('');
|
|
198
|
+
console.log('═══════════════════════════════════════════════════════════════════');
|
|
199
|
+
console.log(' Installation Complete');
|
|
200
|
+
console.log('═══════════════════════════════════════════════════════════════════');
|
|
201
|
+
console.log('');
|
|
202
|
+
|
|
203
|
+
const components = [
|
|
204
|
+
{ name: 'SuperClaude', installed: results.superClaude },
|
|
205
|
+
{ name: 'Spec Kit', installed: results.specKit },
|
|
206
|
+
{ name: 'CLAUDE.md', installed: results.config },
|
|
207
|
+
{ name: 'Claude-Z', installed: results.claudeZ },
|
|
208
|
+
{ name: 'Claude HUD', installed: results.claudeHUD }
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
let installed = 0;
|
|
212
|
+
for (const comp of components) {
|
|
213
|
+
const status = comp.installed ? '✓' : '✗';
|
|
214
|
+
const color = comp.installed ? '\x1b[32m' : '\x1b[31m';
|
|
215
|
+
console.log(` ${color}[${status}]\x1b[0m ${comp.name}`);
|
|
216
|
+
if (comp.installed) installed++;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log(` Components installed: ${installed}/${components.length}`);
|
|
221
|
+
console.log('');
|
|
222
|
+
|
|
223
|
+
console.log(' Next steps:');
|
|
224
|
+
console.log(' 1. Restart your terminal or run: source ~/.bashrc');
|
|
225
|
+
console.log(' 2. Start Claude Code: claude');
|
|
226
|
+
console.log(' 3. Try: /sc:help');
|
|
227
|
+
console.log('');
|
|
228
|
+
|
|
229
|
+
if (results.claudeZ) {
|
|
230
|
+
console.log(' For z.ai backend: claude-z');
|
|
231
|
+
console.log('');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (results.claudeHUD) {
|
|
235
|
+
console.log(' For Claude HUD: run setup-claude-hud');
|
|
236
|
+
console.log('');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = { Installer };
|
package/lib/settings.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings management for ZuppaClaude
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { Logger } = require('./utils/logger');
|
|
8
|
+
const { Platform } = require('./utils/platform');
|
|
9
|
+
const { Prompts } = require('./utils/prompts');
|
|
10
|
+
|
|
11
|
+
const SETTINGS_VERSION = '1.0';
|
|
12
|
+
|
|
13
|
+
class Settings {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.platform = new Platform();
|
|
16
|
+
this.logger = new Logger();
|
|
17
|
+
this.prompts = new Prompts();
|
|
18
|
+
this.configDir = this.platform.zuppaconfigDir;
|
|
19
|
+
this.filePath = path.join(this.configDir, 'zc-settings.json');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if settings file exists
|
|
24
|
+
*/
|
|
25
|
+
exists() {
|
|
26
|
+
return fs.existsSync(this.filePath);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load settings from file
|
|
31
|
+
*/
|
|
32
|
+
load() {
|
|
33
|
+
if (!this.exists()) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const content = fs.readFileSync(this.filePath, 'utf8');
|
|
39
|
+
return JSON.parse(content);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
this.logger.warning(`Could not parse settings file: ${error.message}`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Save settings to file
|
|
48
|
+
*/
|
|
49
|
+
save(settings) {
|
|
50
|
+
this.platform.ensureDir(this.configDir);
|
|
51
|
+
|
|
52
|
+
const timestamp = new Date().toISOString();
|
|
53
|
+
const existing = this.load();
|
|
54
|
+
|
|
55
|
+
const data = {
|
|
56
|
+
version: SETTINGS_VERSION,
|
|
57
|
+
created: existing?.created || timestamp,
|
|
58
|
+
updated: timestamp,
|
|
59
|
+
components: settings.components || {},
|
|
60
|
+
preferences: settings.preferences || {
|
|
61
|
+
auto_update_check: true,
|
|
62
|
+
backup_configs: true,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
67
|
+
fs.chmodSync(this.filePath, 0o600);
|
|
68
|
+
|
|
69
|
+
this.logger.success(`Settings saved to ${this.filePath}`);
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get component settings
|
|
75
|
+
*/
|
|
76
|
+
getComponents() {
|
|
77
|
+
const settings = this.load();
|
|
78
|
+
return settings?.components || {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Update component settings
|
|
83
|
+
*/
|
|
84
|
+
updateComponents(components) {
|
|
85
|
+
const settings = this.load() || { components: {}, preferences: {} };
|
|
86
|
+
settings.components = { ...settings.components, ...components };
|
|
87
|
+
return this.save(settings);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Encode API key to base64
|
|
92
|
+
*/
|
|
93
|
+
encodeApiKey(key) {
|
|
94
|
+
return Buffer.from(key).toString('base64');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Decode API key from base64
|
|
99
|
+
*/
|
|
100
|
+
decodeApiKey(encoded) {
|
|
101
|
+
try {
|
|
102
|
+
return Buffer.from(encoded, 'base64').toString('utf8');
|
|
103
|
+
} catch {
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Show current settings
|
|
110
|
+
*/
|
|
111
|
+
show() {
|
|
112
|
+
if (!this.exists()) {
|
|
113
|
+
this.logger.warning(`No settings file found at: ${this.filePath}`);
|
|
114
|
+
console.log('\nRun the installer first to create settings.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const settings = this.load();
|
|
119
|
+
if (!settings) return;
|
|
120
|
+
|
|
121
|
+
const c = settings.components || {};
|
|
122
|
+
|
|
123
|
+
console.log(`\nSettings file: ${this.filePath}\n`);
|
|
124
|
+
console.log(`Version: ${settings.version}`);
|
|
125
|
+
console.log(`Created: ${settings.created}`);
|
|
126
|
+
console.log(`Updated: ${settings.updated}`);
|
|
127
|
+
console.log('\nComponents:');
|
|
128
|
+
console.log(` SuperClaude: ${c.superclaude?.installed ? 'Yes' : 'No'}`);
|
|
129
|
+
console.log(` Spec Kit: ${c.speckit?.installed ? 'Yes' : 'No'}`);
|
|
130
|
+
console.log(` Claude-Z: ${c.claude_z?.installed ? 'Yes' : 'No'}`);
|
|
131
|
+
if (c.claude_z?.api_key_encoded) {
|
|
132
|
+
console.log(' API Key: [configured]');
|
|
133
|
+
}
|
|
134
|
+
console.log(` Claude HUD: ${c.claude_hud?.installed ? 'Yes' : 'No'}`);
|
|
135
|
+
|
|
136
|
+
const p = settings.preferences || {};
|
|
137
|
+
console.log('\nPreferences:');
|
|
138
|
+
console.log(` Auto-update check: ${p.auto_update_check ? 'Yes' : 'No'}`);
|
|
139
|
+
console.log(` Backup configs: ${p.backup_configs ? 'Yes' : 'No'}`);
|
|
140
|
+
console.log('');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Export settings to file
|
|
145
|
+
*/
|
|
146
|
+
export(exportPath) {
|
|
147
|
+
if (!this.exists()) {
|
|
148
|
+
this.logger.error(`No settings file found at: ${this.filePath}`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const settings = this.load();
|
|
153
|
+
if (!settings) return false;
|
|
154
|
+
|
|
155
|
+
// Add export metadata
|
|
156
|
+
settings._export = {
|
|
157
|
+
exported_at: new Date().toISOString(),
|
|
158
|
+
hostname: require('os').hostname(),
|
|
159
|
+
source_path: this.filePath,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const resolvedPath = path.resolve(exportPath.replace(/^~/, this.platform.homedir));
|
|
163
|
+
fs.writeFileSync(resolvedPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
164
|
+
|
|
165
|
+
this.logger.success(`Settings exported to: ${resolvedPath}`);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Import settings from file
|
|
171
|
+
*/
|
|
172
|
+
import(importPath) {
|
|
173
|
+
const resolvedPath = path.resolve(importPath.replace(/^~/, this.platform.homedir));
|
|
174
|
+
|
|
175
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
176
|
+
this.logger.error(`Import file not found: ${resolvedPath}`);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let settings;
|
|
181
|
+
try {
|
|
182
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
183
|
+
settings = JSON.parse(content);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.logger.error(`Invalid JSON file: ${error.message}`);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Backup existing settings
|
|
190
|
+
if (this.exists()) {
|
|
191
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
192
|
+
const backupPath = `${this.filePath}.backup.${timestamp}`;
|
|
193
|
+
fs.copyFileSync(this.filePath, backupPath);
|
|
194
|
+
this.logger.info(`Existing settings backed up to: ${backupPath}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Remove export metadata
|
|
198
|
+
delete settings._export;
|
|
199
|
+
|
|
200
|
+
// Update timestamp
|
|
201
|
+
settings.updated = new Date().toISOString();
|
|
202
|
+
|
|
203
|
+
this.platform.ensureDir(this.configDir);
|
|
204
|
+
fs.writeFileSync(this.filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
205
|
+
fs.chmodSync(this.filePath, 0o600);
|
|
206
|
+
|
|
207
|
+
this.logger.success(`Settings imported from: ${resolvedPath}`);
|
|
208
|
+
this.logger.info('Run the installer again to apply imported settings');
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Reset settings
|
|
214
|
+
*/
|
|
215
|
+
async reset() {
|
|
216
|
+
if (!this.exists()) {
|
|
217
|
+
this.logger.warning('No settings file found');
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const confirmed = await this.prompts.confirm('Are you sure you want to reset all settings?', false);
|
|
222
|
+
this.prompts.close();
|
|
223
|
+
|
|
224
|
+
if (!confirmed) {
|
|
225
|
+
this.logger.info('Reset cancelled');
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Backup before reset
|
|
230
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
231
|
+
const backupPath = `${this.filePath}.backup.${timestamp}`;
|
|
232
|
+
fs.copyFileSync(this.filePath, backupPath);
|
|
233
|
+
this.logger.info(`Settings backed up to: ${backupPath}`);
|
|
234
|
+
|
|
235
|
+
// Remove settings
|
|
236
|
+
fs.unlinkSync(this.filePath);
|
|
237
|
+
this.logger.success('Settings reset complete');
|
|
238
|
+
this.logger.info('Run the installer again to create new settings');
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = { Settings, SETTINGS_VERSION };
|