@@ -15,17 +15,18 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1515
1616const 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-
2118async 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+
149130async 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 = {}) {
274205program
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
281211program . parse ( ) ;
0 commit comments