prjct-cli 0.4.0

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +312 -0
  2. package/CLAUDE.md +300 -0
  3. package/LICENSE +21 -0
  4. package/README.md +424 -0
  5. package/bin/prjct +214 -0
  6. package/core/agent-detector.js +249 -0
  7. package/core/agents/claude-agent.js +250 -0
  8. package/core/agents/codex-agent.js +256 -0
  9. package/core/agents/terminal-agent.js +465 -0
  10. package/core/analyzer.js +596 -0
  11. package/core/animations-simple.js +240 -0
  12. package/core/animations.js +277 -0
  13. package/core/author-detector.js +218 -0
  14. package/core/capability-installer.js +190 -0
  15. package/core/command-installer.js +775 -0
  16. package/core/commands.js +2050 -0
  17. package/core/config-manager.js +335 -0
  18. package/core/migrator.js +784 -0
  19. package/core/path-manager.js +324 -0
  20. package/core/project-capabilities.js +144 -0
  21. package/core/session-manager.js +439 -0
  22. package/core/version.js +107 -0
  23. package/core/workflow-engine.js +213 -0
  24. package/core/workflow-prompts.js +192 -0
  25. package/core/workflow-rules.js +147 -0
  26. package/package.json +80 -0
  27. package/scripts/install.sh +433 -0
  28. package/scripts/verify-installation.sh +158 -0
  29. package/templates/agents/AGENTS.md +164 -0
  30. package/templates/commands/analyze.md +125 -0
  31. package/templates/commands/cleanup.md +102 -0
  32. package/templates/commands/context.md +105 -0
  33. package/templates/commands/design.md +113 -0
  34. package/templates/commands/done.md +44 -0
  35. package/templates/commands/fix.md +87 -0
  36. package/templates/commands/git.md +79 -0
  37. package/templates/commands/help.md +72 -0
  38. package/templates/commands/idea.md +50 -0
  39. package/templates/commands/init.md +237 -0
  40. package/templates/commands/next.md +74 -0
  41. package/templates/commands/now.md +35 -0
  42. package/templates/commands/progress.md +92 -0
  43. package/templates/commands/recap.md +86 -0
  44. package/templates/commands/roadmap.md +107 -0
  45. package/templates/commands/ship.md +41 -0
  46. package/templates/commands/stuck.md +48 -0
  47. package/templates/commands/task.md +97 -0
  48. package/templates/commands/test.md +94 -0
  49. package/templates/commands/workflow.md +224 -0
  50. package/templates/examples/natural-language-examples.md +320 -0
  51. package/templates/mcp-config.json +8 -0
  52. package/templates/workflows/analyze.md +159 -0
  53. package/templates/workflows/cleanup.md +73 -0
  54. package/templates/workflows/context.md +72 -0
  55. package/templates/workflows/design.md +88 -0
  56. package/templates/workflows/done.md +20 -0
  57. package/templates/workflows/fix.md +201 -0
  58. package/templates/workflows/git.md +192 -0
  59. package/templates/workflows/help.md +13 -0
  60. package/templates/workflows/idea.md +22 -0
  61. package/templates/workflows/init.md +80 -0
  62. package/templates/workflows/natural-language-handler.md +183 -0
  63. package/templates/workflows/next.md +44 -0
  64. package/templates/workflows/now.md +19 -0
  65. package/templates/workflows/progress.md +113 -0
  66. package/templates/workflows/recap.md +66 -0
  67. package/templates/workflows/roadmap.md +95 -0
  68. package/templates/workflows/ship.md +18 -0
  69. package/templates/workflows/stuck.md +25 -0
  70. package/templates/workflows/task.md +109 -0
  71. package/templates/workflows/test.md +243 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Agent Detection Module for prjct-cli
