prjct-cli 0.4.6 → 0.4.8

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/CHANGELOG.md CHANGED
@@ -15,6 +15,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  - Cross-platform file operations
16
16
  - Windows Terminal integration
17
17
 
18
+ ## [0.4.8] - 2025-10-02
19
+
20
+ ### Added
21
+ - **Intelligent Project Initialization** - `prjct init` now works seamlessly in all scenarios
22
+ - Preserves existing project IDs when re-initializing
23
+ - Automatically merges local data with global structure
24
+ - Smart fusion of markdown files (now.md, next.md, shipped.md, ideas.md)
25
+ - Chronological merging of memory/context.jsonl entries
26
+ - Cleans up local `.prjct/` directory, keeping only `prjct.config.json`
27
+ - Multiple developers can initialize the same project without conflicts
28
+
29
+ ### Fixed
30
+ - **Auto-Migration Removed** - Disabled intrusive automatic migration
31
+ - Migration no longer runs automatically during `prjct init` or any command
32
+ - Users can run `prjct migrate-all` manually if needed
33
+ - Removes blocking behavior that prevented normal usage
34
+ - Cleaner, non-intrusive installation and update experience
35
+
36
+ ### Changed
37
+ - **`prjct init` Never Blocks** - Removed "already initialized" error
38
+ - Always allows re-initialization with intelligent data fusion
39
+ - Global architecture enables multi-developer workflows
40
+
41
+ ## [0.4.7] - 2025-10-02
42
+
43
+ ### Fixed
44
+ - **`prjct start` ASCII Art** - Restored beautiful Catppuccin-inspired colors
45
+ - Original design from install.sh with (ノ◕ヮ◕)ノ*:・゚✧
46
+ - Vibrant magenta, cyan, and blue gradient for logo
47
+ - Clean, professional look with value propositions
48
+ - Better visual hierarchy and branding
49
+ - **`prjct start` Display Bug** - Fixed editor names showing as "undefined"
50
+ - Now correctly displays editor names (Claude Code, Cursor, etc.)
51
+ - Uses `commandInstaller.editors[key].name` to get proper editor names
52
+
18
53
  ## [0.4.6] - 2025-10-02
19
54
 
20
55
  ### Added
