prjct-cli 0.44.1 → 0.45.3
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 +114 -0
- package/bin/prjct.ts +131 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +287 -29
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +49 -8
- package/core/commands/commands.ts +60 -23
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +14 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +583 -0
- package/core/context-tools/imports-tool.ts +403 -0
- package/core/context-tools/index.ts +433 -0
- package/core/context-tools/recent-tool.ts +307 -0
- package/core/context-tools/signatures-tool.ts +501 -0
- package/core/context-tools/summary-tool.ts +307 -0
- package/core/context-tools/token-counter.ts +284 -0
- package/core/context-tools/types.ts +253 -0
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -12
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +143 -0
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +170 -329
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -13
- package/core/storage/metrics-storage.ts +320 -0
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -302
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +49 -0
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18907 -13189
- package/dist/core/infrastructure/command-installer.js +96 -111
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +256 -257
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- package/templates/global/docs/validation.md +0 -95
package/core/utils/runtime.ts
CHANGED
|
@@ -32,7 +32,7 @@ export function isBunAvailable(): boolean {
|
|
|
32
32
|
|
|
33
33
|
// Check if bun command exists in PATH
|
|
34
34
|
try {
|
|
35
|
-
const { execSync } = require('child_process')
|
|
35
|
+
const { execSync } = require('node:child_process')
|
|
36
36
|
execSync('bun --version', { stdio: 'ignore' })
|
|
37
37
|
return true
|
|
38
38
|
} catch (_error) {
|
|
@@ -91,7 +91,7 @@ export function getPreferredRuntime(): Runtime {
|
|
|
91
91
|
*/
|
|
92
92
|
export function getRunCommand(scriptPath: string, args: string[] = []): string {
|
|
93
93
|
const runtime = getPreferredRuntime()
|
|
94
|
-
const argsStr = args.length > 0 ?
|
|
94
|
+
const argsStr = args.length > 0 ? ` ${args.join(' ')}` : ''
|
|
95
95
|
|
|
96
96
|
if (runtime === 'bun') {
|
|
97
97
|
return `bun ${scriptPath}${argsStr}`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import path from 'path'
|
|
1
|
+
import path from 'node:path'
|
|
2
2
|
import * as dateHelper from './date-helper'
|
|
3
|
-
import * as jsonlHelper from './jsonl-helper'
|
|
4
3
|
import * as fileHelper from './file-helper'
|
|
4
|
+
import * as jsonlHelper from './jsonl-helper'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Session Helper - High-level session operations
|
|
@@ -285,6 +285,5 @@ export default {
|
|
|
285
285
|
readRecentSessions,
|
|
286
286
|
getSessionStats,
|
|
287
287
|
archiveOldSessions,
|
|
288
|
-
cleanEmptySessionDirs
|
|
288
|
+
cleanEmptySessionDirs,
|
|
289
289
|
}
|
|
290
|
-
|
package/core/utils/version.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Version Manager - Single source of truth for application version
|
|
@@ -132,5 +132,5 @@ export default {
|
|
|
132
132
|
isCompatible,
|
|
133
133
|
needsMigration,
|
|
134
134
|
VERSION,
|
|
135
|
-
PACKAGE_ROOT
|
|
135
|
+
PACKAGE_ROOT,
|
|
136
136
|
}
|
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OnboardingWizard - Interactive first-run setup experience
|
|
3
|
+
*
|
|
4
|
+
* Guides new users through project setup in ~60 seconds:
|
|
5
|
+
* 1. Project type detection + confirmation
|
|
6
|
+
* 2. AI agent selection (multi-select)
|
|
7
|
+
* 3. Stack confirmation
|
|
8
|
+
* 4. Preferences collection
|
|
9
|
+
* 5. Generation + summary
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import chalk from 'chalk'
|
|
13
|
+
import prompts from 'prompts'
|
|
14
|
+
import out from '../utils/output'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export type ProjectType =
|
|
21
|
+
| 'web-app'
|
|
22
|
+
| 'api-backend'
|
|
23
|
+
| 'fullstack'
|
|
24
|
+
| 'cli-tool'
|
|
25
|
+
| 'library'
|
|
26
|
+
| 'monorepo'
|
|
27
|
+
| 'unknown'
|
|
28
|
+
|
|
29
|
+
export type AIAgent = 'claude' | 'cursor' | 'windsurf' | 'copilot' | 'gemini'
|
|
30
|
+
|
|
31
|
+
export interface DetectedStack {
|
|
32
|
+
language: string
|
|
33
|
+
framework?: string
|
|
34
|
+
runtime?: string
|
|
35
|
+
packageManager?: string
|
|
36
|
+
technologies: string[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WizardPreferences {
|
|
40
|
+
verbosity: 'minimal' | 'normal' | 'verbose'
|
|
41
|
+
autoSync: boolean
|
|
42
|
+
telemetry: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface WizardResult {
|
|
46
|
+
projectType: ProjectType
|
|
47
|
+
agents: AIAgent[]
|
|
48
|
+
stack: DetectedStack
|
|
49
|
+
preferences: WizardPreferences
|
|
50
|
+
skipped: boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface WizardStep {
|
|
54
|
+
id: string
|
|
55
|
+
title: string
|
|
56
|
+
run: () => Promise<boolean> // Returns true if should continue, false to abort
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Constants
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
const PROJECT_TYPES: { value: ProjectType; title: string; description: string }[] = [
|
|
64
|
+
{ value: 'web-app', title: 'Web Application', description: 'React, Vue, Angular, Next.js, etc.' },
|
|
65
|
+
{
|
|
66
|
+
value: 'api-backend',
|
|
67
|
+
title: 'API / Backend Service',
|
|
68
|
+
description: 'Express, Hono, FastAPI, etc.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
value: 'fullstack',
|
|
72
|
+
title: 'Full-Stack (Monorepo)',
|
|
73
|
+
description: 'Frontend + Backend in one repo',
|
|
74
|
+
},
|
|
75
|
+
{ value: 'cli-tool', title: 'CLI Tool', description: 'Command-line application' },
|
|
76
|
+
{ value: 'library', title: 'Library / Package', description: 'Reusable npm/pip/cargo package' },
|
|
77
|
+
{
|
|
78
|
+
value: 'monorepo',
|
|
79
|
+
title: 'Monorepo (Multiple Projects)',
|
|
80
|
+
description: 'Turborepo, Nx, Lerna, etc.',
|
|
81
|
+
},
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
const AI_AGENTS: { value: AIAgent; title: string; description: string }[] = [
|
|
85
|
+
{ value: 'claude', title: 'Claude Code', description: "Anthropic's Claude in VS Code/CLI" },
|
|
86
|
+
{ value: 'cursor', title: 'Cursor', description: 'AI-first code editor' },
|
|
87
|
+
{ value: 'windsurf', title: 'Windsurf', description: "Codeium's AI IDE" },
|
|
88
|
+
{ value: 'copilot', title: 'GitHub Copilot', description: "GitHub's AI pair programmer" },
|
|
89
|
+
{ value: 'gemini', title: 'Gemini CLI', description: "Google's Gemini in terminal" },
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// OnboardingWizard Class
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
export class OnboardingWizard {
|
|
97
|
+
private projectPath: string
|
|
98
|
+
private currentStep: number = 0
|
|
99
|
+
private totalSteps: number = 5
|
|
100
|
+
private aborted: boolean = false
|
|
101
|
+
|
|
102
|
+
// Collected data
|
|
103
|
+
private detectedType: ProjectType = 'unknown'
|
|
104
|
+
private confirmedType: ProjectType = 'unknown'
|
|
105
|
+
private selectedAgents: AIAgent[] = []
|
|
106
|
+
private detectedStack: DetectedStack = { language: 'Unknown', technologies: [] }
|
|
107
|
+
private confirmedStack: DetectedStack = { language: 'Unknown', technologies: [] }
|
|
108
|
+
private preferences: WizardPreferences = {
|
|
109
|
+
verbosity: 'normal',
|
|
110
|
+
autoSync: true,
|
|
111
|
+
telemetry: false,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
constructor(projectPath: string = process.cwd()) {
|
|
115
|
+
this.projectPath = projectPath
|
|
116
|
+
|
|
117
|
+
// Handle Ctrl+C gracefully
|
|
118
|
+
prompts.override({})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ==========================================================================
|
|
122
|
+
// Public API
|
|
123
|
+
// ==========================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Run the full wizard flow
|
|
127
|
+
*/
|
|
128
|
+
async run(): Promise<WizardResult> {
|
|
129
|
+
this.printWelcome()
|
|
130
|
+
|
|
131
|
+
const steps: WizardStep[] = [
|
|
132
|
+
{ id: 'project-type', title: 'Project Type', run: () => this.stepProjectType() },
|
|
133
|
+
{ id: 'ai-agents', title: 'AI Agents', run: () => this.stepAIAgents() },
|
|
134
|
+
{ id: 'stack', title: 'Stack Confirmation', run: () => this.stepStack() },
|
|
135
|
+
{ id: 'preferences', title: 'Preferences', run: () => this.stepPreferences() },
|
|
136
|
+
{ id: 'summary', title: 'Summary', run: () => this.stepSummary() },
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
for (const step of steps) {
|
|
140
|
+
this.currentStep++
|
|
141
|
+
this.printStepHeader(step.title)
|
|
142
|
+
|
|
143
|
+
const shouldContinue = await step.run()
|
|
144
|
+
|
|
145
|
+
if (!shouldContinue || this.aborted) {
|
|
146
|
+
return this.buildResult(true)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return this.buildResult(false)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Run in non-interactive mode (--yes flag)
|
|
155
|
+
* Uses all auto-detected values without prompting
|
|
156
|
+
*/
|
|
157
|
+
async runNonInteractive(): Promise<WizardResult> {
|
|
158
|
+
out.spin('Auto-detecting project configuration...')
|
|
159
|
+
|
|
160
|
+
// Auto-detect everything
|
|
161
|
+
this.detectedType = await this.detectProjectType()
|
|
162
|
+
this.confirmedType = this.detectedType
|
|
163
|
+
this.selectedAgents = ['claude'] // Default to Claude
|
|
164
|
+
this.detectedStack = await this.detectStack()
|
|
165
|
+
this.confirmedStack = this.detectedStack
|
|
166
|
+
// Use default preferences
|
|
167
|
+
|
|
168
|
+
out.done('Configuration detected')
|
|
169
|
+
|
|
170
|
+
return this.buildResult(false)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ==========================================================================
|
|
174
|
+
// Step Implementations
|
|
175
|
+
// ==========================================================================
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Step 1: Project Type Detection + Confirmation
|
|
179
|
+
*/
|
|
180
|
+
private async stepProjectType(): Promise<boolean> {
|
|
181
|
+
this.detectedType = await this.detectProjectType()
|
|
182
|
+
|
|
183
|
+
const response = await prompts(
|
|
184
|
+
{
|
|
185
|
+
type: 'select',
|
|
186
|
+
name: 'projectType',
|
|
187
|
+
message:
|
|
188
|
+
this.detectedType !== 'unknown'
|
|
189
|
+
? `Detected: ${this.getProjectTypeLabel(this.detectedType)}. Is this correct?`
|
|
190
|
+
: 'What type of project is this?',
|
|
191
|
+
choices: PROJECT_TYPES.map((pt) => ({
|
|
192
|
+
title: pt.title,
|
|
193
|
+
description: pt.description,
|
|
194
|
+
value: pt.value,
|
|
195
|
+
selected: pt.value === this.detectedType,
|
|
196
|
+
})),
|
|
197
|
+
initial: PROJECT_TYPES.findIndex((pt) => pt.value === this.detectedType),
|
|
198
|
+
},
|
|
199
|
+
{ onCancel: () => this.handleCancel() }
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
if (this.aborted) return false
|
|
203
|
+
|
|
204
|
+
this.confirmedType = response.projectType || this.detectedType
|
|
205
|
+
return true
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Step 2: AI Agent Selection (multi-select)
|
|
210
|
+
*/
|
|
211
|
+
private async stepAIAgents(): Promise<boolean> {
|
|
212
|
+
const detectedAgents = await this.detectInstalledAgents()
|
|
213
|
+
|
|
214
|
+
const response = await prompts(
|
|
215
|
+
{
|
|
216
|
+
type: 'multiselect',
|
|
217
|
+
name: 'agents',
|
|
218
|
+
message: 'Which AI agents do you use?',
|
|
219
|
+
choices: AI_AGENTS.map((agent) => ({
|
|
220
|
+
title: agent.title,
|
|
221
|
+
description: agent.description,
|
|
222
|
+
value: agent.value,
|
|
223
|
+
selected: detectedAgents.includes(agent.value),
|
|
224
|
+
})),
|
|
225
|
+
hint: '- Space to select, Enter to confirm',
|
|
226
|
+
instructions: false,
|
|
227
|
+
min: 1,
|
|
228
|
+
},
|
|
229
|
+
{ onCancel: () => this.handleCancel() }
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if (this.aborted) return false
|
|
233
|
+
|
|
234
|
+
this.selectedAgents = response.agents || ['claude']
|
|
235
|
+
return true
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Step 3: Stack Detection + Confirmation
|
|
240
|
+
*/
|
|
241
|
+
private async stepStack(): Promise<boolean> {
|
|
242
|
+
this.detectedStack = await this.detectStack()
|
|
243
|
+
|
|
244
|
+
const stackDisplay = this.formatStackDisplay(this.detectedStack)
|
|
245
|
+
console.log(chalk.dim(`\n Detected: ${stackDisplay}\n`))
|
|
246
|
+
|
|
247
|
+
const response = await prompts(
|
|
248
|
+
{
|
|
249
|
+
type: 'confirm',
|
|
250
|
+
name: 'confirmed',
|
|
251
|
+
message: 'Is this stack correct?',
|
|
252
|
+
initial: true,
|
|
253
|
+
},
|
|
254
|
+
{ onCancel: () => this.handleCancel() }
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if (this.aborted) return false
|
|
258
|
+
|
|
259
|
+
if (response.confirmed) {
|
|
260
|
+
this.confirmedStack = this.detectedStack
|
|
261
|
+
} else {
|
|
262
|
+
// Allow manual override
|
|
263
|
+
const manualResponse = await prompts(
|
|
264
|
+
[
|
|
265
|
+
{
|
|
266
|
+
type: 'text',
|
|
267
|
+
name: 'language',
|
|
268
|
+
message: 'Primary language:',
|
|
269
|
+
initial: this.detectedStack.language,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
type: 'text',
|
|
273
|
+
name: 'framework',
|
|
274
|
+
message: 'Framework (optional):',
|
|
275
|
+
initial: this.detectedStack.framework || '',
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
{ onCancel: () => this.handleCancel() }
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if (this.aborted) return false
|
|
282
|
+
|
|
283
|
+
this.confirmedStack = {
|
|
284
|
+
...this.detectedStack,
|
|
285
|
+
language: manualResponse.language || this.detectedStack.language,
|
|
286
|
+
framework: manualResponse.framework || undefined,
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Step 4: Preferences Collection
|
|
295
|
+
*/
|
|
296
|
+
private async stepPreferences(): Promise<boolean> {
|
|
297
|
+
const response = await prompts(
|
|
298
|
+
[
|
|
299
|
+
{
|
|
300
|
+
type: 'select',
|
|
301
|
+
name: 'verbosity',
|
|
302
|
+
message: 'Output verbosity:',
|
|
303
|
+
choices: [
|
|
304
|
+
{ title: 'Minimal', description: 'Essential output only', value: 'minimal' },
|
|
305
|
+
{ title: 'Normal (Recommended)', description: 'Balanced information', value: 'normal' },
|
|
306
|
+
{ title: 'Verbose', description: 'Detailed logging', value: 'verbose' },
|
|
307
|
+
],
|
|
308
|
+
initial: 1,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
type: 'confirm',
|
|
312
|
+
name: 'autoSync',
|
|
313
|
+
message: 'Auto-sync context on file changes?',
|
|
314
|
+
initial: true,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
{ onCancel: () => this.handleCancel() }
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if (this.aborted) return false
|
|
321
|
+
|
|
322
|
+
this.preferences = {
|
|
323
|
+
verbosity: response.verbosity || 'normal',
|
|
324
|
+
autoSync: response.autoSync ?? true,
|
|
325
|
+
telemetry: false, // Default off, can be enabled later
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return true
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Step 5: Summary + Generation
|
|
333
|
+
*/
|
|
334
|
+
private async stepSummary(): Promise<boolean> {
|
|
335
|
+
console.log('')
|
|
336
|
+
console.log(chalk.bold(' Configuration Summary'))
|
|
337
|
+
console.log(chalk.dim(` ${'─'.repeat(40)}`))
|
|
338
|
+
console.log(` ${chalk.cyan('Project Type:')} ${this.getProjectTypeLabel(this.confirmedType)}`)
|
|
339
|
+
console.log(
|
|
340
|
+
` ${chalk.cyan('AI Agents:')} ${this.selectedAgents.map((a) => this.getAgentLabel(a)).join(', ')}`
|
|
341
|
+
)
|
|
342
|
+
console.log(` ${chalk.cyan('Stack:')} ${this.formatStackDisplay(this.confirmedStack)}`)
|
|
343
|
+
console.log(` ${chalk.cyan('Verbosity:')} ${this.preferences.verbosity}`)
|
|
344
|
+
console.log(` ${chalk.cyan('Auto-sync:')} ${this.preferences.autoSync ? 'Yes' : 'No'}`)
|
|
345
|
+
console.log('')
|
|
346
|
+
|
|
347
|
+
const response = await prompts(
|
|
348
|
+
{
|
|
349
|
+
type: 'confirm',
|
|
350
|
+
name: 'proceed',
|
|
351
|
+
message: 'Generate configuration with these settings?',
|
|
352
|
+
initial: true,
|
|
353
|
+
},
|
|
354
|
+
{ onCancel: () => this.handleCancel() }
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if (this.aborted || !response.proceed) {
|
|
358
|
+
return false
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return true
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ==========================================================================
|
|
365
|
+
// Detection Methods
|
|
366
|
+
// ==========================================================================
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Detect project type from file system
|
|
370
|
+
*/
|
|
371
|
+
async detectProjectType(): Promise<ProjectType> {
|
|
372
|
+
const fs = await import('node:fs/promises')
|
|
373
|
+
const path = await import('node:path')
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const files = await fs.readdir(this.projectPath)
|
|
377
|
+
|
|
378
|
+
// Check for monorepo indicators
|
|
379
|
+
if (
|
|
380
|
+
files.includes('turbo.json') ||
|
|
381
|
+
files.includes('lerna.json') ||
|
|
382
|
+
files.includes('nx.json')
|
|
383
|
+
) {
|
|
384
|
+
return 'monorepo'
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Check for package.json
|
|
388
|
+
if (files.includes('package.json')) {
|
|
389
|
+
const pkgPath = path.join(this.projectPath, 'package.json')
|
|
390
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8')
|
|
391
|
+
const pkg = JSON.parse(pkgContent)
|
|
392
|
+
|
|
393
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
394
|
+
|
|
395
|
+
// CLI tool indicators
|
|
396
|
+
if (pkg.bin) return 'cli-tool'
|
|
397
|
+
|
|
398
|
+
// Library indicators
|
|
399
|
+
if (pkg.main && !deps.react && !deps.vue && !deps.angular && !deps.express && !deps.hono) {
|
|
400
|
+
return 'library'
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Full-stack indicators
|
|
404
|
+
if ((deps.react || deps.vue) && (deps.express || deps.hono || deps.fastify)) {
|
|
405
|
+
return 'fullstack'
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Frontend indicators
|
|
409
|
+
if (deps.react || deps.vue || deps['@angular/core'] || deps.next || deps.nuxt) {
|
|
410
|
+
return 'web-app'
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Backend indicators
|
|
414
|
+
if (deps.express || deps.hono || deps.fastify || deps.koa || deps.nestjs) {
|
|
415
|
+
return 'api-backend'
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Python project detection
|
|
420
|
+
if (files.includes('pyproject.toml') || files.includes('setup.py')) {
|
|
421
|
+
const hasServer = files.some((f) => ['main.py', 'app.py', 'server.py'].includes(f))
|
|
422
|
+
return hasServer ? 'api-backend' : 'library'
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Go project detection
|
|
426
|
+
if (files.includes('go.mod')) {
|
|
427
|
+
return files.includes('main.go') ? 'cli-tool' : 'library'
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Rust project detection
|
|
431
|
+
if (files.includes('Cargo.toml')) {
|
|
432
|
+
return 'cli-tool' // Most Rust CLIs
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return 'unknown'
|
|
436
|
+
} catch {
|
|
437
|
+
return 'unknown'
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Detect installed AI agents from config files
|
|
443
|
+
*/
|
|
444
|
+
async detectInstalledAgents(): Promise<AIAgent[]> {
|
|
445
|
+
const fs = await import('node:fs/promises')
|
|
446
|
+
const path = await import('node:path')
|
|
447
|
+
const os = await import('node:os')
|
|
448
|
+
|
|
449
|
+
const agents: AIAgent[] = []
|
|
450
|
+
|
|
451
|
+
// Claude Code: Check ~/.claude directory
|
|
452
|
+
try {
|
|
453
|
+
await fs.access(path.join(os.homedir(), '.claude'))
|
|
454
|
+
agents.push('claude')
|
|
455
|
+
} catch {
|
|
456
|
+
/* not installed */
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Cursor: Check for .cursorrules in project
|
|
460
|
+
try {
|
|
461
|
+
await fs.access(path.join(this.projectPath, '.cursorrules'))
|
|
462
|
+
agents.push('cursor')
|
|
463
|
+
} catch {
|
|
464
|
+
/* not installed */
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Windsurf: Check for .windsurfrules
|
|
468
|
+
try {
|
|
469
|
+
await fs.access(path.join(this.projectPath, '.windsurfrules'))
|
|
470
|
+
agents.push('windsurf')
|
|
471
|
+
} catch {
|
|
472
|
+
/* not installed */
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Copilot: Check for .github/copilot-instructions.md
|
|
476
|
+
try {
|
|
477
|
+
await fs.access(path.join(this.projectPath, '.github', 'copilot-instructions.md'))
|
|
478
|
+
agents.push('copilot')
|
|
479
|
+
} catch {
|
|
480
|
+
/* not installed */
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Gemini: Check ~/.gemini
|
|
484
|
+
try {
|
|
485
|
+
await fs.access(path.join(os.homedir(), '.gemini'))
|
|
486
|
+
agents.push('gemini')
|
|
487
|
+
} catch {
|
|
488
|
+
/* not installed */
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Default to Claude if nothing detected
|
|
492
|
+
return agents.length > 0 ? agents : ['claude']
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Detect tech stack from project files
|
|
497
|
+
*/
|
|
498
|
+
async detectStack(): Promise<DetectedStack> {
|
|
499
|
+
const fs = await import('node:fs/promises')
|
|
500
|
+
const path = await import('node:path')
|
|
501
|
+
|
|
502
|
+
const stack: DetectedStack = {
|
|
503
|
+
language: 'Unknown',
|
|
504
|
+
technologies: [],
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
const files = await fs.readdir(this.projectPath)
|
|
509
|
+
|
|
510
|
+
// Language detection
|
|
511
|
+
if (files.includes('package.json')) {
|
|
512
|
+
const pkgPath = path.join(this.projectPath, 'package.json')
|
|
513
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8')
|
|
514
|
+
const pkg = JSON.parse(pkgContent)
|
|
515
|
+
|
|
516
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
517
|
+
|
|
518
|
+
// TypeScript check
|
|
519
|
+
stack.language = deps.typescript ? 'TypeScript' : 'JavaScript'
|
|
520
|
+
|
|
521
|
+
// Framework detection
|
|
522
|
+
if (deps.next) stack.framework = 'Next.js'
|
|
523
|
+
else if (deps.nuxt) stack.framework = 'Nuxt'
|
|
524
|
+
else if (deps.react) stack.framework = 'React'
|
|
525
|
+
else if (deps.vue) stack.framework = 'Vue'
|
|
526
|
+
else if (deps['@angular/core']) stack.framework = 'Angular'
|
|
527
|
+
else if (deps.express) stack.framework = 'Express'
|
|
528
|
+
else if (deps.hono) stack.framework = 'Hono'
|
|
529
|
+
else if (deps.fastify) stack.framework = 'Fastify'
|
|
530
|
+
else if (deps.nestjs || deps['@nestjs/core']) stack.framework = 'NestJS'
|
|
531
|
+
|
|
532
|
+
// Runtime detection
|
|
533
|
+
if (deps.bun || deps['@types/bun']) stack.runtime = 'Bun'
|
|
534
|
+
else if (pkg.engines?.bun) stack.runtime = 'Bun'
|
|
535
|
+
else stack.runtime = 'Node.js'
|
|
536
|
+
|
|
537
|
+
// Package manager detection
|
|
538
|
+
if (files.includes('bun.lockb')) stack.packageManager = 'Bun'
|
|
539
|
+
else if (files.includes('pnpm-lock.yaml')) stack.packageManager = 'pnpm'
|
|
540
|
+
else if (files.includes('yarn.lock')) stack.packageManager = 'Yarn'
|
|
541
|
+
else if (files.includes('package-lock.json')) stack.packageManager = 'npm'
|
|
542
|
+
|
|
543
|
+
// Additional technologies
|
|
544
|
+
if (deps.prisma || deps['@prisma/client']) stack.technologies.push('Prisma')
|
|
545
|
+
if (deps.drizzle || deps['drizzle-orm']) stack.technologies.push('Drizzle')
|
|
546
|
+
if (deps.tailwindcss) stack.technologies.push('Tailwind CSS')
|
|
547
|
+
if (deps.zod) stack.technologies.push('Zod')
|
|
548
|
+
if (deps.trpc || deps['@trpc/server']) stack.technologies.push('tRPC')
|
|
549
|
+
} else if (files.includes('pyproject.toml') || files.includes('requirements.txt')) {
|
|
550
|
+
stack.language = 'Python'
|
|
551
|
+
// Could parse pyproject.toml for framework detection
|
|
552
|
+
} else if (files.includes('go.mod')) {
|
|
553
|
+
stack.language = 'Go'
|
|
554
|
+
} else if (files.includes('Cargo.toml')) {
|
|
555
|
+
stack.language = 'Rust'
|
|
556
|
+
} else if (files.includes('pom.xml') || files.includes('build.gradle')) {
|
|
557
|
+
stack.language = 'Java'
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return stack
|
|
561
|
+
} catch {
|
|
562
|
+
return stack
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ==========================================================================
|
|
567
|
+
// Helper Methods
|
|
568
|
+
// ==========================================================================
|
|
569
|
+
|
|
570
|
+
private printWelcome(): void {
|
|
571
|
+
console.log('')
|
|
572
|
+
console.log(chalk.bold.cyan(' Welcome to prjct-cli!'))
|
|
573
|
+
console.log(chalk.dim(" Let's set up your project in 60 seconds."))
|
|
574
|
+
console.log('')
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
private printStepHeader(title: string): void {
|
|
578
|
+
console.log('')
|
|
579
|
+
console.log(chalk.dim(` Step ${this.currentStep}/${this.totalSteps}: ${title}`))
|
|
580
|
+
console.log('')
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private handleCancel(): void {
|
|
584
|
+
this.aborted = true
|
|
585
|
+
console.log(chalk.yellow('\n Setup cancelled. Run again anytime.\n'))
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private getProjectTypeLabel(type: ProjectType): string {
|
|
589
|
+
return PROJECT_TYPES.find((pt) => pt.value === type)?.title || 'Unknown'
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
private getAgentLabel(agent: AIAgent): string {
|
|
593
|
+
return AI_AGENTS.find((a) => a.value === agent)?.title || agent
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private formatStackDisplay(stack: DetectedStack): string {
|
|
597
|
+
const parts = [stack.language]
|
|
598
|
+
if (stack.framework) parts.push(stack.framework)
|
|
599
|
+
if (stack.runtime && stack.runtime !== 'Node.js') parts.push(stack.runtime)
|
|
600
|
+
if (stack.technologies.length > 0) {
|
|
601
|
+
parts.push(`+ ${stack.technologies.slice(0, 3).join(', ')}`)
|
|
602
|
+
}
|
|
603
|
+
return parts.join(' / ')
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private buildResult(skipped: boolean): WizardResult {
|
|
607
|
+
return {
|
|
608
|
+
projectType: this.confirmedType,
|
|
609
|
+
agents: this.selectedAgents,
|
|
610
|
+
stack: this.confirmedStack,
|
|
611
|
+
preferences: this.preferences,
|
|
612
|
+
skipped,
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ==========================================================================
|
|
617
|
+
// Getters for external access
|
|
618
|
+
// ==========================================================================
|
|
619
|
+
|
|
620
|
+
getSelectedAgents(): AIAgent[] {
|
|
621
|
+
return this.selectedAgents
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
getConfirmedStack(): DetectedStack {
|
|
625
|
+
return this.confirmedStack
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
getPreferences(): WizardPreferences {
|
|
629
|
+
return this.preferences
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export default OnboardingWizard
|