prjct-cli 0.19.0 → 0.20.1
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 +66 -6
- package/CLAUDE.md +56 -15
- package/README.md +5 -6
- package/bin/prjct +59 -42
- package/bin/prjct.ts +60 -0
- package/core/__tests__/agentic/memory-system.test.ts +18 -3
- package/core/__tests__/agentic/plan-mode.test.ts +55 -26
- package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
- package/core/__tests__/utils/project-commands.test.ts +72 -0
- package/core/agentic/agent-router.ts +3 -12
- package/core/agentic/command-executor.ts +372 -3
- package/core/agentic/context-builder.ts +7 -27
- package/core/agentic/ground-truth.ts +604 -5
- package/core/agentic/index.ts +180 -0
- package/core/agentic/loop-detector.ts +418 -4
- package/core/agentic/memory-system.ts +857 -3
- package/core/agentic/plan-mode.ts +491 -4
- package/core/agentic/prompt-builder.ts +44 -65
- package/core/agentic/services.ts +13 -5
- package/core/agentic/skill-loader.ts +112 -0
- package/core/agentic/smart-context.ts +37 -122
- package/core/agentic/template-loader.ts +79 -122
- package/core/agentic/tool-registry.ts +5 -11
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +4 -2
- package/core/bus/bus.ts +262 -0
- package/core/bus/index.ts +3 -313
- package/core/commands/analysis.ts +5 -5
- package/core/commands/analytics.ts +11 -11
- package/core/commands/base.ts +33 -209
- package/core/commands/cleanup.ts +148 -0
- package/core/commands/command-data.ts +346 -0
- package/core/commands/commands.ts +216 -0
- package/core/commands/design.ts +83 -0
- package/core/commands/index.ts +13 -207
- package/core/commands/maintenance.ts +52 -473
- package/core/commands/planning.ts +3 -3
- package/core/commands/register.ts +104 -0
- package/core/commands/registry.ts +441 -0
- package/core/commands/setup.ts +25 -9
- package/core/commands/shipping.ts +48 -11
- package/core/commands/snapshots.ts +299 -0
- package/core/commands/workflow.ts +2 -2
- package/core/constants/index.ts +254 -4
- package/core/domain/agent-loader.ts +5 -6
- package/core/domain/task-stack.ts +555 -4
- package/core/errors.ts +127 -1
- package/core/events/events.ts +87 -0
- package/core/events/index.ts +4 -138
- package/core/index.ts +15 -23
- package/core/infrastructure/agent-detector.ts +126 -201
- package/core/infrastructure/author-detector.ts +99 -171
- package/core/infrastructure/command-installer.ts +476 -4
- package/core/infrastructure/config-manager.ts +41 -37
- package/core/infrastructure/path-manager.ts +59 -9
- package/core/infrastructure/permission-manager.ts +286 -0
- package/core/outcomes/analyzer.ts +7 -41
- package/core/outcomes/index.ts +1 -1
- package/core/outcomes/recorder.ts +1 -1
- package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
- package/core/plugin/loader.ts +5 -5
- package/core/plugin/registry.ts +2 -2
- package/core/schemas/ideas.ts +85 -54
- package/core/schemas/index.ts +14 -33
- package/core/schemas/permissions.ts +177 -0
- package/core/schemas/project.ts +39 -12
- package/core/schemas/roadmap.ts +94 -59
- package/core/schemas/schemas.ts +39 -0
- package/core/schemas/shipped.ts +87 -60
- package/core/schemas/state.ts +110 -70
- package/core/server/index.ts +21 -0
- package/core/server/routes.ts +165 -0
- package/core/server/server.ts +136 -0
- package/core/server/sse.ts +135 -0
- package/core/services/agent-service.ts +170 -0
- package/core/services/breakdown-service.ts +126 -0
- package/core/services/index.ts +21 -0
- package/core/services/memory-service.ts +108 -0
- package/core/services/project-service.ts +146 -0
- package/core/services/skill-service.ts +253 -0
- package/core/session/compaction.ts +257 -0
- package/core/session/index.ts +20 -8
- package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
- package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
- package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +10 -26
- package/core/storage/index.ts +14 -162
- package/core/storage/queue-storage.ts +13 -11
- package/core/storage/shipped-storage.ts +4 -17
- package/core/storage/state-storage.ts +35 -43
- package/core/storage/storage-manager.ts +42 -52
- package/core/storage/storage.ts +160 -0
- package/core/sync/auth-config.ts +1 -8
- package/core/sync/index.ts +17 -10
- package/core/sync/oauth-handler.ts +1 -6
- package/core/sync/sync-client.ts +6 -34
- package/core/sync/sync-manager.ts +11 -40
- package/core/types/agentic.ts +577 -0
- package/core/types/agents.ts +145 -0
- package/core/types/bus.ts +82 -0
- package/core/types/commands.ts +366 -0
- package/core/types/config.ts +66 -0
- package/core/types/core.ts +96 -0
- package/core/types/domain.ts +71 -0
- package/core/types/events.ts +42 -0
- package/core/types/fs.ts +56 -0
- package/core/types/index.ts +387 -500
- package/core/types/infrastructure.ts +196 -0
- package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
- package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
- package/core/types/plugin.ts +25 -0
- package/core/types/server.ts +54 -0
- package/core/types/services.ts +65 -0
- package/core/types/session.ts +135 -0
- package/core/types/storage.ts +148 -0
- package/core/types/sync.ts +121 -0
- package/core/types/task.ts +72 -0
- package/core/types/template.ts +24 -0
- package/core/types/utils.ts +90 -0
- package/core/utils/cache.ts +195 -0
- package/core/utils/collection-filters.ts +245 -0
- package/core/utils/date-helper.ts +1 -5
- package/core/utils/file-helper.ts +20 -10
- package/core/utils/jsonl-helper.ts +5 -8
- package/core/utils/markdown-builder.ts +277 -0
- package/core/utils/project-commands.ts +132 -0
- package/core/utils/runtime.ts +119 -0
- package/dist/bin/prjct.mjs +12568 -0
- package/package.json +13 -8
- package/scripts/build.js +106 -0
- package/scripts/postinstall.js +50 -8
- package/templates/agentic/agents/uxui.md +210 -0
- package/templates/agentic/subagent-generation.md +1 -1
- package/templates/commands/bug.md +219 -41
- package/templates/commands/feature.md +368 -80
- package/templates/commands/serve.md +118 -0
- package/templates/commands/ship.md +152 -14
- package/templates/commands/skill.md +110 -0
- package/templates/commands/sync.md +63 -4
- package/templates/commands/test.md +40 -188
- package/templates/mcp-config.json +0 -36
- package/templates/permissions/default.jsonc +60 -0
- package/templates/permissions/permissive.jsonc +49 -0
- package/templates/permissions/strict.jsonc +62 -0
- package/templates/skills/code-review.md +47 -0
- package/templates/skills/debug.md +61 -0
- package/templates/skills/refactor.md +47 -0
- package/templates/subagents/domain/devops.md +1 -1
- package/templates/subagents/domain/testing.md +6 -10
- package/templates/subagents/workflow/prjct-shipper.md +16 -7
- package/templates/tools/bash.txt +22 -0
- package/templates/tools/edit.txt +18 -0
- package/templates/tools/glob.txt +19 -0
- package/templates/tools/grep.txt +21 -0
- package/templates/tools/read.txt +14 -0
- package/templates/tools/task.txt +20 -0
- package/templates/tools/webfetch.txt +16 -0
- package/templates/tools/websearch.txt +18 -0
- package/templates/tools/write.txt +17 -0
- package/core/agentic/command-executor/command-executor.ts +0 -312
- package/core/agentic/command-executor/index.ts +0 -16
- package/core/agentic/command-executor/status-signal.ts +0 -38
- package/core/agentic/command-executor/types.ts +0 -79
- package/core/agentic/ground-truth/index.ts +0 -76
- package/core/agentic/ground-truth/types.ts +0 -33
- package/core/agentic/ground-truth/utils.ts +0 -48
- package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
- package/core/agentic/ground-truth/verifiers/done.ts +0 -75
- package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
- package/core/agentic/ground-truth/verifiers/index.ts +0 -37
- package/core/agentic/ground-truth/verifiers/init.ts +0 -52
- package/core/agentic/ground-truth/verifiers/now.ts +0 -57
- package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
- package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
- package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
- package/core/agentic/ground-truth/verifiers.ts +0 -6
- package/core/agentic/loop-detector/error-analysis.ts +0 -97
- package/core/agentic/loop-detector/hallucination.ts +0 -71
- package/core/agentic/loop-detector/index.ts +0 -41
- package/core/agentic/loop-detector/loop-detector.ts +0 -222
- package/core/agentic/loop-detector/types.ts +0 -66
- package/core/agentic/memory-system/history.ts +0 -53
- package/core/agentic/memory-system/index.ts +0 -192
- package/core/agentic/memory-system/patterns.ts +0 -156
- package/core/agentic/memory-system/semantic-memories.ts +0 -278
- package/core/agentic/memory-system/session.ts +0 -21
- package/core/agentic/plan-mode/approval.ts +0 -57
- package/core/agentic/plan-mode/constants.ts +0 -44
- package/core/agentic/plan-mode/index.ts +0 -28
- package/core/agentic/plan-mode/plan-mode.ts +0 -407
- package/core/agentic/plan-mode/types.ts +0 -193
- package/core/agents/types.ts +0 -126
- package/core/command-registry/categories.ts +0 -23
- package/core/command-registry/commands.ts +0 -15
- package/core/command-registry/core-commands.ts +0 -344
- package/core/command-registry/index.ts +0 -158
- package/core/command-registry/optional-commands.ts +0 -163
- package/core/command-registry/setup-commands.ts +0 -83
- package/core/command-registry/types.ts +0 -59
- package/core/command-registry.ts +0 -9
- package/core/commands/types.ts +0 -185
- package/core/commands.ts +0 -11
- package/core/constants/formats.ts +0 -187
- package/core/context-sync.ts +0 -18
- package/core/data/index.ts +0 -27
- package/core/data/md-base-manager.ts +0 -203
- package/core/data/md-ideas-manager.ts +0 -155
- package/core/data/md-queue-manager.ts +0 -180
- package/core/data/md-shipped-manager.ts +0 -90
- package/core/data/md-state-manager.ts +0 -137
- package/core/domain/task-stack/index.ts +0 -19
- package/core/domain/task-stack/parser.ts +0 -86
- package/core/domain/task-stack/storage.ts +0 -123
- package/core/domain/task-stack/task-stack.ts +0 -340
- package/core/domain/task-stack/types.ts +0 -51
- package/core/infrastructure/command-installer/command-installer.ts +0 -327
- package/core/infrastructure/command-installer/global-config.ts +0 -136
- package/core/infrastructure/command-installer/index.ts +0 -25
- package/core/infrastructure/command-installer/types.ts +0 -41
- package/core/infrastructure/session-manager/index.ts +0 -23
- package/core/infrastructure/session-manager/types.ts +0 -45
- package/core/infrastructure/session-manager.ts +0 -8
- package/core/serializers/ideas-serializer.ts +0 -187
- package/core/serializers/index.ts +0 -36
- package/core/serializers/queue-serializer.ts +0 -210
- package/core/serializers/shipped-serializer.ts +0 -108
- package/core/serializers/state-serializer.ts +0 -136
- package/core/session/types.ts +0 -29
- /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
|
@@ -1,8 +1,480 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Command Installer
|
|
3
|
+
* Installs prjct commands in Claude Code and other editors.
|
|
4
|
+
*
|
|
5
|
+
* 100% Claude-focused architecture
|
|
6
|
+
* Handles installation and synchronization of /p:* commands
|
|
7
|
+
* to Claude's native slash command system
|
|
8
|
+
*
|
|
9
|
+
* @version 0.5.0
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
|
-
import
|
|
7
|
-
|
|
12
|
+
import fs from 'fs/promises'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
import os from 'os'
|
|
15
|
+
import type {
|
|
16
|
+
InstallResult,
|
|
17
|
+
UninstallResult,
|
|
18
|
+
CheckResult,
|
|
19
|
+
SyncResult,
|
|
20
|
+
GlobalConfigResult,
|
|
21
|
+
} from '../types'
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Global Config
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Install documentation files to ~/.prjct-cli/docs/
|
|
29
|
+
*/
|
|
30
|
+
export async function installDocs(): Promise<{ success: boolean; error?: string }> {
|
|
31
|
+
try {
|
|
32
|
+
const docsDir = path.join(os.homedir(), '.prjct-cli', 'docs')
|
|
33
|
+
const templateDocsDir = path.join(__dirname, '../../templates/global/docs')
|
|
34
|
+
|
|
35
|
+
// Ensure docs directory exists
|
|
36
|
+
await fs.mkdir(docsDir, { recursive: true })
|
|
37
|
+
|
|
38
|
+
// Read all doc files from template
|
|
39
|
+
const docFiles = await fs.readdir(templateDocsDir)
|
|
40
|
+
|
|
41
|
+
// Copy each doc file
|
|
42
|
+
for (const file of docFiles) {
|
|
43
|
+
if (file.endsWith('.md')) {
|
|
44
|
+
const srcPath = path.join(templateDocsDir, file)
|
|
45
|
+
const destPath = path.join(docsDir, file)
|
|
46
|
+
const content = await fs.readFile(srcPath, 'utf-8')
|
|
47
|
+
await fs.writeFile(destPath, content, 'utf-8')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { success: true }
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return { success: false, error: (error as Error).message }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Install or update global CLAUDE.md configuration
|
|
59
|
+
*/
|
|
60
|
+
export async function installGlobalConfig(
|
|
61
|
+
claudeConfigPath: string,
|
|
62
|
+
detectClaude: () => Promise<boolean>
|
|
63
|
+
): Promise<GlobalConfigResult> {
|
|
64
|
+
const claudeDetected = await detectClaude()
|
|
65
|
+
|
|
66
|
+
if (!claudeDetected) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: 'Claude not detected',
|
|
70
|
+
action: 'skipped',
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Ensure ~/.claude directory exists
|
|
76
|
+
const claudeDir = path.join(os.homedir(), '.claude')
|
|
77
|
+
await fs.mkdir(claudeDir, { recursive: true })
|
|
78
|
+
|
|
79
|
+
const globalConfigPath = path.join(claudeDir, 'CLAUDE.md')
|
|
80
|
+
const templatePath = path.join(__dirname, '../../templates/global/CLAUDE.md')
|
|
81
|
+
|
|
82
|
+
// Read template content
|
|
83
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8')
|
|
84
|
+
|
|
85
|
+
// Check if global config already exists
|
|
86
|
+
let existingContent = ''
|
|
87
|
+
let fileExists = false
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
existingContent = await fs.readFile(globalConfigPath, 'utf-8')
|
|
91
|
+
fileExists = true
|
|
92
|
+
} catch {
|
|
93
|
+
// File doesn't exist, will create new
|
|
94
|
+
fileExists = false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!fileExists) {
|
|
98
|
+
// Create new file with full template
|
|
99
|
+
await fs.writeFile(globalConfigPath, templateContent, 'utf-8')
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
action: 'created',
|
|
103
|
+
path: globalConfigPath,
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// File exists - perform intelligent merge
|
|
107
|
+
const startMarker = '<!-- prjct:start - DO NOT REMOVE THIS MARKER -->'
|
|
108
|
+
const endMarker = '<!-- prjct:end - DO NOT REMOVE THIS MARKER -->'
|
|
109
|
+
|
|
110
|
+
// Check if markers exist in existing file
|
|
111
|
+
const hasMarkers =
|
|
112
|
+
existingContent.includes(startMarker) && existingContent.includes(endMarker)
|
|
113
|
+
|
|
114
|
+
if (!hasMarkers) {
|
|
115
|
+
// No markers - append prjct section at the end
|
|
116
|
+
const updatedContent = existingContent + '\n\n' + templateContent
|
|
117
|
+
await fs.writeFile(globalConfigPath, updatedContent, 'utf-8')
|
|
118
|
+
return {
|
|
119
|
+
success: true,
|
|
120
|
+
action: 'appended',
|
|
121
|
+
path: globalConfigPath,
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Markers exist - replace content between markers
|
|
125
|
+
const beforeMarker = existingContent.substring(0, existingContent.indexOf(startMarker))
|
|
126
|
+
const afterMarker = existingContent.substring(
|
|
127
|
+
existingContent.indexOf(endMarker) + endMarker.length
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Extract prjct section from template
|
|
131
|
+
const prjctSection = templateContent.substring(
|
|
132
|
+
templateContent.indexOf(startMarker),
|
|
133
|
+
templateContent.indexOf(endMarker) + endMarker.length
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const updatedContent = beforeMarker + prjctSection + afterMarker
|
|
137
|
+
await fs.writeFile(globalConfigPath, updatedContent, 'utf-8')
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
action: 'updated',
|
|
141
|
+
path: globalConfigPath,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
error: (error as Error).message,
|
|
149
|
+
action: 'failed',
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// =============================================================================
|
|
155
|
+
// Command Installer
|
|
156
|
+
// =============================================================================
|
|
157
|
+
|
|
158
|
+
export class CommandInstaller {
|
|
159
|
+
homeDir: string
|
|
160
|
+
claudeCommandsPath: string
|
|
161
|
+
claudeConfigPath: string
|
|
162
|
+
templatesDir: string
|
|
163
|
+
|
|
164
|
+
constructor() {
|
|
165
|
+
this.homeDir = os.homedir()
|
|
166
|
+
this.claudeCommandsPath = path.join(this.homeDir, '.claude', 'commands', 'p')
|
|
167
|
+
this.claudeConfigPath = path.join(this.homeDir, '.claude')
|
|
168
|
+
this.templatesDir = path.join(__dirname, '..', '..', 'templates', 'commands')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Detect if Claude is installed
|
|
173
|
+
*/
|
|
174
|
+
async detectClaude(): Promise<boolean> {
|
|
175
|
+
try {
|
|
176
|
+
await fs.access(this.claudeConfigPath)
|
|
177
|
+
return true
|
|
178
|
+
} catch {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get list of command files to install
|
|
185
|
+
*/
|
|
186
|
+
async getCommandFiles(): Promise<string[]> {
|
|
187
|
+
try {
|
|
188
|
+
const files = await fs.readdir(this.templatesDir)
|
|
189
|
+
return files.filter((f) => f.endsWith('.md'))
|
|
190
|
+
} catch {
|
|
191
|
+
// Fallback to core commands if template directory not accessible
|
|
192
|
+
return [
|
|
193
|
+
'init.md',
|
|
194
|
+
'now.md',
|
|
195
|
+
'done.md',
|
|
196
|
+
'ship.md',
|
|
197
|
+
'next.md',
|
|
198
|
+
'idea.md',
|
|
199
|
+
'recap.md',
|
|
200
|
+
'progress.md',
|
|
201
|
+
'stuck.md',
|
|
202
|
+
'context.md',
|
|
203
|
+
'analyze.md',
|
|
204
|
+
'sync.md',
|
|
205
|
+
'roadmap.md',
|
|
206
|
+
'task.md',
|
|
207
|
+
'git.md',
|
|
208
|
+
'fix.md',
|
|
209
|
+
'test.md',
|
|
210
|
+
'cleanup.md',
|
|
211
|
+
'design.md',
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Install commands to Claude
|
|
218
|
+
*/
|
|
219
|
+
async installCommands(): Promise<InstallResult> {
|
|
220
|
+
const claudeDetected = await this.detectClaude()
|
|
221
|
+
|
|
222
|
+
if (!claudeDetected) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: 'Claude not detected. Please install Claude Code or Claude Desktop first.',
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
// Ensure commands directory exists
|
|
231
|
+
await fs.mkdir(this.claudeCommandsPath, { recursive: true })
|
|
232
|
+
|
|
233
|
+
const commandFiles = await this.getCommandFiles()
|
|
234
|
+
const installed: string[] = []
|
|
235
|
+
const errors: Array<{ file: string; error: string }> = []
|
|
236
|
+
|
|
237
|
+
for (const file of commandFiles) {
|
|
238
|
+
try {
|
|
239
|
+
const sourcePath = path.join(this.templatesDir, file)
|
|
240
|
+
const destPath = path.join(this.claudeCommandsPath, file)
|
|
241
|
+
|
|
242
|
+
const content = await fs.readFile(sourcePath, 'utf-8')
|
|
243
|
+
await fs.writeFile(destPath, content, 'utf-8')
|
|
244
|
+
|
|
245
|
+
installed.push(file.replace('.md', ''))
|
|
246
|
+
} catch (error) {
|
|
247
|
+
errors.push({ file, error: (error as Error).message })
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
installed,
|
|
254
|
+
errors,
|
|
255
|
+
path: this.claudeCommandsPath,
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
error: (error as Error).message,
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Uninstall commands from Claude
|
|
267
|
+
*/
|
|
268
|
+
async uninstallCommands(): Promise<UninstallResult> {
|
|
269
|
+
try {
|
|
270
|
+
const commandFiles = await this.getCommandFiles()
|
|
271
|
+
const uninstalled: string[] = []
|
|
272
|
+
const errors: Array<{ file: string; error: string }> = []
|
|
273
|
+
|
|
274
|
+
for (const file of commandFiles) {
|
|
275
|
+
try {
|
|
276
|
+
const filePath = path.join(this.claudeCommandsPath, file)
|
|
277
|
+
await fs.unlink(filePath)
|
|
278
|
+
uninstalled.push(file.replace('.md', ''))
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
281
|
+
errors.push({ file, error: (error as Error).message })
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Try to remove the /p directory if empty
|
|
287
|
+
try {
|
|
288
|
+
await fs.rmdir(this.claudeCommandsPath)
|
|
289
|
+
} catch {
|
|
290
|
+
// Directory not empty or doesn't exist - that's fine
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
success: true,
|
|
295
|
+
uninstalled,
|
|
296
|
+
errors,
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
return {
|
|
300
|
+
success: false,
|
|
301
|
+
error: (error as Error).message,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check if commands are already installed
|
|
308
|
+
*/
|
|
309
|
+
async checkInstallation(): Promise<CheckResult> {
|
|
310
|
+
const claudeDetected = await this.detectClaude()
|
|
311
|
+
|
|
312
|
+
if (!claudeDetected) {
|
|
313
|
+
return {
|
|
314
|
+
installed: false,
|
|
315
|
+
claudeDetected: false,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
await fs.access(this.claudeCommandsPath)
|
|
321
|
+
const files = await fs.readdir(this.claudeCommandsPath)
|
|
322
|
+
const installedCommands = files
|
|
323
|
+
.filter((f) => f.endsWith('.md'))
|
|
324
|
+
.map((f) => f.replace('.md', ''))
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
installed: installedCommands.length > 0,
|
|
328
|
+
claudeDetected: true,
|
|
329
|
+
commands: installedCommands,
|
|
330
|
+
path: this.claudeCommandsPath,
|
|
331
|
+
}
|
|
332
|
+
} catch {
|
|
333
|
+
return {
|
|
334
|
+
installed: false,
|
|
335
|
+
claudeDetected: true,
|
|
336
|
+
commands: [],
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Update commands (reinstall with latest templates)
|
|
343
|
+
*/
|
|
344
|
+
async updateCommands(): Promise<InstallResult> {
|
|
345
|
+
// Simply reinstall - will overwrite with latest templates
|
|
346
|
+
console.log('Updating commands with latest templates...')
|
|
347
|
+
const result = await this.installCommands()
|
|
348
|
+
if (result.success && result.installed) {
|
|
349
|
+
console.log(`Updated ${result.installed.length} commands`)
|
|
350
|
+
}
|
|
351
|
+
return result
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Install to all detected editors (alias for installCommands)
|
|
356
|
+
*/
|
|
357
|
+
async installToAll(): Promise<InstallResult> {
|
|
358
|
+
return await this.installCommands()
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Get installation path for Claude commands
|
|
363
|
+
*/
|
|
364
|
+
getInstallPath(): string {
|
|
365
|
+
return this.claudeCommandsPath
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Verify command template exists
|
|
370
|
+
*/
|
|
371
|
+
async verifyTemplate(commandName: string): Promise<boolean> {
|
|
372
|
+
try {
|
|
373
|
+
const templatePath = path.join(this.templatesDir, `${commandName}.md`)
|
|
374
|
+
await fs.access(templatePath)
|
|
375
|
+
return true
|
|
376
|
+
} catch {
|
|
377
|
+
return false
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Sync commands - intelligent update that detects and removes orphans
|
|
383
|
+
*/
|
|
384
|
+
async syncCommands(): Promise<SyncResult> {
|
|
385
|
+
const claudeDetected = await this.detectClaude()
|
|
386
|
+
|
|
387
|
+
if (!claudeDetected) {
|
|
388
|
+
return {
|
|
389
|
+
success: false,
|
|
390
|
+
error: 'Claude not detected',
|
|
391
|
+
added: 0,
|
|
392
|
+
updated: 0,
|
|
393
|
+
removed: 0,
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
// Ensure commands directory exists
|
|
399
|
+
await fs.mkdir(this.claudeCommandsPath, { recursive: true })
|
|
400
|
+
|
|
401
|
+
// Get current state
|
|
402
|
+
const templateFiles = await this.getCommandFiles()
|
|
403
|
+
let installedFiles: string[] = []
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
installedFiles = await fs.readdir(this.claudeCommandsPath)
|
|
407
|
+
installedFiles = installedFiles.filter((f) => f.endsWith('.md'))
|
|
408
|
+
} catch {
|
|
409
|
+
// Directory doesn't exist yet
|
|
410
|
+
installedFiles = []
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const results: SyncResult = {
|
|
414
|
+
success: true,
|
|
415
|
+
added: 0,
|
|
416
|
+
updated: 0,
|
|
417
|
+
removed: 0,
|
|
418
|
+
errors: [],
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Install/update all template files (always overwrite)
|
|
422
|
+
for (const file of templateFiles) {
|
|
423
|
+
try {
|
|
424
|
+
const sourcePath = path.join(this.templatesDir, file)
|
|
425
|
+
const destPath = path.join(this.claudeCommandsPath, file)
|
|
426
|
+
|
|
427
|
+
// Check if file exists in installed location
|
|
428
|
+
const exists = installedFiles.includes(file)
|
|
429
|
+
|
|
430
|
+
// Read and write (always overwrite to ensure latest version)
|
|
431
|
+
const content = await fs.readFile(sourcePath, 'utf-8')
|
|
432
|
+
await fs.writeFile(destPath, content, 'utf-8')
|
|
433
|
+
|
|
434
|
+
if (!exists) {
|
|
435
|
+
results.added++
|
|
436
|
+
} else {
|
|
437
|
+
results.updated++
|
|
438
|
+
}
|
|
439
|
+
} catch (error) {
|
|
440
|
+
results.errors!.push({ file, error: (error as Error).message })
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Note: We do NOT remove orphaned files
|
|
445
|
+
// Legacy commands from older versions are preserved
|
|
446
|
+
// to avoid breaking existing workflows
|
|
447
|
+
|
|
448
|
+
return results
|
|
449
|
+
} catch (error) {
|
|
450
|
+
return {
|
|
451
|
+
success: false,
|
|
452
|
+
error: (error as Error).message,
|
|
453
|
+
added: 0,
|
|
454
|
+
updated: 0,
|
|
455
|
+
removed: 0,
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Install or update global CLAUDE.md configuration
|
|
462
|
+
*/
|
|
463
|
+
async installGlobalConfig(): Promise<GlobalConfigResult> {
|
|
464
|
+
return installGlobalConfig(this.claudeConfigPath, () => this.detectClaude())
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Install documentation files to ~/.prjct-cli/docs/
|
|
469
|
+
*/
|
|
470
|
+
async installDocs(): Promise<{ success: boolean; error?: string }> {
|
|
471
|
+
return installDocs()
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// =============================================================================
|
|
476
|
+
// Exports
|
|
477
|
+
// =============================================================================
|
|
478
|
+
|
|
479
|
+
const commandInstaller = new CommandInstaller()
|
|
8
480
|
export default commandInstaller
|
|
@@ -2,59 +2,58 @@
|
|
|
2
2
|
* ConfigManager - Manages prjct.config.json files
|
|
3
3
|
*
|
|
4
4
|
* Key responsibilities:
|
|
5
|
-
* - Read and write prjct.config.json
|
|
5
|
+
* - Read and write prjct.config.json (supports .jsonc with comments)
|
|
6
6
|
* - Validate configuration structure
|
|
7
7
|
* - Create new configurations
|
|
8
8
|
* - Update existing configurations
|
|
9
9
|
*
|
|
10
|
-
* @version 0.
|
|
10
|
+
* @version 0.3.0
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import fs from 'fs/promises'
|
|
14
14
|
import path from 'path'
|
|
15
|
+
import * as jsonc from 'jsonc-parser'
|
|
15
16
|
import pathManager from './path-manager'
|
|
16
17
|
import authorDetector from './author-detector'
|
|
17
18
|
import { VERSION } from '../utils/version'
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
email: string
|
|
23
|
-
github: string
|
|
24
|
-
firstContribution?: string
|
|
25
|
-
lastActivity?: string
|
|
26
|
-
}
|
|
19
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
20
|
+
import { getErrorMessage } from '../errors'
|
|
21
|
+
import { isNotFoundError } from '../types/fs'
|
|
22
|
+
import type { Author, LocalConfig, GlobalConfig } from '../types'
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
dataPath: string
|
|
31
|
-
authors?: Author[]
|
|
32
|
-
author?: Author
|
|
33
|
-
version?: string
|
|
34
|
-
created?: string
|
|
35
|
-
lastSync?: string
|
|
36
|
-
}
|
|
24
|
+
// Re-export types for convenience
|
|
25
|
+
export type { Author, LocalConfig, GlobalConfig } from '../types'
|
|
37
26
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Parse JSON or JSONC content safely
|
|
29
|
+
* Supports comments (line and block style) in config files
|
|
30
|
+
*/
|
|
31
|
+
function parseJsonc<T>(content: string): T {
|
|
32
|
+
const errors: jsonc.ParseError[] = []
|
|
33
|
+
const result = jsonc.parse(content, errors, {
|
|
34
|
+
allowTrailingComma: true,
|
|
35
|
+
disallowComments: false,
|
|
36
|
+
})
|
|
37
|
+
if (errors.length > 0) {
|
|
38
|
+
const firstError = errors[0]
|
|
39
|
+
throw new SyntaxError(`JSON parse error at offset ${firstError.offset}: ${jsonc.printParseErrorCode(firstError.error)}`)
|
|
40
|
+
}
|
|
41
|
+
return result
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
class ConfigManager {
|
|
47
45
|
/**
|
|
48
46
|
* Read the project configuration file
|
|
47
|
+
* Supports both .json and .jsonc formats (with comments)
|
|
49
48
|
*/
|
|
50
49
|
async readConfig(projectPath: string): Promise<LocalConfig | null> {
|
|
51
50
|
try {
|
|
52
51
|
const configPath = pathManager.getLocalConfigPath(projectPath)
|
|
53
52
|
const content = await fs.readFile(configPath, 'utf-8')
|
|
54
|
-
return
|
|
53
|
+
return parseJsonc<LocalConfig>(content)
|
|
55
54
|
} catch (error) {
|
|
56
55
|
// File not found is expected - return null
|
|
57
|
-
if ((error
|
|
56
|
+
if (isNotFoundError(error)) {
|
|
58
57
|
return null
|
|
59
58
|
}
|
|
60
59
|
// JSON parse errors or other issues - log and return null
|
|
@@ -79,15 +78,16 @@ class ConfigManager {
|
|
|
79
78
|
/**
|
|
80
79
|
* Read the global project configuration file
|
|
81
80
|
* Contains authors array and other system data
|
|
81
|
+
* Supports both .json and .jsonc formats (with comments)
|
|
82
82
|
*/
|
|
83
83
|
async readGlobalConfig(projectId: string): Promise<GlobalConfig | null> {
|
|
84
84
|
try {
|
|
85
85
|
const configPath = pathManager.getGlobalProjectConfigPath(projectId)
|
|
86
86
|
const content = await fs.readFile(configPath, 'utf-8')
|
|
87
|
-
return
|
|
87
|
+
return parseJsonc<GlobalConfig>(content)
|
|
88
88
|
} catch (error) {
|
|
89
89
|
// File not found is expected for new projects
|
|
90
|
-
if ((error
|
|
90
|
+
if (isNotFoundError(error)) {
|
|
91
91
|
return null
|
|
92
92
|
}
|
|
93
93
|
// Log other errors for debugging
|
|
@@ -116,7 +116,7 @@ class ConfigManager {
|
|
|
116
116
|
let globalConfig = await this.readGlobalConfig(projectId)
|
|
117
117
|
|
|
118
118
|
if (!globalConfig) {
|
|
119
|
-
const now =
|
|
119
|
+
const now = getTimestamp()
|
|
120
120
|
globalConfig = {
|
|
121
121
|
projectId,
|
|
122
122
|
authors: [],
|
|
@@ -139,7 +139,7 @@ class ConfigManager {
|
|
|
139
139
|
const projectId = pathManager.generateProjectId(projectPath)
|
|
140
140
|
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
141
141
|
const displayPath = pathManager.getDisplayPath(globalPath)
|
|
142
|
-
const now =
|
|
142
|
+
const now = getTimestamp()
|
|
143
143
|
|
|
144
144
|
const localConfig: LocalConfig = {
|
|
145
145
|
projectId,
|
|
@@ -176,7 +176,7 @@ class ConfigManager {
|
|
|
176
176
|
const projectId = await this.getProjectId(projectPath)
|
|
177
177
|
const globalConfig = await this.readGlobalConfig(projectId)
|
|
178
178
|
if (globalConfig) {
|
|
179
|
-
globalConfig.lastSync =
|
|
179
|
+
globalConfig.lastSync = getTimestamp()
|
|
180
180
|
await this.writeGlobalConfig(projectId, globalConfig)
|
|
181
181
|
}
|
|
182
182
|
}
|
|
@@ -218,7 +218,7 @@ class ConfigManager {
|
|
|
218
218
|
return coreFiles.length === 0
|
|
219
219
|
} catch (error) {
|
|
220
220
|
// Directory not found means migration needed
|
|
221
|
-
if ((error
|
|
221
|
+
if (isNotFoundError(error)) {
|
|
222
222
|
return true
|
|
223
223
|
}
|
|
224
224
|
// Permission errors or other issues - assume migration needed
|
|
@@ -261,7 +261,7 @@ class ConfigManager {
|
|
|
261
261
|
const exists = globalConfig.authors.some((a) => a.github === author.github)
|
|
262
262
|
if (exists) return
|
|
263
263
|
|
|
264
|
-
const now =
|
|
264
|
+
const now = getTimestamp()
|
|
265
265
|
globalConfig.authors.push({
|
|
266
266
|
name: author.name || 'Unknown',
|
|
267
267
|
email: author.email || '',
|
|
@@ -284,7 +284,7 @@ class ConfigManager {
|
|
|
284
284
|
|
|
285
285
|
const author = globalConfig.authors.find((a) => a.github === githubUsername)
|
|
286
286
|
if (author) {
|
|
287
|
-
author.lastActivity =
|
|
287
|
+
author.lastActivity = getTimestamp()
|
|
288
288
|
globalConfig.lastSync = author.lastActivity
|
|
289
289
|
await this.writeGlobalConfig(projectId, globalConfig)
|
|
290
290
|
}
|
|
@@ -297,7 +297,11 @@ class ConfigManager {
|
|
|
297
297
|
const author = await authorDetector.detect()
|
|
298
298
|
|
|
299
299
|
const projectId = await this.getProjectId(projectPath)
|
|
300
|
-
await this.addAuthor(projectId,
|
|
300
|
+
await this.addAuthor(projectId, {
|
|
301
|
+
name: author.name ?? undefined,
|
|
302
|
+
email: author.email ?? undefined,
|
|
303
|
+
github: author.github ?? undefined
|
|
304
|
+
})
|
|
301
305
|
|
|
302
306
|
return author.github || author.name || 'Unknown'
|
|
303
307
|
}
|