package/core/commands.js CHANGED
@@ -192,8 +192,6 @@ class PrjctCommands {
192
192
 
193
193
  this.agent = new Agent()
194
194
 
195
- await this.checkAndRunAutoMigration()
196
-
197
195
  // Check for updates in background (non-blocking)
198
196
  this.checkAndNotifyUpdates().catch(() => {
199
197
  // Fail silently - don't interrupt workflow
@@ -316,41 +314,46 @@ class PrjctCommands {
316
314
  async init(projectPath = process.cwd()) {
317
315
  try {
318
316
  await this.initializeAgent()
319
-
320
- if (await configManager.isConfigured(projectPath)) {
321
- return {
322
- success: false,
323
- message: this.agent.formatResponse('Project already initialized!', 'warning'),
324
- }
325
- }
326
-
327
317
  const author = await authorDetector.detect()
328
318
 
329
- const hasLegacy = await pathManager.hasLegacyStructure(projectPath)
330
- let migrationPerformed = false
319
+ // Check if local config exists
320
+ const isConfigured = await configManager.isConfigured(projectPath)
321
+ let projectId
322
+ let existingLocalData = null
323
+ let fusionPerformed = false
331
324
 
332
- if (hasLegacy) {
333
- const config = await configManager.createConfig(projectPath, author)
334
- const projectId = config.projectId
335
- await pathManager.ensureProjectStructure(projectId)
325
+ if (isConfigured) {
326
+ // Use existing ID from local config
327
+ const existingConfig = await configManager.readConfig(projectPath)
328
+ projectId = existingConfig.projectId
329
+ console.log(`🔄 Using existing project ID: ${projectId}`)
336
330
 
331
+ // Check if there's local data to merge
332
+ const localPrjctPath = path.join(projectPath, '.prjct')
337
333
  try {
338
- const migrationResult = await migrator.migrate(projectPath, {
339
- removeLegacy: false,
340
- cleanupLegacy: true,
341
- dryRun: false,
342
- })
343
- migrationPerformed = migrationResult.success
334
+ const localFiles = await fs.readdir(localPrjctPath)
335
+ if (localFiles.length > 1) { // More than just prjct.config.json
336
+ existingLocalData = await this.collectLocalData(localPrjctPath)
337
+ }
344
338
  } catch (error) {
345
- console.error('[prjct] Migration warning:', error.message)
339
+ // No local data to merge
346
340
  }
341
+ } else {
342
+ // Generate new ID based on path hash
343
+ const config = await configManager.createConfig(projectPath, author)
344
+ projectId = config.projectId
345
+ console.log(`✨ Created new project ID: ${projectId}`)
347
346
  }
348
347
 
349
- if (!migrationPerformed) {
350
- const config = await configManager.createConfig(projectPath, author)
351
- const projectId = config.projectId
352
- await pathManager.ensureProjectStructure(projectId)
348
+ // Ensure global structure exists
349
+ await pathManager.ensureProjectStructure(projectId)
350
+ const globalPath = pathManager.getGlobalProjectPath(projectId)
351
+
352
+ // Check if global data exists
353
+ const globalExists = await this.checkGlobalDataExists(globalPath)
353
354
 
355
+ if (!globalExists) {
356
+ // Create fresh global structure
354
357
  const files = {
355
358
  'core/now.md': '# NOW\n\nNo current task. Use `/p:now` to set focus.\n',
356
359
  'core/next.md': '# NEXT\n\n## Priority Queue\n\n',
@@ -362,22 +365,25 @@ class PrjctCommands {
362
365
  'memory/context.jsonl': '',
363
366
  }
364
367
 
365
- const globalPath = pathManager.getGlobalProjectPath(projectId)
366
368
  for (const [filePath, content] of Object.entries(files)) {
367
369
  await this.agent.writeFile(path.join(globalPath, filePath), content)
368
370
  }
371
+ console.log('✅ Created global structure')
372
+ } else {
373
+ console.log('✅ Using existing global data')
369
374
  }
370
375
 
371
- const config = await configManager.readConfig(projectPath)
372
- const projectId = config.projectId
373
- const globalPath = pathManager.getGlobalProjectPath(projectId)
376
+ // Merge local data if exists
377
+ if (existingLocalData) {
378
+ console.log('🔄 Merging local data with global...')
379
+ await this.mergeProjectData(existingLocalData, globalPath)
380
+ fusionPerformed = true
381
+ }
374
382
 
375
- const projectInfo = await this.detectProjectType(projectPath)
383
+ // Clean up local data (keep only prjct.config.json)
384
+ await this.cleanupLocalData(projectPath)
376
385
 
377
- const installResult = await this.install({ force: false, interactive: true })
378
- const editorsInstalled = installResult.success
379
- ? `\n🤖 Commands installed to: ${installResult.message.split('Editors: ')[1]?.split('\n')[0] || 'selected editors'}`
380
- : ''
386
+ const projectInfo = await this.detectProjectType(projectPath)
381
387
 
382
388
  let analysisMessage = ''
383
389
  const hasExistingCode = await this.detectExistingCode(projectPath)
@@ -403,13 +409,14 @@ class PrjctCommands {
403
409
  }
404
410
 
405
411
  const displayPath = pathManager.getDisplayPath(globalPath)
412
+ const fusionMessage = fusionPerformed ? '\n🔄 Merged local data with global' : ''
406
413
  const message =
407
414
  `Initializing prjct v${VERSION} for ${this.agentInfo.name}...\n` +
408
- `✅ Created global structure at ${displayPath}\n` +
409
- '✅ Created prjct.config.json\n' +
415
+ `✅ Project ID: ${projectId}\n` +
416
+ `✅ Global data: ${displayPath}\n` +
410
417
  `👤 Author: ${authorDetector.formatAuthor(author)}\n` +
411
418
  `📋 Project: ${projectInfo}` +
412
- editorsInstalled +
419
+ fusionMessage +
413
420
  analysisMessage +
414
421
  `\n\nReady! Start with ${this.agentInfo.config.commandPrefix}now "your first task"`
415
422
 
@@ -426,6 +433,270 @@ class PrjctCommands {
426
433
  }
427
434
  }
428
435
 
436
+ /**
437
+ * Check if global data exists for a project
438
+ *
439
+ * @param {string} globalPath - Global project path
440
+ * @returns {Promise<boolean>} True if global data exists
441
+ * @private
442
+ */
443
+ async checkGlobalDataExists(globalPath) {
444
+ try {
445
+ const nowFile = path.join(globalPath, 'core', 'now.md')
446
+ await fs.access(nowFile)
447
+ return true
448
+ } catch {
449
+ return false
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Collect all data from local .prjct directory
455
+ *
456
+ * @param {string} localPrjctPath - Path to local .prjct directory
457
+ * @returns {Promise<Object>} Object with local data organized by category
458
+ * @private
459
+ */
460
+ async collectLocalData(localPrjctPath) {
461
+ const data = {
462
+ core: {},
463
+ progress: {},
464
+ planning: {},
465
+ memory: {},
466
+ }
467
+
468
+ try {
469
+ // Collect core files
470
+ const coreFiles = ['now.md', 'next.md', 'context.md']
471
+ for (const file of coreFiles) {
472
+ try {
473
+ const filePath = path.join(localPrjctPath, file)
474
+ data.core[file] = await this.agent.readFile(filePath)
475
+ } catch {
476
+ // File doesn't exist locally
477
+ }
478
+ }
479
+
480
+ // Collect progress files
481
+ const progressFiles = ['shipped.md', 'metrics.md']
482
+ for (const file of progressFiles) {
483
+ try {
484
+ const filePath = path.join(localPrjctPath, file)
485
+ data.progress[file] = await this.agent.readFile(filePath)
486
+ } catch {
487
+ // File doesn't exist locally
488
+ }
489
+ }
490
+
491
+ // Collect planning files
492
+ const planningFiles = ['ideas.md', 'roadmap.md']
493
+ for (const file of planningFiles) {
494
+ try {
495
+ const filePath = path.join(localPrjctPath, file)
496
+ data.planning[file] = await this.agent.readFile(filePath)
497
+ } catch {
498
+ // File doesn't exist locally
499
+ }
500
+ }
501
+
502
+ // Collect memory
503
+ try {
504
+ const memoryPath = path.join(localPrjctPath, 'memory.jsonl')
505
+ data.memory['context.jsonl'] = await this.agent.readFile(memoryPath)
506
+ } catch {
507
+ // Memory doesn't exist locally
508
+ }
509
+ } catch (error) {
510
+ console.error('[prjct] Error collecting local data:', error.message)
511
+ }
512
+
513
+ return data
514
+ }
515
+
516
+ /**
517
+ * Merge local project data with global data
518
+ *
519
+ * @param {Object} localData - Local data collected from .prjct
520
+ * @param {string} globalPath - Global project path
521
+ * @returns {Promise<void>}
522
+ * @private
523
+ */
524
+ async mergeProjectData(localData, globalPath) {
525
+ try {
526
+ // Merge core files
527
+ for (const [filename, localContent] of Object.entries(localData.core)) {
528
+ if (!localContent) continue
529
+
530
+ const globalFilePath = path.join(globalPath, 'core', filename)
531
+ let globalContent = ''
532
+ try {
533
+ globalContent = await this.agent.readFile(globalFilePath)
534
+ } catch {
535
+ // Global file doesn't exist yet
536
+ }
537
+
538
+ const merged = this.intelligentMerge(localContent, globalContent, filename)
539
+ await this.agent.writeFile(globalFilePath, merged)
540
+ }
541
+
542
+ // Merge progress files
543
+ for (const [filename, localContent] of Object.entries(localData.progress)) {
544
+ if (!localContent) continue
545
+
546
+ const globalFilePath = path.join(globalPath, 'progress', filename)
547
+ let globalContent = ''
548
+ try {
549
+ globalContent = await this.agent.readFile(globalFilePath)
550
+ } catch {
551
+ // Global file doesn't exist yet
552
+ }
553
+
554
+ const merged = this.intelligentMerge(localContent, globalContent, filename)
555
+ await this.agent.writeFile(globalFilePath, merged)
556
+ }
557
+
558
+ // Merge planning files
559
+ for (const [filename, localContent] of Object.entries(localData.planning)) {
560
+ if (!localContent) continue
561
+
562
+ const globalFilePath = path.join(globalPath, 'planning', filename)
563
+ let globalContent = ''
564
+ try {
565
+ globalContent = await this.agent.readFile(globalFilePath)
566
+ } catch {
567
+ // Global file doesn't exist yet
568
+ }
569
+
570
+ const merged = this.intelligentMerge(localContent, globalContent, filename)
571
+ await this.agent.writeFile(globalFilePath, merged)
572
+ }
573
+
574
+ // Merge memory (chronologically)
575
+ if (localData.memory['context.jsonl']) {
576
+ const globalMemoryPath = path.join(globalPath, 'memory', 'context.jsonl')
577
+ let globalMemory = ''
578
+ try {
579
+ globalMemory = await this.agent.readFile(globalMemoryPath)
580
+ } catch {
581
+ // Global memory doesn't exist yet
582
+ }
583
+
584
+ const merged = this.mergeMemory(localData.memory['context.jsonl'], globalMemory)
585
+ await this.agent.writeFile(globalMemoryPath, merged)
586
+ }
587
+ } catch (error) {
588
+ console.error('[prjct] Error merging data:', error.message)
589
+ }
590
+ }
591
+
592
+ /**
593
+ * Intelligent merge of markdown content
594
+ *
595
+ * @param {string} localContent - Content from local file
596
+ * @param {string} globalContent - Content from global file
597
+ * @param {string} filename - Name of the file being merged
598
+ * @returns {string} Merged content
599
+ * @private
600
+ */
601
+ intelligentMerge(localContent, globalContent, filename) {
602
+ // If global is empty, use local
603
+ if (!globalContent || globalContent.trim() === '' || globalContent.includes('No current task')) {
604
+ return localContent
605
+ }
606
+
607
+ // If local is empty, use global
608
+ if (!localContent || localContent.trim() === '') {
609
+ return globalContent
610
+ }
611
+
612
+ // For now.md, prefer local (most recent)
613
+ if (filename === 'now.md') {
614
+ return localContent
615
+ }
616
+
617
+ // For shipped.md, next.md, ideas.md, roadmap.md - combine unique entries
618
+ const localLines = localContent.split('\n').filter((l) => l.trim())
619
+ const globalLines = globalContent.split('\n').filter((l) => l.trim())
620
+
621
+ // Keep header from global
622
+ const header = globalLines[0] || localLines[0]
623
+
624
+ // Combine unique content (skip headers and empty lines)
625
+ const allLines = [...globalLines.slice(1), ...localLines.slice(1)]
626
+ const uniqueLines = [...new Set(allLines)]
627
+
628
+ return [header, '', ...uniqueLines].join('\n')
629
+ }
630
+
631
+ /**
632
+ * Merge memory.jsonl files chronologically
633
+ *
634
+ * @param {string} localMemory - Local memory content
635
+ * @param {string} globalMemory - Global memory content
636
+ * @returns {string} Merged memory sorted by timestamp
637
+ * @private
638
+ */
639
+ mergeMemory(localMemory, globalMemory) {
640
+ if (!globalMemory) return localMemory
641
+ if (!localMemory) return globalMemory
642
+
643
+ const localEntries = localMemory.split('\n').filter((l) => l.trim())
644
+ const globalEntries = globalMemory.split('\n').filter((l) => l.trim())
645
+
646
+ const allEntries = [...globalEntries, ...localEntries].map((entry) => {
647
+ try {
648
+ return JSON.parse(entry)
649
+ } catch {
650
+ return null
651
+ }
652
+ }).filter(Boolean)
653
+
654
+ // Sort by timestamp
655
+ allEntries.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp))
656
+
657
+ // Remove duplicates
658
+ const unique = []
659
+ const seen = new Set()
660
+ for (const entry of allEntries) {
661
+ const key = `${entry.timestamp}-${entry.action}`
662
+ if (!seen.has(key)) {
663
+ seen.add(key)
664
+ unique.push(entry)
665
+ }
666
+ }
667
+
668
+ return unique.map((e) => JSON.stringify(e)).join('\n')
669
+ }
670
+
671
+ /**
672
+ * Clean up local .prjct directory, keeping only prjct.config.json
673
+ *
674
+ * @param {string} projectPath - Project path
675
+ * @returns {Promise<void>}
676
+ * @private
677
+ */
678
+ async cleanupLocalData(projectPath) {
679
+ try {
680
+ const localPrjctPath = path.join(projectPath, '.prjct')
681
+ const files = await fs.readdir(localPrjctPath, { withFileTypes: true })
682
+
683
+ for (const file of files) {
684
+ if (file.name === 'prjct.config.json') continue
685
+
686
+ const filePath = path.join(localPrjctPath, file.name)
687
+ if (file.isDirectory()) {
688
+ await fs.rm(filePath, { recursive: true, force: true })
689
+ } else {
690
+ await fs.unlink(filePath)
691
+ }
692
+ }
693
+
694
+ console.log('🧹 Cleaned up local data - only prjct.config.json remains')
695
+ } catch (error) {
696
+ console.error('[prjct] Warning: Could not clean up local data:', error.message)
697
+ }
698
+ }
699
+
429
700
  /**
430
701
  * Set or view current task
431
702
  *
@@ -1965,18 +2236,24 @@ ${diagram}
1965
2236
 
1966
2237
  // ASCII Art
1967
2238
  const chalk = require('chalk')
1968
- console.log(
1969
- chalk.cyan(`
1970
- ___ ____ _ ____ _____
1971
- / _ \\| _ \\ | / ___|_ _|
1972
- | |_| | |_) |_| | | | |
1973
- | __/| _ <| | | |___ | |
1974
- | | | |_) | | |\\____| | |
1975
- |_| |____/|_| | |_|
1976
- `)
1977
- )
1978
-
1979
- console.log(chalk.cyan('\n📦 Welcome to prjct-cli!\n'))
2239
+ console.log('')
2240
+ console.log('')
2241
+ console.log(chalk.bold.magenta(' (ノ◕ヮ◕)ノ*:・゚✧'))
2242
+ console.log(chalk.bold.cyan(' ██████╗ ██████╗ ██╗ ██████╗████████╗'))
2243
+ console.log(chalk.bold.cyan(' ██╔══██╗██╔══██╗ ██║██╔════╝╚══██╔══╝'))
2244
+ console.log(chalk.bold.blue(' ██████╔╝██████╔╝ ██║██║ ██║'))
2245
+ console.log(chalk.bold.blue(' ██╔═══╝ ██╔══██╗██ ██║██║ ██║'))
2246
+ console.log(chalk.bold.magenta(' ██║ ██║ ██║╚█████╔╝╚██████╗ ██║'))
2247
+ console.log(chalk.bold.magenta(' ╚═╝ ╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═╝'))
2248
+ console.log('')
2249
+ console.log(chalk.dim.white(' Turn ideas into AI-ready roadmaps'))
2250
+ console.log('')
2251
+ console.log(chalk.yellow(' ⚡ Ship faster with zero friction'))
2252
+ console.log(chalk.green(' 📝 From idea to technical tasks in minutes'))
2253
+ console.log(chalk.cyan(' 🤖 Perfect context for AI agents'))
2254
+ console.log('')
2255
+
2256
+ console.log(chalk.bold.magenta('📦 Setup - Install Commands to AI Editors\n'))
1980
2257
 
1981
2258
  // Detect editors
1982
2259
  const commandInstaller = require('./command-installer')
@@ -1995,7 +2272,8 @@ ${diagram}
1995
2272
 
1996
2273
  console.log(chalk.cyan('🔍 Detected AI editors:'))
1997
2274
  detectedEditors.forEach(([key, info]) => {
1998
- console.log(chalk.green(` [] ${info.name} (${info.path})`))
2275
+ const editorName = commandInstaller.editors[key]?.name || key
2276
+ console.log(chalk.green(` [✓] ${editorName} (${info.path})`))
1999
2277
  })
2000
2278
  console.log('')
2001
2279
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "AI-integrated project management for indie hackers - works with Claude Code, Cursor, and Warp",
5
5
  "main": "core/index.js",
6
6
  "bin": {
@@ -81,34 +81,6 @@ async function main() {
81
81
  // Update version in config
82
82
  await editorsConfig.updateVersion(currentVersion)
83
83
 
84
- // Auto-migrate legacy projects (v0.1.0 → v0.4.4)
85
- try {
86
- console.log(chalk.cyan('\n🔍 Checking for legacy projects to migrate...\n'))
87
-
88
- const migrator = require('../core/migrator')
89
-
90
- // Quick scan of common directories (not entire home directory)
91
- const summary = await migrator.migrateAll({
92
- deepScan: false, // Only scan common dirs (fast)
93
- removeLegacy: false, // Keep legacy dirs for safety
94
- cleanupLegacy: true, // Remove old dirs but keep config
95
- dryRun: false, // Actually perform migration
96
- interactive: false // No user prompts during npm install
97
- })
98
-
99
- if (summary.successfullyMigrated > 0) {
100
- console.log(chalk.green(`✅ Migrated ${summary.successfullyMigrated} legacy project(s) to new structure`))
101
- console.log(chalk.gray(` Data location: ~/.prjct-cli/projects/`))
102
- } else if (summary.totalFound > 0 && summary.alreadyMigrated === summary.totalFound) {
103
- console.log(chalk.gray('ℹ️ All projects already using new structure'))
104
- }
105
- } catch (migrationError) {
106
- // Don't block install if migration fails
107
- if (process.env.DEBUG) {
108
- console.error(chalk.yellow('[post-install] Migration warning:'), migrationError.message)
109
- }
110
- }
111
-
112
84
  console.log(chalk.cyan(`\n✨ prjct-cli ${currentVersion} is ready!\n`))
113
85
 
114
86
  } catch (error) {