3
+ * Automatically detects which AI agent is executing commands
4
+ */
5
+
6
+ const fs = require('fs')
7
+ const path = require('path')
8
+
9
+ class AgentDetector {
10
+ constructor() {
11
+ this.detectedAgent = null
12
+ this.detectionMethods = [
13
+ this.detectByEnvironmentVariables.bind(this),
14
+ this.detectByConfigFiles.bind(this),
15
+ this.detectByRuntimeCapabilities.bind(this),
16
+ this.detectByFileSystem.bind(this),
17
+ ]
18
+ }
19
+
20
+ /**
21
+ * Main detection method - tries multiple strategies
22
+ * @returns {Object} Agent information
23
+ */
24
+ async detect() {
25
+ if (this.detectedAgent) {
26
+ return this.detectedAgent
27
+ }
28
+
29
+ for (const method of this.detectionMethods) {
30
+ const result = await method()
31
+ if (result) {
32
+ this.detectedAgent = result
33
+ return result
34
+ }
35
+ }
36
+
37
+ this.detectedAgent = this.getTerminalAgent()
38
+ return this.detectedAgent
39
+ }
40
+
41
+ /**
42
+ * Detect agent by environment variables
43
+ */
44
+ async detectByEnvironmentVariables() {
45
+ if (process.env.CODEX_AGENT || process.env.OPENAI_CODEX) {
46
+ return this.getCodexAgent()
47
+ }
48
+
49
+ if (process.env.CLAUDE_AGENT || process.env.ANTHROPIC_CLAUDE) {
50
+ return this.getClaudeAgent()
51
+ }
52
+
53
+ if (process.env.CODESPACES) {
54
+ return this.getCodexAgent()
55
+ }
56
+
57
+ return null
58
+ }
59
+
60
+ /**
61
+ * Detect agent by configuration files
62
+ */
63
+ async detectByConfigFiles() {
64
+ const projectRoot = process.cwd()
65
+
66
+ if (fs.existsSync(path.join(projectRoot, 'AGENTS.md'))) {
67
+ if (!fs.existsSync(path.join(projectRoot, '.claude'))) {
68
+ return this.getCodexAgent()
69
+ }
70
+ }
71
+
72
+ if (fs.existsSync(path.join(projectRoot, 'CLAUDE.md'))) {
73
+ return this.getClaudeAgent()
74
+ }
75
+
76
+ if (fs.existsSync(path.join(process.env.HOME || '', '.claude'))) {
77
+ return this.getClaudeAgent()
78
+ }
79
+
80
+ return null
81
+ }
82
+
83
+ /**
84
+ * Detect agent by runtime capabilities
85
+ */
86
+ async detectByRuntimeCapabilities() {
87
+ try {
88
+ if (global.mcp || process.env.MCP_AVAILABLE) {
89
+ return this.getClaudeAgent()
90
+ }
91
+ } catch (e) {
92
+ }
93
+
94
+ if (this.isRunningInContainer()) {
95
+ return this.getCodexAgent()
96
+ }
97
+
98
+ return null
99
+ }
100
+
101
+ /**
102
+ * Detect agent by filesystem characteristics
103
+ */
104
+ async detectByFileSystem() {
105
+ if (process.cwd().includes('/sandbox/') || process.cwd().includes('/tmp/codex/')) {
106
+ return this.getCodexAgent()
107
+ }
108
+
109
+ if (process.cwd().includes('/.claude/') || process.cwd().includes('/claude-workspace/')) {
110
+ return this.getClaudeAgent()
111
+ }
112
+
113
+ return null
114
+ }
115
+
116
+ /**
117
+ * Check if running in a container
118
+ */
119
+ isRunningInContainer() {
120
+ if (fs.existsSync('/.dockerenv')) {
121
+ return true
122
+ }
123
+
124
+ try {
125
+ const cgroup = fs.readFileSync('/proc/self/cgroup', 'utf8')
126
+ if (cgroup.includes('docker') || cgroup.includes('containerd')) {
127
+ return true
128
+ }
129
+ } catch (e) {
130
+ }
131
+
132
+ return false
133
+ }
134
+
135
+ /**
136
+ * Get Claude agent configuration
137
+ */
138
+ getClaudeAgent() {
139
+ return {
140
+ type: 'claude',
141
+ name: 'Claude Code',
142
+ capabilities: {
143
+ mcp: true,
144
+ filesystem: 'mcp',
145
+ markdown: true,
146
+ emojis: true,
147
+ colors: true,
148
+ interactive: true,
149
+ },
150
+ config: {
151
+ configFile: 'CLAUDE.md',
152
+ commandPrefix: '/p:',
153
+ responseStyle: 'rich',
154
+ dataDir: '.prjct',
155
+ },
156
+ environment: {
157
+ hasMCP: true,
158
+ sandboxed: false,
159
+ persistent: true,
160
+ },
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Get Codex agent configuration
166
+ */
167
+ getCodexAgent() {
168
+ return {
169
+ type: 'codex',
170
+ name: 'OpenAI Codex',
171
+ capabilities: {
172
+ mcp: false,
173
+ filesystem: 'native',
174
+ markdown: true,
175
+ emojis: true,
176
+ colors: false,
177
+ interactive: false,
178
+ },
179
+ config: {
180
+ configFile: 'AGENTS.md',
181
+ commandPrefix: '/p:',
182
+ responseStyle: 'structured',
183
+ dataDir: '.prjct',
184
+ },
185
+ environment: {
186
+ hasMCP: false,
187
+ sandboxed: true,
188
+ persistent: false,
189
+ },
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Get terminal agent configuration (fallback)
195
+ */
196
+ getTerminalAgent() {
197
+ return {
198
+ type: 'terminal',
199
+ name: 'Terminal/CLI',
200
+ capabilities: {
201
+ mcp: false,
202
+ filesystem: 'native',
203
+ markdown: false,
204
+ emojis: true,
205
+ colors: true,
206
+ interactive: true,
207
+ },
208
+ config: {
209
+ configFile: null,
210
+ commandPrefix: 'prjct',
211
+ responseStyle: 'cli',
212
+ dataDir: '.prjct',
213
+ },
214
+ environment: {
215
+ hasMCP: false,
216
+ sandboxed: false,
217
+ persistent: true,
218
+ },
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Force set agent type (for testing)
224
+ */
225
+ setAgent(type) {
226
+ switch (type) {
227
+ case 'claude':
228
+ this.detectedAgent = this.getClaudeAgent()
229
+ break
230
+ case 'codex':
231
+ this.detectedAgent = this.getCodexAgent()
232
+ break
233
+ case 'terminal':
234
+ default:
235
+ this.detectedAgent = this.getTerminalAgent()
236
+ break
237
+ }
238
+ return this.detectedAgent
239
+ }
240
+
241
+ /**
242
+ * Reset detection (clear cache)
243
+ */
244
+ reset() {
245
+ this.detectedAgent = null
246
+ }
247
+ }
248
+
249
+ module.exports = new AgentDetector()
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Claude Code Agent Adapter
3
+ * Implements prjct commands for Claude Code environment
4
+ */
5
+
6
+ const fs = require('fs').promises
7
+
8
+ class ClaudeAgent {
9
+ constructor() {
10
+ this.name = 'Claude Code'
11
+ this.type = 'claude'
12
+ }
13
+
14
+ /**
15
+ * Format response - minimal, actionable
16
+ */
17
+ formatResponse(message, type = 'info') {
18
+ const emojis = {
19
+ success: '✅',
20
+ error: '❌',
21
+ warning: '⚠️',
22
+ info: 'ℹ️',
23
+ celebrate: '🎉',
24
+ ship: '🚀',
25
+ focus: '🎯',
26
+ idea: '💡',
27
+ progress: '📊',
28
+ task: '📝',
29
+ }
30
+
31
+ return `${emojis[type] || emojis.info} ${message}`
32
+ }
33
+
34
+ /**
35
+ * Read file using MCP if available, fallback to fs
36
+ */
37
+ async readFile(filePath) {
38
+ try {
39
+ if (global.mcp && global.mcp.filesystem) {
40
+ return await global.mcp.filesystem.read(filePath)
41
+ }
42
+ } catch (e) {
43
+
44
+ }
45
+
46
+ return await fs.readFile(filePath, 'utf8')
47
+ }
48
+
49
+ /**
50
+ * Write file using MCP if available, fallback to fs
51
+ */
52
+ async writeFile(filePath, content) {
53
+ try {
54
+ if (global.mcp && global.mcp.filesystem) {
55
+ return await global.mcp.filesystem.write(filePath, content)
56
+ }
57
+ } catch (e) {
58
+
59
+ }
60
+
61
+ await fs.writeFile(filePath, content, 'utf8')
62
+ }
63
+
64
+ /**
65
+ * List directory contents
66
+ */
67
+ async listDirectory(dirPath) {
68
+ try {
69
+ if (global.mcp && global.mcp.filesystem) {
70
+ return await global.mcp.filesystem.list(dirPath)
71
+ }
72
+ } catch (e) {
73
+
74
+ }
75
+
76
+ return await fs.readdir(dirPath)
77
+ }
78
+
79
+ /**
80
+ * Check if file exists
81
+ */
82
+ async fileExists(filePath) {
83
+ try {
84
+ await fs.access(filePath)
85
+ return true
86
+ } catch {
87
+ return false
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Create directory
93
+ */
94
+ async createDirectory(dirPath) {
95
+ await fs.mkdir(dirPath, { recursive: true })
96
+ }
97
+
98
+ /**
99
+ * Get current timestamp in ISO format
100
+ */
101
+ getTimestamp() {
102
+ return new Date().toISOString()
103
+ }
104
+
105
+ /**
106
+ * Format task list - data only
107
+ */
108
+ formatTaskList(tasks) {
109
+ if (!tasks || tasks.length === 0) {
110
+ return '📋 No tasks queued'
111
+ }
112
+
113
+ return '📋 Queue:\n' + tasks.map((t, i) => `${i + 1}. ${t}`).join('\n')
114
+ }
115
+
116
+ /**
117
+ * Format recap - metrics only
118
+ */
119
+ formatRecap(data) {
120
+ return `📊 Recap
121
+
122
+ 🎯 Current: ${data.currentTask || 'None'}
123
+ 🚀 Shipped: ${data.shippedCount}
124
+ 📝 Queue: ${data.queuedCount}
125
+ 💡 Ideas: ${data.ideasCount}
126
+ ${data.recentActivity ? '\n' + data.recentActivity : ''}`
127
+ }
128
+
129
+ /**
130
+ * Format progress - data only
131
+ */
132
+ formatProgress(data) {
133
+ const trend =
134
+ data.velocity > data.previousVelocity ? '📈' : data.velocity < data.previousVelocity ? '📉' : '➡️'
135
+
136
+ return `📊 ${data.period}
137
+
138
+ Shipped: ${data.count}
139
+ Velocity: ${data.velocity.toFixed(1)}/day ${trend}
140
+ ${data.recentFeatures || ''}`
141
+ }
142
+
143
+ /**
144
+ * Get help - actionable steps only
145
+ */
146
+ getHelpContent(issue) {
147
+ const helps = {
148
+ debugging: '🔍 1. Isolate code causing error\n2. Add logs at key points\n3. Search exact error message',
149
+ design: '🎨 1. Define problem clearly\n2. Start with simplest solution\n3. Ship MVP, iterate',
150
+ performance:
151
+ '⚡ 1. Profile/measure first\n2. Optimize slowest parts\n3. Cache expensive operations',
152
+ default: '💡 1. Break into smaller tasks\n2. Start with simplest part\n3. Ship it',
153
+ }
154
+
155
+ const helpType = Object.keys(helps).find((key) => issue.toLowerCase().includes(key)) || 'default'
156
+ return helps[helpType]
157
+ }
158
+
159
+ /**
160
+ * Suggest next action - conversational prompts
161
+ */
162
+ suggestNextAction(context) {
163
+ const suggestions = {
164
+ taskCompleted: `What's next?
165
+ • "start [task]" → Begin working
166
+ • "ship feature" → Track & celebrate
167
+ • "add idea" → Brainstorm
168
+
169
+ Or: /p:now | /p:ship | /p:idea`,
170
+
171
+ featureShipped: `Keep the momentum!
172
+ • "start next task" → Keep building
173
+ • "see progress" → View stats
174
+ • "plan ahead" → Strategic thinking
175
+
176
+ Or: /p:now | /p:recap | /p:roadmap`,
177
+
178
+ ideaCaptured: `Ready to start?
179
+ • "start this" → Begin now
180
+ • "plan more" → Keep brainstorming
181
+ • "see ideas" → View backlog
182
+
183
+ Or: /p:now | /p:idea | /p:recap`,
184
+
185
+ initialized: `Ready to start? Tell me what you want to build!
186
+
187
+ Or type /p:help to see all options`,
188
+
189
+ stuck: `Let's break it down:
190
+ • "start the first part"
191
+ • "add as tasks"
192
+ • "think more"
193
+
194
+ Or: /p:now | /p:task | /p:idea`,
195
+ }
196
+
197
+ return suggestions[context] || `What would you like to do?
198
+
199
+ Type /p:help to see all options`
200
+ }
201
+
202
+ /**
203
+ * Detect user intent from natural language
204
+ */
205
+ detectIntent(message) {
206
+ const msg = message.toLowerCase()
207
+
208
+ // Start/begin task
209
+ if (/^(start|empez|begin|quiero|want|let'?s|voy)/i.test(msg)) {
210
+ return { intent: 'start', command: 'now' }
211
+ }
212
+
213
+ // Complete task
214
+ if (/^(done|termin|finish|acab|complete|listo|ya)/i.test(msg)) {
215
+ return { intent: 'complete', command: 'done' }
216
+ }
217
+
218
+ // Ship feature
219
+ if (/^(ship|deploy|launch|public)/i.test(msg)) {
220
+ return { intent: 'ship', command: 'ship' }
221
+ }
222
+
223
+ // Capture idea
224
+ if (/^(idea|think|thought|ocurr|tengo)/i.test(msg)) {
225
+ return { intent: 'idea', command: 'idea' }
226
+ }
227
+
228
+ // View status/progress
229
+ if (
230
+ /(show|see|view|muestra|ver).*(progress|status|recap|avance)/i.test(msg) ||
231
+ /^(progress|status|recap|avance)/i.test(msg)
232
+ ) {
233
+ return { intent: 'status', command: 'recap' }
234
+ }
235
+
236
+ // Stuck/help
237
+ if (/^(stuck|help|ayud|atascado|perdido)/i.test(msg)) {
238
+ return { intent: 'stuck', command: 'stuck' }
239
+ }
240
+
241
+ // View queue/next
242
+ if (/(what|que).*(next|sigue|after|despues)/i.test(msg) || /^(next|sigue)/i.test(msg)) {
243
+ return { intent: 'next', command: 'next' }
244
+ }
245
+
246
+ return { intent: 'unknown', command: null }
247
+ }
248
+ }
249
+
250
+ module.exports = ClaudeAgent