Skip to content

Commit 939d1b9

Browse files
committed
chore: release v0.5.4
Major updates: - Remove --preview option and all v0.4 code branches - Add dynamic version reading from Simone GitHub tags - Implement proper backup of commands to .claude/simone-commands-backup/ - Protect user data in .simone directory during updates - Update command format from /project:simone:* to /simone:* - Add VERSION file for better version tracking This version focuses on stable Simone installations with better update handling.
1 parent c6f21d1 commit 939d1b9

4 files changed

Lines changed: 146 additions & 206 deletions

File tree

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.5.4

index.js

Lines changed: 132 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,18 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1515

1616
const program = new Command();
1717

18-
const GITHUB_API_URL = 'https://api.github.com/repos/helmi/claude-simone';
19-
const GITHUB_RAW_URL = 'https://raw.githubusercontent.com/helmi/claude-simone/master';
20-
2118
async function fetchGitHubContent(url) {
2219
return new Promise((resolve, reject) => {
2320
https.get(url, { headers: { 'User-Agent': 'hello-simone' } }, (res) => {
2421
let data = '';
2522
res.on('data', chunk => data += chunk);
2623
res.on('end', () => {
2724
if (res.statusCode === 200) {
28-
resolve(data);
25+
try {
26+
resolve(JSON.parse(data));
27+
} catch (e) {
28+
reject(new Error('Failed to parse JSON response from GitHub.'));
29+
}
2930
} else {
3031
reject(new Error(`Failed to fetch ${url}: ${res.statusCode}`));
3132
}
@@ -34,235 +35,165 @@ async function fetchGitHubContent(url) {
3435
});
3536
}
3637

37-
async function downloadFile(url, destPath) {
38-
return new Promise((resolve, reject) => {
39-
const file = createWriteStream(destPath);
40-
https.get(url, { headers: { 'User-Agent': 'hello-simone' } }, (response) => {
41-
if (response.statusCode !== 200) {
42-
reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
43-
return;
44-
}
45-
pipeline(response, file)
46-
.then(() => resolve())
47-
.catch(reject);
48-
}).on('error', reject);
49-
});
50-
}
51-
52-
async function getDirectoryStructure(path = '') {
53-
const url = `${GITHUB_API_URL}/contents/${path}`;
54-
const content = await fetchGitHubContent(url);
55-
return JSON.parse(content);
38+
async function fetchLatestVersion() {
39+
try {
40+
const tags = await fetchGitHubContent('https://api.github.com/repos/helmi/claude-simone/tags');
41+
// Filter for version tags and sort
42+
const versionTags = tags
43+
.map(tag => tag.name)
44+
.filter(name => name.match(/^v\d+\.\d+(\.\d+)?$/))
45+
.sort((a, b) => {
46+
const parseVersion = (v) => v.slice(1).split('.').map(n => parseInt(n, 10));
47+
const va = parseVersion(a);
48+
const vb = parseVersion(b);
49+
for (let i = 0; i < Math.max(va.length, vb.length); i++) {
50+
const diff = (vb[i] || 0) - (va[i] || 0);
51+
if (diff !== 0) return diff;
52+
}
53+
return 0;
54+
});
55+
return versionTags[0] || 'v0.3.5'; // fallback to known version
56+
} catch (error) {
57+
console.warn(chalk.yellow('Unable to fetch latest version, using default.'));
58+
return 'v0.3.5'; // fallback version
59+
}
5660
}
5761

58-
async function checkExistingInstallation() {
59-
const simoneExists = await fs.access('.simone').then(() => true).catch(() => false);
60-
const claudeCommandsExists = await fs.access('.claude/commands/simone').then(() => true).catch(() => false);
61-
return simoneExists || claudeCommandsExists;
62+
async function downloadFile(url, destPath) {
63+
await fs.mkdir(path.dirname(destPath), { recursive: true });
64+
return new Promise((resolve, reject) => {
65+
const file = createWriteStream(destPath);
66+
https.get(url, { headers: { 'User-Agent': 'hello-simone' } }, (response) => {
67+
if (response.statusCode !== 200) {
68+
reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
69+
return;
70+
}
71+
pipeline(response, file)
72+
.then(() => resolve())
73+
.catch(reject);
74+
}).on('error', reject);
75+
});
6276
}
6377

64-
async function backupFile(filePath) {
78+
async function downloadDirectory(githubPath, localPath, spinner, branch) {
6579
try {
66-
const exists = await fs.access(filePath).then(() => true).catch(() => false);
67-
if (exists) {
68-
const backupPath = `${filePath}.bak`;
69-
await fs.copyFile(filePath, backupPath);
70-
return backupPath;
80+
await fs.mkdir(localPath, { recursive: true });
81+
const apiUrl = `https://api.github.com/repos/helmi/claude-simone/contents/${githubPath}?ref=${branch}`;
82+
const items = await fetchGitHubContent(apiUrl);
83+
84+
for (const item of items) {
85+
const itemLocalPath = path.join(localPath, item.name);
86+
if (item.type === 'dir') {
87+
await downloadDirectory(item.path, itemLocalPath, spinner, branch);
88+
} else if (item.type === 'file') {
89+
spinner.text = `Downloading ${item.path}...`;
90+
await downloadFile(item.download_url, itemLocalPath);
91+
}
7192
}
7293
} catch (error) {
73-
// Backup failed, but continue
94+
throw new Error(`Failed to download ${githubPath}: ${error.message}`);
7495
}
75-
return null;
7696
}
7797

78-
async function backupCommandsAndDocs() {
79-
const spinner = ora('Backing up existing commands and documentation...').start();
80-
const backedUpFiles = [];
81-
82-
try {
83-
// Files that will be updated and need backup
84-
const filesToBackup = [
85-
'.simone/CLAUDE.md',
86-
'.simone/02_REQUIREMENTS/CLAUDE.md',
87-
'.simone/03_SPRINTS/CLAUDE.md',
88-
'.simone/04_GENERAL_TASKS/CLAUDE.md'
89-
];
90-
91-
// Backup CLAUDE.md files
92-
for (const file of filesToBackup) {
93-
const backupPath = await backupFile(file);
94-
if (backupPath) {
95-
backedUpFiles.push(backupPath);
96-
}
97-
}
98-
99-
// Backup all command files
100-
const commandsDir = '.claude/commands/simone';
101-
const commandsExist = await fs.access(commandsDir).then(() => true).catch(() => false);
102-
if (commandsExist) {
103-
try {
104-
const commandFiles = await fs.readdir(commandsDir, { recursive: true });
105-
for (const file of commandFiles) {
106-
const filePath = path.join(commandsDir, file);
107-
const stat = await fs.stat(filePath);
108-
if (stat.isFile()) {
109-
const backupPath = await backupFile(filePath);
110-
if (backupPath) {
111-
backedUpFiles.push(backupPath);
112-
}
113-
}
114-
}
115-
} catch (error) {
116-
// Commands directory might be empty or have issues
117-
}
118-
}
119-
120-
if (backedUpFiles.length > 0) {
121-
spinner.succeed(chalk.green(`Backed up ${backedUpFiles.length} files (*.bak)`));
122-
} else {
123-
spinner.succeed(chalk.gray('No existing files to backup'));
98+
async function checkExistingInstallation() {
99+
// Check if Simone is already installed by looking for key directories
100+
const simoneDirs = ['.simone/03_SPRINTS', '.simone/02_REQUIREMENTS'];
101+
for (const dir of simoneDirs) {
102+
try {
103+
await fs.access(dir);
104+
return true; // Found a Simone directory, so it's already installed
105+
} catch {
106+
// Directory not found, continue checking
124107
}
125-
return backedUpFiles;
126-
} catch (error) {
127-
spinner.fail(chalk.red('Backup failed'));
128-
throw error;
129108
}
109+
return false;
130110
}
131111

132-
async function downloadDirectory(githubPath, localPath, spinner) {
133-
await fs.mkdir(localPath, { recursive: true });
112+
async function backupCommands() {
113+
const commandsDir = '.claude/commands/simone';
114+
const backupDir = '.claude/simone-commands-backup';
134115

135-
const items = await getDirectoryStructure(githubPath);
136-
137-
for (const item of items) {
138-
const itemLocalPath = path.join(localPath, item.name);
139-
140-
if (item.type === 'dir') {
141-
await downloadDirectory(item.path, itemLocalPath, spinner);
142-
} else if (item.type === 'file') {
143-
spinner.text = `Downloading ${item.path}...`;
144-
await downloadFile(item.download_url, itemLocalPath);
145-
}
116+
try {
117+
await fs.access(commandsDir);
118+
// Commands exist, create backup
119+
await fs.rm(backupDir, { recursive: true, force: true }); // Remove old backup if exists
120+
await fs.mkdir(path.dirname(backupDir), { recursive: true });
121+
await fs.rename(commandsDir, backupDir);
122+
return true;
123+
} catch {
124+
// No commands to backup
125+
return false;
146126
}
147127
}
148128

129+
149130
async function installSimone(options = {}) {
131+
const branch = 'master';
132+
const latestVersion = await fetchLatestVersion();
133+
const versionDisplay = latestVersion.replace('v', '');
134+
150135
console.log(chalk.blue.bold('\n🎉 Welcome to HelloSimone!\n'));
151-
console.log(chalk.gray('This installer will set up the Simone project management framework'));
136+
console.log(chalk.gray(`This installer will set up the Simone ${latestVersion} project management framework`));
152137
console.log(chalk.gray('for your Claude Code project.\n'));
153138

154-
const hasExisting = await checkExistingInstallation();
155-
156-
if (hasExisting && !options.force) {
157-
const response = await prompts({
158-
type: 'select',
159-
name: 'action',
160-
message: 'Existing Simone installation detected. What would you like to do?',
161-
choices: [
162-
{ title: 'Update (updates commands and docs only, preserves your work)', value: 'update' },
163-
{ title: 'Skip installation', value: 'skip' },
164-
{ title: 'Cancel', value: 'cancel' }
165-
]
166-
});
167-
168-
if (response.action === 'skip' || response.action === 'cancel') {
169-
console.log(chalk.yellow('\nInstallation cancelled.'));
170-
process.exit(0);
171-
}
172-
173-
if (response.action === 'update') {
174-
await backupCommandsAndDocs();
175-
}
176-
}
177-
178-
const spinner = ora('Fetching Simone framework from GitHub...').start();
139+
const spinner = ora('Initializing...').start();
179140

180141
try {
181-
// Create .simone directory structure
182-
const simoneDirs = [
183-
'.simone',
184-
'.simone/01_PROJECT_DOCS',
185-
'.simone/02_REQUIREMENTS',
186-
'.simone/03_SPRINTS',
187-
'.simone/04_GENERAL_TASKS',
188-
'.simone/05_ARCHITECTURE_DECISIONS',
189-
'.simone/10_STATE_OF_PROJECT',
190-
'.simone/99_TEMPLATES'
191-
];
142+
spinner.text = 'Checking for existing installation...';
143+
const hasExisting = await checkExistingInstallation();
192144

193-
for (const dir of simoneDirs) {
194-
await fs.mkdir(dir, { recursive: true });
195-
}
196-
197-
// Only download manifest on fresh installs
198-
if (!hasExisting) {
199-
spinner.text = 'Downloading Simone framework files...';
145+
if (hasExisting) {
146+
// SCENARIO: Update existing installation
147+
spinner.text = 'Updating existing Simone installation...';
200148

201-
// Get the root manifest
202-
try {
203-
const manifestUrl = `${GITHUB_RAW_URL}/.simone/00_PROJECT_MANIFEST.md`;
204-
await downloadFile(manifestUrl, '.simone/00_PROJECT_MANIFEST.md');
205-
} catch (error) {
206-
// If manifest doesn't exist, that's okay
149+
// Backup commands
150+
const backedUp = await backupCommands();
151+
if (backedUp) {
152+
spinner.info(chalk.yellow('Existing commands moved to .claude/simone-commands-backup/'));
207153
}
208-
209-
// Download templates on fresh install
210-
try {
211-
await downloadDirectory('.simone/99_TEMPLATES', '.simone/99_TEMPLATES', spinner);
212-
} catch (error) {
213-
spinner.text = 'Templates directory not found, skipping...';
214-
}
215-
}
216-
217-
// Always update CLAUDE.md documentation files
218-
spinner.text = 'Updating documentation...';
219-
const claudeFiles = [
220-
'.simone/CLAUDE.md',
221-
'.simone/02_REQUIREMENTS/CLAUDE.md',
222-
'.simone/03_SPRINTS/CLAUDE.md',
223-
'.simone/04_GENERAL_TASKS/CLAUDE.md'
224-
];
225-
226-
for (const claudeFile of claudeFiles) {
227-
try {
228-
const claudeUrl = `${GITHUB_RAW_URL}/${claudeFile}`;
229-
await downloadFile(claudeUrl, claudeFile);
230-
} catch (error) {
231-
// If CLAUDE.md doesn't exist, that's okay
154+
155+
// Only update CLAUDE.md files and commands, NOT the .simone directory structure
156+
const GITHUB_RAW_URL = `https://raw.githubusercontent.com/helmi/claude-simone/${branch}`;
157+
const claudeFiles = [
158+
'.simone/CLAUDE.md', '.simone/02_REQUIREMENTS/CLAUDE.md',
159+
'.simone/03_SPRINTS/CLAUDE.md', '.simone/04_GENERAL_TASKS/CLAUDE.md'
160+
];
161+
spinner.text = 'Updating CLAUDE.md files...';
162+
for (const claudeFile of claudeFiles) {
163+
await downloadFile(`${GITHUB_RAW_URL}/${claudeFile}`, claudeFile).catch(()=>{});
232164
}
233-
}
234-
235-
// Create .claude/commands/simone directory
236-
await fs.mkdir('.claude/commands/simone', { recursive: true });
237-
238-
// Always update commands
239-
spinner.text = 'Updating Simone commands...';
240-
try {
241-
await downloadDirectory('.claude/commands/simone', '.claude/commands/simone', spinner);
242-
} catch (error) {
243-
spinner.text = 'Commands directory not found, skipping...';
244-
}
245-
246-
if (hasExisting) {
247-
spinner.succeed(chalk.green('✅ Simone framework updated successfully!'));
248-
console.log(chalk.blue('\n🔄 Updated:'));
249-
console.log(chalk.gray(' • Commands in .claude/commands/simone/'));
250-
console.log(chalk.gray(' • Documentation (CLAUDE.md files)'));
251-
console.log(chalk.green('\n💾 Your work is preserved:'));
252-
console.log(chalk.gray(' • All tasks, sprints, and project files remain untouched'));
253-
console.log(chalk.gray(' • Backups created as *.bak files'));
254165
} else {
255-
spinner.succeed(chalk.green('✅ Simone framework installed successfully!'));
166+
// SCENARIO: Fresh install
167+
spinner.text = 'Installing Simone framework...';
168+
const simoneDirs = [
169+
'.simone', '.simone/01_PROJECT_DOCS', '.simone/02_REQUIREMENTS',
170+
'.simone/03_SPRINTS', '.simone/04_GENERAL_TASKS', '.simone/05_ARCHITECTURE_DECISIONS',
171+
'.simone/10_STATE_OF_PROJECT', '.simone/99_TEMPLATES'
172+
];
173+
for (const dir of simoneDirs) {
174+
await fs.mkdir(dir, { recursive: true });
175+
}
176+
const GITHUB_RAW_URL = `https://raw.githubusercontent.com/helmi/claude-simone/${branch}`;
177+
await downloadFile(`${GITHUB_RAW_URL}/.simone/00_PROJECT_MANIFEST.md`, '.simone/00_PROJECT_MANIFEST.md').catch(()=>{});
178+
await downloadDirectory('.simone/99_TEMPLATES', '.simone/99_TEMPLATES', spinner, branch).catch(()=>{});
179+
180+
const claudeFiles = [
181+
'.simone/CLAUDE.md', '.simone/02_REQUIREMENTS/CLAUDE.md',
182+
'.simone/03_SPRINTS/CLAUDE.md', '.simone/04_GENERAL_TASKS/CLAUDE.md'
183+
];
184+
for (const claudeFile of claudeFiles) {
185+
await downloadFile(`${GITHUB_RAW_URL}/${claudeFile}`, claudeFile).catch(()=>{});
186+
}
256187
}
257188

258-
console.log(chalk.blue('\n📁 Created structure:'));
259-
console.log(chalk.gray(' .simone/ - Project management root'));
260-
console.log(chalk.gray(' .claude/commands/ - Claude custom commands'));
261-
189+
// Always download/update commands
190+
spinner.text = 'Downloading latest commands...';
191+
await downloadDirectory('.claude/commands/simone', '.claude/commands/simone', spinner, branch).catch(()=>{});
192+
193+
spinner.succeed(chalk.green(`✅ Simone ${latestVersion} framework ${hasExisting ? 'updated' : 'installed'} successfully!`));
262194
console.log(chalk.green('\n🚀 Next steps:'));
263195
console.log(chalk.white(' 1. Open this project in Claude Code'));
264-
console.log(chalk.white(' 2. Use /project:simone commands to manage your project'));
265-
console.log(chalk.white(' 3. Start with /project:simone:initialize to set up your project\n'));
196+
console.log(chalk.white(' 2. Use /simone:initialize to set up your project\n'));
266197

267198
} catch (error) {
268199
spinner.fail(chalk.red('Installation failed'));
@@ -274,8 +205,7 @@ async function installSimone(options = {}) {
274205
program
275206
.name('hello-simone')
276207
.description('Installer for the Simone project management framework')
277-
.version('0.3.0')
278-
.option('-f, --force', 'Force installation without prompts')
208+
.version('0.5.4')
279209
.action(installSimone);
280210

281211
program.parse();

0 commit comments

Comments
 (0)